fallow 3.31.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.
- package/README.md +17 -10
- package/capabilities.json +267 -157
- package/issue-registry.json +190 -154
- package/package.json +12 -12
- package/schema.json +31 -1
- package/skills/fallow/SKILL.md +29 -9
- package/skills/fallow/references/cli-reference.md +83 -38
- package/skills/fallow/references/gotchas.md +21 -13
- package/skills/fallow/references/issue-types.md +3 -2
- package/skills/fallow/references/mcp.md +17 -5
- package/skills/fallow/references/node-bindings.md +9 -3
- package/skills/fallow/references/patterns.md +37 -12
- package/skills/fallow-setup/SKILL.md +50 -0
- package/skills/fallow-setup/agents/openai.yaml +4 -0
- package/skills/fallow-setup/references/ci-gate.md +61 -0
- package/skills/fallow-setup/references/configure-and-install.md +56 -0
- package/skills/fallow-setup/references/tooling-detection.md +44 -0
- package/types/output-contract.d.ts +352 -24
|
@@ -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`, `--
|
|
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` |
|
|
@@ -134,6 +134,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
|
|
|
134
134
|
| `--unprovided-injects` | inject() / getContext() reads a key that no provide() / setContext() supplies |
|
|
135
135
|
| `--unrendered-components` | A Vue / Svelte component is reachable through a barrel but rendered nowhere |
|
|
136
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 |
|
|
137
138
|
| `--unused-component-emits` | A Vue <script setup> defineEmits event is emitted nowhere in its own component |
|
|
138
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 |
|
|
139
140
|
| `--unused-component-outputs` | An Angular @Output() / signal output() is emitted (.emit()) nowhere in its own component; needs `@angular/core` dep |
|
|
@@ -155,6 +156,9 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
|
|
|
155
156
|
| `--unused-dependency-overrides` | Unused package-manager dependency overrides |
|
|
156
157
|
| `--misconfigured-dependency-overrides` | Misconfigured package-manager dependency overrides |
|
|
157
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
|
+
|
|
158
162
|
### Examples
|
|
159
163
|
|
|
160
164
|
```bash
|
|
@@ -230,12 +234,17 @@ By default, `fallow dupes` skips generated framework output matching `**/.next/*
|
|
|
230
234
|
| `--cross-language` | `bool` | `false` | Strip type annotations for TS↔JS matching |
|
|
231
235
|
| `--ignore-imports` | `bool` | `false` | Exclude module wiring from clone detection |
|
|
232
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`) |
|
|
233
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. |
|
|
234
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 |
|
|
235
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 |
|
|
236
242
|
|
|
237
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).
|
|
238
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
|
+
|
|
239
248
|
### Detection Modes
|
|
240
249
|
|
|
241
250
|
| Mode | Behavior |
|
|
@@ -291,6 +300,9 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
|
|
|
291
300
|
|
|
292
301
|
Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags).
|
|
293
302
|
<!-- generated:flags:fix:end -->
|
|
303
|
+
|
|
304
|
+
`--force` is an alias for `--yes`.
|
|
305
|
+
|
|
294
306
|
### What gets fixed
|
|
295
307
|
|
|
296
308
|
- Unused exports (removes the `export` keyword; whole-enum block when every member is unused)
|
|
@@ -369,7 +381,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
|
|
|
369
381
|
|
|
370
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.
|
|
371
383
|
|
|
372
|
-
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`, `
|
|
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.
|
|
373
385
|
|
|
374
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`.
|
|
375
387
|
|
|
@@ -412,26 +424,26 @@ fallow hooks uninstall --target git
|
|
|
412
424
|
fallow hooks uninstall --target agent
|
|
413
425
|
```
|
|
414
426
|
|
|
415
|
-
`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.
|
|
416
428
|
|
|
417
429
|
---
|
|
418
430
|
|
|
419
431
|
## `agent`: One-Pass Agent Onboarding
|
|
420
432
|
|
|
421
|
-
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
|
|
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`).
|
|
422
434
|
|
|
423
435
|
Steps per harness:
|
|
424
436
|
|
|
425
437
|
| Step | Claude Code | Codex | Cursor |
|
|
426
438
|
|---|---|---|---|
|
|
427
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) |
|
|
428
|
-
| `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/` |
|
|
429
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` |
|
|
430
|
-
| `hooks` | `.claude/settings.json` PreToolUse gate plus `.claude/hooks/fallow-gate.sh` |
|
|
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`) |
|
|
431
443
|
|
|
432
|
-
The skill is a small pointer to `node_modules/fallow/skills
|
|
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.
|
|
433
445
|
|
|
434
|
-
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
|
|
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.
|
|
435
447
|
|
|
436
448
|
### Flags
|
|
437
449
|
|
|
@@ -442,7 +454,7 @@ Every file or block carries a `<!-- fallow:agent-install v1 ... -->` marker. Re-
|
|
|
442
454
|
| `--dry-run` | `install`, `uninstall` | Print the plan without touching the filesystem |
|
|
443
455
|
| `--force` | `install`, `uninstall` | Replace or remove skills, hook scripts, or config files fallow did not write |
|
|
444
456
|
| `--approve` | `install` | Pre-approve the project MCP server for yourself in `.claude/settings.local.json`; refused when that file is tracked by git |
|
|
445
|
-
| `--user` | `install`, `uninstall` | Skill
|
|
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` |
|
|
446
458
|
| `--gitignore-claude` | `install` | Append `.claude/` to `.gitignore` |
|
|
447
459
|
|
|
448
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.
|
|
@@ -455,7 +467,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
|
|
|
455
467
|
{
|
|
456
468
|
"kind": "agent-install",
|
|
457
469
|
"schema_version": 1,
|
|
458
|
-
"fallow_version": "3.
|
|
470
|
+
"fallow_version": "3.32.0",
|
|
459
471
|
"root": "/abs/path",
|
|
460
472
|
"mode": "install",
|
|
461
473
|
"dry_run": false,
|
|
@@ -555,7 +567,8 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
555
567
|
| `--min-commits` | `string` | - | Minimum number of commits for a file to be included in hotspot ranking. |
|
|
556
568
|
| `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Forces file-scores + hotspot computation. |
|
|
557
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`. |
|
|
558
|
-
| `--
|
|
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. |
|
|
559
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`. |
|
|
560
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. |
|
|
561
574
|
| `--min-invocations-hot` | `string` | `100` | Invocation threshold for hot-path classification. Takes effect only when `--runtime-coverage` is set. |
|
|
@@ -564,6 +577,11 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
564
577
|
|
|
565
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).
|
|
566
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
|
+
|
|
567
585
|
### Exit Codes
|
|
568
586
|
|
|
569
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).
|
|
@@ -659,7 +677,7 @@ fallow health --format json --quiet --trend
|
|
|
659
677
|
{
|
|
660
678
|
"kind": "health",
|
|
661
679
|
"schema_version": 7,
|
|
662
|
-
"version": "3.
|
|
680
|
+
"version": "3.32.0",
|
|
663
681
|
"elapsed_ms": 32,
|
|
664
682
|
"summary": {
|
|
665
683
|
"files_analyzed": 482,
|
|
@@ -852,15 +870,19 @@ All `health` JSON output includes a `vital_signs` object with project-wide metri
|
|
|
852
870
|
}
|
|
853
871
|
```
|
|
854
872
|
|
|
855
|
-
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).
|
|
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.
|
|
856
878
|
|
|
857
879
|
With `--score`, the JSON output includes a `health_score` object:
|
|
858
880
|
|
|
859
881
|
```json
|
|
860
882
|
{
|
|
861
883
|
"health_score": {
|
|
862
|
-
"formula_version":
|
|
863
|
-
"score":
|
|
884
|
+
"formula_version": 3,
|
|
885
|
+
"score": 72.9,
|
|
864
886
|
"grade": "B",
|
|
865
887
|
"penalties": {
|
|
866
888
|
"dead_files": 3.1,
|
|
@@ -878,7 +900,7 @@ With `--score`, the JSON output includes a `health_score` object:
|
|
|
878
900
|
}
|
|
879
901
|
```
|
|
880
902
|
|
|
881
|
-
Score is reproducible: `100 - sum(penalties) == score`. `formula_version` identifies the scoring formula; version
|
|
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).
|
|
882
904
|
|
|
883
905
|
### Health Trend
|
|
884
906
|
|
|
@@ -890,15 +912,16 @@ With `--trend`, the JSON output includes a `health_trend` object comparing curre
|
|
|
890
912
|
"compared_to": {
|
|
891
913
|
"timestamp": "2026-03-25T14:30:00Z",
|
|
892
914
|
"git_sha": "a1b2c3d",
|
|
893
|
-
"score":
|
|
894
|
-
"grade": "B"
|
|
915
|
+
"score": 70.2,
|
|
916
|
+
"grade": "B",
|
|
917
|
+
"score_formula_version": 3
|
|
895
918
|
},
|
|
896
919
|
"metrics": [
|
|
897
920
|
{
|
|
898
921
|
"name": "score",
|
|
899
922
|
"label": "Health Score",
|
|
900
|
-
"previous":
|
|
901
|
-
"current":
|
|
923
|
+
"previous": 70.2,
|
|
924
|
+
"current": 72.9,
|
|
902
925
|
"delta": 2.7,
|
|
903
926
|
"direction": "improving",
|
|
904
927
|
"unit": ""
|
|
@@ -921,7 +944,7 @@ With `--trend`, the JSON output includes a `health_trend` object comparing curre
|
|
|
921
944
|
}
|
|
922
945
|
```
|
|
923
946
|
|
|
924
|
-
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
|
|
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.
|
|
925
948
|
|
|
926
949
|
### Vital Signs Snapshots
|
|
927
950
|
|
|
@@ -929,8 +952,9 @@ Metrics tracked: `score`, `dead_file_pct`, `dead_export_pct`, `avg_cyclomatic`,
|
|
|
929
952
|
|
|
930
953
|
```json
|
|
931
954
|
{
|
|
932
|
-
"snapshot_schema_version":
|
|
955
|
+
"snapshot_schema_version": 12,
|
|
933
956
|
"timestamp": "2025-12-01T10:30:00Z",
|
|
957
|
+
"score_formula_version": 3,
|
|
934
958
|
"vital_signs": {
|
|
935
959
|
"dead_file_pct": 3.2,
|
|
936
960
|
"dead_export_pct": 8.1,
|
|
@@ -1062,7 +1086,7 @@ fallow audit \
|
|
|
1062
1086
|
{
|
|
1063
1087
|
"kind": "audit",
|
|
1064
1088
|
"schema_version": 7,
|
|
1065
|
-
"version": "3.
|
|
1089
|
+
"version": "3.32.0",
|
|
1066
1090
|
"command": "audit",
|
|
1067
1091
|
"verdict": "fail",
|
|
1068
1092
|
"changed_files_count": 12,
|
|
@@ -1150,7 +1174,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
1150
1174
|
```json
|
|
1151
1175
|
{
|
|
1152
1176
|
"schema_version": 7,
|
|
1153
|
-
"version": "3.
|
|
1177
|
+
"version": "3.32.0",
|
|
1154
1178
|
"elapsed_ms": 116,
|
|
1155
1179
|
"feature_flags": [],
|
|
1156
1180
|
"total_flags": 0
|
|
@@ -1251,7 +1275,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1251
1275
|
{
|
|
1252
1276
|
"kind": "security",
|
|
1253
1277
|
"schema_version": "4",
|
|
1254
|
-
"version": "3.
|
|
1278
|
+
"version": "3.32.0",
|
|
1255
1279
|
"elapsed_ms": 42,
|
|
1256
1280
|
"config": {
|
|
1257
1281
|
"rules": {
|
|
@@ -1280,7 +1304,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1280
1304
|
{
|
|
1281
1305
|
"kind": "security",
|
|
1282
1306
|
"schema_version": "4",
|
|
1283
|
-
"version": "3.
|
|
1307
|
+
"version": "3.32.0",
|
|
1284
1308
|
"elapsed_ms": 42,
|
|
1285
1309
|
"config": {
|
|
1286
1310
|
"rules": {
|
|
@@ -1471,7 +1495,7 @@ fallow explain fallow/code-duplication --format json --quiet
|
|
|
1471
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.",
|
|
1472
1496
|
"example": "export const formatPrice = ... exists in src/money.ts, but no module imports formatPrice.",
|
|
1473
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.",
|
|
1474
|
-
"docs": "https://
|
|
1498
|
+
"docs": "https://fallow.tools/docs/explanations/dead-code/#unused-exports"
|
|
1475
1499
|
}
|
|
1476
1500
|
```
|
|
1477
1501
|
|
|
@@ -1561,7 +1585,7 @@ fallow license deactivate
|
|
|
1561
1585
|
|------------|---------|
|
|
1562
1586
|
| `activate` | Install a JWT or start a 30-day trial. JWT input precedence: positional arg > `--from-file` > `--stdin`. |
|
|
1563
1587
|
| `status` | Print tier, seats, features, days-until-expiry, and (when `refresh_after` has passed) a proactive refresh hint. |
|
|
1564
|
-
| `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. |
|
|
1565
1589
|
| `deactivate` | Remove the local license file. |
|
|
1566
1590
|
|
|
1567
1591
|
### `activate` flags
|
|
@@ -1573,6 +1597,19 @@ fallow license deactivate
|
|
|
1573
1597
|
| `--from-file <PATH>` | path | Read a JWT from a file. |
|
|
1574
1598
|
| `--stdin` | bool | Read a JWT from stdin. Conflicts with `--from-file` and positional JWT. |
|
|
1575
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
|
+
|
|
1576
1613
|
### Storage precedence
|
|
1577
1614
|
|
|
1578
1615
|
1. `FALLOW_LICENSE` (env var holding the full JWT string)
|
|
@@ -1593,7 +1630,7 @@ On HTTP error from `api.fallow.cloud`, fallow parses the `{error, message, code}
|
|
|
1593
1630
|
|
|
1594
1631
|
| Operation + code | CLI message |
|
|
1595
1632
|
|------------------|-------------|
|
|
1596
|
-
| `refresh` + `token_stale` |
|
|
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`) |
|
|
1597
1634
|
| `refresh` + `invalid_token` | `your stored license token is missing required claims. Reactivate with: fallow license activate --trial --email <addr>` |
|
|
1598
1635
|
| `refresh` or `trial` + `unauthorized` | `authentication failed. Reactivate with: fallow license activate --trial --email <addr>` |
|
|
1599
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).` |
|
|
@@ -1676,7 +1713,7 @@ The command does not enable tracking. Only the user may opt in with
|
|
|
1676
1713
|
|
|
1677
1714
|
## `coverage`: Production-Coverage Workflow
|
|
1678
1715
|
|
|
1679
|
-
|
|
1716
|
+
Use the `coverage` subcommands for runtime setup, analysis, inventory upload, and scoped cloud reads:
|
|
1680
1717
|
|
|
1681
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`.
|
|
1682
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.
|
|
@@ -1730,13 +1767,14 @@ fallow coverage upload-source-maps --dry-run # print maps and fileNam
|
|
|
1730
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. |
|
|
1731
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. |
|
|
1732
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. |
|
|
1733
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. |
|
|
1734
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`. |
|
|
1735
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`. |
|
|
1736
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%. |
|
|
1737
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. |
|
|
1738
1776
|
|
|
1739
|
-
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.
|
|
1740
1778
|
|
|
1741
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.
|
|
1742
1780
|
|
|
@@ -1866,6 +1904,7 @@ Available on all commands:
|
|
|
1866
1904
|
| `-w, --workspace` | `string` | - | Scope to one or more workspaces (comma-separated, globs, `!` negation) |
|
|
1867
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. |
|
|
1868
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 |
|
|
1869
1908
|
| `--performance` | `bool` | `false` | Show pipeline timing breakdown |
|
|
1870
1909
|
| `--explain` | `bool` | `false` | JSON: include metric definitions in `_meta`. Human: print a `Description:` line under each section header. Always on for MCP. |
|
|
1871
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 |
|
|
@@ -1895,8 +1934,11 @@ Available on all commands:
|
|
|
1895
1934
|
| `--dupes-cross-language` | `bool` | `false` | Enable cross-language duplicate detection in combined mode |
|
|
1896
1935
|
| `--dupes-ignore-imports` | `bool` | `false` | Exclude module wiring from duplicate detection in combined mode |
|
|
1897
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`) |
|
|
1898
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 |
|
|
1899
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 |
|
|
1900
1942
|
| `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Default path: `.fallow/snapshots/<timestamp>.json`. Forces file-scores + hotspot computation |
|
|
1901
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` |
|
|
1902
1944
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
|
|
@@ -1966,6 +2008,7 @@ These are global flags with behavior specific to bare `fallow` combined mode.
|
|
|
1966
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. |
|
|
1967
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. |
|
|
1968
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. |
|
|
1969
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`. |
|
|
1970
2013
|
| `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
|
|
1971
2014
|
| `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
|
|
@@ -2014,6 +2057,8 @@ Set `FALLOW_FORMAT=json` and `FALLOW_QUIET=1` in your agent environment to avoid
|
|
|
2014
2057
|
|
|
2015
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.
|
|
2016
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
|
+
|
|
2017
2062
|
### Flags
|
|
2018
2063
|
|
|
2019
2064
|
| Flag | Type | Description |
|
|
@@ -2068,7 +2113,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2068
2113
|
{
|
|
2069
2114
|
"kind": "dead-code",
|
|
2070
2115
|
"schema_version": 7,
|
|
2071
|
-
"version": "3.
|
|
2116
|
+
"version": "3.32.0",
|
|
2072
2117
|
"elapsed_ms": 45,
|
|
2073
2118
|
"total_issues": 12,
|
|
2074
2119
|
"entry_points": {
|
|
@@ -2228,7 +2273,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
2228
2273
|
{
|
|
2229
2274
|
"kind": "dupes",
|
|
2230
2275
|
"schema_version": 7,
|
|
2231
|
-
"version": "3.
|
|
2276
|
+
"version": "3.32.0",
|
|
2232
2277
|
"elapsed_ms": 82,
|
|
2233
2278
|
"total_clones": 15,
|
|
2234
2279
|
"total_lines_duplicated": 230,
|
|
@@ -2272,11 +2317,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
2272
2317
|
{
|
|
2273
2318
|
"kind": "combined",
|
|
2274
2319
|
"schema_version": 7,
|
|
2275
|
-
"version": "3.
|
|
2320
|
+
"version": "3.32.0",
|
|
2276
2321
|
"elapsed_ms": 159,
|
|
2277
2322
|
"check": {
|
|
2278
2323
|
"schema_version": 7,
|
|
2279
|
-
"version": "3.
|
|
2324
|
+
"version": "3.32.0",
|
|
2280
2325
|
"elapsed_ms": 45,
|
|
2281
2326
|
"total_issues": 12,
|
|
2282
2327
|
"unused_files": [],
|
|
@@ -2339,8 +2384,8 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
2339
2384
|
// Files to ignore (glob patterns)
|
|
2340
2385
|
"ignorePatterns": ["**/*.generated.ts", "**/*.d.ts"],
|
|
2341
2386
|
|
|
2342
|
-
// Dependencies to ignore
|
|
2343
|
-
"ignoreDependencies": ["autoprefixer"],
|
|
2387
|
+
// Dependencies to ignore (exact names, or globs such as "@acme/*")
|
|
2388
|
+
"ignoreDependencies": ["autoprefixer", "@acme/*"],
|
|
2344
2389
|
|
|
2345
2390
|
// Suppress unused-export findings when the symbol is referenced inside its
|
|
2346
2391
|
// declaring file (knip parity). Boolean or { type, interface } object form.
|
|
@@ -2383,7 +2428,7 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
2383
2428
|
"ignoreDefaults": true,
|
|
2384
2429
|
"ignoredClones": ["dup:6f12ab34:2"],
|
|
2385
2430
|
"skipLocal": false,
|
|
2386
|
-
"
|
|
2431
|
+
"ignore": ["**/*.generated.ts"]
|
|
2387
2432
|
},
|
|
2388
2433
|
|
|
2389
2434
|
// Extraction cache settings. FALLOW_CACHE_DIR overrides cache.dir.
|
|
@@ -55,16 +55,18 @@ fallow dead-code | grep "unused"
|
|
|
55
55
|
fallow dead-code --format json --quiet
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
The `--quiet` flag suppresses progress bars on stderr.
|
|
58
|
+
The `--quiet` flag suppresses progress bars on stderr. Keep stderr separate from stdout when parsing JSON.
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
<a id="--changed-since-shows-only-new-issues"></a>
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
## `--changed-since` Scopes Findings to Changed Files
|
|
65
|
+
|
|
66
|
+
The `--changed-since` flag scopes findings to files modified since a git ref. It works with both `dead-code` and `dupes`. Existing findings in those files can remain; dead-code dependency findings remain project-wide. Use `fallow audit --gate new-only` to distinguish introduced findings from inherited ones.
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
|
-
#
|
|
69
|
+
# File-scoped findings are limited to files changed since main
|
|
68
70
|
fallow dead-code --format json --quiet --changed-since main
|
|
69
71
|
|
|
70
72
|
# Same for duplication, only clone groups involving changed files
|
|
@@ -109,10 +111,10 @@ fallow dead-code --format json --quiet
|
|
|
109
111
|
|
|
110
112
|
## Syntactic Analysis: No TypeScript Compiler
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
Default analysis uses Oxc for syntactic references. The optional `--type-aware` mode adds TypeScript checker evidence. Syntactic analysis has these limits:
|
|
113
115
|
|
|
114
116
|
- **Fully dynamic imports** (`import(variable)`) are not resolved. Only static strings, template literals with static prefixes, `import.meta.glob`, and `require.context` patterns
|
|
115
|
-
- **
|
|
117
|
+
- **General type narrowing** is outside syntactic analysis. Fallow does recognize `if (x instanceof Foo)` guards and credits member calls on `x` as uses of `Foo` members
|
|
116
118
|
- **Conditional exports** based on runtime values are not analyzed
|
|
117
119
|
- **Function overload signatures are deduplicated**: TypeScript function overloads (multiple signatures for the same function name) are merged into a single export. They are not reported as separate unused exports
|
|
118
120
|
|
|
@@ -151,7 +153,7 @@ export * from './utils';
|
|
|
151
153
|
import { helper } from './index'; // Resolves through the chain
|
|
152
154
|
```
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
A re-export alone does not prove that an export is used. If Fallow reports an export from a barrel, trace its consumers before removal. Dynamic imports and external callers may be outside static analysis.
|
|
155
157
|
|
|
156
158
|
---
|
|
157
159
|
|
|
@@ -163,10 +165,10 @@ If an export IS flagged as unused despite being in a barrel file, it means no do
|
|
|
163
165
|
| 1 | Error-severity issues found | Review findings |
|
|
164
166
|
| 2 | Runtime error (`fix` without `--yes` in non-TTY, invalid config) | Fix config or add `--yes` |
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
Error-severity findings can trigger exit code 1. Default severity varies by rule: some rules default to `"warn"` or `"off"`. Use the rules system to control which findings fail CI:
|
|
167
169
|
|
|
168
170
|
```jsonc
|
|
169
|
-
//
|
|
171
|
+
// Warn on unused exports and types; other rules keep their defaults
|
|
170
172
|
{
|
|
171
173
|
"rules": {
|
|
172
174
|
"unused-files": "error",
|
|
@@ -177,6 +179,8 @@ Exit code 1 is triggered by issues with `"error"` severity in the rules config.
|
|
|
177
179
|
}
|
|
178
180
|
```
|
|
179
181
|
|
|
182
|
+
Exit code 1 always means that an enforced gate failed. A load warning or a workspace diagnostic (for example `node-modules-missing` or a tsconfig `extends` that does not resolve) never changes the exit code. The only exception is `source-parse-degraded` with `--fail-on-parse-error`. To find the gate, read `gate_outcomes` in the JSON output and look for an entry with `status: "fail"` and `enforced: true`. Under `--quiet` and in every machine format, fallow also prints one stderr line that names the failed gates, for example `[X] Exit code 1: gate health-findings (3 at or above error) failed.` On `health`, the `complexity-*` rules default to `error`, so each complexity finding fails the run. Set them to `warn` or pass `--report-only` to report without a failure.
|
|
183
|
+
|
|
180
184
|
---
|
|
181
185
|
|
|
182
186
|
## `--fail-on-issues` Promotes Warn to Error
|
|
@@ -215,7 +219,7 @@ Commit the baseline file to your repo. Update it periodically as you fix existin
|
|
|
215
219
|
|
|
216
220
|
## Duplication Modes Affect What's Detected
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
Each detection mode normalizes different syntax. Choose the mode that fits the comparison:
|
|
219
223
|
|
|
220
224
|
```bash
|
|
221
225
|
# strict: exact token match only
|
|
@@ -237,6 +241,10 @@ fallow dupes --format json --quiet --mode semantic
|
|
|
237
241
|
|
|
238
242
|
`semantic` mode produces the most findings but may include false positives where similar structure is coincidental.
|
|
239
243
|
|
|
244
|
+
Use `--near` separately when you want function-level clones with small inserted,
|
|
245
|
+
removed, or changed regions. Exact detection still follows `--mode`; near
|
|
246
|
+
detection uses semantic shingles and reports a `similarity` value.
|
|
247
|
+
|
|
240
248
|
---
|
|
241
249
|
|
|
242
250
|
## Workspace Flag Scopes Output, Not Analysis
|
|
@@ -333,7 +341,7 @@ If you use utility decorators that DO NOT imply reflective use (Playwright's `@s
|
|
|
333
341
|
|
|
334
342
|
Conservative semantics: a method carrying any decorator NOT in the list still gets skipped. So `@step` + `@Inject` on the same method stays treated as framework-managed. Matching rule: entries containing `.` (`"decorators.log"`) match the full dotted path; bare entries (`"step"` or `"decorators"`) match the leftmost segment, so a single bare `"decorators"` entry collapses an entire `@decorators.*` namespace. Both `"@step"` and `"step"` round-trip equivalently. Unmatched entries (a decorator name in the config that never appears in your codebase) surface as a one-time warning at end of run.
|
|
335
343
|
|
|
336
|
-
|
|
344
|
+
With the default empty list every decorated method is treated as framework-managed, which is what NestJS, Angular, and TypeORM projects need.
|
|
337
345
|
|
|
338
346
|
### Angular `@Input()` / `@Output()` are still covered by the component rules
|
|
339
347
|
|
|
@@ -654,14 +662,14 @@ Both require a `GITLAB_TOKEN` CI/CD variable (project access token with `api` sc
|
|
|
654
662
|
`fallow license refresh` and `fallow license activate --trial` can fail with a backend error. The CLI always appends the raw HTTP status and the backend error code after the human hint, so scripts can grep for the code without parsing prose:
|
|
655
663
|
|
|
656
664
|
```
|
|
657
|
-
fallow license refresh: your stored license is too stale to refresh
|
|
665
|
+
fallow license refresh: 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) (HTTP 401, code token_stale)
|
|
658
666
|
```
|
|
659
667
|
|
|
660
668
|
Stable codes the CLI surfaces today:
|
|
661
669
|
|
|
662
670
|
| Code | Operation | Meaning |
|
|
663
671
|
|------|-----------|---------|
|
|
664
|
-
| `token_stale` | `refresh` | Stored JWT is more than 45 days past its `exp`.
|
|
672
|
+
| `token_stale` | `refresh` | Stored JWT is more than 45 days past its `exp`. Surfaced only when no full-access API key was available to retry with. |
|
|
665
673
|
| `invalid_token` | `refresh` | Stored JWT is missing required claims (e.g. `sub`). Reactivate. |
|
|
666
674
|
| `unauthorized` | `refresh` or `trial` | Auth failed. Reactivate. |
|
|
667
675
|
| `rate_limit_exceeded` | `trial` | Trial endpoint is capped at 5 per hour per IP. Wait or use a different network. |
|
|
@@ -30,11 +30,11 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
|
|
|
30
30
|
| `duplicate-export` | `--duplicate-exports` | - | `// fallow-ignore-file duplicate-export` | Same symbol exported from multiple modules |
|
|
31
31
|
| `circular-dependency` | `--circular-deps` | - | `// fallow-ignore-next-line circular-dependency` | Import cycles in the module graph |
|
|
32
32
|
| `re-export-cycle` | `--re-export-cycles` | - | `// fallow-ignore-file re-export-cycle` | Barrel files re-exporting from each other in a loop (`kind: "multi-node"`) or a barrel re-exporting from itself (`kind: "self-loop"`). Chain propagation through the loop is a structural no-op so imports through any member may silently come up empty. Default `warn`. Distinct from `circular-dependencies` (runtime cycles, sometimes intentional). File-scoped suppression only: `// fallow-ignore-file re-export-cycle` on any member breaks the cycle. |
|
|
33
|
-
| `package-cycle` | `--package-cycles` | - | `// fallow-ignore-next-line package-cycle` | Two or more workspace packages import each other in a loop, so they cannot be built in dependency order. Edges are resolved imports, not declared `package.json` dependencies. Imports from test, spec, story, fixture and tooling config files do not count. Type-only imports count, and each hop
|
|
33
|
+
| `package-cycle` | `--package-cycles` | - | `// fallow-ignore-next-line package-cycle` | Two or more workspace packages import each other in a loop, so they cannot be built in dependency order. Edges are resolved imports, not declared `package.json` dependencies, so a package cycle can exist without a file-level cycle. Imports from test, spec, story, fixture and tooling config files do not count. Each finding lists `packages` in cycle order and one example import per hop in `edges`. Type-only imports count, and each hop has a `type_only` flag (a type-only hop still matters for declaration builds). Default `warn`. Requires a workspace with two or more packages. A suppression removes one import; the cycle goes away when every import on one hop is suppressed. `group_truncated: true` means the package group has more cycles than listed. `--changed-since` and diff scope use the example import of each hop (`edges[].path`). |
|
|
34
34
|
| `boundary-violation` | `--boundary-violations` | - | `// fallow-ignore-next-line boundary-violation` | Imports crossing architecture zone boundaries. Presets: `layered`, `hexagonal`, `feature-sliced`, `bulletproof`; `autoDiscover` can create one zone per feature directory; per-rule `allowTypeOnly: [zones]` admits `import type` / `export type` crossings while still blocking value imports. Named and default imports through a barrel are judged against the zone of the origin module (`to_path`), and `via_path` names the barrel. Optional sections: `boundaries.coverage.requireAllFiles` reports unzoned source files (`allowUnmatched` globs exempt intentional ones), and `boundaries.calls.forbidden` bans callee patterns per zone (segment-aware and import-resolved, so `child_process.*` covers `node:child_process` named/namespace/default imports; direct callees only, zoned files only). The whole family shares the `boundary-violation` rule and suppression token (`boundary-call-violation` and `boundary-call-violations` accepted as aliases); start the rule at `warn` for a staged rollout |
|
|
35
35
|
| `boundary-coverage` | `--boundary-violations` | - | `// fallow-ignore-file boundary-violation` | Source file matches no configured architecture boundary zone; Requires boundaries.coverage.requireAllFiles |
|
|
36
36
|
| `boundary-call-violation` | `--boundary-violations` | - | `// fallow-ignore-next-line boundary-call-violation` | Zoned file calls a callee its zone forbids; Requires boundaries.calls.forbidden patterns |
|
|
37
|
-
| `policy-violation` | `--policy-violations` | - | `// fallow-ignore-next-line policy-violation` | Calls, imports,
|
|
37
|
+
| `policy-violation` | `--policy-violations` | - | `// fallow-ignore-next-line policy-violation` | Calls, imports, exports, catalogue-derived effects, and graph-resolved GDP proof producer ownership checked by declarative rule packs (`rulePacks` lists standalone JSON/JSONC files; pure data, no project code executes). Rule kinds: `banned-call`, `banned-import`, `banned-export`, `banned-effect`, and `gdp-proof-producer`. GDP rules trace `@gdp-ts/core` proof producers through unambiguous re-exports and require project-relative `allowedFiles`, optionally scoped by exact static `proofKinds`; they do not prove runtime authorization. Findings identified as `<pack>/<rule-id>`. Default `warn` master; per-rule `severity` overrides per finding and the exit gate reads the effective severity. Invalid or missing packs fail config load with exit 2. `fallow rule-pack-schema` prints the pack JSON Schema. Use `policy-violation:<pack>/<rule-id>` to suppress one rule; bare `policy-violation` covers every pack rule on the line or file. |
|
|
38
38
|
| `stale-suppression` | `--stale-suppressions` | - | - | `fallow-ignore` comments or `@expected-unused` JSDoc tags that no longer match any issue |
|
|
39
39
|
| `missing-suppression-reason` | `--stale-suppressions` | - | - | Suppression comment omits a required reason |
|
|
40
40
|
| `unused-catalog-entry` | `--unused-catalog-entries` | yes | - | `pnpm-workspace.yaml` entries no workspace package.json references via `catalog:` (default `warn`) |
|
|
@@ -47,6 +47,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
|
|
|
47
47
|
| `misplaced-directive` | - | - | `// fallow-ignore-next-line misplaced-directive` | "use client" / "use server" directive is not in the leading position and is ignored; Requires the project to declare next |
|
|
48
48
|
| `unprovided-inject` | `--unprovided-injects` | - | `// fallow-ignore-next-line unprovided-inject` | inject() / getContext() reads a key that no provide() / setContext() supplies |
|
|
49
49
|
| `unrendered-component` | `--unrendered-components` | - | `// fallow-ignore-next-line unrendered-component` | A Vue / Svelte component is reachable through a barrel but rendered nowhere |
|
|
50
|
+
| `absent-component-prop` | `--absent-component-props` | - | `// fallow-ignore-next-line absent-component-prop` | Known reachable callers omit an optional prop consumed inside its component; Opt-in manual review candidate; defaults to off. Inspect caller evidence and defaults before changing the component. No automatic fix. |
|
|
50
51
|
| `unused-component-prop` | `--unused-component-props` | - | `// fallow-ignore-next-line unused-component-prop` | A Vue defineProps prop or React component prop is referenced nowhere in its own component |
|
|
51
52
|
| `unused-component-emit` | `--unused-component-emits` | - | `// fallow-ignore-next-line unused-component-emit` | A Vue <script setup> defineEmits event is emitted nowhere in its own component |
|
|
52
53
|
| `unused-component-input` | `--unused-component-inputs` | - | `// fallow-ignore-next-line unused-component-input` | An Angular @Input() / signal input() / model() is read nowhere in its own component (class body or template); needs `@angular/core` dep |
|