fallow 2.84.0 → 2.86.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/package.json +9 -9
- package/schema.json +119 -118
- package/skills/fallow/SKILL.md +47 -31
- package/skills/fallow/references/cli-reference.md +198 -38
- package/skills/fallow/references/gotchas.md +1 -1
- package/skills/fallow/references/patterns.md +10 -4
- package/types/output-contract.d.ts +411 -1198
|
@@ -15,6 +15,7 @@ Complete command and flag specifications for all fallow CLI commands.
|
|
|
15
15
|
- [`health`: Function Complexity Analysis](#health-function-complexity-analysis)
|
|
16
16
|
- [`audit`: Changed-File Quality Gate](#audit-changed-file-quality-gate)
|
|
17
17
|
- [`flags`: Feature Flag Detection](#flags-feature-flag-detection)
|
|
18
|
+
- [`security`: Security Candidate Detection](#security-security-candidate-detection)
|
|
18
19
|
- [`explain`: Rule Explanation](#explain-rule-explanation)
|
|
19
20
|
- [`schema`: CLI Introspection](#schema-cli-introspection)
|
|
20
21
|
- [`config-schema`: Config JSON Schema](#config-schema-config-json-schema)
|
|
@@ -39,6 +40,7 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
|
|
|
39
40
|
|------|------|---------|-------------|
|
|
40
41
|
| `--format` | `human\|json\|sarif\|compact\|markdown\|codeclimate\|gitlab-codequality\|pr-comment-github\|pr-comment-gitlab\|review-github\|review-gitlab` | `human` | Output format |
|
|
41
42
|
| `--quiet` | bool | `false` | Suppress progress bars and timing on stderr |
|
|
43
|
+
| `--legacy-envelope` | bool | `false` | Remove the top-level `kind` field from typed JSON roots for one migration cycle |
|
|
42
44
|
| `--changed-since` | string | — | Only analyze files changed since a git ref (e.g., `main`, `HEAD~3`) |
|
|
43
45
|
| `--production` | bool | `false` | Exclude test/dev files, only start/build scripts (applies to every analysis) |
|
|
44
46
|
| `--production-dead-code` | bool | `false` | Per-analysis production mode for dead-code. Bare combined runs and `fallow audit` only. |
|
|
@@ -70,6 +72,7 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
|
|
|
70
72
|
| `--unlisted-deps` | Unlisted dependencies |
|
|
71
73
|
| `--duplicate-exports` | Duplicate exports |
|
|
72
74
|
| `--circular-deps` | Circular dependencies |
|
|
75
|
+
| `--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). |
|
|
73
76
|
| `--boundary-violations` | Boundary violations (imports crossing architecture zone boundaries) |
|
|
74
77
|
| `--stale-suppressions` | Stale suppression comments or `@expected-unused` JSDoc tags |
|
|
75
78
|
| `--unused-catalog-entries` | Unused pnpm catalog entries |
|
|
@@ -153,7 +156,7 @@ By default, `fallow dupes` skips generated framework output matching `**/.next/*
|
|
|
153
156
|
| `--cross-language` | bool | `false` | Strip type annotations for TS↔JS matching |
|
|
154
157
|
| `--ignore-imports` | bool | `false` | Exclude import declarations from clone detection |
|
|
155
158
|
| `--explain-skipped` | bool | `false` | Human/markdown only: show per-pattern counts for files skipped by default duplicates ignores |
|
|
156
|
-
| `--trace` | `FILE:LINE` | — |
|
|
159
|
+
| `--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 |
|
|
157
160
|
| `--changed-since` | string | — | Only report duplication in files changed since a git ref |
|
|
158
161
|
| `--baseline` | path | — | Compare against baseline |
|
|
159
162
|
| `--save-baseline` | path | — | Save results as baseline |
|
|
@@ -185,6 +188,9 @@ fallow dupes --format json --quiet --skip-local --threshold 5
|
|
|
185
188
|
# Trace clones at a specific location
|
|
186
189
|
fallow dupes --format json --quiet --trace src/utils.ts:42
|
|
187
190
|
|
|
191
|
+
# Deep-dive a clone group by its dup:<id> fingerprint (from the listing or JSON)
|
|
192
|
+
fallow dupes --format json --quiet --trace dup:7f3a2c1e
|
|
193
|
+
|
|
188
194
|
# Only check duplication in changed files
|
|
189
195
|
fallow dupes --format json --quiet --changed-since main
|
|
190
196
|
|
|
@@ -218,6 +224,29 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
|
|
|
218
224
|
- Unused pnpm catalog entries (removed from `pnpm-workspace.yaml` by line-aware deletion). Object-form entries are removed as one block. By default, fallow also removes a contiguous YAML comment block immediately above the entry when it clearly belongs to that entry; configure this with `fix.catalog.deletePrecedingComments` (`"auto"`, `"always"`, or `"never"`). Two escape hatches keep curated comments safe regardless of policy: a `# fallow-keep` marker on any line in the block preserves it, and the `auto` policy additionally preserves section-banner blocks whose body starts with three or more `=`, `-`, `*`, `_`, `~`, `+`, or `#` characters (e.g. `# === React 18 production pins ===`). Other comments and stylistic choices are preserved. When the last entry of a catalog group is removed, the header is rewritten to `catalog: {}` / `<name>: {}` so pnpm doesn't reject the resulting null value. Entries with non-empty `hardcoded_consumers` are skipped to avoid breaking `pnpm install`; the skip is surfaced in the JSON fix output as `{"type": "remove_catalog_entry", "applied": false, "skipped": true, "skip_reason": "hardcoded_consumers", "consumers": [...]}`. The JSON action carries both `line` (first deleted line, the leading comment when policy absorbs one) and `entry_line` (the catalog entry's original 1-based line); use `entry_line` as a stable anchor across policy changes. After a successful catalog edit the CLI emits a one-line `Run pnpm install to refresh pnpm-lock.yaml` reminder, and the human stderr summary appends `(+M catalog comment lines)` to the fixed-issue count when comment lines were absorbed. The JSON envelope carries a top-level `"skipped"` count alongside `"total_fixed"` for partial-fix gating.
|
|
219
225
|
- Duplicate exports (appends an `ignoreExports` rule to your fallow config file). When no fallow config file exists, `.fallowrc.json` is created using the same scaffolding `fallow init` would emit (framework detection, `$schema`, `entry`, `ignorePatterns`, etc.) and the rules are layered on top. Inside a monorepo subpackage (`pnpm-workspace.yaml`, `package.json#workspaces`, `turbo.json`, `lerna.json`, or `rush.json` above the invocation directory) the create-fallback refuses to fire and emits `skip_reason: "monorepo_subpackage"` with a relative `workspace_root` path pointing at the workspace root. The applied entry carries `created_files: [".fallowrc.json"]` so consumers can detect file-creation side effects programmatically.
|
|
220
226
|
|
|
227
|
+
### On-disk drift protection
|
|
228
|
+
|
|
229
|
+
`fallow fix` captures every parsed source file's xxh3 content hash during the in-process analysis and recomputes it at fix time. Files whose hash drifted between analysis and write (parallel editor save, CI rebase, concurrent tool) are skipped with `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "content_changed"}` in the JSON output and `Skipping <path>: file content changed since fallow check ran. Re-run fallow fix to refresh the analysis first.` on stderr (gated on non-quiet). A run with any content-changed skip exits with code 2 so CI does not treat the partial run as a clean no-op. The JSON envelope's top-level `skipped_content_changed: number` is always present and disjoint from `skipped` (which still tallies catalog / YAML guard skips only). Per-file writes are batched: each rewrite is staged to a sibling temp file, and the orchestrator promotes the batch only after every stage succeeds. A stage failure leaves every target file at its original content. Hash precondition covers source files (TS, JS, Vue, Svelte, Astro, MDX); `package.json` and `pnpm-workspace.yaml` are not in the captured hash map because the extract layer does not parse them, but the dep and catalog fixers re-parse those files at fix time as the natural safety net.
|
|
230
|
+
|
|
231
|
+
### Low-confidence export removals
|
|
232
|
+
|
|
233
|
+
Issue #602: `fallow fix` withholds unused-export removals when the consumer may be invisible to static analysis, because stripping a real export breaks `tsc` and the build. Two cases are skipped:
|
|
234
|
+
|
|
235
|
+
- **Off-graph consumer directories.** The file is under any of `__mocks__`, `__fixtures__`, `fixtures`, `e2e`, `e2e-tests`, `cypress`, `playwright`, `examples`, `evals`, `golden` (matched on any path segment). Catches Vitest mock aliases, off-workspace e2e suites, and fixture / golden harnesses. Plain `test` / `tests` / `__tests__` are deliberately NOT on the list, so genuinely-dead test helpers still auto-remove.
|
|
236
|
+
- **Files with an unresolved import.** The file itself imports something fallow could not resolve, so its local usage graph is incomplete.
|
|
237
|
+
|
|
238
|
+
JSON output carries `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "low_confidence_off_graph"}` (or `"low_confidence_unresolved_imports"`) plus a top-level counter `skipped_low_confidence_exports: number` (always present), disjoint from `skipped`. Unlike the drift and encoding skips this is INTENTIONAL and does NOT change the exit code; the export stays reported by `fallow check` for manual review. High-confidence exports in normal source files are removed unchanged. The AI agent should report kept exports to the user and let them decide whether the export is truly unused before removing it by hand.
|
|
239
|
+
|
|
240
|
+
### File encoding contract
|
|
241
|
+
|
|
242
|
+
`fallow fix` is UTF-8 only. Two encoding shapes that previously caused silent corruption are handled explicitly (issue #475):
|
|
243
|
+
|
|
244
|
+
- **UTF-8 BOM round-trip.** Files with a leading UTF-8 byte-order mark (`EF BB BF`, common on Windows-authored TypeScript) are read with the BOM stripped before line-offset computation and parsing, so reported line numbers do not shift by the BOM codepoint, and the BOM is re-prepended on write so the file's encoding is preserved on round-trip. fallow neither adds nor removes a BOM; if your input has one, the output has one.
|
|
245
|
+
|
|
246
|
+
- **Mixed CRLF / LF rejection.** Files containing both `\r\n` and bare-LF line endings (common after cross-platform edits without `core.autocrlf`) are skipped instead of silently rewritten to the wrong offsets. The stderr message names the remediation: `Skipping <path>: file has mixed CRLF/LF line endings. Normalize with dos2unix or set git config core.autocrlf input, then re-run fallow fix.`. JSON output carries `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "mixed_line_endings"}` plus a top-level counter `skipped_mixed_line_endings: number` (always present) disjoint from `skipped_content_changed`. Any non-zero mixed-EOL count exits the run with code 2.
|
|
247
|
+
|
|
248
|
+
**The skip is NOT self-healing**. Re-running `fallow fix` produces the same skip; the AI agent or user must run `dos2unix <path>` (or set `git config core.autocrlf input` and re-checkout) before fallow can act on the file. When the same file carries findings for multiple fixers (e.g. an unused export AND an unused enum member), the skip is reported once per file, not once per fixer.
|
|
249
|
+
|
|
221
250
|
### Examples
|
|
222
251
|
|
|
223
252
|
```bash
|
|
@@ -242,6 +271,7 @@ Inspect discovered files, entry points, detected frameworks, and architecture bo
|
|
|
242
271
|
| `--entry-points` | bool | List detected entry points |
|
|
243
272
|
| `--plugins` | bool | List active framework plugins |
|
|
244
273
|
| `--boundaries` | bool | Show architecture boundary zones, rules, per-zone file counts, and `logical_groups[]` for `autoDiscover` parents |
|
|
274
|
+
| `--workspaces` | bool | Show discovered monorepo workspaces plus any workspace-discovery diagnostics (malformed `package.json`, unreachable glob matches, missing tsconfig references). Available as the `fallow workspaces` alias too. |
|
|
245
275
|
| `--format` | `human\|json` | Output format |
|
|
246
276
|
| `--quiet` | bool | Suppress progress bars |
|
|
247
277
|
|
|
@@ -252,8 +282,12 @@ fallow list --files --format json --quiet
|
|
|
252
282
|
fallow list --entry-points --format json --quiet
|
|
253
283
|
fallow list --plugins --format json --quiet
|
|
254
284
|
fallow list --boundaries --format json --quiet
|
|
285
|
+
fallow list --workspaces --format json --quiet
|
|
286
|
+
fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
|
|
255
287
|
```
|
|
256
288
|
|
|
289
|
+
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`) with a typed payload (`error`, `pattern`, or none). The same `workspace_diagnostics[]` array is also surfaced on `fallow check --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes (omitted when empty). A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues.
|
|
290
|
+
|
|
257
291
|
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`.
|
|
258
292
|
|
|
259
293
|
---
|
|
@@ -333,15 +367,17 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
333
367
|
| `--top` | number | — | Only show the top N most complex functions (and file scores/hotspots/targets) |
|
|
334
368
|
| `--sort` | `cyclomatic\|cognitive\|lines\|severity` | `cyclomatic` | Sort order for complexity findings |
|
|
335
369
|
| `--complexity` | bool | `false` | Show only function complexity findings. When no section flags are set, all sections are shown by default. |
|
|
336
|
-
| `--file-scores` | bool | `false` | Show only per-file maintainability index
|
|
370
|
+
| `--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. |
|
|
337
371
|
| `--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. |
|
|
338
372
|
| `--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. |
|
|
339
373
|
| `--effort` | `low\|medium\|high` | — | Filter refactoring targets by effort level. Implies `--targets`. |
|
|
340
374
|
| `--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. |
|
|
341
|
-
| `--min-score` | number | — | Fail
|
|
375
|
+
| `--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`. |
|
|
376
|
+
| `--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). |
|
|
377
|
+
| `--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`. |
|
|
342
378
|
| `--since` | string | `6m` | Git history window for hotspot analysis. Accepts durations (`6m`, `90d`, `1y`, `2w`) or ISO dates (`2025-06-01`). |
|
|
343
379
|
| `--min-commits` | number | `3` | Minimum number of commits for a file to be included in hotspot ranking. |
|
|
344
|
-
| `--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 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. |
|
|
380
|
+
| `--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. |
|
|
345
381
|
| `--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`. |
|
|
346
382
|
| `--changed-since` | string | — | Only analyze files changed since a git ref |
|
|
347
383
|
| `--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. |
|
|
@@ -353,17 +389,24 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
353
389
|
| `--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`). |
|
|
354
390
|
| `--coverage` | path | none | Path to Istanbul-format coverage data (`coverage-final.json`) for accurate per-function CRAP scores. Uses `CC^2 * (1-cov/100)^3 + CC` instead of static binary model. Relative paths resolve against `--root`. |
|
|
355
391
|
| `--coverage-root` | path | none | 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. |
|
|
356
|
-
| `--runtime-coverage` | path | none | 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`),
|
|
392
|
+
| `--runtime-coverage` | path | none | 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. |
|
|
357
393
|
| `--min-invocations-hot` | number | `100` | Invocation threshold for hot-path classification. Takes effect only when `--runtime-coverage` is set. |
|
|
358
394
|
| `--min-observation-volume` | number | `5000` | Minimum total trace volume before the sidecar may emit high-confidence `safe_to_delete` / `review_required` verdicts. Below this, confidence is capped at `medium`. |
|
|
359
395
|
| `--low-traffic-threshold` | number | `0.001` | Fraction of total trace count below which an invoked function is classified `low_traffic` rather than `active`. Expressed as a decimal (0.001 = 0.1%). |
|
|
360
396
|
|
|
361
397
|
### Exit Codes
|
|
362
398
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
|
366
|
-
|
|
399
|
+
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).
|
|
400
|
+
|
|
401
|
+
| Invocation | Exit 0 when | Exit 1 when |
|
|
402
|
+
|------------|-------------|-------------|
|
|
403
|
+
| `fallow health` (no gate flag) | no function exceeds a threshold | any function exceeds a threshold |
|
|
404
|
+
| `--min-score N` | score >= N (findings informational) | score < N |
|
|
405
|
+
| `--min-severity LEVEL` | no finding at or above LEVEL | any finding at or above LEVEL |
|
|
406
|
+
| `--min-score N --min-severity LEVEL` | score >= N AND no finding >= LEVEL | score < N OR a finding >= LEVEL |
|
|
407
|
+
| `--report-only` | always | never |
|
|
408
|
+
|
|
409
|
+
`--report-only` with `--min-score` / `--min-severity` exits 2 (mutually exclusive). The `--runtime-coverage` and coverage-gap gates stay independent and are not demoted by `--min-score`. For gating only newly-introduced complexity, use `fallow audit --gate new-only`.
|
|
367
410
|
|
|
368
411
|
### Examples
|
|
369
412
|
|
|
@@ -386,10 +429,10 @@ fallow health --format json --quiet --sort cognitive
|
|
|
386
429
|
# Custom thresholds
|
|
387
430
|
fallow health --format json --quiet --max-cyclomatic 15 --max-cognitive 10
|
|
388
431
|
|
|
389
|
-
# Per-file
|
|
432
|
+
# Per-file health scores
|
|
390
433
|
fallow health --format json --quiet --file-scores
|
|
391
434
|
|
|
392
|
-
#
|
|
435
|
+
# Top 20 files by triage concern
|
|
393
436
|
fallow health --format json --quiet --file-scores --top 20
|
|
394
437
|
|
|
395
438
|
# Only analyze files changed since main
|
|
@@ -440,8 +483,9 @@ fallow health --format json --quiet --trend
|
|
|
440
483
|
|
|
441
484
|
```json
|
|
442
485
|
{
|
|
443
|
-
"
|
|
444
|
-
"
|
|
486
|
+
"kind": "health",
|
|
487
|
+
"schema_version": 7,
|
|
488
|
+
"version": "2.86.0",
|
|
445
489
|
"elapsed_ms": 32,
|
|
446
490
|
"summary": {
|
|
447
491
|
"files_analyzed": 482,
|
|
@@ -489,7 +533,8 @@ With `--file-scores`, the JSON output also includes `file_scores` array and `sum
|
|
|
489
533
|
"summary": {
|
|
490
534
|
"files_scored": 482,
|
|
491
535
|
"average_maintainability": 88.5,
|
|
492
|
-
"coverage_model": "static_estimated"
|
|
536
|
+
"coverage_model": "static_estimated",
|
|
537
|
+
"coverage_source_consistency": "uniform"
|
|
493
538
|
},
|
|
494
539
|
"file_scores": [
|
|
495
540
|
{
|
|
@@ -510,7 +555,9 @@ With `--file-scores`, the JSON output also includes `file_scores` array and `sum
|
|
|
510
555
|
}
|
|
511
556
|
```
|
|
512
557
|
|
|
513
|
-
The `
|
|
558
|
+
The `file_scores` array is sorted by risk-aware triage concern: the larger of low-MI concern and CRAP risk. This keeps files with very high untested complexity near the top even when their Maintainability Index is not the lowest.
|
|
559
|
+
|
|
560
|
+
The `crap_max` field is the highest CRAP (Change Risk Anti-Patterns) score among functions in the file, using the canonical formula `CC^2 * (1 - cov/100)^3 + CC`. The default model (`static_estimated`) estimates per-function coverage from export references: directly test-referenced = 85%, indirectly test-reachable = 40%, untested = 0%. Provide `--coverage <path>` with Istanbul-format `coverage-final.json` for exact scores (`istanbul` model). The `crap_above_threshold` field counts functions with CRAP >= 30. When `--file-scores` is active, `summary.coverage_model` indicates the model used (`"static_estimated"` or `"istanbul"`). When CRAP findings carry `coverage_source`, `summary.coverage_source_consistency` is `uniform` or `mixed`; grouped health JSON mirrors this as `groups[].coverage_source_consistency`.
|
|
514
561
|
|
|
515
562
|
Maintainability index formula: `100 - (complexity_density × 30) - (dead_code_ratio × 20) - min(ln(fan_out+1) × 4, 15)`, clamped to 0–100. Higher is better. Type-only exports are excluded from dead_code_ratio. Zero-function files (barrels) are excluded by default.
|
|
516
563
|
|
|
@@ -752,7 +799,7 @@ Audits changed files for dead code, complexity, and duplication. Returns a verdi
|
|
|
752
799
|
| `--production-health` | bool | false | Per-analysis production mode for the health sub-analysis only |
|
|
753
800
|
| `--production-dupes` | bool | false | Per-analysis production mode for the duplication sub-analysis only |
|
|
754
801
|
| `-w, --workspace` | string | — | Scope to one or more workspaces. Comma-separated, globs, `!` negation. |
|
|
755
|
-
| `--explain` | bool | false |
|
|
802
|
+
| `--explain` | bool | false | JSON: include metric definitions in `_meta`. Human: print a `Description:` line under each section header. |
|
|
756
803
|
| `--ci` | bool | false | Equivalent to `--format sarif --fail-on-issues --quiet` |
|
|
757
804
|
| `--fail-on-issues` | bool | false | Exit with code 1 if issues are found |
|
|
758
805
|
| `--sarif-file` | path | — | Write SARIF output to a file alongside primary format |
|
|
@@ -826,8 +873,9 @@ fallow audit \
|
|
|
826
873
|
|
|
827
874
|
```json
|
|
828
875
|
{
|
|
829
|
-
"
|
|
830
|
-
"
|
|
876
|
+
"kind": "audit",
|
|
877
|
+
"schema_version": 7,
|
|
878
|
+
"version": "2.86.0",
|
|
831
879
|
"command": "audit",
|
|
832
880
|
"verdict": "fail",
|
|
833
881
|
"changed_files_count": 12,
|
|
@@ -872,7 +920,7 @@ Audit creates a temporary git worktree to compare against the base ref. When the
|
|
|
872
920
|
|
|
873
921
|
## `flags`: Feature Flag Detection
|
|
874
922
|
|
|
875
|
-
Detects feature flag patterns in the codebase. Identifies environment variable flags (`process.env.FEATURE_*`), SDK calls (LaunchDarkly, Statsig, Unleash, GrowthBook), and config object patterns (opt-in). Reports flag locations, detection confidence, and cross-references with dead code findings.
|
|
923
|
+
Detects feature flag patterns in the codebase. Identifies environment variable flags (`process.env.FEATURE_*`), SDK calls from common providers (LaunchDarkly, Statsig, Unleash, GrowthBook, Split, PostHog, Vercel Flags, ConfigCat, Flagsmith, Optimizely, Eppo), and config object patterns (opt-in). Reports flag locations, detection confidence, and cross-references with dead code findings.
|
|
876
924
|
|
|
877
925
|
### Flags
|
|
878
926
|
|
|
@@ -899,8 +947,8 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
899
947
|
|
|
900
948
|
```json
|
|
901
949
|
{
|
|
902
|
-
"schema_version":
|
|
903
|
-
"version": "2.
|
|
950
|
+
"schema_version": 7,
|
|
951
|
+
"version": "2.86.0",
|
|
904
952
|
"elapsed_ms": 116,
|
|
905
953
|
"feature_flags": [],
|
|
906
954
|
"total_flags": 0
|
|
@@ -909,6 +957,50 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
909
957
|
|
|
910
958
|
---
|
|
911
959
|
|
|
960
|
+
## `security`: Security Candidate Detection
|
|
961
|
+
|
|
962
|
+
Surfaces local security candidates for agent or human verification. The first rule, `client-server-leak`, starts at `"use client"` files and reports a candidate when that client boundary directly reads, or statically imports a path to a module that reads, a non-public `process.env` value.
|
|
963
|
+
|
|
964
|
+
Findings are not confirmed vulnerabilities. Use the structural trace to verify whether the value can actually reach client-bundled code. Public env conventions (`NODE_ENV`, `NEXT_PUBLIC_*`, `VITE_*`, `NUXT_PUBLIC_*`, `REACT_APP_*`, `PUBLIC_*`, `GATSBY_*`, `EXPO_PUBLIC_*`, `STORYBOOK_*`) are excluded.
|
|
965
|
+
|
|
966
|
+
### Flags
|
|
967
|
+
|
|
968
|
+
| Flag | Type | Default | Description |
|
|
969
|
+
|------|------|---------|-------------|
|
|
970
|
+
| `--format` | `human\|json\|sarif` | `human` | Output format |
|
|
971
|
+
| `--quiet` | bool | `false` | Suppress progress output |
|
|
972
|
+
| `--summary` | bool | `false` | Show a compact human summary |
|
|
973
|
+
| `--ci` | bool | `false` | Equivalent to `--format sarif --fail-on-issues --quiet` |
|
|
974
|
+
| `--fail-on-issues` | bool | `false` | Exit 1 when candidates are found |
|
|
975
|
+
| `--sarif-file` | path | none | Write SARIF in addition to the primary output |
|
|
976
|
+
| `--changed-since` | git ref | none | Scope to candidates whose client anchor or trace hops touch changed files |
|
|
977
|
+
| `--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 |
|
|
978
|
+
| `--workspace` | string | none | Scope to selected workspace packages |
|
|
979
|
+
| `--changed-workspaces` | git ref | none | Scope to workspaces changed since a git ref |
|
|
980
|
+
|
|
981
|
+
### Examples
|
|
982
|
+
|
|
983
|
+
```bash
|
|
984
|
+
fallow security --format json --quiet
|
|
985
|
+
fallow security --ci --sarif-file fallow-security.sarif
|
|
986
|
+
git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### JSON Output Structure
|
|
990
|
+
|
|
991
|
+
```json
|
|
992
|
+
{
|
|
993
|
+
"kind": "security",
|
|
994
|
+
"schema_version": "1",
|
|
995
|
+
"security_findings": [],
|
|
996
|
+
"unresolved_edge_files": 0
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, and `actions`. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak`.
|
|
1001
|
+
|
|
1002
|
+
---
|
|
1003
|
+
|
|
912
1004
|
## `explain`: Rule Explanation
|
|
913
1005
|
|
|
914
1006
|
Print rule rationale, examples, fix guidance, and docs URL for one issue type without running analysis.
|
|
@@ -1033,17 +1125,55 @@ On HTTP error from `api.fallow.cloud`, fallow parses the `{error, message, code}
|
|
|
1033
1125
|
|
|
1034
1126
|
Unknown codes fall back to the backend's `message` field, or the raw body.
|
|
1035
1127
|
|
|
1128
|
+
### Clock skew
|
|
1129
|
+
|
|
1130
|
+
License verification rejects JWTs whose `iat` claim is more than 24 hours in the future relative to the local system clock. The same check catches both a forward-signed JWT and a local clock behind reality. Rejection exits non-zero so paid features fail closed.
|
|
1131
|
+
|
|
1132
|
+
| Env var | Default | Effect |
|
|
1133
|
+
|---------|---------|--------|
|
|
1134
|
+
| `FALLOW_LICENSE_SKEW_TOLERANCE_SECONDS` | `86400` (24h) | Overrides the tolerance window applied to the `iat` claim. Lenient parsing: unset / empty / unparsable / negative all fall back to the default. |
|
|
1135
|
+
|
|
1136
|
+
Common non-user causes: CI containers without NTP, machines with a dead BIOS battery, drifted laptop clocks after long sleep.
|
|
1137
|
+
|
|
1036
1138
|
### Exit Codes
|
|
1037
1139
|
|
|
1038
1140
|
| Code | Meaning |
|
|
1039
1141
|
|------|---------|
|
|
1040
1142
|
| `0` | Valid license (or trial/refresh succeeded) |
|
|
1041
1143
|
| `2` | Bad invocation (missing email for `--trial`, unreadable file) |
|
|
1042
|
-
| `3` | License missing, hard-fail expired,
|
|
1144
|
+
| `3` | License missing, hard-fail expired, malformed JWT, or clock skew exceeds tolerance |
|
|
1043
1145
|
| `7` | Network failure or non-success HTTP status from `api.fallow.cloud` |
|
|
1044
1146
|
|
|
1045
1147
|
---
|
|
1046
1148
|
|
|
1149
|
+
## `telemetry`: Opt-in Product Telemetry
|
|
1150
|
+
|
|
1151
|
+
Manage opt-in, off-by-default product telemetry that helps prioritize agent, CI, MCP, and editor workflows. Fallow never collects repository names, file paths, package or dependency names, source code, config values, environment variable names or values, raw command lines, or raw errors. Hashing those values is not used as a workaround.
|
|
1152
|
+
|
|
1153
|
+
```bash
|
|
1154
|
+
fallow telemetry status # effective state, source, and config path
|
|
1155
|
+
fallow telemetry enable # opt in (user action only; agents must not run this)
|
|
1156
|
+
fallow telemetry disable # opt out
|
|
1157
|
+
fallow telemetry inspect --example # print an example payload + field purposes
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
Inspect the exact payload a real command would send, without sending it:
|
|
1161
|
+
|
|
1162
|
+
```bash
|
|
1163
|
+
FALLOW_TELEMETRY=inspect fallow audit --format json --quiet
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
The inspected payload prints to stderr; stdout (including `--format json`) is untouched.
|
|
1167
|
+
|
|
1168
|
+
### Behavior
|
|
1169
|
+
|
|
1170
|
+
- **Off by default.** Precedence: `DO_NOT_TRACK` / `FALLOW_TELEMETRY_DISABLED` (kill switches) > `FALLOW_TELEMETRY_DEBUG` (forces inspect) > `FALLOW_TELEMETRY` env > CI (off unless `FALLOW_TELEMETRY` is set) > user config (`fallow telemetry enable/disable`) > off.
|
|
1171
|
+
- **CI is off** unless `FALLOW_TELEMETRY` is explicitly set in that CI environment; a local `enable` never turns on org CI telemetry.
|
|
1172
|
+
- **Transport:** when enabled, one small JSON event is POSTed to `https://api.fallow.cloud/v1/telemetry/events` (override with `FALLOW_API_URL`), no auth token, no cookies, on a background thread so it does not delay your command. Delivery is best-effort; errors never change output or exit code.
|
|
1173
|
+
- **Agent source:** wrappers may set `FALLOW_AGENT_SOURCE=<allowlisted-value>` so an enabled run is attributed correctly. Allowlist: `codex`, `claude_code`, `cursor`, `copilot`, `opencode`, `aider`, `roo`, `windsurf`, `gemini` (aliases `gemini_cli`/`antigravity`), `cline`, `continue`, `zed`, `goose`, `other_known`, `unknown`, `none`. Setting it never enables telemetry and uploads no codebase content.
|
|
1174
|
+
|
|
1175
|
+
---
|
|
1176
|
+
|
|
1047
1177
|
## `coverage`: Production-Coverage Workflow
|
|
1048
1178
|
|
|
1049
1179
|
Helper subcommand for runtime coverage setup, focused analysis, and cloud inventory upload. Three subcommands today:
|
|
@@ -1125,11 +1255,14 @@ Only plain JS/TS/JSX/TSX sources are walked. Declaration files (`*.d.ts`, `*.d.m
|
|
|
1125
1255
|
- `FALLOW_COV_BIN` — explicit override for the sidecar binary (for `setup`). Wins over all other discovery paths. Must point to an existing file.
|
|
1126
1256
|
- `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.
|
|
1127
1257
|
- `FALLOW_API_URL` — base URL for cloud calls. Overridden by `--api-endpoint`.
|
|
1258
|
+
- `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.
|
|
1128
1259
|
|
|
1129
1260
|
### `coverage upload-source-maps` flags
|
|
1130
1261
|
|
|
1131
1262
|
Coverage CI helper for bundled/minified runtime coverage. It scans a build directory for `.map` files and uploads them to `/v1/coverage/:repo/source-maps` keyed by the commit SHA the beacon reports.
|
|
1132
1263
|
|
|
1264
|
+
Uploads retry network failures, HTTP 429, and HTTP 502/503/504 up to three attempts. HTTP 429 honors `Retry-After` delta seconds and HTTP-date values, capped at 60 seconds. Setup or transport failures that prevent every map from uploading exit 7; mixed per-map failures still exit 1.
|
|
1265
|
+
|
|
1133
1266
|
| Flag | Type | Default | Description |
|
|
1134
1267
|
|------|------|---------|-------------|
|
|
1135
1268
|
| `--dir <PATH>` | path | `dist` | Directory scanned recursively. |
|
|
@@ -1212,7 +1345,8 @@ Available on all commands:
|
|
|
1212
1345
|
| `--performance` | bool | Show pipeline timing breakdown |
|
|
1213
1346
|
| `-w, --workspace` | string | Scope to one or more workspaces (comma-separated, globs, `!` negation) |
|
|
1214
1347
|
| `--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. |
|
|
1215
|
-
| `--explain` | bool |
|
|
1348
|
+
| `--explain` | bool | JSON: include metric definitions in `_meta`. Human: print a `Description:` line under each section header. Always on for MCP. |
|
|
1349
|
+
| `--legacy-envelope` | bool | Emit the previous typed JSON root envelope without top-level `kind` |
|
|
1216
1350
|
| `--only` | string | Run only specific analyses (e.g., `--only dead-code,dupes`). Values: `dead-code` (alias: `check`), `dupes`, `health` |
|
|
1217
1351
|
| `--skip` | string | Skip specific analyses (e.g., `--skip health`). Values: `dead-code` (alias: `check`), `dupes`, `health` |
|
|
1218
1352
|
| `--ci` | bool | CI mode: `--format sarif --fail-on-issues --quiet` |
|
|
@@ -1235,15 +1369,27 @@ Available on all commands:
|
|
|
1235
1369
|
| `FALLOW_BIN` | Path to fallow binary (used by the MCP server). |
|
|
1236
1370
|
| `FALLOW_TIMEOUT_SECS` | MCP server subprocess timeout in seconds (default: `120`). Increase for very large codebases. |
|
|
1237
1371
|
| `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
|
|
1372
|
+
| `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. |
|
|
1373
|
+
| `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. |
|
|
1238
1374
|
| `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
|
|
1239
1375
|
| `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
|
|
1240
1376
|
| `FALLOW_CHANGED_SINCE` | GitLab CI: git ref for incremental analysis. Auto-detected in MR pipelines. |
|
|
1241
1377
|
| `FALLOW_COMMENT` | GitLab CI: set to `true` to post MR summary comments. |
|
|
1242
1378
|
| `FALLOW_REVIEW` | GitLab CI: set to `true` to post inline code review comments on MR diffs. |
|
|
1379
|
+
| `FALLOW_REVIEW_GUIDANCE` | Add collapsed "What to do" guidance blocks to `review-github` / `review-gitlab` inline comments. |
|
|
1380
|
+
| `FALLOW_SUMMARY_SCOPE` | Sticky PR/MR summary scope for `pr-comment-github` / `pr-comment-gitlab`: `all` (default) keeps project-level findings outside the diff; `diff` applies the diff filter to those findings too. Inline review comments are unaffected. |
|
|
1243
1381
|
| `FALLOW_SCORE` | GitLab CI: set to `true` to compute health score in combined mode. Enables health delta header in MR comments. |
|
|
1244
1382
|
| `FALLOW_TREND` | GitLab CI: set to `true` to compare current health metrics against saved snapshot. Implies `FALLOW_SCORE`. |
|
|
1245
1383
|
| `FALLOW_EXTRA_ARGS` | GitLab CI: additional CLI flags passed through to fallow. |
|
|
1246
1384
|
| `FALLOW_VERSION` | GitLab CI: fallow version to install. Empty (default) reads the project's `package.json` `fallow` dependency, then falls back to `latest`; set explicitly to override the local pin. |
|
|
1385
|
+
| `FALLOW_SKIP_BINARY_VERIFY` | Skip Ed25519 + SHA-256 verification of platform binaries on first invocation of `fallow`, `fallow-lsp`, or `fallow-mcp` (and during the GitHub Action installer). Set to `1`, `true`, or `yes` ONLY when deliberately replacing the published binary (source builds, airgapped mirrors, signed-repack registries). The skip is recorded in `fallow --version` output as `verified: skipped (FALLOW_SKIP_BINARY_VERIFY is set)` so it stays visible in CI logs and vendor audits. Never set in regular CI; use the published binary or the documented out-of-band verification recipe in [`SECURITY.md`](https://github.com/fallow-rs/fallow/blob/main/SECURITY.md) instead. |
|
|
1386
|
+
| `FALLOW_VERIFY_CACHE_DIR` | Override where the lazy-verify sentinel file is written. Cascade is platform-pkg-dir, then this override, then `$XDG_CACHE_HOME/fallow/sentinels/` (Linux/macOS) or `%LOCALAPPDATA%\fallow\sentinels\` (Windows). Useful when the platform pkg dir is read-only (yarn PnP, Docker layered images, pnpm verify-store). |
|
|
1387
|
+
| `FALLOW_VERIFY_LOG` | Set to `1`, `true`, or `yes` to emit one structured stderr line per verify outcome (`fallow-verify outcome=ok cache=hit sentinel=...`). Off by default so MCP stdout/stderr stay clean; enable for CI diagnostic logs. |
|
|
1388
|
+
| `FALLOW_TELEMETRY` | Opt-in product telemetry mode, off by default: `off`/`on`/`inspect` (plus `0`/`1`/`true`/`false`/`disabled`/`enabled`/`debug`/`log`). `inspect` prints the exact payload to stderr without sending. Wins over the user telemetry config. |
|
|
1389
|
+
| `FALLOW_TELEMETRY_DISABLED` | Admin/fleet telemetry kill switch (top precedence, with `DO_NOT_TRACK`). Truthy (`1`/`true`/`yes`/`on`) hard-disables telemetry and refuses `fallow telemetry enable`. |
|
|
1390
|
+
| `FALLOW_TELEMETRY_DEBUG` | Forces inspect mode; outranks `FALLOW_TELEMETRY`. |
|
|
1391
|
+
| `DO_NOT_TRACK` | Honored as a top-precedence telemetry kill switch (consoledonottrack.com convention). |
|
|
1392
|
+
| `FALLOW_AGENT_SOURCE` | Declare the calling agent for telemetry classification (only used when telemetry is enabled; never enables it): `codex`, `claude_code`, `cursor`, `copilot`, `opencode`, `aider`, `roo`, `windsurf`, `gemini` (aliases `gemini_cli`/`antigravity`), `cline`, `continue`, `zed`, `goose`, `other_known`, `unknown`, `none`. Unrecognized values are ignored. |
|
|
1247
1393
|
| `GITLAB_TOKEN` | GitLab CI: project access token with `api` scope (for MR comments/reviews; `CI_JOB_TOKEN` is read-only for MR notes in the official GitLab API). |
|
|
1248
1394
|
|
|
1249
1395
|
Set `FALLOW_FORMAT=json` and `FALLOW_QUIET=1` in your agent environment to avoid passing flags on every invocation.
|
|
@@ -1269,6 +1415,8 @@ Set `FALLOW_FORMAT=json` and `FALLOW_QUIET=1` in your agent environment to avoid
|
|
|
1269
1415
|
|
|
1270
1416
|
`fallow ci reconcile-review` reads a typed review envelope (`--format review-github` / `review-gitlab`), looks up existing fingerprints on the PR/MR, and resolves stale review threads when their finding is no longer present in the new envelope. Posts an idempotent "Resolved in `<sha>`" follow-up comment per stale finding (skipped if a marker for the same fingerprint at the current SHA already exists).
|
|
1271
1417
|
|
|
1418
|
+
Provider mutations are fail-fast. 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.
|
|
1419
|
+
|
|
1272
1420
|
### Flags
|
|
1273
1421
|
|
|
1274
1422
|
| Flag | Type | Description |
|
|
@@ -1301,6 +1449,8 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1301
1449
|
| `FALLOW_CHANGED_SINCE` | auto | Git ref for incremental analysis. Auto-detected in MR pipelines (`origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`) |
|
|
1302
1450
|
| `FALLOW_COMMENT` | `false` | Post a summary comment on the MR with findings |
|
|
1303
1451
|
| `FALLOW_REVIEW` | `false` | Post inline code review comments on MR diff lines where issues were found |
|
|
1452
|
+
| `FALLOW_REVIEW_GUIDANCE` | `false` | Add collapsed "What to do" guidance blocks to inline review comments |
|
|
1453
|
+
| `FALLOW_SUMMARY_SCOPE` | `all` | Sticky summary scope: `all` keeps project-level findings outside the diff; `diff` applies the diff filter to those findings too |
|
|
1304
1454
|
| `FALLOW_SCORE` | `false` | Compute health score (0-100 with letter grade) in combined mode. Enables the health delta header in MR comments |
|
|
1305
1455
|
| `FALLOW_TREND` | `false` | Compare current health metrics against saved snapshot. Implies `FALLOW_SCORE`. Shows per-metric deltas |
|
|
1306
1456
|
| `FALLOW_EXTRA_ARGS` | — | Additional CLI flags passed through to fallow |
|
|
@@ -1318,8 +1468,9 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1318
1468
|
|
|
1319
1469
|
```json
|
|
1320
1470
|
{
|
|
1321
|
-
"
|
|
1322
|
-
"
|
|
1471
|
+
"kind": "dead-code",
|
|
1472
|
+
"schema_version": 7,
|
|
1473
|
+
"version": "2.86.0",
|
|
1323
1474
|
"elapsed_ms": 45,
|
|
1324
1475
|
"total_issues": 12,
|
|
1325
1476
|
"entry_points": {
|
|
@@ -1340,6 +1491,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1340
1491
|
"type_only_dependencies": 0,
|
|
1341
1492
|
"test_only_dependencies": 0,
|
|
1342
1493
|
"circular_dependencies": 0,
|
|
1494
|
+
"re_export_cycles": 0,
|
|
1343
1495
|
"boundary_violations": 0,
|
|
1344
1496
|
"stale_suppressions": 0
|
|
1345
1497
|
},
|
|
@@ -1354,6 +1506,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1354
1506
|
"unlisted_dependencies": [{ "name": "chalk", "imported_from": [{ "path": "src/cli.ts", "line": 1, "col": 0 }] }],
|
|
1355
1507
|
"duplicate_exports": [{ "name": "Config", "locations": ["src/config.ts:5", "src/types.ts:12"] }],
|
|
1356
1508
|
"circular_dependencies": [{ "cycle": ["src/a.ts", "src/b.ts", "src/a.ts"], "line": 3, "col": 0, "is_cross_package": false }],
|
|
1509
|
+
"re_export_cycles": [{ "files": ["src/api/index.ts", "src/api/internal/index.ts"], "kind": "multi-node", "actions": [{ "type": "fix", "kind": "refactor-re-export-cycle", "auto_fixable": false, "description": "Remove one `export * from` (or `export { ... } from`) statement on any one member to break the cycle" }, { "type": "suppress-file", "kind": "suppress-file", "auto_fixable": false, "comment": "// fallow-ignore-file re-export-cycle" }] }],
|
|
1357
1510
|
"boundary_violations": [{ "from_path": "src/ui/Button.ts", "to_path": "src/data/db.ts", "from_zone": "ui", "to_zone": "data", "import_specifier": "../data/db", "line": 5, "col": 0 }],
|
|
1358
1511
|
"unused_optional_dependencies": [{ "name": "fsevents" }],
|
|
1359
1512
|
"type_only_dependencies": [{ "name": "zod", "used_in": ["src/schema.ts"], "line": 12 }],
|
|
@@ -1449,9 +1602,9 @@ Health findings (`fallow health` JSON output) include an `actions` array. Primar
|
|
|
1449
1602
|
|
|
1450
1603
|
The `coverage_tier` field is `"none"` (file not test-reachable / Istanbul 0%), `"partial"` (Istanbul `(0, 70)` / estimated 40%), or `"high"` (Istanbul `>= 70` / estimated 85%).
|
|
1451
1604
|
|
|
1452
|
-
Each CRAP finding also carries a `coverage_source` discriminator: `"istanbul"` (direct fnMap match for this function), `"estimated"` (graph-based estimate evaluated against the finding's own file), or `"estimated_component_inherited"` (graph-based estimate inherited from an Angular component `.ts` reached via the inverse `templateUrl` edge). Synthetic `<template>` findings on Angular `.html` templates use the `estimated_component_inherited` source and
|
|
1605
|
+
Each CRAP finding also carries a `coverage_source` discriminator: `"istanbul"` (direct fnMap match for this function), `"estimated"` (graph-based estimate evaluated against the finding's own file), or `"estimated_component_inherited"` (graph-based estimate inherited from an Angular component `.ts` reached via the inverse `templateUrl` edge). The report summary carries `coverage_source_consistency` (`"uniform"` or `"mixed"`) whenever emitted CRAP findings have source data; grouped health JSON also includes `groups[].coverage_source_consistency`. Synthetic `<template>` findings on Angular `.html` templates use the `estimated_component_inherited` source and include an `inherited_from` field with the project-relative path to the owning `.component.ts`. When the inherit path applies, the primary `increase-coverage` action targets that `.ts` file (description names the component path explicitly and includes a `target_path` field) so AI agents add component tests rather than scaffolding tests against a structurally untestable `.html` path. The human `fallow health` output renders `(inherited from <project-relative-path>.component.ts)` after the CRAP score on those rows (project-relative since fallow 2.78.0; was the bare basename before). This is the JIT-test fallback (Angular's runtime renders templates via `ɵɵconditional` / `ɵɵrepeaterCreate` calls; Istanbul never has `fnMap` entries keyed at `.html` paths). AOT-compiled coverage with source-map back-mapping is planned as a phase 2 follow-up; when it lands, `coverage_source` will gain a `"measured_aot_source_map"` variant.
|
|
1453
1606
|
|
|
1454
|
-
When CRAP-only with cyclomatic count within
|
|
1607
|
+
When CRAP-only with cyclomatic count within `health.crapRefactorBand` of `maxCyclomatic` AND cognitive at or above `maxCognitive / 2`, a secondary `refactor-function` is appended. The default band is `5`; set it to `0` to only add the secondary refactor after cyclomatic reaches `maxCyclomatic`. The cognitive floor suppresses false positives on flat type-tag dispatchers and JSX render maps (high CC, near-zero cog). A single finding can carry multiple action types: e.g. a finding that exceeds both cyclomatic and CRAP at `coverage_tier=partial` gets `increase-coverage` AND `refactor-function`. Treat the first non-`suppress-line` action as primary.
|
|
1455
1608
|
|
|
1456
1609
|
The `suppress-line` action is auto-omitted when `--baseline`/`--save-baseline` is set, OR when `health.suggestInlineSuppression: false` in config. The report root carries an `actions_meta: { suppression_hints_omitted: true, reason: "baseline-active" | "config-disabled" }` breadcrumb in that case.
|
|
1457
1610
|
|
|
@@ -1475,8 +1628,9 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1475
1628
|
|
|
1476
1629
|
```json
|
|
1477
1630
|
{
|
|
1478
|
-
"
|
|
1479
|
-
"
|
|
1631
|
+
"kind": "dupes",
|
|
1632
|
+
"schema_version": 7,
|
|
1633
|
+
"version": "2.86.0",
|
|
1480
1634
|
"elapsed_ms": 82,
|
|
1481
1635
|
"total_clones": 15,
|
|
1482
1636
|
"total_lines_duplicated": 230,
|
|
@@ -1518,9 +1672,13 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1518
1672
|
|
|
1519
1673
|
```json
|
|
1520
1674
|
{
|
|
1675
|
+
"kind": "combined",
|
|
1676
|
+
"schema_version": 7,
|
|
1677
|
+
"version": "2.86.0",
|
|
1678
|
+
"elapsed_ms": 159,
|
|
1521
1679
|
"check": {
|
|
1522
|
-
"schema_version":
|
|
1523
|
-
"version": "2.
|
|
1680
|
+
"schema_version": 7,
|
|
1681
|
+
"version": "2.86.0",
|
|
1524
1682
|
"elapsed_ms": 45,
|
|
1525
1683
|
"total_issues": 12,
|
|
1526
1684
|
"unused_files": [],
|
|
@@ -1534,6 +1692,7 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1534
1692
|
"unlisted_dependencies": [],
|
|
1535
1693
|
"duplicate_exports": [],
|
|
1536
1694
|
"circular_dependencies": [],
|
|
1695
|
+
"re_export_cycles": [],
|
|
1537
1696
|
"boundary_violations": [],
|
|
1538
1697
|
"unused_optional_dependencies": [],
|
|
1539
1698
|
"type_only_dependencies": [],
|
|
@@ -1541,18 +1700,12 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1541
1700
|
"stale_suppressions": []
|
|
1542
1701
|
},
|
|
1543
1702
|
"dupes": {
|
|
1544
|
-
"schema_version": 3,
|
|
1545
|
-
"version": "2.75.0",
|
|
1546
|
-
"elapsed_ms": 82,
|
|
1547
1703
|
"total_clones": 15,
|
|
1548
1704
|
"total_lines_duplicated": 230,
|
|
1549
1705
|
"duplication_percentage": 4.2,
|
|
1550
1706
|
"clone_groups": []
|
|
1551
1707
|
},
|
|
1552
1708
|
"health": {
|
|
1553
|
-
"schema_version": 3,
|
|
1554
|
-
"version": "2.75.0",
|
|
1555
|
-
"elapsed_ms": 32,
|
|
1556
1709
|
"summary": {},
|
|
1557
1710
|
"findings": [],
|
|
1558
1711
|
"vital_signs": {}
|
|
@@ -1652,6 +1805,13 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
1652
1805
|
// ]
|
|
1653
1806
|
},
|
|
1654
1807
|
|
|
1808
|
+
// Resolve framework convention auto-imports (Nuxt components) as graph edges.
|
|
1809
|
+
// Edges for `<Card001 />`-style template tags are always synthesized; setting
|
|
1810
|
+
// this to true also drops the Nuxt component entry patterns so an
|
|
1811
|
+
// unreferenced component is reported as unused-file. Kept conservative: a
|
|
1812
|
+
// `components:` key in nuxt.config keeps the entry patterns. Default false.
|
|
1813
|
+
"autoImports": false,
|
|
1814
|
+
|
|
1655
1815
|
// Production mode
|
|
1656
1816
|
"production": false,
|
|
1657
1817
|
|
|
@@ -1724,4 +1884,4 @@ preset = "bulletproof"
|
|
|
1724
1884
|
|
|
1725
1885
|
### Valid Issue Type Tokens
|
|
1726
1886
|
|
|
1727
|
-
`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`, `boundary-violation`, `unused-optional-dependency`, `type-only-dependency`, `test-only-dependency`, `code-duplication`
|
|
1887
|
+
`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`
|
|
@@ -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
|
|
26
|
+
Fallow works with zero configuration for most projects thanks to 114 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
|
|
@@ -154,6 +154,8 @@ The action exposes `outputs.verdict` (`pass`/`warn`/`fail`) and `outputs.gate` f
|
|
|
154
154
|
run: exit 1
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
Three additional outputs surface silent failures in the action's PR comment / review steps. `outputs.changed-files-unavailable` (`true`/`false`, default `false`) signals that the analyze step could not enumerate PR-changed files (transient GitHub API failure, expired token, missing permissions), so analysis ran against the full codebase. `outputs.post-skipped-reason` (`none`/`pagination_failure`) signals the Post review comments step aborted to avoid duplicate threads. `outputs.dedup-lookup-failed` (`true`/`false`) signals a dedup lookup failed on either the Post PR comment or Post review comments step. All three are always emitted regardless of which failure path was taken, so downstream `if:` gates can match positively without absent-vs-false ambiguity. Gate on these to detect degraded posting state and re-run the action.
|
|
158
|
+
|
|
157
159
|
### GitHub Actions: Inline PR Annotations (No Advanced Security)
|
|
158
160
|
|
|
159
161
|
The official action supports inline PR annotations via GitHub workflow commands. This does not require Advanced Security (unlike SARIF upload) and works on any GitHub plan.
|
|
@@ -226,9 +228,10 @@ fallow:
|
|
|
226
228
|
extends: .fallow
|
|
227
229
|
variables:
|
|
228
230
|
FALLOW_COMMENT: "true"
|
|
231
|
+
FALLOW_SUMMARY_SCOPE: "diff"
|
|
229
232
|
```
|
|
230
233
|
|
|
231
|
-
Posts a summary comment on the MR with issue counts and findings. In MR pipelines, `--changed-since` is auto-detected from `$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`, so only issues from changed files are reported. Requires `GITLAB_TOKEN` CI/CD variable (project access token with `api` scope); `CI_JOB_TOKEN` is read-only for MR notes in the official GitLab API.
|
|
234
|
+
Posts a summary comment on the MR with issue counts and findings. In MR pipelines, `--changed-since` is auto-detected from `$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`, so only issues from changed files are reported. `FALLOW_SUMMARY_SCOPE: "diff"` also hides project-level dependency/catalog/override findings whose anchor line is outside the diff. Requires `GITLAB_TOKEN` CI/CD variable (project access token with `api` scope); `CI_JOB_TOKEN` is read-only for MR notes in the official GitLab API.
|
|
232
235
|
|
|
233
236
|
### GitLab CI: With Inline Code Review Comments
|
|
234
237
|
|
|
@@ -240,9 +243,10 @@ fallow:
|
|
|
240
243
|
extends: .fallow
|
|
241
244
|
variables:
|
|
242
245
|
FALLOW_REVIEW: "true"
|
|
246
|
+
FALLOW_REVIEW_GUIDANCE: "true"
|
|
243
247
|
```
|
|
244
248
|
|
|
245
|
-
Posts inline review comments directly on the MR diff lines where issues were found. This gives developers precise feedback without leaving the code review flow. Can be combined with `FALLOW_COMMENT: "true"` for both a summary and inline comments. Requires `GITLAB_TOKEN`.
|
|
249
|
+
Posts inline review comments directly on the MR diff lines where issues were found. `FALLOW_REVIEW_GUIDANCE: "true"` adds collapsed "What to do" guidance blocks to each inline finding. This gives developers precise feedback without leaving the code review flow. Can be combined with `FALLOW_COMMENT: "true"` for both a summary and inline comments. Requires `GITLAB_TOKEN`.
|
|
246
250
|
|
|
247
251
|
### GitLab CI: Combined MR Comments + Review
|
|
248
252
|
|
|
@@ -254,11 +258,13 @@ fallow:
|
|
|
254
258
|
extends: .fallow
|
|
255
259
|
variables:
|
|
256
260
|
FALLOW_COMMENT: "true"
|
|
261
|
+
FALLOW_SUMMARY_SCOPE: "diff"
|
|
257
262
|
FALLOW_REVIEW: "true"
|
|
263
|
+
FALLOW_REVIEW_GUIDANCE: "true"
|
|
258
264
|
FALLOW_FAIL_ON_ISSUES: "true"
|
|
259
265
|
```
|
|
260
266
|
|
|
261
|
-
Posts both a summary comment and inline review comments on the MR. The template auto-detects the package manager (npm/pnpm/yarn) from lockfiles, so review comments show the correct commands for the project (e.g., `pnpm remove` instead of `npm uninstall`).
|
|
267
|
+
Posts both a summary comment and inline review comments on the MR. `FALLOW_SUMMARY_SCOPE: "diff"` only affects the sticky summary; inline review comments remain anchored to diff lines. The template auto-detects the package manager (npm/pnpm/yarn) from lockfiles, so review comments show the correct commands for the project (e.g., `pnpm remove` instead of `npm uninstall`).
|
|
262
268
|
|
|
263
269
|
### GitLab CI: With Health Score and Trend
|
|
264
270
|
|
|
@@ -621,7 +627,7 @@ Focus on findings that are BOTH dead code and duplicated:
|
|
|
621
627
|
|
|
622
628
|
## Custom Plugin Setup
|
|
623
629
|
|
|
624
|
-
For frameworks not covered by the
|
|
630
|
+
For frameworks not covered by the 114 built-in plugins.
|
|
625
631
|
|
|
626
632
|
### Option 1: Inline framework config
|
|
627
633
|
|