fallow 3.33.1 → 3.34.1

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.
@@ -53,6 +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
57
  | `inspect` | Compose one evidence bundle for a file or exported symbol | `--file <path>`, `--symbol <file>:<export>` |
57
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` |
58
59
  | `trace-error` | Resolve a runtime stack trace's frames to the definitions they name (best-effort, syntactic; OFF the ranked path) | `trace_file` |
@@ -77,7 +78,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
77
78
  | `flags` | Detect feature flag patterns (env vars, SDK calls, config objects) | `--top`, `--retirement`, `--reason`, `--min-age`, `--flag-state`, `--max-flag-age` |
78
79
  | `suppressions` | List active fallow-ignore suppression markers (read-only inventory) | `--file` |
79
80
  | `explain` | Explain one issue type without running analysis | `<issue-type>`, `--format json` |
80
- | `audit` | Combined dead-code + complexity + duplication + styling for changed files, returns a verdict; `fallow review` is an alias for `fallow audit --brief` (advisory orientation brief, always exits 0) | `--base`, `--gate`, `--brief`, `--max-decisions`, `--walkthrough-guide`, `--walkthrough-file`, `--show-deprioritized`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--explain`, `--explain-skipped`, `--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`, `--max-crap`, `--coverage`, `--coverage-root`, `--no-css`, `--css-deep`, `--no-css-deep`, `--include-entry-exports` |
81
+ | `audit` | Combined dead-code + complexity + duplication + styling for changed files, returns a verdict; `fallow review` is an alias for `fallow audit --brief` (advisory orientation brief, always exits 0) | `--base`, `--gate`, `--brief`, `--max-decisions`, `--walkthrough-guide`, `--walkthrough-file`, `--show-deprioritized`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--explain`, `--explain-skipped`, `--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`, `--max-crap`, `--coverage`, `--coverage-root`, `--only`, `--skip`, `--no-css`, `--css-deep`, `--no-css-deep`, `--include-entry-exports` |
81
82
  | `audit-cache` | Maintain reusable audit base-snapshot caches | |
82
83
  | `baselines` | Maintain committed baselines: `baselines prune` removes the entries of fixed findings and never adds one | `prune --check`, `prune --dead-code-baseline`, `prune --health-baseline`, `prune --dupes-baseline` |
83
84
  | `decision-surface` | Surface the consequential structural DECISIONS a change embeds (the apex of the review brief), each framed as a judgment question with the routed expert to ask | `--max-decisions` |
@@ -142,6 +143,9 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
142
143
  | `--unused-component-inputs` | An Angular @Input() / signal input() / model() is read nowhere in its own component (class body or template); needs `@angular/core` dep |
143
144
  | `--unused-component-outputs` | An Angular @Output() / signal output() is emitted (.emit()) nowhere in its own component; needs `@angular/core` dep |
144
145
  | `--unused-svelte-events` | A Svelte createEventDispatcher event is listened to nowhere in the project |
146
+ | `--unused-effect-layers` | An Effect layer is composed only into layers that nothing uses |
147
+ | `--unused-tagged-errors` | An Effect tagged error class is referenced only as a type or a catchTag string, and no code constructs or fails with it |
148
+ | `--unproduced-catch-tags` | A catchTag handler names a project error tag that no code produces |
145
149
  | `--unused-server-actions` | A Next.js Server Action exported from a "use server" file is referenced by no code in the project |
146
150
  | `--unused-load-data-keys` | A SvelteKit load() return-object key is read by no consumer |
147
151
  | `--unresolved-imports` | Unresolved imports |
@@ -162,6 +166,8 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
162
166
 
163
167
  `--deprecated-exports-in-use` is an opt-in migration sweep (default `off`). Each finding has the exact `consumer_count` and a sample of up to 10 consumers. `fallow dead-code --trace FILE:EXPORT` lists all of them. Enable it with this flag or with `deprecated-exports-in-use: "warn"` or `"error"` in [`rules`](#configuration-file-format).
164
168
 
169
+ Each consumer in `--trace FILE:EXPORT` and each coordination gap in `--impact-closure` has `search_visibility` (`found`, `renamed`, `missed`). A `grep -w` for the export name does not find a `missed` consumer. When `search_as` is present, search for that name in that consumer file. A coordination gap with more than one consumed symbol names the symbol of these fields in `search_symbol`.
170
+
165
171
  ### Examples
166
172
 
167
173
  ```bash
@@ -434,7 +440,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
434
440
 
435
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.
436
442
 
437
- 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`, `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`, `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.
438
444
 
439
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`.
440
446
 
@@ -520,7 +526,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
520
526
  {
521
527
  "kind": "agent-install",
522
528
  "schema_version": 1,
523
- "fallow_version": "3.33.1",
529
+ "fallow_version": "3.34.1",
524
530
  "root": "/abs/path",
525
531
  "mode": "install",
526
532
  "dry_run": false,
@@ -608,7 +614,7 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
608
614
  | `--hotspots` | `bool` | `false` | Show only hotspots: files that are both complex and frequently changing. Combines git churn history with complexity data. Requires a git repository. When no section flags are set, all sections are shown by default. |
609
615
  | `--ownership` | `bool` | `false` | Attach ownership signals to hotspot entries: bus factor (Avelino truck factor), contributor count, top contributor with stale-days, recent contributors (top-3), `suggested_reviewers`, declared CODEOWNERS owner, `ownership_state`, ownership drift, unowned-hotspot detection. Human output gains a project-level summary line. JSON adds `low-bus-factor`, `unowned-hotspot`, `ownership-drift` action types. Test files get a `[test]` tag. Implies `--hotspots`. Requires git. |
610
616
  | `--ownership-emails` | `raw\|handle\|anonymized\|hash` | - | Privacy mode for author emails. `handle` shows the local-part only (default, with GitHub noreply unwrap and deterministic same-handle disambiguation). `anonymized` emits stable `xxh3:` pseudonyms; `hash` remains accepted as the legacy spelling. `raw` shows full addresses. Use `anonymized` in regulated environments. Implies `--ownership`. Configure default via `health.ownership.emailMode`. |
611
- | `--targets` | `bool` | `false` | Show only refactoring targets: ranked recommendations based on complexity, coupling, churn, and dead code signals. Categories: churn+complexity, circular dep, high impact, dead code, complexity, coupling. When no section flags are set, all sections are shown by default. Each target's JSON can include `direct_callers[]` (direct importers with the symbols they import) and `clone_siblings[]` (duplicate-code siblings with stable `dup:<8hex>` fingerprints for `fallow dupes --trace`); both omitted when empty. Human output adds `importers:` / `clones:` lines only when that evidence is present. |
617
+ | `--targets` | `bool` | `false` | Show only refactoring targets: ranked recommendations based on complexity, coupling, churn, and dead code signals. Categories: churn+complexity, circular dep, high impact, dead code, complexity, coupling, test coverage, hidden coupling. When no section flags are set, all sections are shown by default. Each target's JSON can include `direct_callers[]` (direct importers with the symbols they import) and `clone_siblings[]` (duplicate-code siblings with stable `dup:<8hex>` fingerprints for `fallow dupes --trace`); both omitted when empty. Human output adds `importers:` / `clones:` lines only when that evidence is present. |
612
618
  | `--type-coupling` | `bool` | `false` | Show advisory project-local public-signature type coupling. Requires type-aware analysis and does not change the health score |
613
619
  | `--css` | `bool` | `false` | Add structural CSS analytics: specificity hotspots, !important density, over-complex selectors, deep nesting, and conservative cleanup candidates. Standard CSS is parsed structurally; preprocessor sources are scanned only where fallow can avoid expanding Sass/Less semantics. Also derives `styling_health`, a descriptive A-F grade for CSS quality scored separately from the code `health_score` (never gates); it weights design-token drift (hardcoded value sprawl) over byte-identical repetition. |
614
620
  | `--effort` | `low\|medium\|high` | - | Filter refactoring targets by effort level. Implies `--targets`. |
@@ -624,6 +630,7 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
624
630
  | `--coverage` | `string` | - | Path to coverage data for accurate per-function CRAP scores: an Istanbul map (`coverage-final.json`), a directory containing one, a raw V8 coverage directory (`NODE_V8_COVERAGE=<dir> node --test`), or a single V8 coverage JSON file. Transpiled V8 scripts (tsx, bundles) map back to their source files through the source map that Node records in the dump; a script that differs from the file on disk and has no source map keeps the estimate. Uses `CC^2 * (1-cov/100)^3 + CC` instead of the default estimated coverage. Relative paths resolve against `--root`. Falls back to `FALLOW_COVERAGE`, then `health.coverage`, then auto-detection. |
625
631
  | `--coverage-root` | `string` | - | Absolute prefix to strip from file paths in coverage data before prepending the project root. For CI/Docker environments where coverage was generated with different absolute paths. Falls back to `FALLOW_COVERAGE_ROOT`, then `health.coverageRoot`. |
626
632
  | `--runtime-coverage` | `string` | - | Merge runtime-coverage input into the health report. Accepts a V8 coverage directory (`NODE_V8_COVERAGE=...`), a single V8 coverage JSON file, or an Istanbul `coverage-final.json`. One local capture is free and does not require a license; continuous/cloud or multi-capture runtime monitoring requires an active license or trial (`fallow license activate --trial --email <addr>`). JSON output gains a `runtime_coverage` object with a top-level report verdict, per-finding `verdict` (`safe_to_delete` / `review_required` / `low_traffic` / `coverage_unavailable` / `active`), a per-finding suppression `id` (`fallow:prod:<hash>`, hashes the current line), an optional cross-surface `stable_id` join key (`fallow:fn:<hash>`, hashes file + name + start line; one value per function across findings / hot-paths / blast-radius / importance and across V8/Istanbul/oxc producers), an optional content-digest `source_hash` (line-move-immune, so baselines survive a pure line shift), an evidence block, and percentile-ranked hot paths. On protocol-0.3+ sidecars the `summary` also carries an optional `capture_quality` block (`window_seconds`, `instances_observed`, `lazy_parse_warning`, `untracked_ratio_percent`) that flags short-window captures where lazy-parsed scripts may not appear. |
633
+ | `--mutation-report` | `string` | - | Saved `fallow mutation --format json` report (for example `.fallow/mutation/last-report.json` or the `-o` file of a run). Adds the mutation score of each complexity finding next to its CRAP score. The score never changes CRAP or the health score. A function whose file changed since the run gets no score. Reads the file only; it runs no test |
627
634
  | `--min-invocations-hot` | `string` | `100` | Invocation threshold for hot-path classification. Takes effect only when `--runtime-coverage` is set. |
628
635
  | `--min-observation-volume` | `string` | - | Minimum total trace volume before the sidecar may emit high-confidence `safe_to_delete` / `review_required` verdicts. Below this, confidence is capped at `medium`. |
629
636
  | `--low-traffic-threshold` | `string` | - | Fraction of total trace count below which an invoked function is classified `low_traffic` rather than `active`. Expressed as a decimal (0.001 = 0.1%). |
@@ -730,7 +737,7 @@ fallow health --format json --quiet --trend
730
737
  {
731
738
  "kind": "health",
732
739
  "schema_version": 7,
733
- "version": "3.33.1",
740
+ "version": "3.34.1",
734
741
  "elapsed_ms": 32,
735
742
  "summary": {
736
743
  "files_analyzed": 482,
@@ -834,12 +841,49 @@ With `--hotspots`, the JSON output includes a `hotspots` array and `hotspot_summ
834
841
  "lines_deleted": 180,
835
842
  "complexity_density": 0.22,
836
843
  "fan_in": 8,
837
- "trend": "Accelerating"
844
+ "trend": "Accelerating",
845
+ "median_files_per_commit": 3,
846
+ "median_lines_per_commit": 42
838
847
  }
839
848
  ]
840
849
  }
841
850
  ```
842
851
 
852
+ `median_files_per_commit` and `median_lines_per_commit` measure change dispersion. They are the medians over the commits that changed the file, and they count analyzed files only. A commit with more analyzed files than `health.changeCoupling.maxFilesPerCommit` (default 30) is not counted. Both fields are absent when the history has no commit ids, for example imported churn without the `commit` field. Without `--since`, they read 12 months of history, while the hotspot score keeps 6 months.
853
+
854
+ When the hotspots section runs on git history, the JSON output also includes a `change_coupling` object. It lists hidden pairs: two files that change together while no import or re-export chain connects them. An agent that follows imports does not find the second file of such a pair.
855
+
856
+ ```json
857
+ {
858
+ "change_coupling": {
859
+ "since": "12 months",
860
+ "commits_analyzed": 640,
861
+ "commits_skipped": 12,
862
+ "total_pairs": 4,
863
+ "thresholds": {
864
+ "max_files_per_commit": 30,
865
+ "min_revisions": 5,
866
+ "min_shared_revisions": 5,
867
+ "min_degree": 0.3,
868
+ "strong_degree": 0.5
869
+ },
870
+ "pairs": [
871
+ {
872
+ "path_a": "src/api/orders.ts",
873
+ "path_b": "src/ui/order-form.ts",
874
+ "revisions_a": 12,
875
+ "revisions_b": 10,
876
+ "shared_revisions": 9,
877
+ "degree": 0.82,
878
+ "strong": true
879
+ }
880
+ ]
881
+ }
882
+ }
883
+ ```
884
+
885
+ `degree` is `shared_revisions` divided by the mean of `revisions_a` and `revisions_b`, rounded to two decimals. A pair is `strong` when the rounded `degree` reaches `strong_degree` (0.5). `pairs` is cut to `--top`, and `total_pairs` keeps the full count. The `health.changeCoupling` config object changes the thresholds: `minRevisions` (default 5), `minSharedRevisions` (default 5), `minDegree` (from 0 to 1, default 0.3) and `maxFilesPerCommit` (default 30). Without `--since`, the section reads 12 months of history. The section is advisory: it has no weight in the health score and never gates. A shallow clone, or a window with fewer analyzed commits than `minRevisions`, gives an empty `pairs` list and a `workspace_diagnostics[]` entry with kind `change-coupling-skipped` and `cause` `shallow-clone` or `too-few-commits`.
886
+
843
887
  Hotspot score formula: `normalized_churn × normalized_complexity × 100`, scaled 0–100. Higher means more urgent to refactor. The `trend` field indicates recent change velocity: `Accelerating` (increasing churn), `Stable` (constant), or `Cooling` (decreasing). Files below `--min-commits` are excluded. The `shallow_clone` field warns when git history is truncated (shallow clone), which may undercount commits. The `clock` object reports the single instant `weighted_commits` and ownership `stale_days` were measured against: `source` is `head_commit` (the default, identical for every run over one commit), `environment` (pinned with `FALLOW_CLOCK_EPOCH`), or `wall_clock` (no commit timestamp was readable, so the numbers drift between runs and `reproducible` is false). Pass `epoch_secs` back as `FALLOW_CLOCK_EPOCH` to reproduce a run's churn-derived numbers.
844
888
 
845
889
  With `--targets`, the JSON output includes a `targets` array with ranked refactoring recommendations:
@@ -880,10 +924,12 @@ With `--targets`, the JSON output includes a `targets` array with ranked refacto
880
924
  }
881
925
  ```
882
926
 
883
- 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`.
927
+ 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`, `resolve_hidden_coupling`. Each target includes `efficiency`, `effort` (low/medium/high), `confidence` (high/medium/low, data source reliability), and contributing `factors`.
884
928
 
885
929
  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).
886
930
 
931
+ The `resolve_hidden_coupling` category fires for a file that has a strong hidden pair (degree at least 0.5) and that no other category claims. The rule runs last, so a file keeps its existing category. The target lists the partner files in `evidence.co_change_partners`, strongest first. Every target of a file with a strong hidden pair also gets the `hidden_coupling` factor.
932
+
887
933
  ### Vital Signs
888
934
 
889
935
  All `health` JSON output includes a `vital_signs` object with project-wide metrics:
@@ -1060,6 +1106,8 @@ Audits changed files for dead code, complexity, duplication, and styling. Return
1060
1106
  | `--max-crap` | `string` | - | Forwarded to the health sub-analysis. Functions meeting or exceeding this CRAP score cause audit to fail. Same formula as `health --max-crap`. Pair with coverage data for accurate per-function CRAP. |
1061
1107
  | `--coverage` | `string` | - | Path to Istanbul coverage data (`coverage-final.json`) or raw V8 coverage (a `NODE_V8_COVERAGE` directory or one V8 JSON file) for accurate per-function CRAP scores in the health sub-analysis. Same formats and semantics as `health --coverage`. Also configurable via `FALLOW_COVERAGE`, then `health.coverage` (the same chain as `fallow health`). Relative paths resolve against `--root`. |
1062
1108
  | `--coverage-root` | `string` | - | Absolute prefix to strip from file paths in coverage data before prepending the project root. Also configurable via `FALLOW_COVERAGE_ROOT`, then `health.coverageRoot`. Use when coverage was generated under a different checkout root in CI / Docker (e.g., `/home/runner/work/myapp` on GitHub Actions). |
1109
+ | `--only` | `dead-code\|dupes\|health\|architecture` | - | Run only these sub-analyses (comma-separated: dead-code, architecture, dupes, health), with the same values as bare `fallow --only`. Replaces the `audit.deadCode`, `audit.health` and `audit.dupes` selection. The review brief (`--brief`, `fallow review`) keeps every sub-analysis |
1110
+ | `--skip` | `dead-code\|dupes\|health\|architecture` | - | Skip these sub-analyses (comma-separated: dead-code, architecture, dupes, health), with the same values as bare `fallow --skip`, on top of the `audit.*` selection |
1063
1111
  | `--no-css` | `bool` | `false` | Disable styling analytics in audit |
1064
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` |
1065
1113
  | `--no-css-deep` | `bool` | `false` | Disable deep CSS analysis while keeping local styling analytics on |
@@ -1089,6 +1137,8 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
1089
1137
 
1090
1138
  With `--gate new-only`, inherited error-severity findings can be present in the JSON output while the verdict remains `pass`; check the `attribution` object and per-finding `introduced` booleans.
1091
1139
 
1140
+ A merge of the default branch brings in code that the default branch already has. When the base is auto-detected, audit also compares with the merged default-branch commits: `MERGE_HEAD` of a merge in progress, and the merge-base with the default branch after a merge commit. A finding that one of these commits already has is inherited, so the gate does not block the merge commit or the push after it. Code from a merged branch that the default branch does not contain stays introduced. An explicit `--base` or `FALLOW_AUDIT_BASE` turns this off.
1141
+
1092
1142
  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:
1093
1143
 
1094
1144
  - No base finding matches it.
@@ -1107,6 +1157,10 @@ An unchanged or decreased metric keeps the finding inherited, also when the exce
1107
1157
 
1108
1158
  For CI gating and any "did this PR pass?" question, read `verdict` (or exit code). Counting introduced findings ignores severity and breaks projects with `unused-exports: warn`. For agent triage, read `verdict` first, then `attribution` for new-vs-inherited counts, then the per-category finding arrays for actionable details.
1109
1159
 
1160
+ The review brief (`fallow audit --brief --format json` and `fallow review --format json`) adds `impact_closure.co_change_gap[]` when a changed file has a hidden change-coupling partner that the diff does not touch. Each entry has `changed_file`, `partner_file`, `shared_revisions`, `degree` and a scope `note`. The pairs use the `health.changeCoupling` thresholds over 12 months of history. The fact is advisory: it is history evidence, not proof that the partner must change, and it never gates. The array is absent when it is empty.
1161
+
1162
+ The review brief also adds a top-level `test_reach` object: how tests reach each value export whose declaring statement overlaps an added line. `status` is `measured`, or `no-tests` when the project has no test files. `masking` is `active`, `no-mocks` or `capped` (too many distinct mock sets, so mock detection is off). `counts` has one key per `reach` class: `direct` (a test file references the export), `transitive` (tests reach it only through other modules), `mocked-only` (only tests that mock a module on the import path reference it), `none` (tests load the file but do not reference the export), `none-file` (no test loads the file) and `framework` (a literal framework configuration export, such as the Next.js `dynamic` option). `exports[]` lists at most 25 rows that need attention (`mocked-only`, `none`, `none-file`, `transitive`), with `file`, `export_name`, `line`, `reach` and `change`. `change` is `new` when the file did not export the name at the base of the diff (a new export, a renamed export or an export of a new file), else `modified`. A `mocked-only` row adds `tests`, `tests_total`, `test_changed` and `mocked_targets`. `exports_omitted` counts the rows past the cap. `e2e_runner: true` states that the project uses Playwright or Cypress: those tests drive the running app and do not import files, so the reach classes do not count them. The field is absent when no such runner is present. Treat `reach` as an open set and read an unknown value as "needs attention". The section is advisory and never gates. It is absent when the diff changes no value export.
1163
+
1110
1164
  ### Examples
1111
1165
 
1112
1166
  ```bash
@@ -1146,7 +1200,7 @@ fallow audit \
1146
1200
  {
1147
1201
  "kind": "audit",
1148
1202
  "schema_version": 7,
1149
- "version": "3.33.1",
1203
+ "version": "3.34.1",
1150
1204
  "command": "audit",
1151
1205
  "verdict": "fail",
1152
1206
  "changed_files_count": 12,
@@ -1234,7 +1288,7 @@ fallow flags --format json --quiet --workspace my-package
1234
1288
  ```json
1235
1289
  {
1236
1290
  "schema_version": 7,
1237
- "version": "3.33.1",
1291
+ "version": "3.34.1",
1238
1292
  "elapsed_ms": 116,
1239
1293
  "feature_flags": [],
1240
1294
  "total_flags": 0
@@ -1335,7 +1389,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1335
1389
  {
1336
1390
  "kind": "security",
1337
1391
  "schema_version": "4",
1338
- "version": "3.33.1",
1392
+ "version": "3.34.1",
1339
1393
  "elapsed_ms": 42,
1340
1394
  "config": {
1341
1395
  "rules": {
@@ -1364,7 +1418,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1364
1418
  {
1365
1419
  "kind": "security",
1366
1420
  "schema_version": "4",
1367
- "version": "3.33.1",
1421
+ "version": "3.34.1",
1368
1422
  "elapsed_ms": 42,
1369
1423
  "config": {
1370
1424
  "rules": {
@@ -2179,7 +2233,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2179
2233
  {
2180
2234
  "kind": "dead-code",
2181
2235
  "schema_version": 7,
2182
- "version": "3.33.1",
2236
+ "version": "3.34.1",
2183
2237
  "elapsed_ms": 45,
2184
2238
  "total_issues": 12,
2185
2239
  "entry_points": {
@@ -2339,7 +2393,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2339
2393
  {
2340
2394
  "kind": "dupes",
2341
2395
  "schema_version": 7,
2342
- "version": "3.33.1",
2396
+ "version": "3.34.1",
2343
2397
  "elapsed_ms": 82,
2344
2398
  "total_clones": 15,
2345
2399
  "total_lines_duplicated": 230,
@@ -2383,11 +2437,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2383
2437
  {
2384
2438
  "kind": "combined",
2385
2439
  "schema_version": 7,
2386
- "version": "3.33.1",
2440
+ "version": "3.34.1",
2387
2441
  "elapsed_ms": 159,
2388
2442
  "check": {
2389
2443
  "schema_version": 7,
2390
- "version": "3.33.1",
2444
+ "version": "3.34.1",
2391
2445
  "elapsed_ms": 45,
2392
2446
  "total_issues": 12,
2393
2447
  "unused_files": [],
@@ -2479,7 +2533,7 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
2479
2533
  // Per-path rule overrides
2480
2534
  "overrides": [
2481
2535
  {
2482
- "files": ["*.test.ts", "*.spec.ts"],
2536
+ "files": ["**/*.test.ts", "**/*.spec.ts"],
2483
2537
  "rules": { "unused-exports": "off" }
2484
2538
  }
2485
2539
  ],
@@ -2584,7 +2638,7 @@ ignoreDefaults = true
2584
2638
  ignoredClones = ["dup:6f12ab34:2"]
2585
2639
 
2586
2640
  [[overrides]]
2587
- files = ["*.test.ts"]
2641
+ files = ["**/*.test.ts"]
2588
2642
  [overrides.rules]
2589
2643
  unused-exports = "off"
2590
2644
 
@@ -2596,7 +2650,9 @@ preset = "bulletproof"
2596
2650
 
2597
2651
  - `ignoreExportsUsedInFile`: knip-compatible; suppress unused-export findings when the exported symbol is referenced inside the file that declares it. Boolean (`true` covers all kinds) or `{ "type": true, "interface": true }` object form for knip parity. Fallow groups type aliases and interfaces under the same `unused-types` issue, so both type-kind fields behave identically. References inside the export specifier itself (`export { foo }`, `export default foo`) do not count as same-file uses; those exports are still reported when no other in-file expression references the binding
2598
2652
  - `publicPackages`: workspace packages that are public libraries; exported API surface from these packages is not flagged as unused
2599
- - `dynamicallyLoaded`: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-used
2653
+ - `entry`: glob patterns for files that are manual entry points. A single `*` stays within one path segment; use `**` to match across directories
2654
+ - `dynamicallyLoaded`: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-used. A single `*` stays within one path segment; use `**` to match across directories
2655
+ - Path globs in other fields (`ignorePatterns`, `ignoreFindings`, `overrides[].files`, `ignoreExports[].file`, `duplicates.ignore`, `health.ignore`, boundary zones, rule-pack and `framework[]` globs) use the same rule: a single `*` stays within one path segment. Write `**/*.test.ts` to match test files at any depth
2600
2656
  - `cache.dir`: override the persistent extraction cache directory. `FALLOW_CACHE_DIR` wins over this config field, and `--no-cache` disables caching entirely
2601
2657
  - `cache.maxSizeMb`: cap the serialized extraction cache size in megabytes. `FALLOW_CACHE_MAX_SIZE` wins over this config field
2602
2658
  - `workspaces.changedSince`: map exact workspace roots (as `fallow list --workspaces` prints them) to Git refs. `check`, `dead-code` and `dupes` then report the findings of a mapped package only for files changed since its ref; unlisted packages and root files stay in full scope. A global `--changed-since` replaces the map for one run; `--no-package-baselines` or `FALLOW_PACKAGE_BASELINES=false` turns it off. A key that names no workspace, or a ref Git cannot resolve, leaves every package in full scope with a warning and `request_outcomes["package-baselines"]` as `not-applied`. Save a whole-project baseline with `--no-package-baselines`.
@@ -26,7 +26,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
26
26
  | `unused-class-member` | `--unused-class-members` | - | `// fallow-ignore-next-line unused-class-member` | Methods and properties |
27
27
  | `unused-store-member` | `--unused-store-members` | - | `// fallow-ignore-next-line unused-store-member` | Pinia store state/getter/action (needs `pinia` dep) |
28
28
  | `unresolved-import` | `--unresolved-imports` | - | `// fallow-ignore-next-line unresolved-import` | Imports that can't be resolved |
29
- | `unlisted-dependency` | `--unlisted-deps` | - | - | Used packages missing from package.json. In monorepos, importing a workspace package from a workspace whose own `package.json` does not list it is reported here too; self-references stay allowed without requiring a package to depend on itself. |
29
+ | `unlisted-dependency` | `--unlisted-deps` | - | - | Used packages missing an applicable dependency declaration. This includes root code importing an npm workspace package: a workspace link or hoisted install makes the package resolvable but does not declare it. Add runtime imports to `dependencies`, or tooling/test imports to `devDependencies`, in the owning `package.json`. Private packages and tooling may inherit ancestor declarations. Self-references need no declaration. Use `ignoreDependencies` for a deliberate ambient-package opt-out. |
30
30
  | `duplicate-export` | `--duplicate-exports` | - | `// fallow-ignore-file duplicate-export` | Same symbol exported from multiple modules |
31
31
  | `circular-dependency` | `--circular-deps` | - | `// fallow-ignore-next-line circular-dependency` | Import cycles in the module graph |
32
32
  | `re-export-cycle` | `--re-export-cycles` | - | `// fallow-ignore-file re-export-cycle` | Barrel files re-exporting from each other in a loop (`kind: "multi-node"`) or a barrel re-exporting from itself (`kind: "self-loop"`). Chain propagation through the loop is a structural no-op so imports through any member may silently come up empty. Default `warn`. Distinct from `circular-dependencies` (runtime cycles, sometimes intentional). File-scoped suppression only: `// fallow-ignore-file re-export-cycle` on any member breaks the cycle. |
@@ -53,9 +53,12 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
53
53
  | `unused-component-input` | `--unused-component-inputs` | - | `// fallow-ignore-next-line unused-component-input` | An Angular @Input() / signal input() / model() is read nowhere in its own component (class body or template); needs `@angular/core` dep |
54
54
  | `unused-component-output` | `--unused-component-outputs` | - | `// fallow-ignore-next-line unused-component-output` | An Angular @Output() / signal output() is emitted (.emit()) nowhere in its own component; needs `@angular/core` dep |
55
55
  | `unused-svelte-event` | `--unused-svelte-events` | - | `// fallow-ignore-next-line unused-svelte-event` | A Svelte createEventDispatcher event is listened to nowhere in the project; needs `svelte` dep |
56
+ | `unused-effect-layer` | `--unused-effect-layers` | - | `// fallow-ignore-next-line unused-effect-layer` | An Effect layer is composed only into layers that nothing uses |
57
+ | `unused-tagged-error` | `--unused-tagged-errors` | - | `// fallow-ignore-next-line unused-tagged-error` | An Effect tagged error class is referenced only as a type or a catchTag string, and no code constructs or fails with it |
58
+ | `unproduced-catch-tag` | `--unproduced-catch-tags` | - | `// fallow-ignore-next-line unproduced-catch-tag` | A catchTag handler names a project error tag that no code produces |
56
59
  | `unused-server-action` | `--unused-server-actions` | - | `// fallow-ignore-next-line unused-server-action` | A Next.js Server Action exported from a "use server" file is referenced by no code in the project |
57
60
  | `unused-load-data-key` | `--unused-load-data-keys` | - | `// fallow-ignore-next-line unused-load-data-key` | A SvelteKit load() return-object key is read by no consumer (needs @sveltejs/kit dep) |
58
- | `prop-drilling` | - | - | `// fallow-ignore-next-line prop-drilling` | A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer; Opt-in: set rules.prop-drilling to warn or error to enable. Defaults to off. |
61
+ | `prop-drilling` | - | - | `// fallow-ignore-next-line prop-drilling` | A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer; Opt-in: error gates CI; warn remains advisory even with --fail-on-issues. Defaults to off. Source-hop overrides decide severity. |
59
62
  | `thin-wrapper` | - | - | `// fallow-ignore-next-line thin-wrapper` | A React/Preact component whose whole body is a single spread-forwarded child render (a candidate for inlining); Opt-in: set rules.thin-wrapper to warn or error to enable. Defaults to off. |
60
63
  | `duplicate-prop-shape` | - | - | `// fallow-ignore-next-line duplicate-prop-shape` | Three or more React/Preact components across two or more files declare an identical prop-name set (a missing shared Props type); Opt-in: set rules.duplicate-prop-shape to warn or error to enable. Defaults to off. |
61
64
  | `route-collision` | - | - | `// fallow-ignore-file route-collision` | Two or more Next.js App Router route files resolve to the same URL |
@@ -78,6 +81,11 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
78
81
  | `tainted-sink` | - | - | `// fallow-ignore-next-line security-sink` | Syntactic security sink candidates require verification |
79
82
  | `client-server-leak` | - | - | `// fallow-ignore-file security-client-server-leak` | Client-bound code reaches a non-public env read |
80
83
  | `hardcoded-secret` | - | - | `// fallow-ignore-next-line security-sink` | Provider-prefixed or contextual secret literals require verification; Include-required category: enable via security.categories.include |
84
+ | `weakened-tests` | - | - | `// fallow-ignore-next-line weakened-tests` | The change weakens tests or a coverage threshold |
85
+ | `added-suppression` | - | - | `// fallow-ignore-next-line added-suppression` | The change adds a suppression comment |
86
+ | `test-only-branch` | - | - | `// fallow-ignore-next-line test-only-branch` | The change adds a test-environment branch to source code |
87
+ | `diverged-clone-sibling` | - | - | `// fallow-ignore-next-line diverged-clone-sibling` | The change edits one copy of duplicated code but not its siblings |
88
+ | `added-dependency` | - | - | - | The change adds a dependency. Config suppression only, because `package.json` cannot hold a comment: set `added-dependency` to `off` for the path in an `overrides` entry. |
81
89
 
82
90
  Runtime-coverage verdicts and the full security sink catalogue are listed by `fallow schema` (`issue_types`).
83
91
  <!-- generated:issue-types:end -->
@@ -18,6 +18,8 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
18
18
  | `security_candidates` | analysis | free | `fallow security --format json --quiet` | `gate`, `surface`, `changed_since`, `paths` | Unverified local security candidates, not confirmed vulnerabilities (`fallow security --format json`). Read `security_findings[]` for category, CWE, severity, evidence, trace, optional `reachability`, blind-spot counters, and optional `unresolved_callee_diagnostics` samples for dynamic callee follow-up. `severity` is a review-priority tier, not a verified vulnerability verdict. Each finding also carries an agent-actionable `candidate` (`source_kind`/`sink`/`boundary`), where URL-category sinks may include `url_shape` (`fixed-origin-dynamic-path` or `dynamic-origin`), an optional `taint_flow` source-to-sink triple, and a stable `finding_id` (equal to the SARIF fingerprint) for cross-run correlation; there is no `impact` field (deciding exploitability is the agent's job). Set `surface: true` to include top-level `attack_surface[]` entries with defensive-boundary prompts for a verifier. Set `gate` to `new` for changed-line candidates or `newly-reachable` for candidates that became reachable from entry points; `newly-reachable` requires `changed_since`. `reachability.untrusted_source_trace` is module-level import context only and does not prove value flow; `reachability.taint_confidence` tiers each reachable candidate as `arg-level` (sink argument traces to a same-module source read, strong) or `module-level` (only the module is import-reachable from a source, weak), so tier from this field instead of the evidence text. Verify trace, reachability context, severity, and evidence before editing code. Supports `root`, `config`, `workspace`, `paths`, `changed_since`, `changed_workspaces`, `surface`, `gate`, `no_cache`, and `threads`; `paths` forwards repeated `fallow security --file` filters for finding anchors, trace hops, untrusted-source reachability trace hops, and unresolved-callee diagnostics. See <https://fallow.tools/docs/cli/security-agent-verification/> for the verifier packet and verdict recipe. Inherits `FALLOW_DIFF_FILE` from the server environment for line-level diff scoping; raise `FALLOW_TIMEOUT_SECS` for large repos. |
19
19
  | `find_similar_code` | analysis | free | `fallow similar-code --format json --quiet` | `threshold`, `min_lines`, `top`, `changed_since`, `paths` | Find unverified semantically similar function candidates with the exact pinned local model. Discovery is read-only and never authorizes or performs model setup. Scoped output materializes the exact admitted files once in `generation.scope.paths` as provenance. Ask the user to run `fallow similar-code setup --local` when setup is missing. Cold local inference has a dedicated 15-minute subprocess window. |
20
20
  | `inspect_similar_code` | trace | free | `fallow similar-code inspect <candidate-id> --candidates <report.json> --format json --quiet` | `candidate_id`, `snapshot` | Inspect one exact unverified candidate without rerunning provider retrieval or global ranking. Pass `candidate_id` plus a bounded typed snapshot containing the unchanged discovery `schema_version`, `generation`, selected `candidate`, `completion`, and `diagnostics`. Current source is re-extracted and both endpoint hashes must still match, so stale source fails closed. Keep candidate worthiness, behavioral equivalence, and refactor safety as separate verdicts, and abstain when evidence is incomplete. |
21
+ | `mutation_plan` | analysis | free | `fallow mutation --plan --format json --quiet` | `changed_since`, `all`, `paths`, `max_mutants`, `operators` | Static mutation plan for changed code: mutant ids, operators, dispositions, and static test files; runs no tests |
22
+ | `mutation_inspect` | trace | free | `fallow mutation inspect <mutant-id> [--report <report.json>] --format json --quiet` | `mutant_id`, `report` | Inspect one mutant of a saved mutation report (default: the last run report), with a stale status when its source changed; runs no tests |
21
23
  | `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production`, `include_churn` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Set `include_churn: true` to add target-level git churn from the health hotspot subsystem. Churn is off by default, and unavailable git history or analysis failures remain explicit section states. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Supports `root`, `config`, `production`, `workspace`, `include_churn`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
22
24
  | `guard` | introspection | free | `fallow guard <file> --format json --quiet` | `files` | Report the architecture rules that apply to given files before editing them: boundary zone, allowed import zones, forbidden calls, and rule-pack policies |
23
25
  | `find_dupes` | analysis | free | `fallow dupes --format json --quiet` | `mode`, `near`, `min_tokens`, `min_occurrences`, `top`, `threshold` | Code duplication detection. Set `near: true` for function-level clones with small structural edits. Set `changed_since` to scope to changed files since a git ref. Set `min_occurrences` (≥ 2, default 2) to hide pair-only clones and focus on widespread copy-paste. Each `clone_groups[]` entry carries a normalized `fingerprint` and `spread`; near groups also carry `similarity`. `stats.clone_groups_ignored` reports reviewed groups hidden by config, while `stats.near_candidates_skipped` signals bounded near comparisons that were skipped. Pass the fingerprint to `trace_clone` to inspect the group |
@@ -31,7 +33,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
31
33
  | `get_cloud_review_packet` | runtime-coverage | freemium | `fallow coverage review-packet --repo <owner/repo> --file <path> --format json --quiet` | `repo`, `files`, `functions`, `period_days`, `project_id` | Production facts of a few changed files or functions, read from fallow cloud without the full runtime-context pull |
32
34
  | `get_cloud_deployment_changes` | runtime-coverage | freemium | `fallow coverage deployment-changes --repo <owner/repo> --sha <sha> --format json --quiet` | `repo`, `sha`, `base`, `change` | How production behavior changed between two deployments, read from the fallow cloud change report |
33
35
  | `get_token_blast_radius` | analysis | free | `fallow health --css --format json --quiet` | - | Design-token blast radius for Tailwind v4 @theme tokens and CSS-in-JS token definitions (StyleX, vanilla-extract, PandaCSS): per token, a consumer_count (static lower bound) and a capped located consumers[] sample tagged theme-var/css-var/utility/apply (Tailwind), js-member (member access), or js-call (StyleX theme-group and Panda token calls); descriptive context for sizing a token change, never a deletion gate |
34
- | `audit` | analysis | free | `fallow audit --format json --quiet` | `gate`, `base`, `css_deep`, `max_crap`, `coverage`, `runtime_coverage` | Combined dead-code + complexity + duplication + styling for changed files, returns verdict. Styling analytics are enabled by default; CSS and CSS-in-JS evidence can add `styling_findings`, `css_analytics`, and `styling_health` under the health sub-result. Set `gate` to `"new-only"` or `"all"`. Set `css_deep: false` to skip project-wide styling reachability while keeping local styling checks, or `css_deep: true` to force it back on when config disables it. Optional `runtime_coverage` (V8 dir / V8 JSON / Istanbul JSON) folds runtime findings into the same call; `min_invocations_hot` tunes the hot-path threshold (default 100). Runtime evidence appears under the audit `complexity` sub-result, including `coverage_intelligence` when combined evidence yields actionable recommendations. |
36
+ | `audit` | analysis | free | `fallow audit --format json --quiet` | `gate`, `base`, `skip`, `css_deep`, `max_crap`, `coverage`, `runtime_coverage` | Combined dead-code + complexity + duplication + styling + diff risk for changed files, returns verdict. The diff-risk rules (`weakened-tests`, `added-suppression`, `test-only-branch`, and the opt-in `diverged-clone-sibling` and `added-dependency`, both off by default) report facts about the change in `diff_risk.findings`. Styling analytics are enabled by default; CSS and CSS-in-JS evidence can add `styling_findings`, `css_analytics`, and `styling_health` under the health sub-result. Set `gate` to `"new-only"` or `"all"`. Set `css_deep: false` to skip project-wide styling reachability while keeping local styling checks, or `css_deep: true` to force it back on when config disables it. Optional `runtime_coverage` (V8 dir / V8 JSON / Istanbul JSON) folds runtime findings into the same call; `min_invocations_hot` tunes the hot-path threshold (default 100). Runtime evidence appears under the audit `complexity` sub-result, including `coverage_intelligence` when combined evidence yields actionable recommendations. |
35
37
  | `decision_surface` | analysis | free | `fallow decision-surface --format json --quiet` | `base`, `max_decisions`, `workspace` | Surface the few consequential structural decisions a change embeds (coupling, public API, dependency), each as a judgment question with the routed expert; ranked, capped, and signal_id-anchored |
36
38
  | `fallow_explain` | introspection | free | `fallow explain <issue-type> --format json --quiet` | `issue_type` | Explain one issue type without running analysis. Required `issue_type`; returns rationale, examples, fix guidance, and docs URL |
37
39
  | `fix_preview` | fix | free | `fallow fix --dry-run --format json --quiet` | `no_create_config` | Dry-run auto-fix preview |
@@ -43,13 +45,13 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
43
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 |
44
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 |
45
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) |
46
- | `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 |
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 |
47
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. |
48
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 |
49
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 |
50
52
  | `trace_import_path` | trace | free | `fallow trace --path <from> <to> --format json --quiet` | `from`, `to` | Trace the shortest import path between two modules, hop by hop |
51
53
  | `trace_error` | trace | free | `fallow trace-error - --format json --quiet` | `trace` | Resolve a runtime stack trace's frames against the project graph |
52
- | `impact_closure` | trace | free | `fallow dead-code --impact-closure <path> --format json --quiet` | `path` | Trace the transitive affected-but-not-in-diff set and coordination gaps for one file. Supports `root`, `config`, `production`, `workspace`, `no_cache`, and `threads`. Use as review-planning evidence for a file contract, not proof that affected files are wrong |
54
+ | `impact_closure` | trace | free | `fallow dead-code --impact-closure <path> --format json --quiet` | `path` | Trace the transitive affected-but-not-in-diff set and coordination gaps for one file. Supports `root`, `config`, `production`, `workspace`, `no_cache`, and `threads`. Use as review-planning evidence for a file contract, not proof that affected files are wrong. Each coordination gap has `search_visibility`. When `search_as` is present, search for that name in that consumer file |
53
55
  | `trace_dependency` | trace | free | `fallow dead-code --trace-dependency <package> --format json --quiet` | `package_name`, `specifiers`, `sites`, `limit`, `cursor` | Trace where a dependency is imported (`fallow dead-code --trace-dependency PACKAGE --format json`). Required `package_name`. Returns importing files, type-only importers, total import count, `used_in_scripts` (true when invoked from package.json scripts or CI configs), and `is_used` (combined import + script signal; mirrors the unused-deps detector so build tools like `microbundle` or `vitest` are not falsely flagged as unused). Use before removing a dependency or moving between `dependencies` and `devDependencies` |
54
56
  | `trace_clone` | trace | free | `fallow dupes --trace <file:line> --format json --quiet` | `file`, `line`, `fingerprint`, `near`, `min_occurrences` | Deep-dive a duplicate-code clone group (`fallow dupes --trace <spec> --format json`). Address by exactly one of: `file` + `line` (a source location), or `fingerprint` (a `dup:<id>` from a prior `find_dupes` `clone_groups[].fingerprint`, usually `dup:<8hex>` and widened only on rare report collisions). Returns the matched clone instance plus every clone group containing it; each traced group carries its `fingerprint`, an extract-function `suggestion` with estimated savings, and a best-effort `suggested_name` (omitted when no confident name). Supports `mode`, `near`, `min_tokens`, `min_lines`, `min_occurrences`, `threshold`, `skip_local`, `ignore_symlinks`, `cross_language`, `ignore_imports`. Use the same `near` value as the originating `find_dupes` call. Use to consolidate duplication when you need exact sibling locations and a refactor target |
55
57
  <!-- generated:mcp-tools:end -->
@@ -784,6 +784,7 @@ 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
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.
788
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.
789
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.