fallow 2.91.0 → 2.93.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.
@@ -20,6 +20,7 @@ Complete command and flag specifications for all fallow CLI commands.
20
20
  - [`schema`: CLI Introspection](#schema-cli-introspection)
21
21
  - [`config-schema`: Config JSON Schema](#config-schema-config-json-schema)
22
22
  - [`plugin-schema`: Plugin JSON Schema](#plugin-schema-plugin-json-schema)
23
+ - [`rule-pack-schema`: Rule Pack JSON Schema](#rule-pack-schema-rule-pack-json-schema)
23
24
  - [`config`: Show Resolved Config](#config-show-resolved-config)
24
25
  - [Global Flags](#global-flags)
25
26
  - [Environment Variables](#environment-variables)
@@ -42,17 +43,17 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
42
43
  | `--quiet` | bool | `false` | Suppress progress bars and timing on stderr |
43
44
  | `-o, --output-file` | path | (none) | Write the report to a file instead of stdout, for any `--format` (no ANSI codes). Progress and a confirmation stay on stderr (suppressed by `--quiet`). Valid with dead-code/dupes/health/security/bare; composes with `--sarif-file`. |
44
45
  | `--legacy-envelope` | bool | `false` | Remove the top-level `kind` field from typed JSON roots for one migration cycle |
45
- | `--changed-since` | string | | Only analyze files changed since a git ref (e.g., `main`, `HEAD~3`) |
46
+ | `--changed-since` | string | - | Only analyze files changed since a git ref (e.g., `main`, `HEAD~3`) |
46
47
  | `--max-file-size` | int (MB) | `5` | Skip source files larger than N megabytes at discovery instead of parsing them (`0` disables). Guards against the OOM a single multi-MB generated/vendored/bundled file causes on large repos. `.d.ts` files are always analyzed. Skipped files appear on stderr and in `--format json` under `workspace_diagnostics` (`kind: "skipped-large-file"`). Also settable via `FALLOW_MAX_FILE_SIZE`. Global flag. |
47
48
  | `--production` | bool | `false` | Exclude test/dev files, only start/build scripts (applies to every analysis) |
48
49
  | `--no-production` | bool | `false` | Force production mode off, overriding a project config's `production: true` (applies to every analysis; conflicts with `--production`) |
49
50
  | `--production-dead-code` | bool | `false` | Per-analysis production mode for dead-code. Bare combined runs and `fallow audit` only. |
50
51
  | `--production-health` | bool | `false` | Per-analysis production mode for health. Bare combined runs and `fallow audit` only. |
51
52
  | `--production-dupes` | bool | `false` | Per-analysis production mode for duplication. Bare combined runs and `fallow audit` only. |
52
- | `--baseline` | path | | Compare against a saved baseline |
53
- | `--save-baseline` | path | | Save current results as a baseline |
54
- | `--workspace` | string | | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. |
55
- | `--changed-workspaces` | string (git ref) | | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF` (e.g. `origin/main`). Auto-derives the workspace set from `git diff`. Mutually exclusive with `--workspace`. Missing ref is a hard error (exit 2), not silent full-scope fallback. |
53
+ | `--baseline` | path | - | Compare against a saved baseline |
54
+ | `--save-baseline` | path | - | Save current results as a baseline |
55
+ | `--workspace` | string | - | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. |
56
+ | `--changed-workspaces` | string (git ref) | - | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF` (e.g. `origin/main`). Auto-derives the workspace set from `git diff`. Mutually exclusive with `--workspace`. Missing ref is a hard error (exit 2), not silent full-scope fallback. |
56
57
  | `--include-dupes` | bool | `false` | Cross-reference with duplication findings |
57
58
  | `--dupes-mode` | enum | `config` | Override duplicate detection mode in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --mode`. |
58
59
  | `--dupes-threshold` | number | `config` | Override the duplication percentage failure threshold in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --threshold`. |
@@ -62,11 +63,11 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
62
63
  | `--dupes-skip-local` | bool | `config` | Only report cross-directory duplicates in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --skip-local`. |
63
64
  | `--dupes-cross-language` | bool | `config` | Enable TypeScript to JavaScript duplicate matching in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --cross-language`. |
64
65
  | `--dupes-ignore-imports` | bool | `config` | Exclude import declarations from duplicate detection in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --ignore-imports`. |
65
- | `--file` | path (multiple) | | 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 |
66
+ | `--file` | path (multiple) | - | 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 |
66
67
  | `--include-entry-exports` | bool | `false` | Report unused exports in entry files (package.json `main`/`exports`, framework pages). Catches typos like `meatdata` vs `metadata`. Global flag, also accepted on combined mode (`fallow --include-entry-exports`) and `fallow audit`. Also configurable as `includeEntryExports: true` in fallow config |
67
- | `--trace` | `FILE:EXPORT` | | Trace export usage chain |
68
- | `--trace-file` | path | | Show all edges for a file |
69
- | `--trace-dependency` | string | | Trace where a dependency is used |
68
+ | `--trace` | `FILE:EXPORT` | - | Trace export usage chain |
69
+ | `--trace-file` | path | - | Show all edges for a file |
70
+ | `--trace-dependency` | string | - | Trace where a dependency is used |
70
71
 
71
72
  ### Issue Type Filters
72
73
 
@@ -84,7 +85,8 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
84
85
  | `--duplicate-exports` | Duplicate exports |
85
86
  | `--circular-deps` | Circular dependencies |
86
87
  | `--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). |
87
- | `--boundary-violations` | Boundary violations (imports crossing architecture zone boundaries) |
88
+ | `--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) |
89
+ | `--policy-violations` | Rule-pack policy violations (banned calls and banned imports declared via the `rulePacks` config key) |
88
90
  | `--stale-suppressions` | Stale suppression comments or `@expected-unused` JSDoc tags |
89
91
  | `--unused-catalog-entries` | Unused pnpm catalog entries |
90
92
  | `--empty-catalog-groups` | Empty named pnpm catalog groups |
@@ -157,7 +159,7 @@ By default, `fallow dupes` skips generated framework output matching `**/.next/*
157
159
  |------|------|---------|-------------|
158
160
  | `--format` | `human\|json\|sarif\|compact\|markdown\|codeclimate\|gitlab-codequality\|pr-comment-github\|pr-comment-gitlab\|review-github\|review-gitlab` | `human` | Output format |
159
161
  | `--quiet` | bool | `false` | Suppress progress bars |
160
- | `--top` | number | | Show only the N most-duplicated clone groups (sorted by instance count desc, tiebreak: line count desc, then path/line). Summary stats reflect the full project. |
162
+ | `--top` | number | - | Show only the N most-duplicated clone groups (sorted by instance count desc, tiebreak: line count desc, then path/line). Summary stats reflect the full project. |
161
163
  | `--mode` | `strict\|mild\|weak\|semantic` | `mild` | Detection mode |
162
164
  | `--min-tokens` | number | `50` | Minimum token count for a clone |
163
165
  | `--min-lines` | number | `5` | Minimum line count for a clone |
@@ -167,13 +169,13 @@ By default, `fallow dupes` skips generated framework output matching `**/.next/*
167
169
  | `--cross-language` | bool | `false` | Strip type annotations for TS↔JS matching |
168
170
  | `--ignore-imports` | bool | `false` | Exclude import declarations from clone detection |
169
171
  | `--explain-skipped` | bool | `false` | Human/markdown only: show per-pattern counts for files skipped by default duplicates ignores |
170
- | `--trace` | `FILE:LINE` \| `dup:<fp>` | | 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 |
171
- | `--changed-since` | string | | Only report duplication in files changed since a git ref |
172
- | `--baseline` | path | | Compare against baseline |
173
- | `--save-baseline` | path | | Save results as baseline |
174
- | `--workspace` | string | | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. |
175
- | `--changed-workspaces` | string (git ref) | | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF`. Mutually exclusive with `--workspace`. Missing ref is a hard error. |
176
- | `--group-by` | `owner\|directory\|package\|section` | | Partition the report into per-group sections. Each clone group is attributed to its **largest owner** (most instances; 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` (every group tagged with `primary_owner` and per-instance `owner`), and `clone_families`. SARIF results carry `properties.group`, CodeClimate issues a top-level `group` field. Compact and markdown fall back to ungrouped with a stderr note. |
172
+ | `--trace` | `FILE:LINE` \| `dup:<fp>` | - | 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 |
173
+ | `--changed-since` | string | - | Only report duplication in files changed since a git ref |
174
+ | `--baseline` | path | - | Compare against baseline |
175
+ | `--save-baseline` | path | - | Save results as baseline |
176
+ | `--workspace` | string | - | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. |
177
+ | `--changed-workspaces` | string (git ref) | - | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF`. Mutually exclusive with `--workspace`. Missing ref is a hard error. |
178
+ | `--group-by` | `owner\|directory\|package\|section` | - | Partition the report into per-group sections. Each clone group is attributed to its **largest owner** (most instances; 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` (every group tagged with `primary_owner` and per-instance `owner`), and `clone_families`. SARIF results carry `properties.group`, CodeClimate issues a top-level `group` field. Compact and markdown fall back to ungrouped with a stderr note. |
177
179
 
178
180
  ### Detection Modes
179
181
 
@@ -312,6 +314,7 @@ Creates a config file in the project root.
312
314
  | Flag | Type | Description |
313
315
  |------|------|-------------|
314
316
  | `--toml` | bool | Create `fallow.toml` instead of `.fallowrc.json` |
317
+ | `--agents` | bool | Scaffold a starter `AGENTS.md` guide for coding agents. Prefills Install (from the `packageManager` field, or pnpm via `pnpm-workspace.yaml`), Test (only when exactly one of Vitest / Jest / Playwright is present), Typecheck (`tsc --noEmit` when `tsconfig.json` exists), and monorepo module-boundary lines; everything ambiguous stays blank (no lockfile sniffing). Prefilled command lines carry an HTML provenance comment. Refuses to overwrite an existing `AGENTS.md` |
315
318
  | `--hooks` | bool | Scaffold a pre-commit git hook that runs `fallow audit --base <ref> --quiet`. Alias for `fallow hooks install --target git` |
316
319
  | `--branch` | string | Fallback base branch for the pre-commit hook when no upstream is set (default: `main`). Only used with `--hooks` |
317
320
 
@@ -320,6 +323,7 @@ Creates a config file in the project root.
320
323
  ```bash
321
324
  fallow init # creates .fallowrc.json with $schema
322
325
  fallow init --toml # creates fallow.toml
326
+ fallow init --agents # scaffolds a starter AGENTS.md prefilled from detected project info (never overwrites)
323
327
  fallow hooks install --target git
324
328
  fallow hooks install --target git --branch develop # fallback base branch when no upstream is set
325
329
  ```
@@ -375,28 +379,28 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
375
379
  | `--max-cyclomatic` | number | `20` | Fail if any function exceeds this cyclomatic complexity |
376
380
  | `--max-cognitive` | number | `15` | Fail if any function exceeds this cognitive complexity |
377
381
  | `--max-crap` | number | `30.0` | Fail if any function has CRAP score >= threshold. CRAP combines complexity with coverage (`CC^2 * (1 - cov/100)^3 + CC`). Pair with `--coverage` for accurate per-function CRAP; without Istanbul data fallow estimates coverage from the module graph. |
378
- | `--top` | number | | Only show the top N most complex functions (and file scores/hotspots/targets) |
382
+ | `--top` | number | - | Only show the top N most complex functions (and file scores/hotspots/targets) |
379
383
  | `--sort` | `cyclomatic\|cognitive\|lines\|severity` | `cyclomatic` | Sort order for complexity findings |
380
384
  | `--complexity` | bool | `false` | Show only function complexity findings. When no section flags are set, all sections are shown by default. |
381
385
  | `--complexity-breakdown` | bool | `false` | Add a per-decision-point `contributions[]` array to each complexity finding in `--format json`. Each entry names the construct (`if`, `else-if`, `ternary`, boolean operator, loop, `case`, `catch`, `optional-chain`, ...) and carries its source line, the metric it adds to (`cyclomatic` or `cognitive`), its weight, and the nesting depth, so a consumer can explain WHY a function scored high. Off by default (no change to existing JSON/SARIF/markdown). Used by the VS Code inline editor breakdown and the MCP `check_health` `complexity_breakdown` param. |
382
386
  | `--file-scores` | bool | `false` | Show only per-file health scores (maintainability index, LOC, fan-in, fan-out, dead code ratio, complexity density, CRAP risk). Runs the full analysis pipeline. Sorted by risk-aware triage concern: lower maintainability index and higher CRAP risk first. When no section flags are set, all sections are shown by default. |
383
387
  | `--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. |
384
- | `--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. |
385
- | `--effort` | `low\|medium\|high` | | Filter refactoring targets by effort level. Implies `--targets`. |
388
+ | `--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. |
389
+ | `--effort` | `low\|medium\|high` | - | Filter refactoring targets by effort level. Implies `--targets`. |
386
390
  | `--score` | bool | `false` | Show only the project health score (0-100) with letter grade (A/B/C/D/F). The score is included by default when no section flags are set. JSON includes `health_score` object with `score`, `grade`, and `penalties` breakdown. As of v2.55.0, plain `--score` skips the churn-backed hotspot penalty so it does not run a `git log` shell-out per invocation; pass `--hotspots` (or `--targets` with `--score`) to include the hotspot penalty. Snapshot (`--save-snapshot`) and trend (`--trend`) flows still trigger hotspot vital signs so saved data stays complete. |
387
- | `--min-score` | number | | Fail (exit 1) only when the health score is below this threshold. Implies `--score`. Authoritative CI quality gate: when set, complexity findings are demoted to informational and the exit code is driven solely by the score, so `--min-score 0` always exits 0. Composes with `--min-severity`. |
388
- | `--min-severity` | `moderate\|high\|critical` | | Only exit with an error for findings at or above this severity. Composes with `--min-score` (the run fails if either gate trips). |
391
+ | `--min-score` | number | - | Fail (exit 1) only when the health score is below this threshold. Implies `--score`. Authoritative CI quality gate: when set, complexity findings are demoted to informational and the exit code is driven solely by the score, so `--min-score 0` always exits 0. Composes with `--min-severity`. |
392
+ | `--min-severity` | `moderate\|high\|critical` | - | Only exit with an error for findings at or above this severity. Composes with `--min-score` (the run fails if either gate trips). |
389
393
  | `--report-only` | bool | `false` | Print the score and findings but never fail CI (always exit 0). Advisory mode. Mutually exclusive with `--min-score` and `--min-severity`. |
390
394
  | `--since` | string | `6m` | Git history window for hotspot analysis. Accepts durations (`6m`, `90d`, `1y`, `2w`) or ISO dates (`2025-06-01`). Ignored when `--churn-file` is set. |
391
395
  | `--min-commits` | number | `3` | Minimum number of commits for a file to be included in hotspot ranking. |
392
396
  | `--churn-file` | string | (none) | Import change history from a `fallow-churn/v1` JSON file (`{schema, events:[{path, timestamp, author, added, deleted}]}`, one entry per changed file per commit) instead of `git log`, so `--hotspots`/`--ownership`/`--targets` work on non-git VCS (Yandex Arc, Mercurial, Perforce). Resolved relative to `--root`; wins over git. Authoritative for the window, so `--since` then only labels output. Malformed file exits 2; `audit`/`impact`/`--changed-since` still require git. |
393
397
  | `--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. |
394
398
  | `--ownership-emails` | `raw\|handle\|anonymized\|hash` | `handle` | 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`. |
395
- | `--changed-since` | string | | Only analyze files changed since a git ref |
396
- | `--workspace` | string | | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. Vital signs, health score, hotspots, file scores, findings, and `summary.files_analyzed` are all recomputed against the scoped subset. |
397
- | `--group-by` | `owner\|directory\|package\|section` | | Partition the report into per-group sections. JSON adds `grouped_by` plus a `groups` array; each group contains its own `vital_signs`, `health_score`, `findings`, `file_scores`, `hotspots`, `large_functions`, and `targets` recomputed against the group's files. The top-level metrics stay project-wide so consumers that ignore grouping still see the project headline. Human output adds a per-group score / files / hot / p90 summary block (sorted worst-first when `--score`). SARIF results carry `properties.group` and CodeClimate issues carry a top-level `group` field so GitHub Code Scanning / GitLab Code Quality can partition per team / package. Compact, markdown, and badge fall back to ungrouped output with a stderr note. |
398
- | `--baseline` | path | | Compare against a saved baseline. When set, the JSON `actions` array on each finding omits `suppress-line` (the baseline already suppresses) and the report root carries an `actions_meta: { suppression_hints_omitted: true, reason: "baseline-active" }` breadcrumb. |
399
- | `--save-baseline` | path | | Save current results as a baseline. Same `suppress-line` omission as `--baseline`. |
399
+ | `--changed-since` | string | - | Only analyze files changed since a git ref |
400
+ | `--workspace` | string | - | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. Vital signs, health score, hotspots, file scores, findings, and `summary.files_analyzed` are all recomputed against the scoped subset. |
401
+ | `--group-by` | `owner\|directory\|package\|section` | - | Partition the report into per-group sections. JSON adds `grouped_by` plus a `groups` array; each group contains its own `vital_signs`, `health_score`, `findings`, `file_scores`, `hotspots`, `large_functions`, and `targets` recomputed against the group's files. The top-level metrics stay project-wide so consumers that ignore grouping still see the project headline. Human output adds a per-group score / files / hot / p90 summary block (sorted worst-first when `--score`). SARIF results carry `properties.group` and CodeClimate issues carry a top-level `group` field so GitHub Code Scanning / GitLab Code Quality can partition per team / package. Compact, markdown, and badge fall back to ungrouped output with a stderr note. |
402
+ | `--baseline` | path | - | Compare against a saved baseline. When set, the JSON `actions` array on each finding omits `suppress-line` (the baseline already suppresses) and the report root carries an `actions_meta: { suppression_hints_omitted: true, reason: "baseline-active" }` breadcrumb. |
403
+ | `--save-baseline` | path | - | Save current results as a baseline. Same `suppress-line` omission as `--baseline`. |
400
404
  | `--save-snapshot` | path (optional) | `.fallow/snapshots/<timestamp>.json` | Save vital signs snapshot for trend tracking. Forces file-scores + hotspot computation. |
401
405
  | `--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`. |
402
406
  | `--coverage-gaps` | bool | `false` | Show runtime files and exports that no test dependency path reaches. Opt-in (default off). Configure severity via the `coverage-gaps` rule (`error`/`warn`/`off`). |
@@ -498,7 +502,7 @@ fallow health --format json --quiet --trend
498
502
  {
499
503
  "kind": "health",
500
504
  "schema_version": 7,
501
- "version": "2.91.0",
505
+ "version": "2.93.0",
502
506
  "elapsed_ms": 32,
503
507
  "summary": {
504
508
  "files_analyzed": 482,
@@ -612,7 +616,7 @@ With `--targets`, the JSON output includes a `targets` array with ranked refacto
612
616
  "path": "src/parser.ts",
613
617
  "priority": 82.5,
614
618
  "efficiency": 27.5,
615
- "recommendation": "Split high-impact file 25 dependents amplify every change",
619
+ "recommendation": "Split high-impact file - 25 dependents amplify every change",
616
620
  "category": "split_high_impact",
617
621
  "effort": "high",
618
622
  "confidence": "medium",
@@ -799,34 +803,34 @@ The snapshot `snapshot_schema_version` is independent of the report `schema_vers
799
803
 
800
804
  ## `audit`: Changed-File Quality Gate
801
805
 
802
- Audits changed files for dead code, complexity, and duplication. Returns a verdict (pass/warn/fail). Purpose-built for PR quality gates and reviewing AI-generated code. Auto-detects the base branch if `--base` is not set. Defaults to `--gate new-only`, which fails only on findings introduced by the current changeset and reports inherited findings as context.
806
+ Audits changed files for dead code, complexity, and duplication. Returns a verdict (pass/warn/fail). Purpose-built for PR quality gates and reviewing AI-generated code. When `--base` is not set, the base is the `git merge-base` against the branch's upstream or the remote default (`origin/HEAD`, `origin/main`, `origin/master`); set `FALLOW_AUDIT_BASE` to pin it without a flag. Defaults to `--gate new-only`, which fails only on findings introduced by the current changeset and reports inherited findings as context.
803
807
 
804
808
  ### Flags
805
809
 
806
810
  | Flag | Type | Default | Description |
807
811
  |------|------|---------|-------------|
808
- | `--base` | string | auto-detect | Git ref to compare against (alias for `--changed-since`) |
812
+ | `--base` | string | merge-base | Git ref to compare against (alias for `--changed-since`). When unset, resolves to the `git merge-base` against the upstream or remote default; `FALLOW_AUDIT_BASE` pins it without a flag. |
809
813
  | `--gate` | `new-only\|all` | `new-only` | Which findings affect the verdict. `new-only` gates only introduced findings; `all` gates every finding in changed files and skips the extra base-snapshot attribution pass. |
810
814
  | `--production` | bool | false | Exclude test/story/dev files (applies to dead-code, health, and dupes) |
811
815
  | `--no-production` | bool | false | Force production mode off, overriding a project config's `production: true` (conflicts with `--production`) |
812
816
  | `--production-dead-code` | bool | false | Per-analysis production mode for the dead-code sub-analysis only |
813
817
  | `--production-health` | bool | false | Per-analysis production mode for the health sub-analysis only |
814
818
  | `--production-dupes` | bool | false | Per-analysis production mode for the duplication sub-analysis only |
815
- | `-w, --workspace` | string | | Scope to one or more workspaces. Comma-separated, globs, `!` negation. |
819
+ | `-w, --workspace` | string | - | Scope to one or more workspaces. Comma-separated, globs, `!` negation. |
816
820
  | `--explain` | bool | false | JSON: include metric definitions in `_meta`. Human: print a `Description:` line under each section header. |
817
821
  | `--ci` | bool | false | Equivalent to `--format sarif --fail-on-issues --quiet` |
818
822
  | `--fail-on-issues` | bool | false | Exit with code 1 if issues are found |
819
- | `--sarif-file` | path | | Write SARIF output to a file alongside primary format |
820
- | `--dead-code-baseline` | path | | Baseline file (produced by `fallow dead-code --save-baseline`). Pre-existing dead-code issues are excluded from the verdict. |
821
- | `--health-baseline` | path | | Baseline file (produced by `fallow health --save-baseline`). Pre-existing complexity findings are excluded from the verdict. |
822
- | `--dupes-baseline` | path | | Baseline file (produced by `fallow dupes --save-baseline`). Pre-existing clone groups are excluded from the verdict. |
823
+ | `--sarif-file` | path | - | Write SARIF output to a file alongside primary format |
824
+ | `--dead-code-baseline` | path | - | Baseline file (produced by `fallow dead-code --save-baseline`). Pre-existing dead-code issues are excluded from the verdict. |
825
+ | `--health-baseline` | path | - | Baseline file (produced by `fallow health --save-baseline`). Pre-existing complexity findings are excluded from the verdict. |
826
+ | `--dupes-baseline` | path | - | Baseline file (produced by `fallow dupes --save-baseline`). Pre-existing clone groups are excluded from the verdict. |
823
827
  | `--max-crap` | number | `30.0` | 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. |
824
828
  | `--coverage` | path | none | Path to Istanbul-format coverage data (`coverage-final.json`) for accurate per-function CRAP scores in the health sub-analysis. Same format and semantics as `health --coverage`. Also configurable via `FALLOW_COVERAGE`. Relative paths resolve against `--root`. |
825
829
  | `--coverage-root` | path | none | Absolute prefix to strip from file paths in coverage data before prepending the project root. Use when coverage was generated under a different checkout root in CI / Docker (e.g., `/home/runner/work/myapp` on GitHub Actions). |
826
830
  | `--fail-on-regression` | bool | false | Fail if issues increased beyond tolerance vs regression baseline |
827
831
  | `--tolerance` | string | `0` | Allowed increase before regression fails (`N` or `N%`) |
828
832
  | `--regression-baseline` | path | `.fallow/regression-baseline.json` | Path to the regression baseline file |
829
- | `--save-regression-baseline` | path | | Save current issue counts as a regression baseline |
833
+ | `--save-regression-baseline` | path | - | Save current issue counts as a regression baseline |
830
834
 
831
835
  ### Verdicts
832
836
 
@@ -889,11 +893,12 @@ fallow audit \
889
893
  {
890
894
  "kind": "audit",
891
895
  "schema_version": 7,
892
- "version": "2.91.0",
896
+ "version": "2.93.0",
893
897
  "command": "audit",
894
898
  "verdict": "fail",
895
899
  "changed_files_count": 12,
896
- "base_ref": "main",
900
+ "base_ref": "611d151e8250146426ff3178e94207f8a8d3cc7b",
901
+ "base_description": "merge-base with origin/main",
897
902
  "head_sha": "d4a2f91",
898
903
  "elapsed_ms": 2140,
899
904
  "summary": {
@@ -942,7 +947,7 @@ Detects feature flag patterns in the codebase. Identifies environment variable f
942
947
  |------|------|---------|-------------|
943
948
  | `--format` | `human\|json\|sarif\|compact\|markdown\|codeclimate\|gitlab-codequality\|pr-comment-github\|pr-comment-gitlab\|review-github\|review-gitlab` | `human` | Output format |
944
949
  | `--quiet` | bool | `false` | Suppress progress bars |
945
- | `--top` | number | | Show only the top N flags |
950
+ | `--top` | number | - | Show only the top N flags |
946
951
 
947
952
  ### Examples
948
953
 
@@ -962,7 +967,7 @@ fallow flags --format json --quiet --workspace my-package
962
967
  ```json
963
968
  {
964
969
  "schema_version": 7,
965
- "version": "2.91.0",
970
+ "version": "2.93.0",
966
971
  "elapsed_ms": 116,
967
972
  "feature_flags": [],
968
973
  "total_flags": 0
@@ -1027,7 +1032,7 @@ The second rule family is a data-driven `tainted-sink` catalogue: syntactic dang
1027
1032
  | `secret-to-network` | 201 | a non-public `process.env` / `import.meta.env` secret reaching a network call body (`fetch` / `axios` / `got` / ...) via same-identifier flow (include-required) |
1028
1033
  | `xpath-injection` | 643 | `xpath.select` / `select1` with a non-literal expression |
1029
1034
 
1030
- Build-config and test files are excluded from candidate generation. Security rule families default to `off` and are surfaced only by `fallow security`, never under bare `fallow` or the `audit` gate. Scope which catalogue categories run with `security.categories` include / exclude lists in config. `hardcoded-secret` and `secret-to-network` are intentionally include-required and only run when listed in `security.categories.include` (`secret-to-network` is opt-in because legitimate auth is also a secret reaching a network call). Public-by-convention env vars (`NEXT_PUBLIC_`, `VITE_`, ...) are never treated as secrets.
1035
+ Build-config and test files are excluded from candidate generation. Security rule families default to `off` and are surfaced only by `fallow security`, never under bare `fallow` or the `audit` gate. Scope which catalogue categories run with `security.categories` include / exclude lists in config. Add project-local request object names with `security.requestReceivers`; it extends the built-in `req` / `request` / `ctx` / `context` / `event` allowlist for HTTP `query`, `params`, and `body` reads. The setting is additive only and does not gate `*.searchParams`. `hardcoded-secret` and `secret-to-network` are intentionally include-required and only run when listed in `security.categories.include` (`secret-to-network` is opt-in because legitimate auth is also a secret reaching a network call). Public-by-convention env vars (`NEXT_PUBLIC_`, `VITE_`, ...) are never treated as secrets.
1031
1036
 
1032
1037
  ### Flags
1033
1038
 
@@ -1044,7 +1049,7 @@ Build-config and test files are excluded from candidate generation. Security rul
1044
1049
  | `--diff-file` | path | none | Scope candidates to added hunks on the client anchor or import trace. Secret-source hops use file-level retention because member-access spans are not yet stored. Use `-` for stdin |
1045
1050
  | `--diff-stdin` | bool | `false` | Read the unified diff from stdin (equivalent to `--diff-file -`) for line-level scoping and the regression gate |
1046
1051
  | `--surface` | bool | `false` | Include the agent-facing `attack_surface[]` inventory in JSON output |
1047
- | `--gate` | `new` | none | Fail (exit code **8**) only when the change introduces a NEW security-sink candidate in the changed lines, not on the whole candidate backlog. Requires a diff source (`--changed-since`, `--diff-file`, or `--diff-stdin`); a diff the gate cannot compute is a loud exit 2, never a green gate. Human output says `REVIEW REQUIRED` (not `FAIL`); SARIF keeps every result at `level: note` with the verdict in `run.properties.fallowGate`; `--format json` carries an additive `gate` block (`mode` / `verdict` / `new_count`) |
1052
+ | `--gate` | `new\|newly-reachable` | none | `new` fails (exit code **8**) only when the change introduces a NEW security-sink candidate in the changed lines. It requires a diff source (`--changed-since`, `--diff-file`, or `--diff-stdin`). `newly-reachable` fails when an existing candidate becomes reachable from entry points compared with `--changed-since <ref>`; diff-only inputs exit 2 because this mode analyzes the base tree. Human output says `REVIEW REQUIRED` (not `FAIL`); SARIF keeps every result at `level: note` with the verdict in `run.properties.fallowGate`; `--format json` carries an additive `gate` block (`mode` / `verdict` / `new_count`) |
1048
1053
  | `--workspace` | string | none | Scope to selected workspace packages |
1049
1054
  | `--changed-workspaces` | git ref | none | Scope to workspaces changed since a git ref |
1050
1055
 
@@ -1057,6 +1062,9 @@ git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
1057
1062
  # Regression gate: fail (exit 8) only on candidates introduced in the changed lines
1058
1063
  fallow security --gate new --changed-since origin/main
1059
1064
  git diff --cached --unified=0 | fallow security --gate new --diff-stdin
1065
+
1066
+ # Reachability gate: fail when existing sinks become entry-point reachable
1067
+ fallow security --gate newly-reachable --changed-since origin/main
1060
1068
  ```
1061
1069
 
1062
1070
  ### JSON Output Structure
@@ -1064,19 +1072,92 @@ git diff --cached --unified=0 | fallow security --gate new --diff-stdin
1064
1072
  ```json
1065
1073
  {
1066
1074
  "kind": "security",
1067
- "schema_version": "2",
1075
+ "schema_version": "4",
1076
+ "version": "2.93.0",
1077
+ "elapsed_ms": 42,
1078
+ "config": {
1079
+ "rules": {
1080
+ "security_client_server_leak": {
1081
+ "configured": "off",
1082
+ "effective": "warn"
1083
+ },
1084
+ "security_sink": {
1085
+ "configured": "off",
1086
+ "effective": "warn"
1087
+ }
1088
+ },
1089
+ "categories_include": null,
1090
+ "categories_exclude": null
1091
+ },
1068
1092
  "security_findings": [],
1069
1093
  "unresolved_edge_files": 0,
1070
- "unresolved_callee_sites": 0
1094
+ "unresolved_callee_sites": 0,
1095
+ "unresolved_callee_diagnostics": null
1096
+ }
1097
+ ```
1098
+
1099
+ `fallow security --summary --format json --quiet` emits the same `kind`, `schema_version`, `version`, `elapsed_ms`, and `config` metadata, but replaces candidate arrays with `summary` aggregate counts:
1100
+
1101
+ ```json
1102
+ {
1103
+ "kind": "security",
1104
+ "schema_version": "4",
1105
+ "version": "2.93.0",
1106
+ "elapsed_ms": 42,
1107
+ "config": {
1108
+ "rules": {
1109
+ "security_client_server_leak": {
1110
+ "configured": "off",
1111
+ "effective": "warn"
1112
+ },
1113
+ "security_sink": {
1114
+ "configured": "off",
1115
+ "effective": "warn"
1116
+ }
1117
+ },
1118
+ "categories_include": null,
1119
+ "categories_exclude": null
1120
+ },
1121
+ "summary": {
1122
+ "security_findings": 0,
1123
+ "by_severity": {
1124
+ "high": 0,
1125
+ "medium": 0,
1126
+ "low": 0
1127
+ },
1128
+ "by_category": {},
1129
+ "by_reachability": {
1130
+ "entry_reachable": 0,
1131
+ "untrusted_source_reachable": 0,
1132
+ "arg_level": 0,
1133
+ "module_level": 0,
1134
+ "crosses_boundary": 0,
1135
+ "source_backed": 0
1136
+ },
1137
+ "by_runtime_state": {
1138
+ "runtime_hot": 0,
1139
+ "runtime_cold": 0,
1140
+ "never_executed": 0,
1141
+ "low_traffic": 0,
1142
+ "coverage_unavailable": 0,
1143
+ "runtime_unknown": 0,
1144
+ "not_collected": 0
1145
+ },
1146
+ "unresolved_edge_files": 0,
1147
+ "unresolved_callee_sites": 0,
1148
+ "attack_surface_entries": 0
1149
+ }
1071
1150
  }
1072
1151
  ```
1073
1152
 
1074
1153
  Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`; `client-server-leak` findings omit both. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module; it is ranking and triage context only, not proof that a specific value reaches the sink. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
1075
1154
 
1155
+ When present, `unresolved_callee_diagnostics` adds bounded unresolved-callee metadata for follow-up review: `sampled[]` rows with `path`, `line`, `col`, `reason`, and `expression_kind`, `top_files[]` counts, `by_reason[]` counts, and the emitted sample/top-file limits. It is blind-spot metadata, not a finding list, and follows the same `--file`, `--workspace`, `--changed-since`, and `--gate new` scoping as security candidates.
1156
+
1076
1157
  Every finding also carries an agent-actionable `candidate { source_kind, sink, boundary }`, an optional `taint_flow { source, sink, path }`, and a stable `finding_id`:
1077
1158
 
1078
1159
  - `candidate.source_kind`: the untrusted-input kind that reaches the sink, as a stable catalogue id (`"http-request-input"`, `"process-env"`, `"process-argv"`, `"message-event-data"`, `"location-input"`, ...). Absent when no source matched (always absent for `client-server-leak`). Treat an unknown id as an untrusted source of unknown kind; never drop the candidate on that basis.
1079
- - `candidate.sink`: a self-contained sink (`path`, `line`, `col`, `category`, `cwe`, `callee`), actionable without reading the rest of the finding.
1160
+ - `candidate.sink`: a self-contained sink (`path`, `line`, `col`, `category`, `cwe`, `callee`, optional `url_shape`), actionable without reading the rest of the finding. URL-category sinks use `url_shape` to distinguish `fixed-origin-dynamic-path` from `dynamic-origin` when the construction is statically visible.
1080
1161
  - `candidate.boundary`: `client_server` (a `"use client"` file in the trace), `cross_module` (the source reaches the sink across import hops), and optional `architecture_zone` (`from`/`to`) when the anchor also crosses a declared architecture boundary.
1081
1162
  - `candidate.network`: present only on `secret-to-network` (#890) candidates. `destination` is the network call's URL when it is a static literal (usually intended auth) or absent when the destination is dynamic (the higher-signal exfil case). Use it to triage exfil from intended auth without re-reading source.
1082
1163
  - There is no `impact` field: deciding exploitability is the verifying agent's job; `severity` is only the review-priority tier.
@@ -1120,14 +1201,24 @@ MCP equivalent: `fallow_explain` with required `issue_type`.
1120
1201
 
1121
1202
  ---
1122
1203
 
1123
- ## `schema`: CLI Introspection
1204
+ ## `schema`: Capability Manifest
1124
1205
 
1125
- Dumps the full CLI interface definition as machine-readable JSON.
1206
+ Dumps fallow's complete capability manifest as machine-readable JSON (always JSON, regardless of `--format`). The single source of truth for agent introspection.
1126
1207
 
1127
1208
  ```bash
1128
1209
  fallow schema
1129
1210
  ```
1130
1211
 
1212
+ Top-level blocks:
1213
+
1214
+ - `manifest_version`: manifest shape discriminator (currently `"1"`).
1215
+ - `commands` + `global_flags`: every CLI command and flag, derived live from the CLI definition.
1216
+ - `issue_types`: one row per reportable issue type across ALL analyses (dead-code, health, dupes, flags, security). Each row carries `id` (the bare rule id; several rows share one suppression token, e.g. all complexity rules suppress via `complexity`), `rule_id` (SARIF id), `command`, `category`, `filter_flag` (null when none), `fixable`, `suppressible`, `suppress_comment` (copy-pasteable, null when not suppressible), `note`, `license` (`free` | `freemium`), and `docs_url`. Nullable fields are always present (null, never absent).
1217
+ - `mcp_tools`: all MCP server tools with `kind` grouping (analysis/trace/fix/introspection/runtime-coverage/composition), one-line description, `key_params` (curated subset; live MCP `list_tools` schemas are authoritative), `license` + `license_note` (the 5 runtime-coverage tools are `freemium`: a single local capture is free, continuous monitoring is paid), and `read_only`.
1218
+ - `plugins`: built-in framework plugin count + names, derived live from the registry.
1219
+ - `environment_variables`: every user-facing `FALLOW_*` variable (internal plumbing excluded).
1220
+ - `output_formats`, `exit_codes`, `severity_levels`, `suppression_comments`.
1221
+
1131
1222
  ---
1132
1223
 
1133
1224
  ## `config-schema`: Config JSON Schema
@@ -1150,6 +1241,18 @@ fallow plugin-schema > plugin-schema.json
1150
1241
 
1151
1242
  ---
1152
1243
 
1244
+ ## `rule-pack-schema`: Rule Pack JSON Schema
1245
+
1246
+ Prints the JSON Schema for declarative rule pack files (the `rulePacks` config key), for editor autocomplete when authoring packs.
1247
+
1248
+ ```bash
1249
+ fallow rule-pack-schema > rule-pack-schema.json
1250
+ ```
1251
+
1252
+ Pack files can also reference the published schema directly: `"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/rule-pack-schema.json"`.
1253
+
1254
+ ---
1255
+
1153
1256
  ## `license`: Manage Continuous Runtime License
1154
1257
 
1155
1258
  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).
@@ -1177,7 +1280,7 @@ fallow license deactivate
1177
1280
 
1178
1281
  | Flag | Type | Description |
1179
1282
  |------|------|-------------|
1180
- | `--trial` | bool | Start a 30-day email-gated trial. Requires `--email`. **Rate-limited to 5 requests per hour per IP** in CI or behind a shared NAT, start the trial locally and set `FALLOW_LICENSE` on the runner. |
1283
+ | `--trial` | bool | Start a 30-day email-gated trial. Requires `--email`. **Rate-limited to 5 requests per hour per IP** - in CI or behind a shared NAT, start the trial locally and set `FALLOW_LICENSE` on the runner. |
1181
1284
  | `--email <ADDR>` | string | Email for the trial flow. On success, `trialEndsAt` is printed to stdout so you can see the trial window without decoding the JWT. |
1182
1285
  | `--from-file <PATH>` | path | Read a JWT from a file. |
1183
1286
  | `--stdin` | bool | Read a JWT from stdin. Conflicts with `--from-file` and positional JWT. |
@@ -1262,9 +1365,9 @@ The inspected payload prints to stderr; stdout (including `--format json`) is un
1262
1365
 
1263
1366
  Helper subcommand for runtime coverage setup, focused analysis, and cloud inventory upload. Three subcommands today:
1264
1367
 
1265
- - `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`.
1266
- - `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.
1267
- - `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).
1368
+ - `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`.
1369
+ - `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.
1370
+ - `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).
1268
1371
 
1269
1372
  ```bash
1270
1373
  fallow coverage setup # interactive
@@ -1287,10 +1390,10 @@ fallow coverage upload-source-maps --dry-run # print maps and fileNam
1287
1390
 
1288
1391
  ### `setup` flow
1289
1392
 
1290
- 1. **License check** if missing or hard-fail, offers to start a trial.
1291
- 2. **Sidecar discovery** resolves `FALLOW_COV_BIN`, `FALLOW_COV_BINARY_PATH`, platform-package binaries in npm/bun/pnpm layouts, project-local `node_modules/.bin/fallow-cov`, package-manager bin, `~/.fallow/bin/fallow-cov`, and `PATH`. When an explicit env path is set but points to a non-existent file, setup errors fast instead of falling through.
1292
- 3. **Coverage recipe** detects framework (Next.js, Nuxt, Astro, SvelteKit, Remix, NestJS, Vite browser apps, plain Node) and package manager (npm, pnpm, yarn, bun), then writes `docs/collect-coverage.md` with the correct commands.
1293
- 4. **Handoff** if `./coverage/coverage-final.json` or a V8 coverage directory already exists, setup runs `fallow health --runtime-coverage <path>` directly.
1393
+ 1. **License check** - if missing or hard-fail, offers to start a trial.
1394
+ 2. **Sidecar discovery** - resolves `FALLOW_COV_BIN`, `FALLOW_COV_BINARY_PATH`, platform-package binaries in npm/bun/pnpm layouts, project-local `node_modules/.bin/fallow-cov`, package-manager bin, `~/.fallow/bin/fallow-cov`, and `PATH`. When an explicit env path is set but points to a non-existent file, setup errors fast instead of falling through.
1395
+ 3. **Coverage recipe** - detects framework (Next.js, Nuxt, Astro, SvelteKit, Remix, NestJS, Vite browser apps, plain Node) and package manager (npm, pnpm, yarn, bun), then writes `docs/collect-coverage.md` with the correct commands.
1396
+ 4. **Handoff** - if `./coverage/coverage-final.json` or a V8 coverage directory already exists, setup runs `fallow health --runtime-coverage <path>` directly.
1294
1397
 
1295
1398
  ### `analyze` flags
1296
1399
 
@@ -1336,9 +1439,9 @@ Only plain JS/TS/JSX/TSX sources are walked. Declaration files (`*.d.ts`, `*.d.m
1336
1439
 
1337
1440
  ### Environment
1338
1441
 
1339
- - `FALLOW_COV_BIN` explicit override for the sidecar binary (for `setup`). Wins over all other discovery paths. Must point to an existing file.
1340
- - `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.
1341
- - `FALLOW_API_URL` base URL for cloud calls. Overridden by `--api-endpoint`.
1442
+ - `FALLOW_COV_BIN` - explicit override for the sidecar binary (for `setup`). Wins over all other discovery paths. Must point to an existing file.
1443
+ - `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.
1444
+ - `FALLOW_API_URL` - base URL for cloud calls. Overridden by `--api-endpoint`.
1342
1445
  - `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.
1343
1446
 
1344
1447
  ### `coverage upload-source-maps` flags
@@ -1455,8 +1558,10 @@ Available on all commands:
1455
1558
  | `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
1456
1559
  | `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. |
1457
1560
  | `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. |
1561
+ | `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. |
1458
1562
  | `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 silently fall back to config / default. |
1459
1563
  | `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. |
1564
+ | `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`. |
1460
1565
  | `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
1461
1566
  | `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
1462
1567
  | `FALLOW_CHANGED_SINCE` | GitLab CI: git ref for incremental analysis. Auto-detected in MR pipelines. |
@@ -1522,9 +1627,9 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1522
1627
 
1523
1628
  ## CI Integration
1524
1629
 
1525
- - **GitHub Actions**: `uses: fallow-rs/fallow@v2` supports SARIF upload to Code Scanning, inline PR annotations (`annotations: true`), PR comments, all commands. Annotations use workflow commands (no Advanced Security required); limit with `max-annotations` (default 50). Set `score: true` to compute health score and enable the health delta header in PR comments
1526
- - **GitLab CI**: include `ci/gitlab-ci.yml` template and extend `.fallow` generates Code Quality reports via `--format codeclimate` / `--format gitlab-codequality` (inline MR annotations), rich MR comments, code review comments, all commands. Use `fallow ci-template gitlab --vendor` when runners cannot reach `raw.githubusercontent.com`; commit the generated `ci/` and `action/` files and use GitLab's local include syntax. Variables use `FALLOW_` prefix (e.g., `FALLOW_COMMAND`, `FALLOW_FAIL_ON_ISSUES`). Set `FALLOW_SCORE: "true"` to compute health score; `FALLOW_TREND: "true"` to compare against saved snapshots
1527
- - **Any CI**: `npx fallow --ci` equivalent to `--format sarif --fail-on-issues --quiet`
1630
+ - **GitHub Actions**: `uses: fallow-rs/fallow@v2` - supports SARIF upload to Code Scanning, inline PR annotations (`annotations: true`), PR comments, all commands. Annotations use workflow commands (no Advanced Security required); limit with `max-annotations` (default 50). Set `score: true` to compute health score and enable the health delta header in PR comments
1631
+ - **GitLab CI**: include `ci/gitlab-ci.yml` template and extend `.fallow` - generates Code Quality reports via `--format codeclimate` / `--format gitlab-codequality` (inline MR annotations), rich MR comments, code review comments, all commands. Use `fallow ci-template gitlab --vendor` when runners cannot reach `raw.githubusercontent.com`; commit the generated `ci/` and `action/` files and use GitLab's local include syntax. Variables use `FALLOW_` prefix (e.g., `FALLOW_COMMAND`, `FALLOW_FAIL_ON_ISSUES`). Set `FALLOW_SCORE: "true"` to compute health score; `FALLOW_TREND: "true"` to compare against saved snapshots
1632
+ - **Any CI**: `npx fallow --ci` - equivalent to `--format sarif --fail-on-issues --quiet`
1528
1633
 
1529
1634
  ### GitLab CI Variables
1530
1635
 
@@ -1539,8 +1644,8 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1539
1644
  | `FALLOW_SUMMARY_SCOPE` | `all` | Sticky summary scope: `all` keeps project-level findings outside the diff; `diff` applies the diff filter to those findings too |
1540
1645
  | `FALLOW_SCORE` | `false` | Compute health score (0-100 with letter grade) in combined mode. Enables the health delta header in MR comments |
1541
1646
  | `FALLOW_TREND` | `false` | Compare current health metrics against saved snapshot. Implies `FALLOW_SCORE`. Shows per-metric deltas |
1542
- | `FALLOW_EXTRA_ARGS` | | Additional CLI flags passed through to fallow |
1543
- | `GITLAB_TOKEN` | | Project access token with `api` scope (required for `FALLOW_COMMENT` and `FALLOW_REVIEW`). Alternatively, enable job token API access |
1647
+ | `FALLOW_EXTRA_ARGS` | - | Additional CLI flags passed through to fallow |
1648
+ | `GITLAB_TOKEN` | - | Project access token with `api` scope (required for `FALLOW_COMMENT` and `FALLOW_REVIEW`). Alternatively, enable job token API access |
1544
1649
 
1545
1650
  **Package manager detection**: The GitLab template auto-detects the project's package manager (npm, pnpm, or yarn) from lockfiles. MR comments and review comments show the correct install/run commands for the detected manager (e.g., `pnpm add -D` vs `npm install --save-dev`).
1546
1651
 
@@ -1556,7 +1661,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1556
1661
  {
1557
1662
  "kind": "dead-code",
1558
1663
  "schema_version": 7,
1559
- "version": "2.91.0",
1664
+ "version": "2.93.0",
1560
1665
  "elapsed_ms": 45,
1561
1666
  "total_issues": 12,
1562
1667
  "entry_points": {
@@ -1716,7 +1821,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
1716
1821
  {
1717
1822
  "kind": "dupes",
1718
1823
  "schema_version": 7,
1719
- "version": "2.91.0",
1824
+ "version": "2.93.0",
1720
1825
  "elapsed_ms": 82,
1721
1826
  "total_clones": 15,
1722
1827
  "total_lines_duplicated": 230,
@@ -1760,11 +1865,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
1760
1865
  {
1761
1866
  "kind": "combined",
1762
1867
  "schema_version": 7,
1763
- "version": "2.91.0",
1868
+ "version": "2.93.0",
1764
1869
  "elapsed_ms": 159,
1765
1870
  "check": {
1766
1871
  "schema_version": 7,
1767
- "version": "2.91.0",
1872
+ "version": "2.93.0",
1768
1873
  "elapsed_ms": 45,
1769
1874
  "total_issues": 12,
1770
1875
  "unused_files": [],
@@ -1976,4 +2081,4 @@ preset = "bulletproof"
1976
2081
 
1977
2082
  ### Valid Issue Type Tokens
1978
2083
 
1979
- `unused-file`, `unused-export`, `unused-type`, `unused-dependency`, `unused-dev-dependency`, `unused-enum-member`, `unused-class-member`, `unresolved-import`, `unlisted-dependency`, `duplicate-export`, `circular-dependency`, `re-export-cycle`, `boundary-violation`, `unused-optional-dependency`, `type-only-dependency`, `test-only-dependency`, `code-duplication`
2084
+ `unused-file`, `unused-export`, `unused-type`, `unused-dependency`, `unused-dev-dependency`, `unused-enum-member`, `unused-class-member`, `unresolved-import`, `unlisted-dependency`, `duplicate-export`, `circular-dependency`, `re-export-cycle`, `boundary-violation`, `policy-violation`, `unused-optional-dependency`, `type-only-dependency`, `test-only-dependency`, `code-duplication`
@@ -23,7 +23,7 @@ Always preview with `--dry-run` before applying. This is a destructive operation
23
23
 
24
24
  ## Don't Create Config Unless Needed
25
25
 
26
- Fallow works with zero configuration for most projects thanks to 118 auto-detecting framework plugins. Creating an unnecessary config file can mask issues or override detection behavior.
26
+ Fallow works with zero configuration for most projects thanks to 121 auto-detecting framework plugins. Creating an unnecessary config file can mask issues or override detection behavior.
27
27
 
28
28
  ```bash
29
29
  # WRONG: creating config for a standard Next.js project
@@ -118,6 +118,19 @@ Parse the JSON to list specific files and exports that became unused.
118
118
  changed-since: main
119
119
  ```
120
120
 
121
+ ### GitHub Actions: Security Delta Gate
122
+
123
+ Fail a PR only when it introduces new security candidates (or makes existing ones newly reachable). Gated failures exit with code 8; the `issues` output counts only matching gate candidates. PR comment and review renderers skip security envelopes.
124
+
125
+ ```yaml
126
+ - uses: fallow-rs/fallow@v2
127
+ with:
128
+ command: security
129
+ security-gate: new # or newly-reachable (needs a base ref via changed-since or PR auto-scoping)
130
+ ```
131
+
132
+ GitLab equivalent: `FALLOW_COMMAND: "security"` with `FALLOW_SECURITY_GATE: "new"`.
133
+
121
134
  ### GitHub Actions: With Health Score
122
135
 
123
136
  ```yaml
@@ -627,7 +640,7 @@ Focus on findings that are BOTH dead code and duplicated:
627
640
 
628
641
  ## Custom Plugin Setup
629
642
 
630
- For frameworks not covered by the 118 built-in plugins.
643
+ For frameworks not covered by the 122 built-in plugins.
631
644
 
632
645
  ### Option 1: Inline framework config
633
646