fallow 3.30.0 → 3.32.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.
@@ -52,7 +52,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
52
52
  | `doctor` | Diagnose project readiness without analysis or mutation | |
53
53
  | `similar-code` | Find semantically similar functions with a pinned local model (opt-in) | `--threshold`, `--min-lines`, `--top`, `--file` |
54
54
  | `inspect` | Compose one evidence bundle for a file or exported symbol | `--file <path>`, `--symbol <file>:<export>` |
55
- | `trace` | Trace a symbol's call chain (best-effort, syntactic; OFF the ranked path) | `symbol`, `--callers`, `--callees`, `--depth`, `--path`, `--eager-only` |
55
+ | `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` |
56
56
  | `trace-error` | Resolve a runtime stack trace's frames to the definitions they name (best-effort, syntactic; OFF the ranked path) | `trace_file` |
57
57
  | `fix` | Auto-remove unused exports/deps | `--dry-run`, `--yes` (required in non-TTY) |
58
58
  | `init` | Generate config file, AGENTS.md agent guide, or pre-commit hook | `--toml`, `--agents`, `--hooks`, `--branch` |
@@ -113,6 +113,7 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
113
113
  | `--symbol-impact` | `string` | - | Compute exact-symbol consumers, affected files, and targeted tests |
114
114
  | `--top` | `string` | - | Show only the top N items per category |
115
115
  | `--file` | `string` | - | Scope output to specific files. Only issues in the specified files are reported. Project-wide dependency issues are suppressed. Warns on non-existent paths. Useful for lint-staged |
116
+ | `--finding-id` | `string` | - | Only report the findings with these `finding_id` values. Repeat the flag or pass a comma-separated list. Ids stay the same under every filter. The JSON output adds `finding_id_query`: a missing id means "resolved" only when `conclusive` is true |
116
117
 
117
118
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags), [`--output-file`](#global-flags), [`--changed-since`](#global-flags), [`--max-file-size`](#global-flags), [`--production`](#global-flags), [`--no-production`](#global-flags), [`--production-dead-code`](#global-flags), [`--baseline`](#global-flags), [`--save-baseline`](#global-flags), [`--workspace`](#global-flags), [`--changed-workspaces`](#global-flags), [`--include-entry-exports`](#global-flags).
118
119
  <!-- generated:flags:dead-code:end -->
@@ -133,6 +134,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
133
134
  | `--unprovided-injects` | inject() / getContext() reads a key that no provide() / setContext() supplies |
134
135
  | `--unrendered-components` | A Vue / Svelte component is reachable through a barrel but rendered nowhere |
135
136
  | `--unused-component-props` | A Vue defineProps prop or React component prop is referenced nowhere in its own component |
137
+ | `--absent-component-props` | Known reachable callers omit an optional prop consumed inside its component |
136
138
  | `--unused-component-emits` | A Vue <script setup> defineEmits event is emitted nowhere in its own component |
137
139
  | `--unused-component-inputs` | An Angular @Input() / signal input() / model() is read nowhere in its own component (class body or template); needs `@angular/core` dep |
138
140
  | `--unused-component-outputs` | An Angular @Output() / signal output() is emitted (.emit()) nowhere in its own component; needs `@angular/core` dep |
@@ -144,6 +146,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
144
146
  | `--duplicate-exports` | Duplicate exports |
145
147
  | `--circular-deps` | Circular dependencies |
146
148
  | `--re-export-cycles` | Re-export cycles (`kind: multi-node` for barrel files re-exporting from each other in a loop, `kind: self-loop` for a barrel re-exporting from itself). File-scoped finding; chain propagation through the loop is a no-op so imports may silently come up empty. Distinct from `--circular-deps` (runtime cycles). |
149
+ | `--package-cycles` | Two or more workspace packages import each other in a loop |
147
150
  | `--boundary-violations` | Boundary violations (imports crossing architecture zone boundaries, unzoned source files when `boundaries.coverage.requireAllFiles` is set, and forbidden calls from `boundaries.calls.forbidden`; suppression token `boundary-violation`, with `boundary-call-violation` and `boundary-call-violations` accepted as aliases for the whole family) |
148
151
  | `--policy-violations` | Rule-pack policy violations (banned calls, imports, and catalogue-derived effects declared via the `rulePacks` config key) |
149
152
  | `--stale-suppressions` | Stale suppression comments or `@expected-unused` JSDoc tags |
@@ -153,6 +156,9 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
153
156
  | `--unused-dependency-overrides` | Unused package-manager dependency overrides |
154
157
  | `--misconfigured-dependency-overrides` | Misconfigured package-manager dependency overrides |
155
158
  <!-- generated:flags:dead-code-filters:end -->
159
+
160
+ `--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).
161
+
156
162
  ### Examples
157
163
 
158
164
  ```bash
@@ -228,12 +234,17 @@ By default, `fallow dupes` skips generated framework output matching `**/.next/*
228
234
  | `--cross-language` | `bool` | `false` | Strip type annotations for TS↔JS matching |
229
235
  | `--ignore-imports` | `bool` | `false` | Exclude module wiring from clone detection |
230
236
  | `--no-ignore-imports` | `bool` | `false` | Count module wiring as clone candidates (opt out of the default exclusion) |
237
+ | `--ignore-symlinks` | `bool` | `false` | Omit clone instances whose path is a symlink, or lies under a symlinked directory. A clone group with fewer than two remaining instances is not reported. Without this flag, JSON output marks these instances with `is_symlink: true` |
238
+ | `--no-ignore-symlinks` | `bool` | `false` | Report symlinked clone instances (opt out of a config `duplicates.ignoreSymlinks: true`) |
231
239
  | `--top` | `string` | - | Show only the N highest-ranked clone groups. Ranking multiplies token count and occurrences, then adds a capped spread boost for distant files or same-file locations. `clone_families[]` narrows with the groups. Summary stats reflect the scoped project; `clone_groups_shown` / `clone_groups_omitted` and `clone_families_shown` / `clone_families_omitted` report both splits. Refused with exit code 2 alongside `--group-by`, which reports per-bucket stats over every clone group in a bucket that a global top-N truncation would contradict. |
232
240
  | `--no-fragments` | `bool` | `false` | Omit the verbatim source text from each clone instance in `--format json`. The file and line/column range still address the same code, and this is most of the payload on a duplicated codebase |
233
241
  | `--trace` | `string` | - | Deep-dive clones. `FILE:LINE` traces all clones at a location; `dup:<id>` traces a clone group by the stable fingerprint shown in the listing and on `clone_groups[].fingerprint` in JSON. Fingerprints are usually `dup:<8hex>` and widen only on rare report collisions. Trace output adds an extract-function suggestion, estimated savings, and a best-effort proposed name per group |
234
242
 
235
243
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags), [`--changed-since`](#global-flags), [`--baseline`](#global-flags), [`--save-baseline`](#global-flags), [`--workspace`](#global-flags), [`--changed-workspaces`](#global-flags), [`--group-by`](#global-flags), [`--explain-skipped`](#global-flags).
236
244
  <!-- generated:flags:dupes:end -->
245
+
246
+ With `--group-by`, each clone group goes to its **largest owner** (the most instances; an alphabetical tiebreak): a group split 2 `src` / 1 `lib` appears under `src`. JSON adds `grouped_by` plus a `groups` array. Each bucket carries dedup-aware `stats`, `clone_groups` (each group has `primary_owner` and a per-instance `owner`), and `clone_families`. SARIF results carry `properties.group`, and CodeClimate issues carry a top-level `group` field. Compact and markdown output fall back to ungrouped output with a stderr note.
247
+
237
248
  ### Detection Modes
238
249
 
239
250
  | Mode | Behavior |
@@ -289,6 +300,9 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
289
300
 
290
301
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags).
291
302
  <!-- generated:flags:fix:end -->
303
+
304
+ `--force` is an alias for `--yes`.
305
+
292
306
  ### What gets fixed
293
307
 
294
308
  - Unused exports (removes the `export` keyword; whole-enum block when every member is unused)
@@ -363,11 +377,11 @@ fallow list --entry-weight --format json --quiet
363
377
  fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
364
378
  ```
365
379
 
366
- The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per runtime entry point, heaviest first. Each row has `eager_modules` and `eager_bytes` (the project modules and source bytes that load before the entry runs; `import type` and a declaration file do not count), `eager_css_bytes`, `deferred_modules` and `deferred_bytes` (reached only through `import()` or a lazy glob), `out_of_thread_modules` and `out_of_thread_bytes` (reached only through a `new URL(..., import.meta.url)` reference such as a worker URL, `child_process.fork`, a pino transport or a `module.register` hook), `eager_packages[]` with the specifiers as written, and `dominating_imports[]`. A dominating import is one import that alone keeps `exclusive_bytes` on the startup path. The unit is `source_bytes`: types and comments count, and tree shaking does not apply, so the value is not a bundle size. An import without the `type` keyword counts as eager, even when it brings in only types that TypeScript removes, so `eager_bytes` can be too high. Treat a dominating import as evidence for a review, not as a fix: a lazy load of code that the first screen needs can make startup slower.
380
+ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per runtime entry point, heaviest first. Each row has `eager_modules` and `eager_bytes` (the project modules and source bytes that load before the entry runs; `import type` and a declaration file do not count), `eager_css_bytes`, `deferred_modules` and `deferred_bytes` (reached only through `import()` or a lazy glob), `out_of_thread_modules` and `out_of_thread_bytes` (reached only through a `new URL(..., import.meta.url)` reference such as a worker URL, a webpack worker loader request such as `worker-loader!./work.js`, `child_process.fork`, a pino transport or a `module.register` hook), `eager_packages[]` with the specifiers as written, and `dominating_imports[]`. A dominating import is one import that alone keeps `exclusive_bytes` on the startup path. The unit is `source_bytes`: types and comments count, and tree shaking does not apply, so the value is not a bundle size. An import without the `type` keyword counts as eager, even when it brings in only types that TypeScript removes, so `eager_bytes` can be too high. Treat a dominating import as evidence for a review, not as a fix: a lazy load of code that the first screen needs can make startup slower.
367
381
 
368
382
  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.
369
383
 
370
- The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (`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`, `bun-lockb-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 two analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`) 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.
384
+ 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.
371
385
 
372
386
  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`.
373
387
 
@@ -410,26 +424,26 @@ fallow hooks uninstall --target git
410
424
  fallow hooks uninstall --target agent
411
425
  ```
412
426
 
413
- `hooks status` is read-only and reports `git`, `claude`, and `codex` surfaces. Each surface includes `installed`, `managed_block_present`, `user_edited`, and `path`; generated agent scripts also include `script_version` and `min_version_floor`. Use it before mutating setup so agents can distinguish fallow-managed artifacts from user-owned hooks or partial managed blocks.
427
+ `hooks status` is read-only and reports `git`, `claude`, `codex` (the `AGENTS.md` routing block), and `codex_gate` (the `.codex/hooks.json` gate) surfaces. Each surface includes `installed`, `managed_block_present`, `user_edited`, and `path`; generated agent scripts also include `script_version` and `min_version_floor`. Use it before mutating setup so agents can distinguish fallow-managed artifacts from user-owned hooks or partial managed blocks.
414
428
 
415
429
  ---
416
430
 
417
431
  ## `agent`: One-Pass Agent Onboarding
418
432
 
419
- Wires fallow into the coding-agent harnesses a project uses. `install` detects Claude Code, Codex, and Cursor from the project (`.claude/`, `CLAUDE.md`, `.mcp.json`, `.codex/`, `.cursor/`; `AGENTS.md` is not a signal because every harness and fallow itself write it), the home directory, and the session environment (`CLAUDECODE`, `CODEX_THREAD_ID`, `CURSOR_AGENT`), or takes `--harness`. When nothing is detected only harness-neutral files are written (`AGENTS.md` and `.agents/skills/fallow`).
433
+ Wires fallow into the coding-agent harnesses a project uses. `install` detects Claude Code, Codex, and Cursor from the project (`.claude/`, `CLAUDE.md`, `.mcp.json`, `.codex/`, `.cursor/`; `AGENTS.md` is not a signal because every harness and fallow itself write it), the home directory, and the session environment (`CLAUDECODE`, `CODEX_THREAD_ID`, `CURSOR_AGENT`), or takes `--harness`. When nothing is detected only harness-neutral files are written (`AGENTS.md`, `.agents/skills/fallow`, and `.agents/skills/fallow-setup`).
420
434
 
421
435
  Steps per harness:
422
436
 
423
437
  | Step | Claude Code | Codex | Cursor |
424
438
  |---|---|---|---|
425
439
  | `guide` | `AGENTS.md` task map; `CLAUDE.md` gains an `@AGENTS.md` import (created when absent, appended as a marked block otherwise) | `AGENTS.md` task map | `AGENTS.md` task map (Cursor reads it) |
426
- | `skill` | `.claude/skills/fallow/` | `.agents/skills/fallow/` | `.agents/skills/fallow/` |
440
+ | `skill` | `.claude/skills/fallow/` and `.claude/skills/fallow-setup/` | `.agents/skills/fallow/` and `.agents/skills/fallow-setup/` | `.agents/skills/fallow/` and `.agents/skills/fallow-setup/` |
427
441
  | `mcp` | `mcpServers.fallow` in `.mcp.json` (`--approve` also lists it in `.claude/settings.local.json`) | `[mcp_servers.fallow]` in `.codex/config.toml` (applies once the project is trusted; the `codex mcp add` next step works immediately) | `mcpServers.fallow` in `.cursor/mcp.json` |
428
- | `hooks` | `.claude/settings.json` PreToolUse gate plus `.claude/hooks/fallow-gate.sh` | marked gate block in `AGENTS.md` | skipped (`unsupported_harness`) |
442
+ | `hooks` | `.claude/settings.json` PreToolUse gate plus `.claude/hooks/fallow-gate.sh` | `.codex/hooks.json` PreToolUse gate plus `.codex/hooks/fallow-gate.sh` (Codex runs it after you trust it in `/hooks`), and a marked routing block in `AGENTS.md` | skipped (`unsupported_harness`) |
429
443
 
430
- The skill is a small pointer to `node_modules/fallow/skills/fallow` when that copy exists (so it never drifts from the installed binary); otherwise the tree embedded in the binary is written. The MCP command is probed before anything is written: `npx --no fallow-mcp` for an npm-installed project, `fallow-mcp` from `PATH`, or the running multicall binary; when none exists the step is `skipped` with `mcp_entry_unavailable` rather than writing a config that cannot start.
444
+ The step writes each released skill: `fallow` for analysis and `fallow-setup` for setting up code-quality tooling. Each skill is a small pointer to `node_modules/fallow/skills/<name>` when that copy exists (so it never drifts from the installed binary); otherwise the tree embedded in the binary is written. Each skill picks its source on its own, so an older npm package without `fallow-setup` still gets the embedded copy of that skill. The MCP command is probed before anything is written: `npx --no fallow-mcp` for an npm-installed project, `fallow-mcp` from `PATH`, or the running multicall binary; when none exists the step is `skipped` with `mcp_entry_unavailable` rather than writing a config that cannot start.
431
445
 
432
- Every file or block carries a `<!-- fallow:agent-install v1 ... -->` marker. Re-running is byte-stable. An existing skill named `fallow` without a marker is `refused` (`skill_name_taken`) unless `--force`. JSON and TOML cannot carry a marker, so a `fallow` MCP entry counts as fallow-managed only when its command is one fallow writes; any other entry is `refused` (`mcp_entry_foreign`) and never removed without `--force`. `--force` on an unparsable config file saves the old bytes as `<file>.fallow-bak` before rewriting. `uninstall` removes managed content, deletes a config file it emptied (and an emptied `.cursor/` or `.codex/` directory), and deletes `AGENTS.md` or `CLAUDE.md` only while the file still matches what fallow authored.
446
+ Every file or block carries a `<!-- fallow:agent-install v1 ... -->` marker. Re-running is byte-stable. An existing skill named `fallow` or `fallow-setup` without a marker is `refused` (`skill_name_taken`) unless `--force`; the other skill is still written. JSON and TOML cannot carry a marker, so a `fallow` MCP entry counts as fallow-managed only when its command is one fallow writes; any other entry is `refused` (`mcp_entry_foreign`) and never removed without `--force`. `--force` on an unparsable config file saves the old bytes as `<file>.fallow-bak` before rewriting. `uninstall` removes managed content, deletes a config file it emptied (and an emptied `.cursor/` or `.codex/` directory), and deletes `AGENTS.md` or `CLAUDE.md` only while the file still matches what fallow authored.
433
447
 
434
448
  ### Flags
435
449
 
@@ -440,7 +454,7 @@ Every file or block carries a `<!-- fallow:agent-install v1 ... -->` marker. Re-
440
454
  | `--dry-run` | `install`, `uninstall` | Print the plan without touching the filesystem |
441
455
  | `--force` | `install`, `uninstall` | Replace or remove skills, hook scripts, or config files fallow did not write |
442
456
  | `--approve` | `install` | Pre-approve the project MCP server for yourself in `.claude/settings.local.json`; refused when that file is tracked by git |
443
- | `--user` | `install`, `uninstall` | Skill and MCP config under `$HOME` (`~/.claude/skills`, `~/.agents/skills`, `~/.codex/config.toml`, `~/.cursor/mcp.json`); the guide step is skipped, and Claude Code prints the `claude mcp add --scope user` command instead of editing `~/.claude.json` |
457
+ | `--user` | `install`, `uninstall` | Skill, MCP config, and gate under `$HOME` (`~/.claude/skills`, `~/.agents/skills`, `~/.codex/config.toml`, `~/.cursor/mcp.json`, `~/.claude/hooks`, `~/.codex/hooks.json`); the guide step and the `AGENTS.md` routing block are skipped, and Claude Code prints the `claude mcp add --scope user` command instead of editing `~/.claude.json` |
444
458
  | `--gitignore-claude` | `install` | Append `.claude/` to `.gitignore` |
445
459
 
446
460
  Root: the git toplevel of the current directory unless `--root` is passed explicitly, so a run from a monorepo package still writes where the harnesses read. The chosen root is the first line of output and `root` in JSON.
@@ -453,7 +467,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
453
467
  {
454
468
  "kind": "agent-install",
455
469
  "schema_version": 1,
456
- "fallow_version": "3.30.0",
470
+ "fallow_version": "3.32.0",
457
471
  "root": "/abs/path",
458
472
  "mode": "install",
459
473
  "dry_run": false,
@@ -553,7 +567,8 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
553
567
  | `--min-commits` | `string` | - | Minimum number of commits for a file to be included in hotspot ranking. |
554
568
  | `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Forces file-scores + hotspot computation. |
555
569
  | `--trend` | `bool` | `false` | Compare current metrics against the most recent saved snapshot. Reads from `.fallow/snapshots/` and shows per-metric deltas with directional indicators (improving/declining/stable). Implies `--score`. |
556
- | `--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 static binary model. Relative paths resolve against `--root`. Falls back to `FALLOW_COVERAGE`, then `health.coverage`, then auto-detection. |
570
+ | `--trend-from` | `string` | - | Compare current metrics against this snapshot file instead of the newest file in `.fallow/snapshots/`. Use it to restore a baseline from external storage in CI. With --group-by, groups are compared by key when the snapshot holds the same grouping. Implies --trend |
571
+ | `--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. |
557
572
  | `--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`. |
558
573
  | `--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. |
559
574
  | `--min-invocations-hot` | `string` | `100` | Invocation threshold for hot-path classification. Takes effect only when `--runtime-coverage` is set. |
@@ -562,6 +577,11 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
562
577
 
563
578
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags), [`--changed-since`](#global-flags), [`--churn-file`](#global-flags), [`--workspace`](#global-flags), [`--group-by`](#global-flags), [`--baseline`](#global-flags), [`--baseline-mode`](#global-flags), [`--save-baseline`](#global-flags), [`--production`](#global-flags), [`--no-production`](#global-flags), [`--explain`](#global-flags).
564
579
  <!-- generated:flags:health:end -->
580
+
581
+ With `--workspace`, vital signs, the health score, hotspots, file scores, findings, and `summary.files_analyzed` are all recomputed against the scoped subset.
582
+
583
+ With `--group-by`, JSON adds `grouped_by` plus a `groups` array. Each group has its own `vital_signs`, `health_score`, `findings`, `file_scores`, `hotspots`, `large_functions`, and `targets`, recomputed against the files of the group. The top-level metrics stay project-wide, so a consumer that ignores grouping still sees the project headline. Human output adds a per-group score / files / hot / p90 summary block (worst first when `--score` is set). Markdown adds a grouped health table. SARIF results carry `properties.group`, and CodeClimate issues carry a top-level `group` field. Compact and badge output fall back to ungrouped output with a stderr note.
584
+
565
585
  ### Exit Codes
566
586
 
567
587
  The gate flag in play determines what drives the exit code. Plain `fallow health` (no gate flag) stays advisory but still fails on any finding (back-compat).
@@ -657,7 +677,7 @@ fallow health --format json --quiet --trend
657
677
  {
658
678
  "kind": "health",
659
679
  "schema_version": 7,
660
- "version": "3.30.0",
680
+ "version": "3.32.0",
661
681
  "elapsed_ms": 32,
662
682
  "summary": {
663
683
  "files_analyzed": 482,
@@ -850,15 +870,19 @@ All `health` JSON output includes a `vital_signs` object with project-wide metri
850
870
  }
851
871
  ```
852
872
 
853
- Fields are `null` when the corresponding data source is not available (e.g., `hotspot_count` is null without `--hotspots` or when git is not available). Health score formula v2 also uses scale-invariant density/tail fields: `critical_complexity_pct`, `hotspot_top_pct_count`, `maintainability_low_pct`, `unused_deps_per_k_files`, `circular_deps_per_k_files`, and `functions_over_60_loc_per_k`. The `unit_size_profile` and `unit_interfacing_profile` are risk distribution histograms (low risk / medium risk / high risk / very high risk as percentages). `p95_fan_in` is the 95th percentile of incoming dependencies. `coupling_high_pct` is the percentage of files above the effective coupling threshold.
873
+ Fields are `null` when the corresponding data source is not available (e.g., `hotspot_count` is null without `--hotspots` or when git is not available).
874
+
875
+ Health score formula v3 retains the scale-invariant density and tail fields: `critical_complexity_pct`, `maintainability_low_pct`, `unused_deps_per_k_files`, `circular_deps_per_k_files`, and `functions_over_60_loc_per_k`. The hotspot penalty is `hotspot_count / max(ceil(total_files × 0.01), 1) × 10`, capped at 10. Only files with a hotspot score of at least 50 enter `hotspot_count`; an empty scope has no hotspot penalty. `hotspot_top_pct_count` remains a rank diagnostic and does not determine the penalty.
876
+
877
+ The `unit_size_profile` and `unit_interfacing_profile` are risk distribution histograms (low risk / medium risk / high risk / very high risk as percentages). `p95_fan_in` is the 95th percentile of incoming dependencies. `coupling_high_pct` is the percentage of files above the effective coupling threshold.
854
878
 
855
879
  With `--score`, the JSON output includes a `health_score` object:
856
880
 
857
881
  ```json
858
882
  {
859
883
  "health_score": {
860
- "formula_version": 2,
861
- "score": 76.9,
884
+ "formula_version": 3,
885
+ "score": 72.9,
862
886
  "grade": "B",
863
887
  "penalties": {
864
888
  "dead_files": 3.1,
@@ -876,7 +900,7 @@ With `--score`, the JSON output includes a `health_score` object:
876
900
  }
877
901
  ```
878
902
 
879
- Score is reproducible: `100 - sum(penalties) == score`. `formula_version` identifies the scoring formula; version 2 uses scale-invariant density and tail metrics for monorepo-safe scoring. Penalty fields are absent when the pipeline didn't run. `--score` automatically runs duplication analysis; add `--hotspots` (or combine `--score --targets`) when the score should include the churn-backed hotspot penalty. Grades: A (>= 85), B (70-84), C (55-69), D (40-54), F (< 40).
903
+ Score is reproducible: `100 - sum(penalties) == score`. `formula_version` identifies the scoring formula; version 3 retains the density and tail metrics from version 2 and corrects the hotspot penalty to use the thresholded count. Penalty fields are absent when the pipeline didn't run. `--score` automatically runs duplication analysis; add `--hotspots` (or combine `--score --targets`) when the score should include the churn-backed hotspot penalty. Grades: A (>= 85), B (70-84), C (55-69), D (40-54), F (< 40).
880
904
 
881
905
  ### Health Trend
882
906
 
@@ -888,15 +912,16 @@ With `--trend`, the JSON output includes a `health_trend` object comparing curre
888
912
  "compared_to": {
889
913
  "timestamp": "2026-03-25T14:30:00Z",
890
914
  "git_sha": "a1b2c3d",
891
- "score": 74.2,
892
- "grade": "B"
915
+ "score": 70.2,
916
+ "grade": "B",
917
+ "score_formula_version": 3
893
918
  },
894
919
  "metrics": [
895
920
  {
896
921
  "name": "score",
897
922
  "label": "Health Score",
898
- "previous": 74.2,
899
- "current": 76.9,
923
+ "previous": 70.2,
924
+ "current": 72.9,
900
925
  "delta": 2.7,
901
926
  "direction": "improving",
902
927
  "unit": ""
@@ -919,7 +944,7 @@ With `--trend`, the JSON output includes a `health_trend` object comparing curre
919
944
  }
920
945
  ```
921
946
 
922
- Metrics tracked: `score`, `dead_file_pct`, `dead_export_pct`, `avg_cyclomatic`, `maintainability_avg`, `unused_dep_count`, `circular_dep_count`, `hotspot_count`, `unit_size_very_high_pct`, `p95_fan_in`, `duplication_pct`. Each metric includes `direction` (`improving`, `declining`, `stable`). Percentage metrics include `previous_count`/`current_count` with raw numerator/denominator. `--trend` requires at least one saved snapshot in `.fallow/snapshots/`. When comparing against a snapshot from an older schema version (current: v8), the trend output warns that score deltas may reflect formula changes.
947
+ Metrics tracked: `score`, `dead_file_pct`, `dead_export_pct`, `avg_cyclomatic`, `maintainability_avg`, `unused_dep_count`, `circular_dep_count`, `hotspot_count`, `unit_size_very_high_pct`, `p95_fan_in`, `duplication_pct`. Each metric includes `direction` (`improving`, `declining`, `stable`). Percentage metrics include `previous_count`/`current_count` with raw numerator and denominator. `--trend` requires at least one saved snapshot in `.fallow/snapshots/`. Current snapshots use schema version 12 and record `score_formula_version`. A score delta is emitted only when both scores exist and their formula versions are known and equal. An older snapshot without this metadata retains its historical score and grade, but has no score delta. Raw metric trends still compare across formula changes. The JSON baseline exposes the stored formula version when known; human, Markdown, GitHub and compact output explain an omitted score comparison.
923
948
 
924
949
  ### Vital Signs Snapshots
925
950
 
@@ -927,8 +952,9 @@ Metrics tracked: `score`, `dead_file_pct`, `dead_export_pct`, `avg_cyclomatic`,
927
952
 
928
953
  ```json
929
954
  {
930
- "snapshot_schema_version": 8,
955
+ "snapshot_schema_version": 12,
931
956
  "timestamp": "2025-12-01T10:30:00Z",
957
+ "score_formula_version": 3,
932
958
  "vital_signs": {
933
959
  "dead_file_pct": 3.2,
934
960
  "dead_export_pct": 8.1,
@@ -1060,7 +1086,7 @@ fallow audit \
1060
1086
  {
1061
1087
  "kind": "audit",
1062
1088
  "schema_version": 7,
1063
- "version": "3.30.0",
1089
+ "version": "3.32.0",
1064
1090
  "command": "audit",
1065
1091
  "verdict": "fail",
1066
1092
  "changed_files_count": 12,
@@ -1148,7 +1174,7 @@ fallow flags --format json --quiet --workspace my-package
1148
1174
  ```json
1149
1175
  {
1150
1176
  "schema_version": 7,
1151
- "version": "3.30.0",
1177
+ "version": "3.32.0",
1152
1178
  "elapsed_ms": 116,
1153
1179
  "feature_flags": [],
1154
1180
  "total_flags": 0
@@ -1249,7 +1275,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1249
1275
  {
1250
1276
  "kind": "security",
1251
1277
  "schema_version": "4",
1252
- "version": "3.30.0",
1278
+ "version": "3.32.0",
1253
1279
  "elapsed_ms": 42,
1254
1280
  "config": {
1255
1281
  "rules": {
@@ -1278,7 +1304,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1278
1304
  {
1279
1305
  "kind": "security",
1280
1306
  "schema_version": "4",
1281
- "version": "3.30.0",
1307
+ "version": "3.32.0",
1282
1308
  "elapsed_ms": 42,
1283
1309
  "config": {
1284
1310
  "rules": {
@@ -1469,7 +1495,7 @@ fallow explain fallow/code-duplication --format json --quiet
1469
1495
  "rationale": "Named exports that are never imported by any other module in the project. Includes both direct exports and re-exports through barrel files. The export may still be used locally within the same file.",
1470
1496
  "example": "export const formatPrice = ... exists in src/money.ts, but no module imports formatPrice.",
1471
1497
  "how_to_fix": "Remove the export or make it file-local. If it is public API, import it from an entry point or add an intentional suppression with context.",
1472
- "docs": "https://docs.fallow.tools/explanations/dead-code#unused-exports"
1498
+ "docs": "https://fallow.tools/docs/explanations/dead-code/#unused-exports"
1473
1499
  }
1474
1500
  ```
1475
1501
 
@@ -1539,7 +1565,7 @@ Pack files can also reference the published schema directly: `"$schema": "https:
1539
1565
 
1540
1566
  ---
1541
1567
 
1542
- ## `license`: Manage Continuous Runtime License
1568
+ ## `license`: Manage the Fallow Cloud License
1543
1569
 
1544
1570
  Manage the local JWT used to unlock continuous/cloud runtime monitoring. Single-capture local runtime analysis does not require a license. Verification is fully offline against an Ed25519 public key compiled into the binary. Only `--trial` and `refresh` hit the network (`api.fallow.cloud`, 5s connect / 10s total timeout).
1545
1571
 
@@ -1559,7 +1585,7 @@ fallow license deactivate
1559
1585
  |------------|---------|
1560
1586
  | `activate` | Install a JWT or start a 30-day trial. JWT input precedence: positional arg > `--from-file` > `--stdin`. |
1561
1587
  | `status` | Print tier, seats, features, days-until-expiry, and (when `refresh_after` has passed) a proactive refresh hint. |
1562
- | `refresh` | Fetch a fresh JWT using the currently stored one as identity proof. Exit 7 on network failure. |
1588
+ | `refresh` | Fetch a fresh JWT using the currently stored one as identity proof, falling back to a full-access API key when that token is missing or too stale. Exit 7 on network failure. |
1563
1589
  | `deactivate` | Remove the local license file. |
1564
1590
 
1565
1591
  ### `activate` flags
@@ -1571,6 +1597,19 @@ fallow license deactivate
1571
1597
  | `--from-file <PATH>` | path | Read a JWT from a file. |
1572
1598
  | `--stdin` | bool | Read a JWT from stdin. Conflicts with `--from-file` and positional JWT. |
1573
1599
 
1600
+ ### `refresh` flags
1601
+
1602
+ | Flag | Type | Description |
1603
+ |------|------|-------------|
1604
+ | `--api-key <KEY>` | string | Full-access API key used as the bearer when the stored license JWT is missing or the cloud reports it as `token_stale`. Precedence: this flag > `$FALLOW_API_KEY`. Prefer the environment variable on shared runners so the key stays out of argv. |
1605
+
1606
+ ### Credential order for `refresh`
1607
+
1608
+ 1. The stored license JWT (`FALLOW_LICENSE`, `FALLOW_LICENSE_PATH`, or `~/.fallow/license.jwt`).
1609
+ 2. A full-access API key, when the stored JWT is absent or the cloud answers `token_stale`.
1610
+
1611
+ A machine that has not run fallow for weeks holds a JWT the cloud refuses, and the trial endpoint rejects an organisation that already pays, so the API key is the recovery path. With neither credential available, `refresh` names that route instead of the trial flow.
1612
+
1574
1613
  ### Storage precedence
1575
1614
 
1576
1615
  1. `FALLOW_LICENSE` (env var holding the full JWT string)
@@ -1591,7 +1630,7 @@ On HTTP error from `api.fallow.cloud`, fallow parses the `{error, message, code}
1591
1630
 
1592
1631
  | Operation + code | CLI message |
1593
1632
  |------------------|-------------|
1594
- | `refresh` + `token_stale` | `your stored license is too stale to refresh. Reactivate with: fallow license activate --trial --email <addr>` |
1633
+ | `refresh` + `token_stale` | your stored license is too stale to refresh: set `FALLOW_API_KEY` to a full-access key and run `fallow license refresh` again (generate one at `https://fallow.cloud/settings#api-keys`) |
1595
1634
  | `refresh` + `invalid_token` | `your stored license token is missing required claims. Reactivate with: fallow license activate --trial --email <addr>` |
1596
1635
  | `refresh` or `trial` + `unauthorized` | `authentication failed. Reactivate with: fallow license activate --trial --email <addr>` |
1597
1636
  | `trial` + `rate_limit_exceeded` | `trial creation is rate-limited to 5 per hour per IP. Wait an hour or retry from a different network (in CI, start the trial locally and set FALLOW_LICENSE on the runner).` |
@@ -1674,11 +1713,13 @@ The command does not enable tracking. Only the user may opt in with
1674
1713
 
1675
1714
  ## `coverage`: Production-Coverage Workflow
1676
1715
 
1677
- Helper subcommand for runtime coverage setup, focused analysis, and cloud inventory upload. Three subcommands today:
1716
+ Use the `coverage` subcommands for runtime setup, analysis, inventory upload, and scoped cloud reads:
1678
1717
 
1679
1718
  - `coverage setup` - resumable state machine that wires sidecar installation, framework-aware coverage recipe writing, optional license activation for continuous monitoring, and automatic handoff into `fallow health --runtime-coverage`.
1680
1719
  - `coverage analyze` - focused runtime coverage analysis. Local mode reads `--runtime-coverage <path>`; cloud mode requires explicit `--cloud`, `--runtime-coverage-cloud`, or `FALLOW_RUNTIME_COVERAGE_SOURCE=cloud` and never triggers from `FALLOW_API_KEY` alone.
1681
- - `coverage upload-inventory` - push a static function inventory to fallow cloud so the dashboard can surface `untracked` functions (those in the codebase but never called at runtime).
1720
+ - `coverage upload-inventory` - push a static function inventory to Fallow Cloud so the dashboard can surface `untracked` functions (those in the codebase but never called at runtime).
1721
+ - `coverage review-packet` - read the production facts of changed files or functions from Fallow Cloud as JSON, with no local analysis and no full runtime-context pull. `--file <path>` and `--function <file>:<name>[:<line>]` select the scope; with neither, the source files changed against `--base` (resolved like `fallow audit`) are sent.
1722
+ - `coverage deployment-changes` - read the Fallow Cloud deployment change report for `--sha` (default `HEAD`) against `--base` (default: the previous deployment with production runtime) as JSON.
1682
1723
 
1683
1724
  ```bash
1684
1725
  fallow coverage setup # interactive
@@ -1690,6 +1731,10 @@ fallow coverage setup --yes --json --explain # add _meta field docs, enums, war
1690
1731
  fallow coverage analyze --runtime-coverage ./coverage --format json
1691
1732
  fallow coverage analyze --cloud --repo owner/repo --format json
1692
1733
 
1734
+ fallow coverage review-packet --repo owner/repo # files changed against the merge-base
1735
+ fallow coverage review-packet --repo owner/repo --file src/api.ts
1736
+ fallow coverage deployment-changes --repo owner/repo --sha <sha> --base <sha>
1737
+
1693
1738
  fallow coverage upload-inventory # infers project-id, git-sha, API key
1694
1739
  fallow coverage upload-inventory --dry-run # print what would be uploaded, exit 0
1695
1740
 
@@ -1712,7 +1757,7 @@ fallow coverage upload-source-maps --dry-run # print maps and fileNam
1712
1757
  |------|------|---------|-------------|
1713
1758
  | `--runtime-coverage <PATH>` | path | none | Local V8 directory, V8 JSON file, or Istanbul coverage map. Mutually exclusive with cloud mode. |
1714
1759
  | `--cloud`, `--runtime-coverage-cloud` | bool | false | Explicitly fetch cloud runtime facts from `/v1/coverage/:repo/runtime-context`. |
1715
- | `--api-key <KEY>` | string | `$FALLOW_API_KEY` | Fallow cloud bearer token, used only after explicit cloud opt-in. |
1760
+ | `--api-key <KEY>` | string | `$FALLOW_API_KEY` | Fallow Cloud bearer token, used only after explicit cloud opt-in. |
1716
1761
  | `--api-endpoint <URL>` | string | `$FALLOW_API_URL` or `https://api.fallow.cloud` | Override for staging / on-prem. |
1717
1762
  | `--repo <OWNER/REPO>` | string | `$FALLOW_REPO`, then parsed git origin | Repository whose latest cloud runtime facts should be pulled. Slashes are percent-encoded as one route segment. |
1718
1763
  | `--coverage-period <DAYS>` | integer | 30 | Cloud observation window, 1 through 90 days. |
@@ -1722,13 +1767,14 @@ fallow coverage upload-source-maps --dry-run # print maps and fileNam
1722
1767
  | `--top <N>` | integer | unset | Show only the top N runtime findings, hot paths, blast-radius entries, and importance entries. Truncation happens before rendering, so it propagates to JSON, human, and cloud-merge output equally. |
1723
1768
  | `--blast-radius` | bool | false | Show the first-class blast-radius section in human output. JSON always includes `runtime_coverage.blast_radius` whenever runtime coverage analysis runs. |
1724
1769
  | `--importance` | bool | false | Show the first-class importance section in human output. JSON always includes `runtime_coverage.importance` whenever runtime coverage analysis runs. |
1770
+ | `--debug-unmatched` | bool | false | Cloud mode only. List every cloud runtime function with no local counterpart on stderr, highest traffic first, instead of only counting them in the `cloud_functions_unmatched` warning. Stdout stays machine-readable. |
1725
1771
  | `--production` | bool | false | Run analyze in production mode, matching `fallow health --production`. Filters out test files and dev-only code paths before merging runtime data. |
1726
1772
  | `--min-invocations-hot <N>` | integer | 100 | Hot-path classification threshold. Functions invoked at least N times during the captured window are classified as hot. Mirrors the same flag on `fallow health --runtime-coverage`. |
1727
1773
  | `--min-observation-volume <N>` | integer | 5000 | Minimum total trace volume before the sidecar emits high-confidence `safe_to_delete` / `review_required` verdicts. Below this, confidence is capped at `medium`. |
1728
1774
  | `--low-traffic-threshold <RATIO>` | decimal | 0.001 | Fraction of total trace count below which an invoked function is classified `low_traffic` rather than `active`. `0.001` = 0.1%. |
1729
1775
  | `--explain` | bool | false | With `--format json`, attach a top-level `_meta` block with field definitions, enum values (`data_source`, `test_coverage`, `v8_tracking`, `action_type`, etc.), warning-code documentation, and the docs URL. |
1730
1776
 
1731
- Cloud analysis emits the same `runtime_coverage` JSON block as local mode. Its summary includes `data_source: "cloud"`, `last_received_at`, and `capture_quality` derived from the pulled runtime window. Cloud functions that cannot be matched to the local AST/static index are omitted from findings and reported through a `cloud_functions_unmatched` warning.
1777
+ Cloud analysis emits the same `runtime_coverage` JSON block as local mode. Its summary includes `data_source: "cloud"`, `last_received_at`, and `capture_quality` derived from the pulled runtime window. Cloud functions that cannot be matched to the local AST/static index are omitted from findings and reported through a `cloud_functions_unmatched` warning. Matching resolves a runtime file path onto the local tree by file name plus a segment-wise suffix comparison, so a containerized `/app/src/a.ts` reaches `src/a.ts`, and a function whose runtime name differs from the source name (an anonymous callback carrying its callee's name, an accessor keeping its `get` prefix) is matched on position within that file. Ambiguity is never guessed: two local files equally entitled to a runtime path, or two definitions opening on one line with no end line to separate them, stay unmatched. Pass `--debug-unmatched` to list what remains.
1732
1778
 
1733
1779
  Each finding's `actions[].type` uses the canonical kebab-case vocabulary: `delete-cold-code` is emitted on `verdict=safe_to_delete`, `review-runtime` on `verdict=review_required`. The sidecar may emit additional protocol-specific identifiers, so consumers should treat unknown values as forward-compat extensions rather than schema violations.
1734
1780
 
@@ -1738,7 +1784,7 @@ Under `--production` the evidence block also carries `test_only_reference`. It i
1738
1784
 
1739
1785
  | Flag | Type | Default | Description |
1740
1786
  |------|------|---------|-------------|
1741
- | `--api-key <KEY>` | string | `$FALLOW_API_KEY` | Fallow cloud bearer token. Generate at `https://fallow.cloud/settings#api-keys`. **Prefer `$FALLOW_API_KEY` on shared CI runners**: `--api-key` on the command line may be visible to other processes via `ps`. |
1787
+ | `--api-key <KEY>` | string | `$FALLOW_API_KEY` | Fallow Cloud bearer token. Generate at `https://fallow.cloud/settings#api-keys`. **Prefer `$FALLOW_API_KEY` on shared CI runners**: `--api-key` on the command line may be visible to other processes via `ps`. |
1742
1788
  | `--api-endpoint <URL>` | string | `$FALLOW_API_URL` or `https://api.fallow.cloud` | Override for staging / on-prem. |
1743
1789
  | `--project-id <OWNER/REPO>` | string | `$GITHUB_REPOSITORY` → `$CI_PROJECT_PATH` → `git remote get-url origin` | Project identifier. |
1744
1790
  | `--git-sha <SHA>` | string | `git rev-parse HEAD` | Commit SHA this inventory is keyed to. Max 64 chars; `[A-Za-z0-9._-]` only. |
@@ -1753,7 +1799,7 @@ Only plain JS/TS/JSX/TSX sources are walked. Declaration files (`*.d.ts`, `*.d.m
1753
1799
  ### Environment
1754
1800
 
1755
1801
  - `FALLOW_COV_BIN` - explicit override for the sidecar binary (for `setup`). Wins over all other discovery paths. Must point to an existing file.
1756
- - `FALLOW_API_KEY` - fallow cloud bearer token (for `upload-inventory` and `upload-source-maps`). Overridden by `--api-key` for `upload-inventory`; `upload-source-maps` reads only the env var so secrets stay out of argv.
1802
+ - `FALLOW_API_KEY` - Fallow Cloud bearer token (for `upload-inventory` and `upload-source-maps`). Overridden by `--api-key` for `upload-inventory`; `upload-source-maps` reads only the env var so secrets stay out of argv.
1757
1803
  - `FALLOW_API_URL` - base URL for cloud calls. Overridden by `--api-endpoint`.
1758
1804
  - `FALLOW_CA_BUNDLE` - PEM certificate bundle for cloud calls. Relative paths resolve from the process cwd. The bundle replaces default WebPKI roots, so private-CA runners should pass a complete bundle that includes public roots plus the private CA.
1759
1805
 
@@ -1841,6 +1887,7 @@ Available on all commands:
1841
1887
  | `--no-cache` | `bool` | `false` | Disable incremental caching |
1842
1888
  | `--threads` | `string` | - | Number of parser threads |
1843
1889
  | `--changed-since` | `string` | - | Only report issues in files changed since this git ref (e.g., main, HEAD~5) |
1890
+ | `--no-package-baselines` | `bool` | `false` | Ignore the per-package refs of `workspaces.changedSince` for this run |
1844
1891
  | `--diff-file` | `string` | - | Unified diff for line-level scoping. Use `-` to read from stdin. Project-level findings still bypass this filter. When both this and `--changed-since` are set, the diff filter wins for finding scope while `--changed-since` still drives file discovery |
1845
1892
  | `--diff-stdin` | `bool` | `false` | Read the unified diff from stdin. Equivalent to `--diff-file -` |
1846
1893
  | `--churn-file` | `string` | - | Import change history from a `fallow-churn/v1` JSON file instead of `git log`, powering hotspots, ownership, and bus-factor on projects with no git repository (Yandex Arc, Mercurial, Perforce). A small wrapper translates your VCS log into the contract. Resolved relative to `--root`. Affects `health --hotspots` / `--ownership` / `--targets` only; `audit`, `impact`, and `--changed-since` still require git |
@@ -1857,6 +1904,7 @@ Available on all commands:
1857
1904
  | `-w, --workspace` | `string` | - | Scope to one or more workspaces (comma-separated, globs, `!` negation) |
1858
1905
  | `--changed-workspaces` | `string` | - | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF`. Mutually exclusive with `--workspace`. Missing ref is a hard error. |
1859
1906
  | `--group-by` | `owner\|directory\|package\|section` | - | Group output by CODEOWNERS ownership (`owner`), first path component (`directory`), workspace package (`package`, aliases: `workspace`, `pkg`), or GitLab CODEOWNERS `[Section]` headers (`section`, alias: `gl-section`). All output formats partition issues into labeled groups. `section` mode attaches an `owners` array to each group in JSON output |
1907
+ | `--group` | `string` | - | Keep only the matching groups of a `--group-by` health run. Accepts exact group keys, glob patterns, and `!`-prefixed negations. Values can be comma-separated or repeated. Project-level sections are not filtered. Supported by `fallow health` only |
1860
1908
  | `--performance` | `bool` | `false` | Show pipeline timing breakdown |
1861
1909
  | `--explain` | `bool` | `false` | JSON: include metric definitions in `_meta`. Human: print a `Description:` line under each section header. Always on for MCP. |
1862
1910
  | `--explain-skipped` | `bool` | `false` | Human/markdown only: show per-pattern counts for files skipped by the default duplicates ignores. `dupes` prints only that breakdown; on `check`, `dead-code`, `audit` and the default run the same flag reports source files the built-in discovery ignores removed |
@@ -1868,6 +1916,8 @@ Available on all commands:
1868
1916
  | `--report-path-prefix` | `string` | - | Prefix prepended to every path in the CI-facing formats (`github-annotations`, `github-summary`, `codeclimate`, `review-github`, `review-gitlab`). CI platforms address files by repository-root-relative path, so when the analyzed project lives in a subdirectory (e.g. `packages/app/`), paths need that offset. fallow detects the offset via the git toplevel automatically; this flag overrides the detection. Pass an empty string to disable rebasing and emit paths relative to `--root` |
1869
1917
  | `--fail-on-regression` | `bool` | `false` | Fail if issue count increased beyond tolerance vs a regression baseline |
1870
1918
  | `--fail-on-stale-baseline` | `bool` | `false` | Exit with code 1 if a loaded --baseline has entries that match nothing in this run |
1919
+ | `--fail-on-baseline-growth` | `bool` | `false` | Exit with code 1 if a loaded baseline has a key that the same file at the base ref does not have |
1920
+ | `--baseline-base` | `string` | - | The git ref that --fail-on-baseline-growth compares the baseline with |
1871
1921
  | `--fail-on-parse-error` | `bool` | `false` | Exit with code 1 if fallow could not parse a source file cleanly |
1872
1922
  | `--tolerance` | `string` | `0` | Allowed increase: `"2%"` (percentage) or `"5"` (absolute). Default: `"0"` |
1873
1923
  | `--regression-baseline` | `string` | - | Path to a standalone regression baseline file. Without it, fallow uses `regression.baseline` from the config |
@@ -1884,8 +1934,11 @@ Available on all commands:
1884
1934
  | `--dupes-cross-language` | `bool` | `false` | Enable cross-language duplicate detection in combined mode |
1885
1935
  | `--dupes-ignore-imports` | `bool` | `false` | Exclude module wiring from duplicate detection in combined mode |
1886
1936
  | `--dupes-no-ignore-imports` | `bool` | `false` | Count module wiring as clone candidates in combined mode (opt out of the default exclusion) |
1937
+ | `--dupes-ignore-symlinks` | `bool` | `false` | Omit clone instances whose path is a symlink, or lies under a symlinked directory, in combined mode |
1938
+ | `--dupes-no-ignore-symlinks` | `bool` | `false` | Report symlinked clone instances in combined mode (opt out of a config `duplicates.ignoreSymlinks: true`) |
1887
1939
  | `--score` | `bool` | `false` | Compute health score (0-100 with letter grade) in combined mode. Enables the health delta header in PR comments. JSON includes `health_score` object with `score`, `grade`, and `penalties` breakdown |
1888
1940
  | `--trend` | `bool` | `false` | Compare current health metrics against saved snapshot. Implies `--score`. Shows per-metric deltas with directional indicators. Requires at least one saved snapshot in `.fallow/snapshots/` |
1941
+ | `--trend-from` | `string` | - | Compare current health metrics against this snapshot file in combined mode. Implies --trend and --score |
1889
1942
  | `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Default path: `.fallow/snapshots/<timestamp>.json`. Forces file-scores + hotspot computation |
1890
1943
  | `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for exact CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
1891
1944
  | `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
@@ -1948,12 +2001,14 @@ These are global flags with behavior specific to bare `fallow` combined mode.
1948
2001
  | `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
1949
2002
  | `FALLOW_CACHE_DIR` | Override the persistent extraction cache directory. Wins over `cache.dir`. Useful for read-only checkouts or CI cache volumes. `--no-cache` disables this knob. |
1950
2003
  | `FALLOW_CACHE_MAX_SIZE` | Maximum on-disk extraction cache (`.fallow/cache.bin`) size in megabytes (default: `256`). Triggers LRU eviction when crossed. Wins over `cache.maxSizeMb` config field. Intended for CI runners with disk quotas. `--no-cache` short-circuits this knob. |
2004
+ | `FALLOW_PACKAGE_BASELINES` | Set to `false`, `0`, `no` or `off` to ignore `workspaces.changedSince` for every run of the process, like `--no-package-baselines`. Other values keep the map. |
1951
2005
  | `FALLOW_COVERAGE` | Path to Istanbul or raw V8 coverage data for exact CRAP scoring in `health`, `audit`, and bare `fallow`. |
1952
2006
  | `FALLOW_COVERAGE_ROOT` | Absolute coverage-data prefix to strip before matching Istanbul paths in `health`, `audit`, and bare `fallow`. |
1953
2007
  | `FALLOW_TYPE_AWARE` | Enable or disable TypeScript semantic (type-aware) analysis for the run. Accepts `true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off`; any other value is a hard error. Sits mid-chain in the precedence: the `--type-aware`/`--no-type-aware` CLI flags win over it, and it wins over the `audit.typeAware` config field, which wins over `typeAware.enabled`. |
1954
2008
  | `FALLOW_AUDIT_BASE` | Pin the `fallow audit` comparison base when `--base` / `--changed-since` is unset (precedence: flag > env > auto-detect). Escape hatch for the agent gate and forks, e.g. `FALLOW_AUDIT_BASE=upstream/main`. When unset, audit auto-detects the `git merge-base` against the branch's upstream or the remote default. A malformed value exits 2. |
1955
2009
  | `FALLOW_AUDIT_CACHE_MAX_AGE_DAYS` | Max age (in days since last reuse or fresh create) of a persistent reusable `fallow audit` base-snapshot worktree cache. Older entries are reclaimed at the top of the next `fallow audit` invocation (default: `30`). Wins over `audit.cacheMaxAgeDays` config field. `0` disables the GC; invalid values log a warning and fall back to config / default. Each sweep also reclaims abandoned entries from other repo identities (deleted or moved repos, other git worktrees); entries whose recorded owner root still exists are left to that repo's own sweep and setting. |
1956
2010
  | `FALLOW_UPDATE_CHECK` | Set to `off`, `0`, `false`, `disabled`, or `no` to disable the human-TTY upgrade nudge and its background latest-version check. `DO_NOT_TRACK`, `FALLOW_TELEMETRY_DISABLED`, and CI also suppress it. |
2011
+ | `FALLOW_CLAUDE_CODE_HINT` | Set to `off`, `0`, `false`, `no`, or `disabled` to suppress the one-line Claude Code plugin hint. Fallow writes the hint to stderr only inside a Claude Code session, for human output without `--quiet`, outside CI, and when the project has no Fallow plugin or skill. |
1957
2012
  | `FALLOW_SUGGESTIONS` | Set to `off`, `0`, `false`, `no`, or `disabled` to suppress the top-level `next_steps[]` array of read-only follow-up commands in JSON output (and the human `Next:` line on bare `fallow`). Default on. Inherited by the MCP-spawned CLI, so it disables `next_steps` on MCP responses too. Useful for CI consumers that snapshot-diff raw `--format json`. |
1958
2013
  | `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
1959
2014
  | `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
@@ -2002,6 +2057,8 @@ Set `FALLOW_FORMAT=json` and `FALLOW_QUIET=1` in your agent environment to avoid
2002
2057
 
2003
2058
  Provider mutations are isolated per fingerprint. A failed mutation blocks only the remaining operations of that same fingerprint, which is retried whole on the next run, while every other stale fingerprint is still applied. (A preflight failure is different: preflight runs before any mutation, and a failure there abandons the whole plan because the state snapshot is untrustworthy.) If a preflight check, permission error, or provider mutation fails, JSON output keeps `apply_errors` and can add `apply_hint`, `failed_fingerprints`, and `unapplied_fingerprints` so agents and CI wrappers can report what was not fully applied. `fallow ci post-review` reports those same three fields for the reconcile pass it runs after posting new inline comments.
2004
2059
 
2060
+ Review comments end with a `<!-- fallow-fingerprint:v3: <fp> -->` marker. A dead-code fingerprint comes from the `finding_id`, so a line shift above the finding keeps the thread. When a comment had another fingerprint in an older release, the envelope comment carries that value as `legacy_fingerprint`. For one release, both commands match an open thread with the older `v2` marker through `legacy_fingerprint`: they do not post the finding again and do not resolve the thread as stale.
2061
+
2005
2062
  ### Flags
2006
2063
 
2007
2064
  | Flag | Type | Description |
@@ -2056,7 +2113,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2056
2113
  {
2057
2114
  "kind": "dead-code",
2058
2115
  "schema_version": 7,
2059
- "version": "3.30.0",
2116
+ "version": "3.32.0",
2060
2117
  "elapsed_ms": 45,
2061
2118
  "total_issues": 12,
2062
2119
  "entry_points": {
@@ -2216,7 +2273,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2216
2273
  {
2217
2274
  "kind": "dupes",
2218
2275
  "schema_version": 7,
2219
- "version": "3.30.0",
2276
+ "version": "3.32.0",
2220
2277
  "elapsed_ms": 82,
2221
2278
  "total_clones": 15,
2222
2279
  "total_lines_duplicated": 230,
@@ -2260,11 +2317,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2260
2317
  {
2261
2318
  "kind": "combined",
2262
2319
  "schema_version": 7,
2263
- "version": "3.30.0",
2320
+ "version": "3.32.0",
2264
2321
  "elapsed_ms": 159,
2265
2322
  "check": {
2266
2323
  "schema_version": 7,
2267
- "version": "3.30.0",
2324
+ "version": "3.32.0",
2268
2325
  "elapsed_ms": 45,
2269
2326
  "total_issues": 12,
2270
2327
  "unused_files": [],
@@ -2327,8 +2384,8 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
2327
2384
  // Files to ignore (glob patterns)
2328
2385
  "ignorePatterns": ["**/*.generated.ts", "**/*.d.ts"],
2329
2386
 
2330
- // Dependencies to ignore
2331
- "ignoreDependencies": ["autoprefixer"],
2387
+ // Dependencies to ignore (exact names, or globs such as "@acme/*")
2388
+ "ignoreDependencies": ["autoprefixer", "@acme/*"],
2332
2389
 
2333
2390
  // Suppress unused-export findings when the symbol is referenced inside its
2334
2391
  // declaring file (knip parity). Boolean or { type, interface } object form.
@@ -2371,7 +2428,7 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
2371
2428
  "ignoreDefaults": true,
2372
2429
  "ignoredClones": ["dup:6f12ab34:2"],
2373
2430
  "skipLocal": false,
2374
- "ignorePatterns": ["**/*.generated.ts"]
2431
+ "ignore": ["**/*.generated.ts"]
2375
2432
  },
2376
2433
 
2377
2434
  // Extraction cache settings. FALLOW_CACHE_DIR overrides cache.dir.
@@ -2476,9 +2533,11 @@ preset = "bulletproof"
2476
2533
  - `dynamicallyLoaded`: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-used
2477
2534
  - `cache.dir`: override the persistent extraction cache directory. `FALLOW_CACHE_DIR` wins over this config field, and `--no-cache` disables caching entirely
2478
2535
  - `cache.maxSizeMb`: cap the serialized extraction cache size in megabytes. `FALLOW_CACHE_MAX_SIZE` wins over this config field
2536
+ - `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`.
2479
2537
  - `usedClassMembers`: class method/property names that extend the built-in Angular/React lifecycle allowlist with framework-invoked names. Each entry is a plain string (global suppression) or a scoped object `{ extends?, implements?, members }` matching only classes with the given heritage. Strings can be exact names (`"agInit"`) or glob patterns (`"*"` matches every member, `"enter*"` prefix, `"*Handler"` suffix, `"on*Event"` combined). Use scoped rules for common names like `refresh` or `execute` to avoid false negatives on unrelated classes; global strings for unique names like `agInit`. Example: `["agInit", { "implements": "ICellRendererAngularComp", "members": ["refresh"] }, { "extends": "BaseCommand", "members": ["execute"] }, { "extends": "GrammarBaseListener", "members": ["enter*", "exit*"] }]`. Glob patterns that match zero members emit a `WARN` so dead allowlist entries surface. An unconstrained scoped rule (no `extends` or `implements`) is rejected at load time. Use plugin-level `usedClassMembers` in a `.fallow/plugins/*.jsonc` file for library-specific allowlists
2480
2538
  - `resolve.conditions`: additional package.json `exports` / `imports` condition names to honor during module resolution. Baseline conditions (`development`, `import`, `require`, `default`, `types`, `node`, plus `react-native` / `browser` under RN/Expo) are always included; user entries prepend ahead of them. Use for community conditions like `worker`, `edge-light`, `deno`, or custom bundler conditions. Example: `{ "resolve": { "conditions": ["worker", "edge-light"] } }`
2481
2539
  - `unusedComponentProps.ignorePattern`: opt-in regex that exempts a component prop from `unused-component-props` when the prop's LOCAL destructure binding name matches (the leading-underscore "accepted-but-intentionally-unused" convention, mirroring TS `noUnusedParameters` + ESLint `varsIgnorePattern` / `argsIgnorePattern`). Applies to Vue, Svelte, Astro, and React/Preact props. The match is on the local alias (`_stage` in `let { stage: _stage } = $props()`), not the public prop name the finding reports (`stage`); matching is unanchored like ESLint's `RegExp.test`, so anchor with `^_`. An invalid regex fails config load. Example: `{ "unusedComponentProps": { "ignorePattern": "^_" } }`
2540
+ - `circularDependencies.ignoreLazyImports`: opt-in boolean, default `false`. When `true`, `circular-dependencies` skips an import edge on which every import loads its target on demand or on another thread: an `import()` inside a function, a template `import()`, a lazy `import.meta.glob`, a worker URL, or a webpack worker loader request such as `worker-loader!./work.js`. A top-level `await import()`, `require()`, an eager `import.meta.glob`, and an edge that also has a static import stay. The filter runs before cycles are counted, so lazy cycles do not use the per-group cycle limit. Example: `{ "circularDependencies": { "ignoreLazyImports": true } }`
2482
2541
 
2483
2542
  ---
2484
2543