fallow 3.15.0 → 3.16.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.15.0",
3
+ "version": "3.16.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": [
@@ -6006,9 +6006,9 @@
6006
6006
  "rule_pack_schema_command": "fallow rule-pack-schema",
6007
6007
  "rule_pack_schema_note": "JSON Schema for a declarative rule pack referenced from rulePacks.",
6008
6008
  "plugin_schema_command": "fallow plugin-schema",
6009
- "plugin_schema_note": "JSON Schema for a user-authored external plugin (fallow-plugin-*.jsonc). Teach fallow about an unsupported framework declaratively: detection, entryPoints, alwaysUsed, usedExports, usedClassMembers, and manifestEntries (derive entry points from per-package manifest files).",
6009
+ "plugin_schema_note": "JSON Schema for a user-authored external plugin (fallow-plugin-*.jsonc). Teach fallow about an unsupported framework declaratively: detection, entryPoints, alwaysUsed, usedExports, usedClassMembers, and manifestEntries (derive entry points from per-package manifest files, including typed [*] traversal and exists predicates).",
6010
6010
  "plugin_check_command": "fallow plugin-check",
6011
- "plugin_check_note": "Read-only dry-run of your external plugins: reports which activated, which manifests each manifestEntries rule matched, what it seeded (with path-exists), and typed warnings (manifests-matched-none, when-excluded-all, field-path-unresolved, entries-empty, manifest-parse-failed, entry-outside-root, seeded-paths-missing). Run it after authoring a fallow-plugin-*.jsonc to verify it before a full analysis.",
6011
+ "plugin_check_note": "Read-only dry-run of your external plugins: reports which activated, which manifests each manifestEntries rule matched, what it seeded (with path-exists), and typed warnings (manifests-matched-none, when-excluded-all, field-path-unresolved, entries-empty, manifest-parse-failed, field-values-limit-exceeded, entry-expansion-limit-exceeded, entry-outside-root, seeded-paths-missing). Run it after authoring a fallow-plugin-*.jsonc to verify it before a full analysis.",
6012
6012
  "config_files": [
6013
6013
  ".fallowrc.json",
6014
6014
  ".fallowrc.jsonc",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -87,14 +87,14 @@
87
87
  "@tanstack/intent": "0.3.6"
88
88
  },
89
89
  "optionalDependencies": {
90
- "@fallow-cli/darwin-arm64": "3.15.0",
91
- "@fallow-cli/darwin-x64": "3.15.0",
92
- "@fallow-cli/linux-x64-gnu": "3.15.0",
93
- "@fallow-cli/linux-arm64-gnu": "3.15.0",
94
- "@fallow-cli/linux-x64-musl": "3.15.0",
95
- "@fallow-cli/linux-arm64-musl": "3.15.0",
96
- "@fallow-cli/win32-arm64-msvc": "3.15.0",
97
- "@fallow-cli/win32-x64-msvc": "3.15.0",
98
- "fallow-type-aware": "3.15.0"
90
+ "@fallow-cli/darwin-arm64": "3.16.0",
91
+ "@fallow-cli/darwin-x64": "3.16.0",
92
+ "@fallow-cli/linux-x64-gnu": "3.16.0",
93
+ "@fallow-cli/linux-arm64-gnu": "3.16.0",
94
+ "@fallow-cli/linux-x64-musl": "3.16.0",
95
+ "@fallow-cli/linux-arm64-musl": "3.16.0",
96
+ "@fallow-cli/win32-arm64-msvc": "3.16.0",
97
+ "@fallow-cli/win32-x64-msvc": "3.16.0",
98
+ "fallow-type-aware": "3.16.0"
99
99
  }
100
100
  }
package/schema.json CHANGED
@@ -569,9 +569,11 @@
569
569
  "default": "jsonc"
570
570
  },
571
571
  "when": {
572
- "description": "Manifest-level gate: a map of dotted field path to an expected scalar\nvalue. ALL entries must match by STRICT EQUALITY for the manifest to be\nprocessed. An empty map matches every manifest.",
572
+ "description": "Manifest-level gate: a map of field path to an equality expectation or\nan explicit `exists` predicate. Paths use dotted object keys and may\ntraverse arrays with `[*]`. ALL entries must match; a wildcard equality\nmatches when any yielded value equals the expectation. An empty map\nmatches every manifest.",
573
573
  "type": "object",
574
- "additionalProperties": true,
574
+ "additionalProperties": {
575
+ "$ref": "#/$defs/ManifestCondition"
576
+ },
575
577
  "default": {}
576
578
  },
577
579
  "entries": {
@@ -602,18 +604,46 @@
602
604
  }
603
605
  ]
604
606
  },
607
+ "ManifestCondition": {
608
+ "description": "A typed condition used by a manifest entry gate.\n\nPlain JSON values retain strict equality semantics. The reserved\n`{ \"exists\": bool }` object tests field presence without truthiness.",
609
+ "anyOf": [
610
+ {
611
+ "description": "Require the field path to resolve (`true`) or not resolve (`false`).",
612
+ "$ref": "#/$defs/ManifestExistsPredicate"
613
+ },
614
+ {
615
+ "description": "Require at least one yielded value to equal this JSON value exactly."
616
+ }
617
+ ]
618
+ },
619
+ "ManifestExistsPredicate": {
620
+ "description": "The explicit field-presence predicate accepted by [`ManifestCondition`].",
621
+ "type": "object",
622
+ "properties": {
623
+ "exists": {
624
+ "description": "Whether the field path must resolve to at least one value.",
625
+ "type": "boolean"
626
+ }
627
+ },
628
+ "additionalProperties": false,
629
+ "required": [
630
+ "exists"
631
+ ]
632
+ },
605
633
  "ManifestSeedRule": {
606
634
  "description": "A single entry seeded by a [`ManifestEntryRule`], resolved relative to the\nmanifest's directory.",
607
635
  "type": "object",
608
636
  "properties": {
609
637
  "path": {
610
- "description": "Entry glob relative to the manifest directory. May contain\n`${dotted.field}` interpolation that fans out over string / array\nmanifest field values (a missing or empty field seeds nothing). The glob\nmust encode its own extension (e.g. `public/index.{ts,tsx}`); glob entry\npatterns are matched literally against discovered files without\nsource-extension probing.",
638
+ "description": "Entry glob relative to the manifest directory. May contain\n`${dotted.field}` interpolation that fans out over string / array\nmanifest field values; `[*]` traverses object arrays. A missing or empty\nfield seeds nothing. The glob must encode its own extension, such as\n`public/index.{ts,tsx}`. Glob entry patterns are matched literally\nagainst discovered files without\nsource-extension probing.",
611
639
  "type": "string"
612
640
  },
613
641
  "when": {
614
- "description": "Per-entry gate (strict equality), evaluated against the same manifest.\nAn empty map always passes.",
642
+ "description": "Per-entry gate, using the same strict equality and `[*]` traversal as the\nmanifest-level gate. An empty map always passes.",
615
643
  "type": "object",
616
- "additionalProperties": true,
644
+ "additionalProperties": {
645
+ "$ref": "#/$defs/ManifestCondition"
646
+ },
617
647
  "default": {}
618
648
  }
619
649
  },
@@ -977,7 +1007,7 @@
977
1007
  "default": 15
978
1008
  },
979
1009
  "maxCrap": {
980
- "description": "Maximum allowed CRAP (Change Risk Anti-Patterns) score per function\n(default: 30.0). CRAP combines cyclomatic complexity with test\ncoverage: high complexity plus low coverage produces a high CRAP\nscore. Functions meeting or exceeding this threshold are reported.\nUse `--coverage` with Istanbul data for accurate per-function CRAP;\notherwise fallow estimates coverage from the module graph. Governs\nfindings only, never the health score.",
1010
+ "description": "Maximum allowed CRAP (Change Risk Anti-Patterns) score per function\n(default: 30.0). CRAP combines cyclomatic complexity with test\ncoverage: high complexity plus low coverage produces a high CRAP\nscore. Functions meeting or exceeding this threshold are reported.\nUse `--coverage` with Istanbul data for accurate per-function CRAP;\notherwise fallow estimates coverage from the module graph. Governs\nfindings and the threshold-relative file-score signals\n(`crap_above_threshold`, the `risk` triage tag, and the\n`add_test_coverage` refactoring target); measured values such as\n`crap_max` and the overall health score never move with it. Set to\n`0` to disable CRAP enforcement entirely: no findings, nothing counts\nabove threshold, and file-score rows disclose baseline breaches as\nexempt instead.",
981
1011
  "type": "number",
982
1012
  "format": "double",
983
1013
  "default": 30.0
@@ -521,7 +521,7 @@ fallow health --format json --quiet --trend
521
521
  {
522
522
  "kind": "health",
523
523
  "schema_version": 7,
524
- "version": "3.15.0",
524
+ "version": "3.16.0",
525
525
  "elapsed_ms": 32,
526
526
  "summary": {
527
527
  "files_analyzed": 482,
@@ -595,7 +595,7 @@ With `--file-scores`, the JSON output also includes `file_scores` array and `sum
595
595
 
596
596
  The `file_scores` array is sorted by risk-aware triage concern: the larger of low-MI concern and CRAP risk. This keeps files with very high untested complexity near the top even when their Maintainability Index is not the lowest.
597
597
 
598
- The `crap_max` field is the highest CRAP (Change Risk Anti-Patterns) score among functions in the file, using the canonical formula `CC^2 * (1 - cov/100)^3 + CC`. The default model (`static_estimated`) estimates per-function coverage from export references: directly test-referenced = 85%, indirectly test-reachable = 40%, untested = 0%. Provide `--coverage <path>` with Istanbul-format `coverage-final.json` for exact scores (`istanbul` model). The `crap_above_threshold` field counts functions with CRAP >= 30. When `--file-scores` is active, `summary.coverage_model` indicates the model used (`"static_estimated"` or `"istanbul"`). When CRAP findings carry `coverage_source`, `summary.coverage_source_consistency` is `uniform` or `mixed`; grouped health JSON mirrors this as `groups[].coverage_source_consistency`.
598
+ The `crap_max` field is the highest CRAP (Change Risk Anti-Patterns) score among functions in the file, using the canonical formula `CC^2 * (1 - cov/100)^3 + CC`. It is always the raw measured value. The default model (`static_estimated`) estimates per-function coverage from export references: directly test-referenced = 85%, indirectly test-reachable = 40%, untested = 0%. Provide `--coverage <path>` with Istanbul-format `coverage-final.json` for exact scores (`istanbul` model). The `crap_above_threshold` field counts functions whose rounded CRAP meets or exceeds their effective ceiling, resolved from `health.thresholdOverrides` over the global `maxCrap` / `--max-crap` value (default 30); it is 0 when CRAP enforcement is disabled (`maxCrap: 0`). Rows whose breaches were let through by configuration carry two additional fields: `crap_exempted` (functions at or above the canonical 30 baseline but below their effective ceiling; omitted when 0) and `crap_effective_threshold` (the lowest effective ceiling among the file's functions, present only when it differs from `summary.max_crap_threshold`). When `--file-scores` is active, `summary.coverage_model` indicates the model used (`"static_estimated"` or `"istanbul"`). When CRAP findings carry `coverage_source`, `summary.coverage_source_consistency` is `uniform` or `mixed`; grouped health JSON mirrors this as `groups[].coverage_source_consistency`.
599
599
 
600
600
  Maintainability index formula: `100 - (complexity_density × 30) - (dead_code_ratio × 20) - min(ln(fan_out+1) × 4, 15)`, clamped to 0–100. Higher is better. Type-only exports are excluded from dead_code_ratio. Zero-function files (barrels) are excluded by default.
601
601
 
@@ -668,7 +668,7 @@ With `--targets`, the JSON output includes a `targets` array with ranked refacto
668
668
 
669
669
  Targets are sorted by `efficiency` (priority / effort_numeric) descending, surfacing quick wins first. The `target_thresholds` object exposes the adaptive percentile-based thresholds used for scoring. Priority formula: `min(complexity_density, 1) x 30 + hotspot_boost x 25 + dead_code_ratio x 20 + fan_in_norm x 15 + fan_out_norm x 10`, clamped to 0-100. Fan-in and fan-out normalization uses the project's p95 values (with floors). Categories: `urgent_churn_complexity`, `break_circular_dependency`, `split_high_impact`, `remove_dead_code`, `extract_complex_functions`, `extract_dependencies`, `add_test_coverage`. Each target includes `efficiency`, `effort` (low/medium/high), `confidence` (high/medium/low, data source reliability), and contributing `factors`.
670
670
 
671
- The `add_test_coverage` category fires when a file has 2+ functions with CRAP scores >= 30 and complexity density > 0.3. The `crap_max` metric appears in contributing factors for these targets.
671
+ The `add_test_coverage` category fires when a file has 2+ functions whose rounded CRAP meets or exceeds their effective ceiling (`health.thresholdOverrides` over the global `maxCrap` / `--max-crap`, default 30) and complexity density > 0.3. A file whose breaching functions are all exempted by configuration produces no target. The `crap_max` metric appears in contributing factors for these targets, with `threshold` set to the file's lowest effective ceiling (the run global when no override applies).
672
672
 
673
673
  ### Vital Signs
674
674
 
@@ -919,7 +919,7 @@ fallow audit \
919
919
  {
920
920
  "kind": "audit",
921
921
  "schema_version": 7,
922
- "version": "3.15.0",
922
+ "version": "3.16.0",
923
923
  "command": "audit",
924
924
  "verdict": "fail",
925
925
  "changed_files_count": 12,
@@ -996,7 +996,7 @@ fallow flags --format json --quiet --workspace my-package
996
996
  ```json
997
997
  {
998
998
  "schema_version": 7,
999
- "version": "3.15.0",
999
+ "version": "3.16.0",
1000
1000
  "elapsed_ms": 116,
1001
1001
  "feature_flags": [],
1002
1002
  "total_flags": 0
@@ -1097,7 +1097,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1097
1097
  {
1098
1098
  "kind": "security",
1099
1099
  "schema_version": "4",
1100
- "version": "3.15.0",
1100
+ "version": "3.16.0",
1101
1101
  "elapsed_ms": 42,
1102
1102
  "config": {
1103
1103
  "rules": {
@@ -1126,7 +1126,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1126
1126
  {
1127
1127
  "kind": "security",
1128
1128
  "schema_version": "4",
1129
- "version": "3.15.0",
1129
+ "version": "3.16.0",
1130
1130
  "elapsed_ms": 42,
1131
1131
  "config": {
1132
1132
  "rules": {
@@ -1361,7 +1361,7 @@ fallow plugin-schema > plugin-schema.json
1361
1361
 
1362
1362
  ## `plugin-check`: Verify external plugins
1363
1363
 
1364
- Read-only dry-run of your external plugins. Reports, per plugin, whether it activated (with the unmet `detection`/`enabler` requirement when inactive), and for `manifestEntries` rules which manifests each matched, what it seeded (with `path_exists`), and typed warnings (`manifests-matched-none`, `when-excluded-all`, `field-path-unresolved`, `entries-empty`, `manifest-parse-failed`, `entry-outside-root`, `seeded-paths-missing`). Run it after authoring a `fallow-plugin-*.jsonc` to verify it before a full analysis. Deterministic output; always exits 0 (advisory, never a gate).
1364
+ Read-only dry-run of your external plugins. Reports, per plugin, whether it activated (with the unmet `detection`/`enabler` requirement when inactive), and for `manifestEntries` rules which manifests each matched, what it seeded (with `path_exists`), and typed warnings (`manifests-matched-none`, `when-excluded-all`, `field-path-unresolved`, `entries-empty`, `manifest-parse-failed`, `field-values-limit-exceeded`, `entry-expansion-limit-exceeded`, `entry-outside-root`, `seeded-paths-missing`). Run it after authoring a `fallow-plugin-*.jsonc` to verify it before a full analysis. Deterministic output; always exits 0 (advisory, never a gate).
1365
1365
 
1366
1366
  ```bash
1367
1367
  fallow plugin-check --format json
@@ -1891,7 +1891,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1891
1891
  {
1892
1892
  "kind": "dead-code",
1893
1893
  "schema_version": 7,
1894
- "version": "3.15.0",
1894
+ "version": "3.16.0",
1895
1895
  "elapsed_ms": 45,
1896
1896
  "total_issues": 12,
1897
1897
  "entry_points": {
@@ -2051,7 +2051,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2051
2051
  {
2052
2052
  "kind": "dupes",
2053
2053
  "schema_version": 7,
2054
- "version": "3.15.0",
2054
+ "version": "3.16.0",
2055
2055
  "elapsed_ms": 82,
2056
2056
  "total_clones": 15,
2057
2057
  "total_lines_duplicated": 230,
@@ -2095,11 +2095,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2095
2095
  {
2096
2096
  "kind": "combined",
2097
2097
  "schema_version": 7,
2098
- "version": "3.15.0",
2098
+ "version": "3.16.0",
2099
2099
  "elapsed_ms": 159,
2100
2100
  "check": {
2101
2101
  "schema_version": 7,
2102
- "version": "3.15.0",
2102
+ "version": "3.16.0",
2103
2103
  "elapsed_ms": 45,
2104
2104
  "total_issues": 12,
2105
2105
  "unused_files": [],
@@ -273,7 +273,9 @@ export type AddToConfigValue = (string | IgnoreExportsRule[] | {
273
273
  * means it was inherited. Duplication findings carry one carve-out: a clone
274
274
  * group whose structural key is new but whose instances contain no added line
275
275
  * from the diff (a group re-shaped by removing duplication elsewhere) is
276
- * demoted to inherited and serializes `false` (issue #2164).
276
+ * demoted to inherited and serializes `false` (issue #2164). Such demoted
277
+ * groups additionally carry a `demotion_reason` field naming the rule, and
278
+ * are counted in `attribution.duplication_demoted` (issue #2220).
277
279
  *
278
280
  * Outside of audit sub-results the field is omitted, so call sites typically
279
281
  * hold `Option<AuditIntroduced>`. Renders to the JSON wire as a bare boolean.
@@ -445,6 +447,15 @@ kind: "source-read-failure"
445
447
  * emitted by the legacy `build_clone_group_actions` walker.
446
448
  */
447
449
  export type CloneGroupActionType = ("extract-shared" | "suppress-line")
450
+ /**
451
+ * Why the audit new-only gate demoted an introduced clone group to
452
+ * inherited. Serializes as a kebab-case string on the wire (for example
453
+ * `"no-added-lines"`).
454
+ *
455
+ * Further variants may be added in later releases; consumers should treat an
456
+ * unknown value as "some demotion reason" rather than failing.
457
+ */
458
+ export type CloneDemotionReason = "no-added-lines"
448
459
  /**
449
460
  * The kind of refactoring suggested for a clone family.
450
461
  */
@@ -1157,6 +1168,7 @@ duplication_introduced: number
1157
1168
  duplication_inherited: number
1158
1169
  styling_introduced: number
1159
1170
  styling_inherited: number
1171
+ duplication_demoted: number
1160
1172
  }
1161
1173
  /**
1162
1174
  * Metric and rule definitions emitted under `_meta` when `--explain` is
@@ -4891,6 +4903,13 @@ actions: CloneGroupAction[]
4891
4903
  * to the merge-base. `None` when serialized directly from Rust.
4892
4904
  */
4893
4905
  introduced?: (AuditIntroduced | null)
4906
+ /**
4907
+ * Set only by `fallow audit` under `--gate new-only`, on groups whose
4908
+ * `introduced` flag the gate demoted to `false`: why the demotion
4909
+ * happened. `None` everywhere else, including `fallow dupes
4910
+ * --format json` (issue #2220).
4911
+ */
4912
+ demotion_reason?: (CloneDemotionReason | null)
4894
4913
  }
4895
4914
  /**
4896
4915
  * A single instance of duplicated code at a specific location.
@@ -5759,6 +5778,12 @@ cognitive: number
5759
5778
  * Current CRAP score, when coverage data exists.
5760
5779
  */
5761
5780
  crap?: (number | null)
5781
+ /**
5782
+ * Measured line count of the matched unit. Present on complexity rows,
5783
+ * where `maxUnitSize` participates in the dimension; absent on CRAP rows
5784
+ * and `<component>` rollup rows, which are never scored on unit size.
5785
+ */
5786
+ line_count?: (number | null)
5762
5787
  }
5763
5788
  /**
5764
5789
  * Project-wide vital signs: a fixed set of metrics for trend tracking.
@@ -6134,13 +6159,31 @@ function_count: number
6134
6159
  */
6135
6160
  lines: number
6136
6161
  /**
6137
- * Highest CRAP score among the file's functions.
6162
+ * Highest CRAP score among the file's functions. Always the raw measured
6163
+ * value; threshold overrides never rewrite it.
6138
6164
  */
6139
6165
  crap_max: number
6140
6166
  /**
6141
- * Functions whose CRAP score exceeds the threshold.
6167
+ * Functions whose rounded CRAP score meets or exceeds their effective
6168
+ * ceiling, resolved from `health.thresholdOverrides` over the global
6169
+ * `maxCrap` / `--max-crap` value. Zero when CRAP enforcement is disabled
6170
+ * (global ceiling `0`).
6142
6171
  */
6143
6172
  crap_above_threshold: number
6173
+ /**
6174
+ * Functions whose rounded CRAP score is at or above the canonical 30.0
6175
+ * baseline but below their effective ceiling: the count the configuration
6176
+ * let through. Stays `0` when the effective ceiling is stricter than 30.
6177
+ * When CRAP enforcement is disabled (global ceiling `0`), counts every
6178
+ * function at or above the canonical baseline. Omitted when zero.
6179
+ */
6180
+ crap_exempted?: number
6181
+ /**
6182
+ * Lowest effective CRAP ceiling among the file's functions, present only
6183
+ * when it differs from the run global (`summary.max_crap_threshold`).
6184
+ * Consumers fall back to `summary.max_crap_threshold` when absent.
6185
+ */
6186
+ crap_effective_threshold?: (number | null)
6144
6187
  }
6145
6188
  /**
6146
6189
  * Static test coverage gaps derived from the module graph. Shows runtime files
@@ -8971,6 +9014,7 @@ file: string
8971
9014
  * The export name being traced.
8972
9015
  */
8973
9016
  export_name: string
9017
+ namespace?: SemanticNamespace
8974
9018
  /**
8975
9019
  * Whether the file is reachable from an entry point.
8976
9020
  */