fallow 3.42.0 → 3.43.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 +3 -1
- package/capabilities.json +127 -40
- package/issue-registry.json +68 -32
- package/package.json +9 -10
- package/schema.json +51 -4
- package/scripts/lazy-verify.js +5 -4
- package/scripts/platform-package.js +25 -0
- package/scripts/run-binary.js +149 -30
- package/scripts/verify-binary.js +25 -21
- package/skills/fallow/SKILL.md +25 -3
- package/skills/fallow/references/cli-reference.md +53 -29
- package/skills/fallow/references/gotchas.md +2 -0
- package/skills/fallow/references/issue-types.md +1 -0
- package/skills/fallow/references/mcp.md +3 -3
- package/skills/fallow/references/similar-code.md +1 -1
- package/types/output-contract.d.ts +758 -29
|
@@ -48,7 +48,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
|
|
|
48
48
|
|---|---|---|
|
|
49
49
|
| `fallow` | Run full codebase analysis: cleanup + duplication + health (default) | `--only`, `--skip`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--ci`, `--fail-on-issues`, `--group-by`, `--summary`, `--fail-on-regression`, `--tolerance`, `--regression-baseline`, `--save-regression-baseline`, `--score`, `--trend`, `--save-snapshot`, `--include-entry-exports` |
|
|
50
50
|
| `dead-code` | Dead code analysis (`check` is an alias) | `--unused-exports`, `--changed-since`, `--changed-workspaces`, `--production`, `--file`, `--include-entry-exports`, `--stale-suppressions`, `--ci`, `--group-by`, `--summary`, `--fail-on-regression`, `--tolerance`, `--regression-baseline`, `--save-regression-baseline` |
|
|
51
|
-
| `architecture` | Check import cycles, boundaries and policy rules
|
|
51
|
+
| `architecture` | Check import cycles, boundaries and policy rules; optionally inventory shared-state access | `--cycles`, `--boundaries`, `--policy`, `--shared-state`, `--file`, `--finding-id`, `path` |
|
|
52
52
|
| `watch` | Watch for changes and re-run analysis | `--no-clear` |
|
|
53
53
|
| `type-aware` | Inspect the optional TypeScript semantic companion | |
|
|
54
54
|
| `doctor` | Diagnose project readiness without analysis or mutation | |
|
|
@@ -141,6 +141,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
|
|
|
141
141
|
| `--unrendered-components` | A Vue / Svelte component is reachable through a barrel but rendered nowhere |
|
|
142
142
|
| `--unused-component-props` | A Vue defineProps prop or React component prop is referenced nowhere in its own component |
|
|
143
143
|
| `--absent-component-props` | Known reachable callers omit an optional prop consumed inside its component |
|
|
144
|
+
| `--constant-parameters` | Each visible call passes a function parameter in the same way |
|
|
144
145
|
| `--unused-component-emits` | A Vue <script setup> defineEmits event is emitted nowhere in its own component |
|
|
145
146
|
| `--unused-component-inputs` | An Angular @Input() / signal input() / model() is read nowhere in its own component (class body or template); needs `@angular/core` dep |
|
|
146
147
|
| `--unused-component-outputs` | An Angular @Output() / signal output() is emitted (.emit()) nowhere in its own component; needs `@angular/core` dep |
|
|
@@ -444,7 +445,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
|
|
|
444
445
|
|
|
445
446
|
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.
|
|
446
447
|
|
|
447
|
-
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues. The dead-code result adds two more kinds, each with a `pattern` payload and `path: "."`: `ignore-dependencies-glob-unmatched` (an `ignoreDependencies` glob matched no declared dependency; omitted when the run reports no dependency finding, for example with `--unused-files` or `--file`) and `ignore-findings-pattern-unmatched` (an `ignoreFindings` pattern matched no finding). It also adds three kinds for an entry that matched but changed nothing, with the same payload: `entry-pattern-redundant` (every file the `entry` pattern matches is already a runtime entry point from another source), `ignore-dependencies-entry-redundant` (the `ignoreDependencies` entry hid no dependency finding, and a `--production` run has no reason to need it; same gate as the unmatched glob) and `ignore-patterns-entry-redundant` (the `ignorePatterns` glob excluded no file that a built-in ignore, `.gitignore` or the file type filter does not already skip; a glob for build output has an effect only after a build). A `--production` run reports none of the three. It also adds `override-rule-ineffective` for an `overrides` rule that cannot change a result, with `rule` (canonical name), `files` (the globs as written), `reason` and `path: "."`. It also adds `requested-rule-off` when an explicit issue-type flag asks for a rule that the config sets to `off`, with `rule`, `flag` and `path: "."`. A zero count for that rule then measures nothing. The exit code does not change. `--lean` keeps the first entry of each kind plus a `count`, so run without `--lean` to see each rule. The `reason` token set is open. `project-level` marks a rule that reads only the top-level severity: unused-dependencies, unused-dev-dependencies, unused-optional-dependencies, unlisted-dependencies, type-only-dependencies, test-only-dependencies, dev-dependencies-in-production, duplicate-exports, re-export-cycle and unused-catalog-entries. `base-rule-off` marks a rule that is off at top level after CLI flags, so the detector does not run. The `message` names the mechanism that works. The dependency rows follow the same dependency-report gate as the unmatched glob, and `--production` keeps every row. 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, a `Redundant config entries` section for the three redundant kinds, a `Requested checks that did not run` section for `requested-rule-off`, and an `Override rules with no effect` section for `override-rule-ineffective`. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
|
|
448
|
+
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`, `client-server-leak-no-client-roots`) 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). It also adds three kinds for an entry that matched but changed nothing, with the same payload: `entry-pattern-redundant` (every file the `entry` pattern matches is already a runtime entry point from another source), `ignore-dependencies-entry-redundant` (the `ignoreDependencies` entry hid no dependency finding, and a `--production` run has no reason to need it; same gate as the unmatched glob) and `ignore-patterns-entry-redundant` (the `ignorePatterns` glob excluded no file that a built-in ignore, `.gitignore` or the file type filter does not already skip; a glob for build output has an effect only after a build). A `--production` run reports none of the three. It also adds `override-rule-ineffective` for an `overrides` rule that cannot change a result, with `rule` (canonical name), `files` (the globs as written), `reason` and `path: "."`. It also adds `requested-rule-off` when an explicit issue-type flag asks for a rule that the config sets to `off`, with `rule`, `flag` and `path: "."`. A zero count for that rule then measures nothing. The exit code does not change. `--lean` keeps the first entry of each kind plus a `count`, so run without `--lean` to see each rule. The `reason` token set is open. `project-level` marks a rule that reads only the top-level severity: unused-dependencies, unused-dev-dependencies, unused-optional-dependencies, unlisted-dependencies, type-only-dependencies, test-only-dependencies, dev-dependencies-in-production, duplicate-exports, re-export-cycle and unused-catalog-entries. `base-rule-off` marks a rule that is off at top level after CLI flags, so the detector does not run. The `message` names the mechanism that works. The dependency rows follow the same dependency-report gate as the unmatched glob, and `--production` keeps every row. 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, a `Redundant config entries` section for the three redundant kinds, a `Requested checks that did not run` section for `requested-rule-off`, and an `Override rules with no effect` section for `override-rule-ineffective`. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
|
|
448
449
|
|
|
449
450
|
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`.
|
|
450
451
|
|
|
@@ -461,8 +462,8 @@ Creates a config file in the project root.
|
|
|
461
462
|
|---|---|---|---|
|
|
462
463
|
| `--toml` | `bool` | `false` | Create `fallow.toml` instead of `.fallowrc.json` |
|
|
463
464
|
| `--agents` | `bool` | `false` | Scaffold a starter `AGENTS.md` guide for coding agents. Prefills Install (from the `packageManager` field, or pnpm via `pnpm-workspace.yaml`), Test (only when exactly one of Vitest / Jest / Playwright is present), Typecheck (`tsc --noEmit` when `tsconfig.json` exists), and monorepo module-boundary lines; everything ambiguous stays blank (no lockfile sniffing). Prefilled command lines carry an HTML provenance comment. Refuses to overwrite an existing `AGENTS.md` |
|
|
464
|
-
| `--hooks` | `bool` | `false` | Scaffold a pre-commit git hook that runs `fallow audit --
|
|
465
|
-
| `--branch` | `string` | - |
|
|
465
|
+
| `--hooks` | `bool` | `false` | Scaffold a pre-commit git hook that runs `fallow audit --staged --quiet --gate-marker pre-commit`. Alias for `fallow hooks install --target git` |
|
|
466
|
+
| `--branch` | `string` | - | Has no effect. The pre-commit hook audits the staged changes against HEAD. Kept so that existing install scripts still run. Only used with `--hooks` |
|
|
466
467
|
| `--decline` | `bool` | `false` | Record that this project deliberately stays unconfigured: persists a decline so the first-contact setup hint and the `setup` next-step stop appearing here. Writes no config file; idempotent |
|
|
467
468
|
|
|
468
469
|
Common global flags for this command: [`--root`](#global-flags), [`--config`](#global-flags).
|
|
@@ -473,8 +474,7 @@ Common global flags for this command: [`--root`](#global-flags), [`--config`](#g
|
|
|
473
474
|
fallow init # creates .fallowrc.json with $schema
|
|
474
475
|
fallow init --toml # creates fallow.toml
|
|
475
476
|
fallow init --agents # scaffolds a starter AGENTS.md prefilled from detected project info (never overwrites)
|
|
476
|
-
fallow hooks install --target git
|
|
477
|
-
fallow hooks install --target git --branch develop # fallback base branch when no upstream is set
|
|
477
|
+
fallow hooks install --target git # pre-commit hook: fallow audit --staged
|
|
478
478
|
```
|
|
479
479
|
|
|
480
480
|
## `hooks`: Managed Hook Status And Installation
|
|
@@ -487,6 +487,8 @@ fallow hooks uninstall --target git
|
|
|
487
487
|
fallow hooks uninstall --target agent
|
|
488
488
|
```
|
|
489
489
|
|
|
490
|
+
The Git pre-commit hook runs `fallow audit --staged`. It audits the staged changes against HEAD and reads each file from the index, so unstaged edits and untracked files do not change the verdict. Before the first commit of a repository, the hook skips the audit and prints one line on stderr.
|
|
491
|
+
|
|
490
492
|
`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.
|
|
491
493
|
|
|
492
494
|
---
|
|
@@ -530,7 +532,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
|
|
|
530
532
|
{
|
|
531
533
|
"kind": "agent-install",
|
|
532
534
|
"schema_version": 1,
|
|
533
|
-
"fallow_version": "3.
|
|
535
|
+
"fallow_version": "3.43.0",
|
|
534
536
|
"root": "/abs/path",
|
|
535
537
|
"mode": "install",
|
|
536
538
|
"dry_run": false,
|
|
@@ -742,7 +744,7 @@ fallow health --format json --quiet --trend
|
|
|
742
744
|
{
|
|
743
745
|
"kind": "health",
|
|
744
746
|
"schema_version": 7,
|
|
745
|
-
"version": "3.
|
|
747
|
+
"version": "3.43.0",
|
|
746
748
|
"elapsed_ms": 32,
|
|
747
749
|
"summary": {
|
|
748
750
|
"files_analyzed": 482,
|
|
@@ -816,7 +818,7 @@ With `--file-scores`, the JSON output also includes `file_scores` array and `sum
|
|
|
816
818
|
|
|
817
819
|
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.
|
|
818
820
|
|
|
819
|
-
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`. It is always the raw measured value. 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 an Istanbul `coverage-final.json` or raw V8 coverage for
|
|
821
|
+
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`. It is always the raw measured value. 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 an Istanbul `coverage-final.json` or raw V8 coverage for measured scores (`istanbul` model; `summary.coverage_input_format` is `istanbul` or `v8`). When CRAP findings use the static estimate, the human output prints one note above the findings that says no coverage file was found; `--explain` adds the estimate tiers to that note. The `crap_above_threshold` field counts functions whose rounded CRAP meets or exceeds their effective ceiling, resolved from `health.thresholdOverrides` over the global `maxCrap` / `--max-crap` value (default 30); it is 0 when CRAP enforcement is disabled (`maxCrap: 0`). Rows whose breaches were let through by configuration carry two additional fields: `crap_exempted` (functions at or above the canonical 30 baseline but below their effective ceiling; omitted when 0) and `crap_effective_threshold` (the lowest effective ceiling among the file's functions, present only when it differs from `summary.max_crap_threshold`). 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`.
|
|
820
822
|
|
|
821
823
|
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.
|
|
822
824
|
|
|
@@ -1095,7 +1097,7 @@ The snapshot `snapshot_schema_version` is independent of the report `schema_vers
|
|
|
1095
1097
|
|
|
1096
1098
|
## `audit`: Changed-File Quality Gate
|
|
1097
1099
|
|
|
1098
|
-
Audits changed files for dead code, complexity, duplication, and styling. Returns a verdict (pass/warn/fail). Purpose-built for PR quality gates and reviewing AI-generated code. When `--base` is not set, the base is the `git merge-base` against the branch's upstream or the remote default (`origin/HEAD`, `origin/main`, `origin/master`); set `FALLOW_AUDIT_BASE` to pin it without a flag. Defaults to `--gate new-only`, which fails only on findings introduced by the current changeset and reports inherited findings as context.
|
|
1100
|
+
Audits changed files for dead code, complexity, duplication, and styling. Returns a verdict (pass/warn/fail). Purpose-built for PR quality gates and reviewing AI-generated code. When `--base` is not set, the base is the `git merge-base` against the branch's upstream or the remote default (`origin/HEAD`, `origin/main`, `origin/master`); set `FALLOW_AUDIT_BASE` to pin it without a flag. Defaults to `--gate new-only`, which fails only on findings introduced by the current changeset and reports inherited findings as context. Under `new-only`, the audit also reports an unused file or unused export outside the changed files when the base does not report it. Such a finding is introduced: the change removed the last use of that code, or a config change (for example a removed entry point) made it unreachable. An unused file or unused export outside the changed files that the base also reports stays out of the report.
|
|
1099
1101
|
|
|
1100
1102
|
### Flags
|
|
1101
1103
|
|
|
@@ -1116,11 +1118,13 @@ Audits changed files for dead code, complexity, duplication, and styling. Return
|
|
|
1116
1118
|
| `--no-css` | `bool` | `false` | Disable styling analytics in audit |
|
|
1117
1119
|
| `--css-deep` | `bool` | `false` | Enable deep CSS analysis for audit explicitly: project-wide styling reachability, narrowed back to changed anchors. Deep CSS is on by default; use this to override `audit.cssDeep = false` |
|
|
1118
1120
|
| `--no-css-deep` | `bool` | `false` | Disable deep CSS analysis while keeping local styling analytics on |
|
|
1119
|
-
| `--gate` | `new-only\|all` | - | Which findings affect the verdict. `new-only` gates only introduced findings; `all` gates every finding in changed files and skips the extra base-snapshot attribution pass. |
|
|
1121
|
+
| `--gate` | `new-only\|all` | - | Which findings affect the verdict. `new-only` gates only introduced findings, including unused files and exports outside the changed files that the base does not report; `all` gates every finding in changed files and skips the extra base-snapshot attribution pass. |
|
|
1120
1122
|
| `--worsened` | `warn\|error\|off` | - | How a worsened complexity finding affects the verdict under `new-only`. A worsened finding was already in the base, and a measured value is higher now. The line count must grow by at least 5 lines, and it includes comments and blank lines. `warn` (default) gives at least a `warn` verdict. `error` counts the finding like an introduced finding. `off` reports no worsened finding. A grown exceeded metric stays introduced in both modes. |
|
|
1121
1123
|
| `--runtime-coverage` | `string` | - | Runtime coverage input. Accepts a V8 directory, a single V8 JSON file, or an Istanbul coverage map JSON. Runs the `fallow-cov` sidecar inside the audit, so the `hot-path-touched` verdict shows next to the dead-code and complexity findings without a second `fallow health` run in CI. The verdict is informational and does not change the exit code. A single local capture is free. Continuous or multi-capture monitoring needs a license (see `fallow license`) |
|
|
1122
1124
|
| `--min-invocations-hot` | `string` | `100` | Threshold for hot-path classification, forwarded to the sidecar when `--runtime-coverage` is set |
|
|
1125
|
+
| `--mutation-report` | `string` | - | Saved `fallow mutation --format json` report (for example `.fallow/mutation/last-report.json`). The review brief counts the surviving mutants on the added lines of each changed file in the `mutation` component of the focus score. A file whose source changed since the run adds nothing. Reads the file only; it runs no test. Only the review brief reads it |
|
|
1123
1126
|
| `--gate-marker` | `string` | - | Internal marker identifying a gate run (e.g. `pre-commit`), set by the generated git hook so Fallow Impact can record a containment event when the gate blocks then clears. Hidden; never changes the verdict, exit code, or output |
|
|
1127
|
+
| `--staged` | `bool` | `false` | Audit the staged index instead of the working tree. The changed files are the staged changes against HEAD (`git diff --cached`), and the analysis reads every file from the index, so unstaged edits and untracked files do not affect the verdict. A file that is staged and then deleted from the working tree is still audited. The base is HEAD, so `--base` and `--changed-since` are rejected. The generated pre-commit hook uses this flag |
|
|
1124
1128
|
| `--brief` | `bool` | `false` | Render the deterministic review brief instead of the gating audit report. The brief answers "where do I look?" rather than "will CI block this?", runs the same analysis, and ALWAYS exits 0 (the verdict is carried informationally). Implied by `fallow review`. Orthogonal to `--format` |
|
|
1125
1129
|
| `--max-decisions` | `string` | `4` | Cap on the number of consequential structural decisions surfaced in the review brief's decision surface (the working-memory limit). Default 4; clamped to the 3-5 band (4 plus or minus 1). Only consulted on the brief path |
|
|
1126
1130
|
| `--walkthrough-guide` | `bool` | `false` | Emit the agent-contract WALKTHROUGH GUIDE: the current digest (brief + decision surface), the review direction, the JSON schema the agent must return, and a deterministic graph-snapshot hash pinned into the digest. The digest is built from the graph only (PR prose is never folded in, so it is injection-resistant). Implies the brief; always exits 0. A thin agent skill calls this to fetch the current guide, produces judgment JSON, then reopens with `--walkthrough-file` |
|
|
@@ -1217,7 +1221,7 @@ fallow audit \
|
|
|
1217
1221
|
{
|
|
1218
1222
|
"kind": "audit",
|
|
1219
1223
|
"schema_version": 7,
|
|
1220
|
-
"version": "3.
|
|
1224
|
+
"version": "3.43.0",
|
|
1221
1225
|
"command": "audit",
|
|
1222
1226
|
"verdict": "fail",
|
|
1223
1227
|
"changed_files_count": 12,
|
|
@@ -1257,7 +1261,7 @@ fallow audit \
|
|
|
1257
1261
|
}
|
|
1258
1262
|
```
|
|
1259
1263
|
|
|
1260
|
-
The `verdict` field is always present and is the primary decision signal. With the default `new-only` gate, the `attribution` object counts introduced vs inherited findings across dead code, complexity, duplication, and styling, and audit sub-results annotate individual findings with `introduced: true/false`. With `gate=all`, audit skips that extra base-snapshot attribution pass, so introduced/inherited counts stay `0` and per-finding `introduced` fields are omitted. Dead code, complexity, and duplication sections follow their respective schemas from the individual commands. Thresholds for complexity are inherited from `fallow health` config (defaults: cyclomatic 20, cognitive 15).
|
|
1264
|
+
The `verdict` field is always present and is the primary decision signal. With the default `new-only` gate, the `attribution` object counts introduced vs inherited findings across dead code, complexity, duplication, and styling, and audit sub-results annotate individual findings with `introduced: true/false`. With `gate=all`, audit skips that extra base-snapshot attribution pass, so introduced/inherited counts stay `0` and per-finding `introduced` fields are omitted. A clone group where each instance lies inside a different instance of one base clone group is inherited: an edit inside one copy of a base clone (a changed line, a renamed parameter) splits the base group into smaller groups, and the change did not write that duplication. A pasted copy in a new file has no base lines, so its clone group stays introduced. A group stays introduced also when every copy holds an added line, because the change then wrote the same text into each copy. Dead code, complexity, and duplication sections follow their respective schemas from the individual commands. Thresholds for complexity are inherited from `fallow health` config (defaults: cyclomatic 20, cognitive 15).
|
|
1261
1265
|
|
|
1262
1266
|
Audit creates a temporary git worktree to compare against the base ref. When the current checkout has `node_modules`, audit links it into the base worktree so tsconfig `extends` chains into installed packages and path aliases resolve like the working tree. The worktree is removed on normal exit. If the process is force-killed, run `git worktree prune` to clean up stale `.git/worktrees/fallow-audit-base-*` entries.
|
|
1263
1267
|
|
|
@@ -1305,7 +1309,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
1305
1309
|
```json
|
|
1306
1310
|
{
|
|
1307
1311
|
"schema_version": 7,
|
|
1308
|
-
"version": "3.
|
|
1312
|
+
"version": "3.43.0",
|
|
1309
1313
|
"elapsed_ms": 116,
|
|
1310
1314
|
"feature_flags": [],
|
|
1311
1315
|
"total_flags": 0
|
|
@@ -1406,7 +1410,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1406
1410
|
{
|
|
1407
1411
|
"kind": "security",
|
|
1408
1412
|
"schema_version": "4",
|
|
1409
|
-
"version": "3.
|
|
1413
|
+
"version": "3.43.0",
|
|
1410
1414
|
"elapsed_ms": 42,
|
|
1411
1415
|
"config": {
|
|
1412
1416
|
"rules": {
|
|
@@ -1435,7 +1439,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1435
1439
|
{
|
|
1436
1440
|
"kind": "security",
|
|
1437
1441
|
"schema_version": "4",
|
|
1438
|
-
"version": "3.
|
|
1442
|
+
"version": "3.43.0",
|
|
1439
1443
|
"elapsed_ms": 42,
|
|
1440
1444
|
"config": {
|
|
1441
1445
|
"rules": {
|
|
@@ -1483,11 +1487,11 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1483
1487
|
}
|
|
1484
1488
|
```
|
|
1485
1489
|
|
|
1486
|
-
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`. `client-server-leak` findings carry `category` (`"client-server-leak"` for a secret leak, `"server-only-import"` for server-only code) and omit `cwe`. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module (a test, story or tooling-config file, and a `process.env` secret, never start this trace); it is ranking and triage context only, not proof that a specific value reaches the sink. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
|
|
1490
|
+
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`. `client-server-leak` findings carry `category` (`"client-server-leak"` for a secret leak, `"server-only-import"` for server-only code) and omit `cwe`. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module (a test, story or tooling-config file, and a `process.env` secret, never start this trace); it is ranking and triage context only, not proof that a specific value reaches the sink. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. When no analyzed file has a top-level `"use client"` directive, `workspace_diagnostics[]` carries `client-server-leak-no-client-roots` and the human output prints a blind-spot line: the rule had no starting point, so its zero count measures nothing. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
|
|
1487
1491
|
|
|
1488
1492
|
When present, `unresolved_callee_diagnostics` adds bounded unresolved-callee metadata for follow-up review: `sampled[]` rows with `path`, `line`, `col`, `reason`, and `expression_kind`, `top_files[]` counts, `by_reason[]` counts, and the emitted sample/top-file limits. It is blind-spot metadata, not a finding list, and follows the same `--file`, `--workspace`, `--changed-since`, and `--gate new` scoping as security candidates.
|
|
1489
1493
|
|
|
1490
|
-
Every finding also carries an agent-actionable `candidate { source_kind, sink, boundary }`, an optional `taint_flow { source, sink, path }`, and
|
|
1494
|
+
Every finding also carries an agent-actionable `candidate { source_kind, sink, boundary }`, an optional `taint_flow { source, sink, path }`, a `finding_id` and an optional `stable_finding_id`:
|
|
1491
1495
|
|
|
1492
1496
|
- `candidate.source_kind`: the untrusted-input kind that reaches the sink, as a stable catalogue id (`"http-request-input"`, `"process-env"`, `"process-argv"`, `"message-event-data"`, `"location-input"`, ...). Absent when no source matched (always absent for `client-server-leak`). Treat an unknown id as an untrusted source of unknown kind; never drop the candidate on that basis.
|
|
1493
1497
|
- `candidate.sink`: a self-contained sink (`path`, `line`, `col`, `category`, `cwe`, `callee`, optional `url_shape`), actionable without reading the rest of the finding. URL-category sinks use `url_shape` to distinguish `fixed-origin-dynamic-path` from `dynamic-origin` when the construction is statically visible.
|
|
@@ -1495,7 +1499,8 @@ Every finding also carries an agent-actionable `candidate { source_kind, sink, b
|
|
|
1495
1499
|
- `candidate.network`: present only on `secret-to-network` (#890) candidates. `destination` is the network call's URL when it is a static literal (usually intended auth) or absent when the destination is dynamic (the higher-signal exfil case). Use it to triage exfil from intended auth without re-reading source.
|
|
1496
1500
|
- There is no `impact` field: deciding exploitability is the verifying agent's job; `severity` is only the review-priority tier.
|
|
1497
1501
|
- `taint_flow`: present only when an untrusted source is import-reachable to the sink. `path` is the compact `{ intra_module, cross_module_hops }` shape; the full ordered hops stay in `reachability.untrusted_source_trace`.
|
|
1498
|
-
- `finding_id`: a
|
|
1502
|
+
- `finding_id`: a location id, identical across runs for the same rule/path/line/column and identical to SARIF `partialFingerprints["fallowSecurity/v2"]`. Use it to join JSON with SARIF. An edit above the sink changes it. On upgrading from line-only IDs, regenerate candidates and their verdicts together, including ID-based evaluation labels. Saved candidate/verdict pairs from the same version remain usable; old review history does not transfer automatically to the new IDs.
|
|
1503
|
+
- `stable_finding_id`: a line-independent id of the form `sec1:<16 hex digits>`. It hashes the rule, the project-relative path and the exact text of the full sink expression (the full assignment, or the full call with all arguments), so whitespace, comments, line breaks, trailing commas and an edit above the sink do not change it. A new quote style changes the text of a string literal and gives a new id. A hard-coded secret hashes its binding name and provider family, never the value. A client-server leak hashes the paths of its first and last trace hop and the names of the env secrets that it reads. Identical sinks in one file get the suffix `~1`, `~2` and so on; an inline suppression does not move these suffixes. Key stored verdicts by this id. `fallow security survivors` accepts it or `finding_id` in a verdict. The field is absent for a sink in Vue, Svelte or Angular template markup and when fallow cannot read the anchor file; use `finding_id` then.
|
|
1499
1504
|
|
|
1500
1505
|
---
|
|
1501
1506
|
|
|
@@ -2107,7 +2112,7 @@ Available on all commands:
|
|
|
2107
2112
|
| `--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/` |
|
|
2108
2113
|
| `--trend-from` | `string` | - | Compare current health metrics against this snapshot file in combined mode. Implies --trend and --score |
|
|
2109
2114
|
| `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Default path: `.fallow/snapshots/<timestamp>.json`. Forces file-scores + hotspot computation |
|
|
2110
|
-
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for
|
|
2115
|
+
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for measured CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
2111
2116
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
|
|
2112
2117
|
| `--dupes-baseline` | `string` | - | Compare duplication clone groups against a saved baseline in combined mode (produced by `fallow dupes --save-baseline`) |
|
|
2113
2118
|
| `--health-baseline` | `string` | - | Compare health findings against a saved baseline in combined mode (produced by `fallow health --save-baseline`) |
|
|
@@ -2151,7 +2156,7 @@ guarded edits.
|
|
|
2151
2156
|
| `--score` | `bool` | `false` | Compute health score in combined mode |
|
|
2152
2157
|
| `--trend` | `bool` | `false` | Compare current health metrics against the most recent saved snapshot |
|
|
2153
2158
|
| `--save-snapshot` | `string` | - | Save a vital signs snapshot for trend tracking in combined mode. Provide a path or omit for the default `.fallow/snapshots/` location |
|
|
2154
|
-
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for
|
|
2159
|
+
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for measured CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
2155
2160
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
|
|
2156
2161
|
|
|
2157
2162
|
These are global flags with behavior specific to bare `fallow` combined mode.
|
|
@@ -2179,7 +2184,7 @@ These are global flags with behavior specific to bare `fallow` combined mode.
|
|
|
2179
2184
|
| `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. |
|
|
2180
2185
|
| `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`. |
|
|
2181
2186
|
| `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
|
|
2182
|
-
| `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
|
|
2187
|
+
| `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. For dead-code, check, architecture and the dead-code part of the bare run, only findings whose rule is `error` count, the same as the CLI exit code. |
|
|
2183
2188
|
| `FALLOW_CHANGED_SINCE` | GitLab CI: git ref for incremental analysis. Auto-detected in MR pipelines. |
|
|
2184
2189
|
| `FALLOW_COMMENT` | GitLab CI: set to `true` to post MR summary comments. |
|
|
2185
2190
|
| `FALLOW_REVIEW` | GitLab CI: set to `true` to post inline code review comments on MR diffs. |
|
|
@@ -2263,12 +2268,29 @@ Use this mode to check whether specific tests are redundant without a full kill
|
|
|
2263
2268
|
|
|
2264
2269
|
- Only the mutants that a target test covers run. The other mutants are `pruned` with `prune_reason: "no-target-coverage"`.
|
|
2265
2270
|
- Each mutant runs its target tests with bail disabled, then its other covering tests with bail enabled, up to the first failure.
|
|
2266
|
-
- `kill_matrix.targets` lists each target with the `test_ids` that it selects. A target that selects no test exits `2` with `FALLOW_MUTATION_TARGET_UNMATCHED`. A test ID target that names a duplicate test ID (two tests of one file with the same title) exits `2` with `FALLOW_MUTATION_TARGET_DUPLICATE_TEST_ID`. A file target drops the duplicate IDs, and the `duplicate-test-ids` entry of `errors` names them.
|
|
2271
|
+
- `kill_matrix.targets` lists each target with the `test_ids` that it selects. A target that selects no test is `incomplete` with the reason `unmatched-target`, and the run goes on. The reason `not-a-test-file` means that the runner does not list the file. The reason `no-planned-code` means that the file reaches no planned code, for example a test that runs the code only in a child process. When no target selects a test, the run exits `2` with `FALLOW_MUTATION_TARGET_UNMATCHED`. When the dry run runs the file of no target, the run stops before the dry run. A test ID target that names a duplicate test ID (two tests of one file with the same title) exits `2` with `FALLOW_MUTATION_TARGET_DUPLICATE_TEST_ID`. A file target drops the duplicate IDs, and the `duplicate-test-ids` entry of `errors` names them.
|
|
2267
2272
|
- Each target has its own `status` (`complete` or `incomplete`) and `reasons`. Only the tests of a complete target can be candidates. `candidates` can be nonempty while the top-level `status` is `incomplete`.
|
|
2268
2273
|
- `kill_matrix.tests` has rows for target tests only. `unique_kills` are kills that no other test detects, target or not. `target_only_kills` lists kills that only other targets share, each with those `targets`. `subsuming_tests` is empty (note `subsumers-not-computed`).
|
|
2269
2274
|
- A target with kills and no `unique_kills` is a candidate. With `target_only_kills`, it stays redundant only while one of the sharing targets stays. Do not remove all sharing targets together.
|
|
2270
2275
|
- The evidence covers the planned scope only. A unique kill outside the scope is not visible.
|
|
2271
2276
|
|
|
2277
|
+
### `mutation inspect --replay`: Find a test input for a survivor
|
|
2278
|
+
|
|
2279
|
+
```bash
|
|
2280
|
+
fallow mutation inspect <mutant-id> --replay --args '[2, 3]' --format json --quiet
|
|
2281
|
+
```
|
|
2282
|
+
|
|
2283
|
+
The replay calls the export that holds the mutated function with `--args` on the original and on the mutant, three times each, in turns. It runs project code, but no test. Read `replay.verdict`:
|
|
2284
|
+
|
|
2285
|
+
- `witness`: each side repeats its result, and the sides differ. Read `calls[]` of the `original` side. When its `outcome` is `returned`, write a test with this input that asserts that `value`. When its `outcome` is `threw`, assert that the call throws (`value.name`, `value.message`).
|
|
2286
|
+
- `no_witness_found`: this input shows no difference. Try another input. This verdict never means that the mutant is equivalent.
|
|
2287
|
+
- `unstable`: one side changes between calls (for example `crypto.randomUUID()`). Do not use the difference as a test oracle.
|
|
2288
|
+
- `timeout`, `not_callable`, `not_replayable`: read `replay.reason`. A function that no callable export holds (a method of a class or of an object literal, or a local function) needs `--export <name>` of an export that calls it. `--call-timeout 30s` gives a slow call more time.
|
|
2289
|
+
|
|
2290
|
+
Without `--args`, the replay uses up to 3 inputs that `fallow mutation --record-args values` recorded (`replay.input.source` is `recorded`, with its `input_id`; `replay.inputs_checked` counts the inputs that ran). The values stay in the local cache, and the inspect output shows them, so keep that output local. The run report shows only counts and reasons under `recording`.
|
|
2291
|
+
|
|
2292
|
+
Each call runs in a new worker with a fresh module, a clock that starts at a fixed time, and a fixed `Math.random` seed. Arguments and results use the replay encoding: JSON, plus objects with a `$replay` tag for values that JSON cannot hold. Decode them when you write the test: `{"$replay": "undefined"}` is `undefined`, `{"$replay": "number", "value": "NaN"}` is `NaN`, `{"$replay": "bigint", "value": "12"}` is `12n`, and `map`, `set`, `date`, `regexp` and `bytes` are a `Map`, a `Set`, a `Date`, a `RegExp` and a typed array. Only the Vitest and `node:test` runners replay.
|
|
2293
|
+
|
|
2272
2294
|
## `ci`: Provider-Aware Review Automation
|
|
2273
2295
|
|
|
2274
2296
|
`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).
|
|
@@ -2296,7 +2318,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2296
2318
|
|
|
2297
2319
|
## CI Integration
|
|
2298
2320
|
|
|
2299
|
-
- **GitHub Actions**: `uses: fallow-rs/fallow@v3` - supports SARIF upload to Code Scanning, inline PR annotations (`annotations: true`), PR comments, all commands. Annotations use workflow commands (no Advanced Security required); limit with `max-annotations` (default 50). Set `score: true` to compute health score and enable the health delta header in PR comments
|
|
2321
|
+
- **GitHub Actions**: `uses: fallow-rs/fallow@v3` - supports SARIF upload to Code Scanning, inline PR annotations (`annotations: true`), PR comments, all commands. `fail-on-issues` counts dead-code findings only when their rule is `error`, the same as the CLI exit code. An audit that compared no changed file (no pull request base) prints a `::warning::`. Annotations use workflow commands (no Advanced Security required); limit with `max-annotations` (default 50). Set `score: true` to compute health score and enable the health delta header in PR comments
|
|
2300
2322
|
- **GitLab CI**: include `ci/gitlab-ci.yml` template and extend `.fallow` - generates Code Quality reports via `--format codeclimate` / `--format gitlab-codequality` (inline MR annotations), rich MR comments, code review comments, all commands. Use `fallow ci-template gitlab --vendor` when runners cannot reach `raw.githubusercontent.com`; commit the generated `ci/` and `action/` files and use GitLab's local include syntax. Variables use `FALLOW_` prefix (e.g., `FALLOW_COMMAND`, `FALLOW_FAIL_ON_ISSUES`). Set `FALLOW_SCORE: "true"` to compute health score; `FALLOW_TREND: "true"` to compare against saved snapshots
|
|
2301
2323
|
- **Any CI**: `npx fallow --ci` - equivalent to `--format sarif --fail-on-issues --quiet`
|
|
2302
2324
|
|
|
@@ -2305,7 +2327,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2305
2327
|
| Variable | Default | Description |
|
|
2306
2328
|
|----------|---------|-------------|
|
|
2307
2329
|
| `FALLOW_COMMAND` | `dead-code` | Command to run (`dead-code`, `dupes`, `health`, or default combined) |
|
|
2308
|
-
| `FALLOW_FAIL_ON_ISSUES` | `
|
|
2330
|
+
| `FALLOW_FAIL_ON_ISSUES` | `true` | Exit 1 if issues found. Dead-code findings count only when their rule is `error`. An audit with no changed file prints a warning. |
|
|
2309
2331
|
| `FALLOW_CHANGED_SINCE` | auto | Git ref for incremental analysis. Auto-detected in MR pipelines (`origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`) |
|
|
2310
2332
|
| `FALLOW_COMMENT` | `false` | Post a summary comment on the MR with findings |
|
|
2311
2333
|
| `FALLOW_REVIEW` | `false` | Post inline code review comments on MR diff lines where issues were found |
|
|
@@ -2331,7 +2353,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2331
2353
|
{
|
|
2332
2354
|
"kind": "dead-code",
|
|
2333
2355
|
"schema_version": 7,
|
|
2334
|
-
"version": "3.
|
|
2356
|
+
"version": "3.43.0",
|
|
2335
2357
|
"elapsed_ms": 45,
|
|
2336
2358
|
"total_issues": 12,
|
|
2337
2359
|
"entry_points": {
|
|
@@ -2491,7 +2513,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
2491
2513
|
{
|
|
2492
2514
|
"kind": "dupes",
|
|
2493
2515
|
"schema_version": 7,
|
|
2494
|
-
"version": "3.
|
|
2516
|
+
"version": "3.43.0",
|
|
2495
2517
|
"elapsed_ms": 82,
|
|
2496
2518
|
"total_clones": 15,
|
|
2497
2519
|
"total_lines_duplicated": 230,
|
|
@@ -2535,11 +2557,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
2535
2557
|
{
|
|
2536
2558
|
"kind": "combined",
|
|
2537
2559
|
"schema_version": 7,
|
|
2538
|
-
"version": "3.
|
|
2560
|
+
"version": "3.43.0",
|
|
2539
2561
|
"elapsed_ms": 159,
|
|
2540
2562
|
"check": {
|
|
2541
2563
|
"schema_version": 7,
|
|
2542
|
-
"version": "3.
|
|
2564
|
+
"version": "3.43.0",
|
|
2543
2565
|
"elapsed_ms": 45,
|
|
2544
2566
|
"total_issues": 12,
|
|
2545
2567
|
"unused_files": [],
|
|
@@ -2772,6 +2794,8 @@ preset = "bulletproof"
|
|
|
2772
2794
|
| `// fallow-ignore-file` | Suppress all issues in a file |
|
|
2773
2795
|
| `// fallow-ignore-file unused-export` | Suppress specific issue type file-wide |
|
|
2774
2796
|
|
|
2797
|
+
The `/* */` and JSDoc `/** */` block forms work the same way. In a multi-line JSDoc comment, the marker can start any ` * ` line. A next-line marker in a block comment applies to the line after the closing `*/`. In a block comment, a line that starts with the marker word is a marker only when each target before an optional `-- reason` is an issue kind or looks like one (lowercase words joined by `-`). A wrapped prose line such as ` * fallow-ignore-next-line is how you hide a finding.` is not a marker. A block comment must start its line, and a single-line block comment must end its line. For `fallow dupes` and for Vue, Svelte, Astro, MDX, HTML and CSS files, a multi-line block comment must open with `/*` or `/**` alone on its line, or with the marker on that line. Markup files also accept `<!-- fallow-ignore-next-line -->` on one line.
|
|
2798
|
+
|
|
2775
2799
|
### Valid Issue Type Tokens
|
|
2776
2800
|
|
|
2777
2801
|
`unused-file`, `unused-export`, `unused-type`, `unused-dependency`, `unused-dev-dependency`, `unused-enum-member`, `unused-class-member`, `unused-store-member`, `unresolved-import`, `unlisted-dependency`, `duplicate-export`, `circular-dependency`, `re-export-cycle`, `boundary-violation`, `policy-violation`, `unused-optional-dependency`, `type-only-dependency`, `test-only-dependency`, `code-duplication`
|
|
@@ -612,6 +612,8 @@ The `type-only-dependencies` rule defaults to `warn`. Suppress with `"type-only-
|
|
|
612
612
|
|
|
613
613
|
Fallow detects production dependencies that are only imported from test files (`*.test.*`, `*.spec.*`, `__tests__/**`). Since these packages are never used in production code, they should be in `devDependencies` instead.
|
|
614
614
|
|
|
615
|
+
A test runner config or setup file (`vitest.config.*`, `vitest.unit.config.ts`, `jest.setup.*`, `playwright-ct.config.ts`) counts as a test file. Other config files (`astro.config.mjs`, `vite.config.ts`, `next.config.js`) count as production use, so a package that only they import is not reported as test-only.
|
|
616
|
+
|
|
615
617
|
```typescript
|
|
616
618
|
// If "msw" is in dependencies (not devDependencies):
|
|
617
619
|
// src/handlers.test.ts
|
|
@@ -51,6 +51,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
|
|
|
51
51
|
| `unprovided-inject` | `--unprovided-injects` | - | `// fallow-ignore-next-line unprovided-inject` | inject() / getContext() reads a key that no provide() / setContext() supplies |
|
|
52
52
|
| `unrendered-component` | `--unrendered-components` | - | `// fallow-ignore-next-line unrendered-component` | A Vue / Svelte component is reachable through a barrel but rendered nowhere |
|
|
53
53
|
| `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. |
|
|
54
|
+
| `constant-parameter` | `--constant-parameters` | - | `// fallow-ignore-next-line constant-parameter` | Each visible call passes a function parameter in the same way; Opt-in manual review candidate; defaults to off. Inspect the calls and the intent of the parameter before changing the function. No automatic fix. |
|
|
54
55
|
| `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 |
|
|
55
56
|
| `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 |
|
|
56
57
|
| `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 |
|
|
@@ -15,12 +15,12 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
|
|
|
15
15
|
| `analyze` | analysis | free | `fallow dead-code --format json --quiet` | `issue_types`, `production`, `workspace`, `baseline`, `group_by`, `file` | Full dead code analysis (unused files/exports/types/dependencies/members + circular dependencies + re-export cycles (barrel files that form a structural loop, silently breaking re-exports) + package cycles (workspace packages that import each other in a loop) + boundary violations + rule-pack policy violations (banned calls, imports, and catalogue-derived effects declared via the `rulePacks` config key) + stale suppressions). Private type leaks are an opt-in API hygiene check via `issue_types: ["private-type-leaks"]`. Deprecated exports that still have consumers are an opt-in migration sweep via `issue_types: ["deprecated-exports-in-use"]`. Set `boundary_violations: true` as a convenience alias for `issue_types: ["boundary-violations"]`. Set `group_by` to `"owner"`, `"directory"`, `"package"`, or `"section"` to partition results. The `section` mode reads GitLab CODEOWNERS `[Section]` headers and emits `owners` metadata per group |
|
|
16
16
|
| `check_changed` | analysis | free | `fallow dead-code --changed-since <ref> --format json --quiet` | `since`, `baseline`, `fail_on_regression` | Incremental analysis of files changed since a git ref |
|
|
17
17
|
| `check_architecture` | analysis | free | `fallow architecture --format json --quiet` | `cycles`, `boundaries`, `policy`, `baseline`, `group_by`, `file` | Architecture check: import cycles, boundary violations, and rule-pack policy violations in the `architecture` envelope, each with a stable finding_id |
|
|
18
|
-
| `security_candidates` | analysis | free | `fallow security --format json --quiet` | `gate`, `surface`, `changed_since`, `paths` | Unverified local security candidates, not confirmed vulnerabilities (`fallow security --format json`). Read `security_findings[]` for category, CWE, severity, evidence, trace, optional `reachability`, blind-spot counters, and optional `unresolved_callee_diagnostics` samples for dynamic callee follow-up. `severity` is a review-priority tier, not a verified vulnerability verdict. Each finding also carries an agent-actionable `candidate` (`source_kind`/`sink`/`boundary`), where URL-category sinks may include `url_shape` (`fixed-origin-dynamic-path` or `dynamic-origin`), an optional `taint_flow` source-to-sink triple,
|
|
18
|
+
| `security_candidates` | analysis | free | `fallow security --format json --quiet` | `gate`, `surface`, `changed_since`, `paths` | Unverified local security candidates, not confirmed vulnerabilities (`fallow security --format json`). Read `security_findings[]` for category, CWE, severity, evidence, trace, optional `reachability`, blind-spot counters, and optional `unresolved_callee_diagnostics` samples for dynamic callee follow-up. `severity` is a review-priority tier, not a verified vulnerability verdict. Each finding also carries an agent-actionable `candidate` (`source_kind`/`sink`/`boundary`), where URL-category sinks may include `url_shape` (`fixed-origin-dynamic-path` or `dynamic-origin`), an optional `taint_flow` source-to-sink triple, a `finding_id` (equal to the SARIF fingerprint) that changes when the sink moves to another line, and an optional `stable_finding_id` that survives line moves, so key stored verdicts by `stable_finding_id`; there is no `impact` field (deciding exploitability is the agent's job). Set `surface: true` to include top-level `attack_surface[]` entries with defensive-boundary prompts for a verifier. Set `gate` to `new` for changed-line candidates or `newly-reachable` for candidates that became reachable from entry points; `newly-reachable` requires `changed_since`. `reachability.untrusted_source_trace` is module-level import context only and does not prove value flow; `reachability.taint_confidence` tiers each reachable candidate as `arg-level` (sink argument traces to a same-module source read, strong) or `module-level` (only the module is import-reachable from a source, weak), so tier from this field instead of the evidence text. Verify trace, reachability context, severity, and evidence before editing code. Supports `root`, `config`, `workspace`, `paths`, `changed_since`, `changed_workspaces`, `surface`, `gate`, `no_cache`, and `threads`; `paths` forwards repeated `fallow security --file` filters for finding anchors, trace hops, untrusted-source reachability trace hops, and unresolved-callee diagnostics. See <https://fallow.tools/docs/cli/security-agent-verification/> for the verifier packet and verdict recipe. Inherits `FALLOW_DIFF_FILE` from the server environment for line-level diff scoping; raise `FALLOW_TIMEOUT_SECS` for large repos. |
|
|
19
19
|
| `find_similar_code` | analysis | free | `fallow similar-code --format json --quiet` | `threshold`, `min_lines`, `top`, `changed_since`, `paths` | Find unverified semantically similar function candidates with the exact pinned local model. Discovery is read-only and never authorizes or performs model setup. Scoped output materializes the exact admitted files once in `generation.scope.paths` as provenance. Ask the user to run `fallow similar-code setup --local` when setup is missing. Cold local inference has a dedicated 15-minute subprocess window. |
|
|
20
20
|
| `inspect_similar_code` | trace | free | `fallow similar-code inspect <candidate-id> --candidates <report.json> --format json --quiet` | `candidate_id`, `snapshot` | Inspect one exact unverified candidate without rerunning provider retrieval or global ranking. Pass `candidate_id` plus a bounded typed snapshot containing the unchanged discovery `schema_version`, `generation`, selected `candidate`, `completion`, and `diagnostics`. Current source is re-extracted and both endpoint hashes must still match, so stale source fails closed. Keep candidate worthiness, behavioral equivalence, and refactor safety as separate verdicts, and abstain when evidence is incomplete. |
|
|
21
21
|
| `mutation_plan` | analysis | free | `fallow mutation --plan --format json --quiet` | `changed_since`, `all`, `paths`, `max_mutants`, `mutants_per_line`, `function_scope`, `operators` | Static mutation plan for changed code: mutant ids, operators, dispositions, and static test files; runs no tests |
|
|
22
22
|
| `mutation_inspect` | trace | free | `fallow mutation inspect <mutant-id> [--report <report.json>] --format json --quiet` | `mutant_id`, `report` | Inspect one mutant of a saved mutation report (default: the last run report), with a stale status when its source changed; runs no tests |
|
|
23
|
-
| `mutation_tasks` | trace | free | `fallow mutation tasks [--report <report.json>] --format json --quiet` | `report` | The open tasks of a saved mutation report (default: the last run report) as one batch: each survived or uncovered mutant with its source lines before and after the change; runs no tests |
|
|
23
|
+
| `mutation_tasks` | trace | free | `fallow mutation tasks [--report <report.json>] --format json --quiet` | `report` | The open tasks of a saved mutation report (default: the last run report) as one batch: each survived or uncovered mutant with its source lines before and after the change, and after a `--record-args values` run a `witness` with a ready test input and the result of each side; runs no tests |
|
|
24
24
|
| `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production`, `include_churn`, `cloud` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Set `include_churn: true` to add target-level git churn from the health hotspot subsystem. Churn is off by default, and unavailable git history or analysis failures remain explicit section states. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Set `cloud: true` on a file target to add the `cloud` evidence section of `fallow inspect --cloud` (production data per local function). The CLI reads the key: `FALLOW_API_KEY` in the server environment, else the key that `fallow cloud login` stored; the key is never a parameter. `repo` (`owner/repo`) selects the repository and needs `cloud: true`. The section `status` is `ok`, `no_key`, `plan_required`, `unavailable` or `untracked`; a status other than `ok` never fails the call. `cloud: true` with a symbol target is refused. Set `format: "compact"` to get the plain text of `fallow inspect --format compact` (about 1 KB, no JSON) instead of the JSON bundle. Supports `root`, `config`, `production`, `workspace`, `include_churn`, `cloud`, `repo`, `format`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
|
|
25
25
|
| `guard` | introspection | free | `fallow guard <file> --format json --quiet` | `files` | Report the architecture rules that apply to given files before editing them: boundary zone, allowed import zones, forbidden calls, and rule-pack policies |
|
|
26
26
|
| `find_dupes` | analysis | free | `fallow dupes --format json --quiet` | `mode`, `near`, `min_tokens`, `min_occurrences`, `top`, `threshold` | Code duplication detection. Set `near: true` for function-level clones with small structural edits. Set `changed_since` to scope to changed files since a git ref. Set `min_occurrences` (≥ 2, default 2) to hide pair-only clones and focus on widespread copy-paste. Each `clone_groups[]` entry carries a normalized `fingerprint` and `spread`; near groups also carry `similarity`. `stats.clone_groups_ignored` reports reviewed groups hidden by config, while `stats.near_candidates_skipped` signals bounded near comparisons that were skipped. Pass the fingerprint to `trace_clone` to inspect the group |
|
|
@@ -170,7 +170,7 @@ The hosted data is context. It never proves that code is dead: the delete rule o
|
|
|
170
170
|
|
|
171
171
|
## Shared params, JSON output, and next_steps
|
|
172
172
|
|
|
173
|
-
Most tools accept `root`, `config`, `no_cache`, and `threads` params. Exceptions: `impact` takes only `root`; `code_execute` takes `code`, optional `root`, `timeout_ms`, and `max_output_bytes`. Similar-code file scope is named `paths` on both standalone MCP tools and forwards repeated CLI `--file` flags. MCP subprocesses default to 120 seconds, except similar-code discovery and inspection which default to 15 minutes for cold local inference. `FALLOW_TIMEOUT_SECS` overrides either default. Code Mode does not expose similar-code because its 30-second cap cannot satisfy that contract.
|
|
173
|
+
Most tools accept `root`, `config`, `no_cache`, and `threads` params. Exceptions: `impact` takes only `root`; `code_execute` takes `code`, optional `root`, `timeout_ms`, and `max_output_bytes`. Similar-code file scope is named `paths` on both standalone MCP tools and forwards repeated CLI `--file` flags. MCP subprocesses default to 120 seconds, except similar-code discovery and inspection which default to 15 minutes for cold local inference. `FALLOW_TIMEOUT_SECS` overrides either default. A tool response over 1,000,000 bytes is refused with `isError: true` and `code: FALLOW_MCP_SUBPROCESS_OUTPUT_LIMIT`; call again with the parameters in its `narrow` list. `FALLOW_MCP_MAX_OUTPUT_BYTES` sets that cap for the server, up to 16 MiB. Code Mode does not expose similar-code because its 30-second cap cannot satisfy that contract.
|
|
174
174
|
|
|
175
175
|
Every dead-code, health, and duplication finding in JSON responses includes a structured `actions` array for programmatic fixes or suppression.
|
|
176
176
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Similar-code is an opt-in, local semantic discovery workflow. It complements deterministic `fallow dupes`; it does not replace clone detection, tests, or human judgment.
|
|
4
4
|
|
|
5
|
-
1. Check readiness with `fallow similar-code status --format json --quiet`. If the pinned model is missing, ask the user to run `fallow similar-code setup --local`. Agents must not authorize setup or run it on the user's behalf.
|
|
5
|
+
1. Check readiness with `fallow similar-code status --format json --quiet`. If the companion is missing, ask the user to install `fallow-similar-code` at the same version as `fallow`. If the pinned model is missing, ask the user to run `fallow similar-code setup --local`. Agents must not authorize setup or run it on the user's behalf.
|
|
6
6
|
2. Run discovery once. Preserve its independent JSON envelope so inspection and review use the exact candidate set:
|
|
7
7
|
|
|
8
8
|
```bash
|