fallow 3.28.0 → 3.29.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 +1 -1
- package/capabilities.json +137 -68
- package/issue-registry.json +85 -48
- package/package.json +10 -10
- package/schema.json +77 -4
- package/scripts/lazy-verify.js +14 -16
- package/scripts/lazy-verify.test.js +24 -13
- package/scripts/sentinel-path.js +4 -4
- package/scripts/sentinel-path.test.js +1 -4
- package/scripts/verify-binary.js +4 -2
- package/skills/fallow/SKILL.md +2 -2
- package/skills/fallow/references/cli-reference.md +25 -21
- package/skills/fallow/references/issue-types.md +1 -0
- package/skills/fallow/references/mcp.md +5 -3
- package/types/output-contract.d.ts +834 -101
package/scripts/lazy-verify.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// bin/fallow exits non-zero before execing the binary. FALLOW_SKIP_BINARY_VERIFY
|
|
16
16
|
// remains the documented escape hatch.
|
|
17
17
|
//
|
|
18
|
-
//
|
|
18
|
+
// See SECURITY.md for binary distribution and verification.
|
|
19
19
|
//
|
|
20
20
|
// No external deps beyond node:fs / node:path / node:crypto.
|
|
21
21
|
|
|
@@ -24,7 +24,12 @@ const path = require("node:path");
|
|
|
24
24
|
const crypto = require("node:crypto");
|
|
25
25
|
|
|
26
26
|
const { resolveSentinelPath } = require("./sentinel-path");
|
|
27
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
verifyInstalledSync,
|
|
29
|
+
binaryTargetsForPlatform,
|
|
30
|
+
isSkipRequested,
|
|
31
|
+
SKIP_ENV,
|
|
32
|
+
} = require("./verify-binary");
|
|
28
33
|
|
|
29
34
|
// Bumped to 2 when SHA-256 + platformPkgDir binding landed (closes the
|
|
30
35
|
// cross-install reuse gap in the shared $XDG fallback cache), and to 3 when
|
|
@@ -66,11 +71,10 @@ function emitVerifyLog(env, payload) {
|
|
|
66
71
|
process.stderr.write(`fallow-verify ${parts.join(" ")}\n`);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return [`fallow${ext}`, `fallow-similar-code${ext}`];
|
|
74
|
+
// The sentinel binds the same binaries that verify-binary checks, so a new
|
|
75
|
+
// binary cannot pass verification without also invalidating the sentinel.
|
|
76
|
+
function binaryNamesForPlatform(platform) {
|
|
77
|
+
return binaryTargetsForPlatform(platform).map((t) => t.binary);
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
function statMtimeMs(absPath) {
|
|
@@ -145,7 +149,7 @@ function sha256OfFile(absPath) {
|
|
|
145
149
|
// integrity gate that defends against same-mtime cross-install reuse where a
|
|
146
150
|
// tampered binary happens to land with the recorded mtime.
|
|
147
151
|
function sentinelBinariesMatch(parsed, platformPkgDir, platform) {
|
|
148
|
-
for (const target of
|
|
152
|
+
for (const target of binaryNamesForPlatform(platform)) {
|
|
149
153
|
const recorded = parsed.binaries[target];
|
|
150
154
|
if (!recorded || typeof recorded.mtimeMs !== "number") return false;
|
|
151
155
|
if (typeof recorded.sha256 !== "string" || recorded.sha256.length !== 64) return false;
|
|
@@ -173,7 +177,7 @@ function isSentinelValid(sentinelPath, platformPkgDir, manifest, platform) {
|
|
|
173
177
|
|
|
174
178
|
function buildSentinelPayload(platformPkgDir, manifest, platform) {
|
|
175
179
|
const binaries = {};
|
|
176
|
-
for (const target of
|
|
180
|
+
for (const target of binaryNamesForPlatform(platform)) {
|
|
177
181
|
const binaryPath = path.join(platformPkgDir, target);
|
|
178
182
|
const mtimeMs = statMtimeMs(binaryPath);
|
|
179
183
|
const sha256 = sha256OfFile(binaryPath);
|
|
@@ -213,11 +217,6 @@ function writeSentinel(sentinelPath, payload) {
|
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
function isSkipRequested(env) {
|
|
217
|
-
const v = (env || process.env)[SKIP_ENV];
|
|
218
|
-
return v === "1" || v === "true" || v === "yes";
|
|
219
|
-
}
|
|
220
|
-
|
|
221
220
|
// Main entry point. Synchronous by design: bin/fallow runs this before
|
|
222
221
|
// execFileSync, so the verify result must be available without awaiting.
|
|
223
222
|
//
|
|
@@ -300,7 +299,7 @@ function ensureVerified(input) {
|
|
|
300
299
|
platform = process.platform,
|
|
301
300
|
} = input || {};
|
|
302
301
|
|
|
303
|
-
if (isSkipRequested(env)) {
|
|
302
|
+
if (isSkipRequested(env || process.env)) {
|
|
304
303
|
const reason = `${SKIP_ENV} is set`;
|
|
305
304
|
// Warn once per process so the bypass stays visible in CI logs and
|
|
306
305
|
// vendor audits regardless of whether the user runs `--version` or
|
|
@@ -364,6 +363,5 @@ function _resetWarningState() {
|
|
|
364
363
|
module.exports = {
|
|
365
364
|
ensureVerified,
|
|
366
365
|
SENTINEL_SCHEMA_VERSION,
|
|
367
|
-
VERIFY_LOG_ENV,
|
|
368
366
|
_resetWarningState,
|
|
369
367
|
};
|
|
@@ -5,14 +5,9 @@ const fs = require("node:fs");
|
|
|
5
5
|
const os = require("node:os");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
|
|
8
|
-
const {
|
|
9
|
-
ensureVerified,
|
|
10
|
-
SENTINEL_SCHEMA_VERSION,
|
|
11
|
-
VERIFY_LOG_ENV,
|
|
12
|
-
_resetWarningState,
|
|
13
|
-
} = require("./lazy-verify");
|
|
8
|
+
const { ensureVerified, SENTINEL_SCHEMA_VERSION, _resetWarningState } = require("./lazy-verify");
|
|
14
9
|
const { SENTINEL_FILENAME } = require("./sentinel-path");
|
|
15
|
-
const { _verifyWithKey, SKIP_ENV } = require("./verify-binary");
|
|
10
|
+
const { _verifyWithKey, binaryTargetsForPlatform, SKIP_ENV } = require("./verify-binary");
|
|
16
11
|
|
|
17
12
|
// ---- shared fixtures ------------------------------------------------------
|
|
18
13
|
|
|
@@ -149,6 +144,28 @@ test("ensureVerified verifies and caches a win32 executable on any host", (t) =>
|
|
|
149
144
|
assert.equal(cached.cached, true);
|
|
150
145
|
});
|
|
151
146
|
|
|
147
|
+
test("sentinel records the same binaries that verify-binary verifies", (t) => {
|
|
148
|
+
const cases = [
|
|
149
|
+
{ platform: "linux", platformId: "linux-x64-gnu" },
|
|
150
|
+
{ platform: "win32", platformId: "win32-x64-msvc" },
|
|
151
|
+
];
|
|
152
|
+
for (const { platform, platformId } of cases) {
|
|
153
|
+
_resetWarningState();
|
|
154
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
155
|
+
const dir = mkPlatformDir(privateKey, { platform });
|
|
156
|
+
t.after(() => cleanup(dir));
|
|
157
|
+
const input = baseInput(dir, (binaryPath) => _verifyWithKey(binaryPath, rawPub), {
|
|
158
|
+
platform,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const result = ensureVerified(input);
|
|
162
|
+
assert.equal(result.ok, true);
|
|
163
|
+
const sentinel = JSON.parse(fs.readFileSync(result.sentinelPath, "utf8"));
|
|
164
|
+
const verified = binaryTargetsForPlatform(platformId).map((target) => target.binary);
|
|
165
|
+
assert.deepEqual(Object.keys(sentinel.binaries), verified);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
152
169
|
test("ensureVerified returns cached:true on a valid sentinel", (t) => {
|
|
153
170
|
_resetWarningState();
|
|
154
171
|
const { privateKey, rawPub } = makeKeypair();
|
|
@@ -543,9 +560,3 @@ test("ensureVerified warns once on stderr when FALLOW_SKIP_BINARY_VERIFY is set"
|
|
|
543
560
|
);
|
|
544
561
|
assert.equal(warnings.length, 1, "warning should fire exactly once per process");
|
|
545
562
|
});
|
|
546
|
-
|
|
547
|
-
// ---- VERIFY_LOG_ENV export ------------------------------------------------
|
|
548
|
-
|
|
549
|
-
test("VERIFY_LOG_ENV is exported with the documented name", () => {
|
|
550
|
-
assert.equal(VERIFY_LOG_ENV, "FALLOW_VERIFY_LOG");
|
|
551
|
-
});
|
package/scripts/sentinel-path.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// 4. Every location read-only: returns { path: null, location: 'none', writable: false }.
|
|
10
10
|
// Callers run verify on every invocation and surface FALLOW_SKIP_BINARY_VERIFY=1 as the escape.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
12
|
+
// See SECURITY.md for binary distribution and verification.
|
|
13
13
|
|
|
14
14
|
const fs = require("node:fs");
|
|
15
15
|
const os = require("node:os");
|
|
@@ -19,8 +19,8 @@ const SENTINEL_FILENAME = ".fallow-verified";
|
|
|
19
19
|
|
|
20
20
|
// Returns true when the directory exists and the current process can create
|
|
21
21
|
// a file in it. Tries an atomic O_CREAT|O_EXCL write so we never disturb an
|
|
22
|
-
// existing sentinel during the writability probe.
|
|
23
|
-
//
|
|
22
|
+
// existing sentinel during the writability probe. Any failed probe makes this
|
|
23
|
+
// location unavailable so resolution can try the next cache directory.
|
|
24
24
|
function isWritable(dir) {
|
|
25
25
|
if (typeof dir !== "string" || dir.length === 0) {
|
|
26
26
|
return false;
|
|
@@ -126,7 +126,7 @@ function tryXdgFallback(env, homeDir, platformId, filename, ensureDir, writableP
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// Resolve the sentinel path according to the cascade documented above.
|
|
129
|
-
// Dependency-inject env / homedir / platform /
|
|
129
|
+
// Dependency-inject env / homedir / platform / isWritable / ensureDir so tests can
|
|
130
130
|
// exercise every branch without touching the real filesystem state.
|
|
131
131
|
//
|
|
132
132
|
// Returns: {
|
|
@@ -89,10 +89,7 @@ test("resolveSentinelPath falls back to FALLOW_VERIFY_CACHE_DIR when platform pk
|
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
test("resolveSentinelPath
|
|
93
|
-
// Per the cascade documented in the source, the platform pkg dir wins when
|
|
94
|
-
// writable. The cache-dir env is the FALLBACK for when the platform dir is
|
|
95
|
-
// read-only. We pass a non-existent platform dir to force the fallback.
|
|
92
|
+
test("resolveSentinelPath uses FALLOW_VERIFY_CACHE_DIR when platform pkg dir is unset", () => {
|
|
96
93
|
const cacheDir = mkTmp();
|
|
97
94
|
try {
|
|
98
95
|
const result = resolveSentinelPath({
|
package/scripts/verify-binary.js
CHANGED
|
@@ -308,8 +308,8 @@ function binaryTargetsForPlatform(platformId) {
|
|
|
308
308
|
];
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
function isSkipRequested() {
|
|
312
|
-
const v =
|
|
311
|
+
function isSkipRequested(env = process.env) {
|
|
312
|
+
const v = env[SKIP_ENV];
|
|
313
313
|
return v === "1" || v === "true" || v === "yes";
|
|
314
314
|
}
|
|
315
315
|
|
|
@@ -664,4 +664,6 @@ module.exports = {
|
|
|
664
664
|
EMBEDDED_PUBLIC_KEY,
|
|
665
665
|
ED25519_SPKI_HEADER,
|
|
666
666
|
SKIP_ENV,
|
|
667
|
+
binaryTargetsForPlatform,
|
|
668
|
+
isSkipRequested,
|
|
667
669
|
};
|
package/skills/fallow/SKILL.md
CHANGED
|
@@ -6,10 +6,10 @@ license: MIT
|
|
|
6
6
|
|
|
7
7
|
# Fallow: codebase intelligence for TypeScript and JavaScript
|
|
8
8
|
|
|
9
|
-
Codebase intelligence for TypeScript and JavaScript. The static layer analyzes code and styles and reports quality, changed-code risk, cleanup opportunities, circular dependencies, code duplication, complexity hotspots, architecture boundary violations, design-system styling drift, feature flag patterns, and opt-in security candidates. Runtime coverage merges production execution data into the same `fallow health` report for hot-path review
|
|
9
|
+
Codebase intelligence for TypeScript and JavaScript. The static layer analyzes code and styles and reports quality, changed-code risk, cleanup opportunities, circular dependencies, code duplication, complexity hotspots, architecture boundary violations, design-system styling drift, feature flag patterns, and opt-in security candidates. Runtime coverage merges production execution data into the same `fallow health` report for hot-path review and cold-path deletion confidence, with a single local capture available by default and continuous/cloud runtime monitoring available as an optional mode. Broad framework plugin coverage, zero configuration, sub-second static analysis.
|
|
10
10
|
|
|
11
11
|
## When to Use
|
|
12
|
-
- Find cleanup opportunities: unused files, exports, types, members, dependencies, or
|
|
12
|
+
- Find cleanup opportunities: unused files, exports, types, members, dependencies, or feature flags that guard unused exports.
|
|
13
13
|
- Detect code duplication, circular dependencies, architecture boundary issues, and complexity hotspots.
|
|
14
14
|
- Find functions that may implement the same intent despite different names, syntax, or control flow (`fallow similar-code`).
|
|
15
15
|
- Check styling consistency, CSS dead surface, and design-token drift.
|
|
@@ -126,6 +126,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
|
|
|
126
126
|
| `--unused-deps` | Unused dependencies, devDependencies, optionalDependencies, type-only production deps, and test-only production deps |
|
|
127
127
|
| `--unused-types` | Unused types |
|
|
128
128
|
| `--private-type-leaks` | Opt-in API hygiene check (default `off`) for exported signatures that reference same-file private types. Storybook `*.stories.*` story files and framework routing convention files (Next.js App + Pages Router, Gatsby, Remix v2, TanStack Router, Expo Router) are skipped to avoid noise. Enable via this flag or `private-type-leaks: "warn"` / `"error"` in [`rules`](#configuration-file-format). |
|
|
129
|
+
| `--deprecated-exports-in-use` | Export marked @deprecated is still referenced |
|
|
129
130
|
| `--unused-enum-members` | Unused enum members |
|
|
130
131
|
| `--unused-class-members` | Unused class members |
|
|
131
132
|
| `--unused-store-members` | Unused Pinia store members |
|
|
@@ -446,7 +447,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
|
|
|
446
447
|
{
|
|
447
448
|
"kind": "agent-install",
|
|
448
449
|
"schema_version": 1,
|
|
449
|
-
"fallow_version": "3.
|
|
450
|
+
"fallow_version": "3.29.0",
|
|
450
451
|
"root": "/abs/path",
|
|
451
452
|
"mode": "install",
|
|
452
453
|
"dry_run": false,
|
|
@@ -524,7 +525,7 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
524
525
|
|---|---|---|---|
|
|
525
526
|
| `--max-cyclomatic` | `string` | - | Fail if any function exceeds this cyclomatic complexity |
|
|
526
527
|
| `--max-cognitive` | `string` | - | Fail if any function exceeds this cognitive complexity |
|
|
527
|
-
| `--max-crap` | `string` | - | Fail if any function has CRAP score >= threshold. CRAP combines complexity with coverage (`CC^2 * (1 - cov/100)^3 + CC`). Pair with `--coverage` for accurate per-function CRAP; without
|
|
528
|
+
| `--max-crap` | `string` | - | Fail if any function has CRAP score >= threshold. CRAP combines complexity with coverage (`CC^2 * (1 - cov/100)^3 + CC`). Pair with `--coverage` for accurate per-function CRAP; without coverage data fallow estimates coverage from the module graph. |
|
|
528
529
|
| `--top` | `string` | - | Only show the top N most complex functions (and file scores/hotspots/targets) |
|
|
529
530
|
| `--sort` | `severity\|cyclomatic\|cognitive\|lines` | `cyclomatic` | Sort order for complexity findings |
|
|
530
531
|
| `--complexity` | `bool` | `false` | Show only function complexity findings. When no section flags are set, all sections are shown by default. |
|
|
@@ -546,7 +547,7 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
546
547
|
| `--min-commits` | `string` | - | Minimum number of commits for a file to be included in hotspot ranking. |
|
|
547
548
|
| `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Forces file-scores + hotspot computation. |
|
|
548
549
|
| `--trend` | `bool` | `false` | Compare current metrics against the most recent saved snapshot. Reads from `.fallow/snapshots/` and shows per-metric deltas with directional indicators (improving/declining/stable). Implies `--score`. |
|
|
549
|
-
| `--coverage` | `string` | - | Path to
|
|
550
|
+
| `--coverage` | `string` | - | Path to coverage data for accurate per-function CRAP scores: an Istanbul map (`coverage-final.json`), a directory containing one, a raw V8 coverage directory (`NODE_V8_COVERAGE=<dir> node --test`), or a single V8 coverage JSON file. Transpiled V8 scripts (tsx, bundles) map back to their source files through the source map that Node records in the dump; a script that differs from the file on disk and has no source map keeps the estimate. Uses `CC^2 * (1-cov/100)^3 + CC` instead of static binary model. Relative paths resolve against `--root`. Falls back to `FALLOW_COVERAGE`, then `health.coverage`, then auto-detection. |
|
|
550
551
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from file paths in coverage data before prepending the project root. For CI/Docker environments where coverage was generated with different absolute paths. Falls back to `FALLOW_COVERAGE_ROOT`, then `health.coverageRoot`. |
|
|
551
552
|
| `--runtime-coverage` | `string` | - | Merge runtime-coverage input into the health report. Accepts a V8 coverage directory (`NODE_V8_COVERAGE=...`), a single V8 coverage JSON file, or an Istanbul `coverage-final.json`. One local capture is free and does not require a license; continuous/cloud or multi-capture runtime monitoring requires an active license or trial (`fallow license activate --trial --email <addr>`). JSON output gains a `runtime_coverage` object with a top-level report verdict, per-finding `verdict` (`safe_to_delete` / `review_required` / `low_traffic` / `coverage_unavailable` / `active`), a per-finding suppression `id` (`fallow:prod:<hash>`, hashes the current line), an optional cross-surface `stable_id` join key (`fallow:fn:<hash>`, hashes file + name + start line; one value per function across findings / hot-paths / blast-radius / importance and across V8/Istanbul/oxc producers), an optional content-digest `source_hash` (line-move-immune, so baselines survive a pure line shift), an evidence block, and percentile-ranked hot paths. On protocol-0.3+ sidecars the `summary` also carries an optional `capture_quality` block (`window_seconds`, `instances_observed`, `lazy_parse_warning`, `untracked_ratio_percent`) that flags short-window captures where lazy-parsed scripts may not appear. |
|
|
552
553
|
| `--min-invocations-hot` | `string` | `100` | Invocation threshold for hot-path classification. Takes effect only when `--runtime-coverage` is set. |
|
|
@@ -650,7 +651,7 @@ fallow health --format json --quiet --trend
|
|
|
650
651
|
{
|
|
651
652
|
"kind": "health",
|
|
652
653
|
"schema_version": 7,
|
|
653
|
-
"version": "3.
|
|
654
|
+
"version": "3.29.0",
|
|
654
655
|
"elapsed_ms": 32,
|
|
655
656
|
"summary": {
|
|
656
657
|
"files_analyzed": 482,
|
|
@@ -724,7 +725,7 @@ With `--file-scores`, the JSON output also includes `file_scores` array and `sum
|
|
|
724
725
|
|
|
725
726
|
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.
|
|
726
727
|
|
|
727
|
-
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 Istanbul
|
|
728
|
+
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 exact scores (`istanbul` model; `summary.coverage_input_format` is `istanbul` or `v8`). 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`.
|
|
728
729
|
|
|
729
730
|
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.
|
|
730
731
|
|
|
@@ -972,13 +973,13 @@ Audits changed files for dead code, complexity, duplication, and styling. Return
|
|
|
972
973
|
| `--health-baseline` | `string` | - | Baseline file (produced by `fallow health --save-baseline`). Pre-existing complexity findings are excluded from the verdict. |
|
|
973
974
|
| `--dupes-baseline` | `string` | - | Baseline file (produced by `fallow dupes --save-baseline`). Pre-existing clone groups are excluded from the verdict. |
|
|
974
975
|
| `--max-crap` | `string` | - | Forwarded to the health sub-analysis. Functions meeting or exceeding this CRAP score cause audit to fail. Same formula as `health --max-crap`. Pair with coverage data for accurate per-function CRAP. |
|
|
975
|
-
| `--coverage` | `string` | - | Path to Istanbul
|
|
976
|
+
| `--coverage` | `string` | - | Path to Istanbul coverage data (`coverage-final.json`) or raw V8 coverage (a `NODE_V8_COVERAGE` directory or one V8 JSON file) for accurate per-function CRAP scores in the health sub-analysis. Same formats and semantics as `health --coverage`. Also configurable via `FALLOW_COVERAGE`, then `health.coverage` (the same chain as `fallow health`). Relative paths resolve against `--root`. |
|
|
976
977
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from file paths in coverage data before prepending the project root. Also configurable via `FALLOW_COVERAGE_ROOT`, then `health.coverageRoot`. Use when coverage was generated under a different checkout root in CI / Docker (e.g., `/home/runner/work/myapp` on GitHub Actions). |
|
|
977
978
|
| `--no-css` | `bool` | `false` | Disable styling analytics in audit |
|
|
978
979
|
| `--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` |
|
|
979
980
|
| `--no-css-deep` | `bool` | `false` | Disable deep CSS analysis while keeping local styling analytics on |
|
|
980
981
|
| `--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. |
|
|
981
|
-
| `--runtime-coverage` | `string` | - |
|
|
982
|
+
| `--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`) |
|
|
982
983
|
| `--min-invocations-hot` | `string` | `100` | Threshold for hot-path classification, forwarded to the sidecar when `--runtime-coverage` is set |
|
|
983
984
|
| `--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 |
|
|
984
985
|
| `--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` |
|
|
@@ -1053,7 +1054,7 @@ fallow audit \
|
|
|
1053
1054
|
{
|
|
1054
1055
|
"kind": "audit",
|
|
1055
1056
|
"schema_version": 7,
|
|
1056
|
-
"version": "3.
|
|
1057
|
+
"version": "3.29.0",
|
|
1057
1058
|
"command": "audit",
|
|
1058
1059
|
"verdict": "fail",
|
|
1059
1060
|
"changed_files_count": 12,
|
|
@@ -1130,7 +1131,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
1130
1131
|
```json
|
|
1131
1132
|
{
|
|
1132
1133
|
"schema_version": 7,
|
|
1133
|
-
"version": "3.
|
|
1134
|
+
"version": "3.29.0",
|
|
1134
1135
|
"elapsed_ms": 116,
|
|
1135
1136
|
"feature_flags": [],
|
|
1136
1137
|
"total_flags": 0
|
|
@@ -1203,7 +1204,7 @@ Build-config and test files are excluded from candidate generation. Security rul
|
|
|
1203
1204
|
<!-- generated:flags:security:start -->
|
|
1204
1205
|
| Flag | Type | Default | Description |
|
|
1205
1206
|
|---|---|---|---|
|
|
1206
|
-
| `--runtime-coverage` | `string` | - |
|
|
1207
|
+
| `--runtime-coverage` | `string` | - | Runtime coverage input. Accepts a V8 directory, a single V8 JSON file, or an Istanbul coverage map JSON. When set, `fallow security` adds production runtime state to tainted-sink candidates and uses that state as an extra ranking signal. A single local capture is free. Continuous or multi-capture monitoring needs a license (see `fallow license`) |
|
|
1207
1208
|
| `--min-invocations-hot` | `string` | `100` | Threshold for hot-path classification, forwarded to the sidecar when `--runtime-coverage` is set |
|
|
1208
1209
|
| `--file` | `string` | - | Scope output to candidates whose finding anchor or trace hop matches the selected file. The full graph is still analyzed |
|
|
1209
1210
|
| `--gate` | `new\|newly-reachable` | - | `new` fails (exit code **8**) only when the change introduces a NEW security-sink candidate in the changed lines. It requires a diff source (`--changed-since`, `--diff-file`, or `--diff-stdin`). `newly-reachable` fails when an existing candidate becomes reachable from entry points compared with `--changed-since <ref>`; diff-only inputs exit 2 because this mode analyzes the base tree. Human output says `REVIEW REQUIRED` (not `FAIL`); SARIF keeps every result at `level: note` with the verdict in `run.properties.fallowGate`; `--format json` carries an additive `gate` block (`mode` / `verdict` / `new_count`) |
|
|
@@ -1231,7 +1232,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1231
1232
|
{
|
|
1232
1233
|
"kind": "security",
|
|
1233
1234
|
"schema_version": "4",
|
|
1234
|
-
"version": "3.
|
|
1235
|
+
"version": "3.29.0",
|
|
1235
1236
|
"elapsed_ms": 42,
|
|
1236
1237
|
"config": {
|
|
1237
1238
|
"rules": {
|
|
@@ -1260,7 +1261,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1260
1261
|
{
|
|
1261
1262
|
"kind": "security",
|
|
1262
1263
|
"schema_version": "4",
|
|
1263
|
-
"version": "3.
|
|
1264
|
+
"version": "3.29.0",
|
|
1264
1265
|
"elapsed_ms": 42,
|
|
1265
1266
|
"config": {
|
|
1266
1267
|
"rules": {
|
|
@@ -1320,7 +1321,7 @@ Every finding also carries an agent-actionable `candidate { source_kind, sink, b
|
|
|
1320
1321
|
- `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.
|
|
1321
1322
|
- There is no `impact` field: deciding exploitability is the verifying agent's job; `severity` is only the review-priority tier.
|
|
1322
1323
|
- `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`.
|
|
1323
|
-
- `finding_id`: a stable correlation id, identical across runs for the same rule/path/line and identical to
|
|
1324
|
+
- `finding_id`: a stable correlation id, identical across runs for the same rule/path/line/column and identical to SARIF `partialFingerprints["fallowSecurity/v2"]`, for tracking a candidate across runs and joining JSON with SARIF. 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.
|
|
1324
1325
|
|
|
1325
1326
|
---
|
|
1326
1327
|
|
|
@@ -1845,6 +1846,7 @@ Available on all commands:
|
|
|
1845
1846
|
| `--report-path-prefix` | `string` | - | Prefix prepended to every path in the CI-facing formats (`github-annotations`, `github-summary`, `codeclimate`, `review-github`, `review-gitlab`). CI platforms address files by repository-root-relative path, so when the analyzed project lives in a subdirectory (e.g. `packages/app/`), paths need that offset. fallow detects the offset via the git toplevel automatically; this flag overrides the detection. Pass an empty string to disable rebasing and emit paths relative to `--root` |
|
|
1846
1847
|
| `--fail-on-regression` | `bool` | `false` | Fail if issue count increased beyond tolerance vs a regression baseline |
|
|
1847
1848
|
| `--fail-on-stale-baseline` | `bool` | `false` | Exit with code 1 if a loaded --baseline has entries that match nothing in this run |
|
|
1849
|
+
| `--fail-on-parse-error` | `bool` | `false` | Exit with code 1 if fallow could not parse a source file cleanly |
|
|
1848
1850
|
| `--tolerance` | `string` | `0` | Allowed increase: `"2%"` (percentage) or `"5"` (absolute). Default: `"0"` |
|
|
1849
1851
|
| `--regression-baseline` | `string` | - | Path to a standalone regression baseline file. Without it, fallow uses `regression.baseline` from the config |
|
|
1850
1852
|
| `--save-regression-baseline` | `string` | - | Save current issue counts. With no path, update `regression.baseline` in the discovered fallow config or create `.fallowrc.json`; with a path, write a standalone baseline file |
|
|
@@ -1863,8 +1865,10 @@ Available on all commands:
|
|
|
1863
1865
|
| `--score` | `bool` | `false` | Compute health score (0-100 with letter grade) in combined mode. Enables the health delta header in PR comments. JSON includes `health_score` object with `score`, `grade`, and `penalties` breakdown |
|
|
1864
1866
|
| `--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/` |
|
|
1865
1867
|
| `--save-snapshot` | `string` | - | Save vital signs snapshot for trend tracking. Default path: `.fallow/snapshots/<timestamp>.json`. Forces file-scores + hotspot computation |
|
|
1866
|
-
| `--coverage` | `string` | - | Path to Istanbul coverage data for exact CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
1868
|
+
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for exact CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
1867
1869
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
|
|
1870
|
+
| `--dupes-baseline` | `string` | - | Compare duplication clone groups against a saved baseline in combined mode (produced by `fallow dupes --save-baseline`) |
|
|
1871
|
+
| `--health-baseline` | `string` | - | Compare health findings against a saved baseline in combined mode (produced by `fallow health --save-baseline`) |
|
|
1868
1872
|
| `--include-entry-exports` | `bool` | `false` | Report unused exports in entry files instead of auto-marking them as used |
|
|
1869
1873
|
| `--type-aware` | `bool` | `false` | Opt in to TypeScript semantic analysis for project-wide symbol evidence. This does not emit compiler diagnostics or typed lint findings |
|
|
1870
1874
|
| `--no-type-aware` | `bool` | `false` | Disable TypeScript semantic analysis even when `typeAware.enabled` or `FALLOW_TYPE_AWARE` opts in, keeping this run fully syntactic |
|
|
@@ -1904,7 +1908,7 @@ guarded edits.
|
|
|
1904
1908
|
| `--score` | `bool` | `false` | Compute health score in combined mode |
|
|
1905
1909
|
| `--trend` | `bool` | `false` | Compare current health metrics against the most recent saved snapshot |
|
|
1906
1910
|
| `--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 |
|
|
1907
|
-
| `--coverage` | `string` | - | Path to Istanbul coverage data for exact CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
1911
|
+
| `--coverage` | `string` | - | Path to Istanbul or raw V8 coverage data for exact CRAP scores in combined mode. Also settable via `FALLOW_COVERAGE` or `health.coverage` |
|
|
1908
1912
|
| `--coverage-root` | `string` | - | Absolute prefix to strip from Istanbul file paths in combined mode. Also settable via `FALLOW_COVERAGE_ROOT` or `health.coverageRoot` |
|
|
1909
1913
|
|
|
1910
1914
|
These are global flags with behavior specific to bare `fallow` combined mode.
|
|
@@ -1922,7 +1926,7 @@ These are global flags with behavior specific to bare `fallow` combined mode.
|
|
|
1922
1926
|
| `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
|
|
1923
1927
|
| `FALLOW_CACHE_DIR` | Override the persistent extraction cache directory. Wins over `cache.dir`. Useful for read-only checkouts or CI cache volumes. `--no-cache` disables this knob. |
|
|
1924
1928
|
| `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. |
|
|
1925
|
-
| `FALLOW_COVERAGE` | Path to Istanbul coverage data for exact CRAP scoring in `health`, `audit`, and bare `fallow`. |
|
|
1929
|
+
| `FALLOW_COVERAGE` | Path to Istanbul or raw V8 coverage data for exact CRAP scoring in `health`, `audit`, and bare `fallow`. |
|
|
1926
1930
|
| `FALLOW_COVERAGE_ROOT` | Absolute coverage-data prefix to strip before matching Istanbul paths in `health`, `audit`, and bare `fallow`. |
|
|
1927
1931
|
| `FALLOW_TYPE_AWARE` | Enable or disable TypeScript semantic (type-aware) analysis for the run. Accepts `true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off`; any other value is a hard error. Sits mid-chain in the precedence: the `--type-aware`/`--no-type-aware` CLI flags win over it, and it wins over the `audit.typeAware` config field, which wins over `typeAware.enabled`. |
|
|
1928
1932
|
| `FALLOW_AUDIT_BASE` | Pin the `fallow audit` comparison base when `--base` / `--changed-since` is unset (precedence: flag > env > auto-detect). Escape hatch for the agent gate and forks, e.g. `FALLOW_AUDIT_BASE=upstream/main`. When unset, audit auto-detects the `git merge-base` against the branch's upstream or the remote default. A malformed value exits 2. |
|
|
@@ -2030,7 +2034,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2030
2034
|
{
|
|
2031
2035
|
"kind": "dead-code",
|
|
2032
2036
|
"schema_version": 7,
|
|
2033
|
-
"version": "3.
|
|
2037
|
+
"version": "3.29.0",
|
|
2034
2038
|
"elapsed_ms": 45,
|
|
2035
2039
|
"total_issues": 12,
|
|
2036
2040
|
"entry_points": {
|
|
@@ -2162,7 +2166,7 @@ Health findings (`fallow health` JSON output) include an `actions` array. Primar
|
|
|
2162
2166
|
|
|
2163
2167
|
The `coverage_tier` field is `"none"` (file not test-reachable / Istanbul 0%), `"partial"` (Istanbul `(0, 70)` / estimated 40%), or `"high"` (Istanbul `>= 70` / estimated 85%).
|
|
2164
2168
|
|
|
2165
|
-
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.
|
|
2169
|
+
Each CRAP finding also carries a `coverage_source` discriminator: `"istanbul"` (direct fnMap match for this function, from an Istanbul map or from raw V8 coverage; `summary.coverage_input_format` names which), `"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.
|
|
2166
2170
|
|
|
2167
2171
|
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.
|
|
2168
2172
|
|
|
@@ -2190,7 +2194,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
2190
2194
|
{
|
|
2191
2195
|
"kind": "dupes",
|
|
2192
2196
|
"schema_version": 7,
|
|
2193
|
-
"version": "3.
|
|
2197
|
+
"version": "3.29.0",
|
|
2194
2198
|
"elapsed_ms": 82,
|
|
2195
2199
|
"total_clones": 15,
|
|
2196
2200
|
"total_lines_duplicated": 230,
|
|
@@ -2234,11 +2238,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
2234
2238
|
{
|
|
2235
2239
|
"kind": "combined",
|
|
2236
2240
|
"schema_version": 7,
|
|
2237
|
-
"version": "3.
|
|
2241
|
+
"version": "3.29.0",
|
|
2238
2242
|
"elapsed_ms": 159,
|
|
2239
2243
|
"check": {
|
|
2240
2244
|
"schema_version": 7,
|
|
2241
|
-
"version": "3.
|
|
2245
|
+
"version": "3.29.0",
|
|
2242
2246
|
"elapsed_ms": 45,
|
|
2243
2247
|
"total_issues": 12,
|
|
2244
2248
|
"unused_files": [],
|
|
@@ -15,6 +15,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
|
|
|
15
15
|
| `unused-export` | `--unused-exports` | yes | `// fallow-ignore-next-line unused-export` | Symbols never imported elsewhere |
|
|
16
16
|
| `unused-type` | `--unused-types` | - | `// fallow-ignore-next-line unused-type` | Type aliases and interfaces |
|
|
17
17
|
| `private-type-leak` | `--private-type-leaks` | - | `// fallow-ignore-next-line private-type-leak` | Opt-in API hygiene check (default `off`) for exported signatures whose type references a same-file private type |
|
|
18
|
+
| `deprecated-export-in-use` | `--deprecated-exports-in-use` | - | `// fallow-ignore-next-line deprecated-export-in-use` | Export marked @deprecated is still referenced; Opt-in migration sweep; the rule defaults to off |
|
|
18
19
|
| `unused-dependency` | `--unused-deps` | yes | - | Packages in `dependencies` never imported. In monorepos, internal workspace package names (e.g., `@repo/ui`) declared in another workspace's `package.json` but never imported are reported here too. `--unused-deps` also covers the dev/optional/type-only/test-only sibling rows below. |
|
|
19
20
|
| `unused-dev-dependency` | `--unused-deps` | yes | - | Packages in `devDependencies` never imported by test files, config files, or scripts |
|
|
20
21
|
| `unused-optional-dependency` | `--unused-deps` | yes | - | Packages in `optionalDependencies` never imported (often platform-specific; verify before removing) |
|
|
@@ -11,8 +11,8 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
|
|
|
11
11
|
<!-- generated:mcp-tools:start -->
|
|
12
12
|
| Tool | Kind | License | CLI fallback | Key params | Description |
|
|
13
13
|
|---|---|---|---|---|---|
|
|
14
|
-
| `code_execute` | composition | free | - | `code`, `timeout_ms`, `max_output_bytes` | Bounded read-only Code Mode for composing multiple fallow analysis calls in one JavaScript snippet. The snippet receives `{ fallow, root }`, returns JSON-serializable data, and can call read-only helpers such as `fallow.projectInfo`, `fallow.audit`, `fallow.checkHealth`, and `fallow.run(tool, params)` for the same allowlist. `fallow.all(requests)` fans out independent calls in one go: pass `[{ tool, params }, ...]` and get back a positionally aligned array of `{ ok: true, value }` or `{ ok: false, error }`, so one failing element never hides the rest. Host calls are memoized for the duration of one snippet, so repeating the same tool with the same params (key order does not matter) is served from cache, spends no `max_host_calls` slot and no output budget, and is reported in `calls[]` with `cache_hit: true`; a call refused before dispatch (unknown tool, malformed params) spends no slot either, and `limits.max_rejected_host_calls` bounds how many of those the response records. Similar-code is excluded because Code Mode is capped at 30 seconds; use standalone `find_similar_code` and `inspect_similar_code`, which have dedicated 15-minute timeouts. Mutating fix tools are not exposed. The sandbox has no filesystem, network, imports, `process`, `require`, `Deno`, `Bun`, or shell access, and no dynamic code compilation: `eval`, `Function`, and the async and generator function constructors are removed, including the `constructor` route reachable through function prototypes. Params: `code`, optional `root`, `timeout_ms` (capped at 30000), and `max_output_bytes` (capped at 4000000). `max_output_bytes` bounds two separate things: the total fallow JSON host calls read, shared across a `fallow.all` fan-out rather than granted per element, and the serialized snippet result. An oversized result is refused with `ok:false`, `truncated:true`, `result_bytes`, and a short `result_preview` in place of the value, never returned whole, so return a projection rather than a whole report. |
|
|
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) + 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"]`. 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 |
|
|
14
|
+
| `code_execute` | composition | free | - | `code`, `timeout_ms`, `max_output_bytes` | Bounded read-only Code Mode for composing multiple fallow analysis calls in one JavaScript snippet. The snippet receives `{ fallow, root }`, returns JSON-serializable data, and can call read-only helpers such as `fallow.projectInfo`, `fallow.audit`, `fallow.checkHealth`, and `fallow.run(tool, params)` for the same allowlist. `fallow.all(requests)` fans out independent calls in one go: pass `[{ tool, params }, ...]` and get back a positionally aligned array of `{ ok: true, value }` or `{ ok: false, error }`, so one failing element never hides the rest. Host calls are memoized for the duration of one snippet, so repeating the same tool with the same params (key order does not matter) is served from cache, spends no `max_host_calls` slot and no output budget, and is reported in `calls[]` with `cache_hit: true`; a call refused before dispatch (unknown tool, malformed params) spends no slot either, and `limits.max_rejected_host_calls` bounds how many of those the response records. Similar-code is excluded because Code Mode is capped at 30 seconds; use standalone `find_similar_code` and `inspect_similar_code`, which have dedicated 15-minute timeouts. Mutating fix tools are not exposed, and a host call that passes `save_baseline`, `save_regression_baseline` or `save_snapshot` is refused with the name of the standalone tool to call. The sandbox has no filesystem, network, imports, `process`, `require`, `Deno`, `Bun`, or shell access, and no dynamic code compilation: `eval`, `Function`, and the async and generator function constructors are removed, including the `constructor` route reachable through function prototypes. Params: `code`, optional `root`, `timeout_ms` (capped at 30000), and `max_output_bytes` (capped at 4000000). `max_output_bytes` bounds two separate things: the total fallow JSON host calls read, shared across a `fallow.all` fan-out rather than granted per element, and the serialized snippet result. An oversized result is refused with `ok:false`, `truncated:true`, `result_bytes`, and a short `result_preview` in place of the value, never returned whole, so return a projection rather than a whole report. |
|
|
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) + 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
|
| `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, and a stable `finding_id` (equal to the SARIF fingerprint) for cross-run correlation; 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://docs.fallow.tools/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. |
|
|
18
18
|
| `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. |
|
|
@@ -51,6 +51,8 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
|
|
|
51
51
|
| `trace_clone` | trace | free | `fallow dupes --trace <file:line> --format json --quiet` | `file`, `line`, `fingerprint`, `near`, `min_occurrences` | Deep-dive a duplicate-code clone group (`fallow dupes --trace <spec> --format json`). Address by exactly one of: `file` + `line` (a source location), or `fingerprint` (a `dup:<id>` from a prior `find_dupes` `clone_groups[].fingerprint`, usually `dup:<8hex>` and widened only on rare report collisions). Returns the matched clone instance plus every clone group containing it; each traced group carries its `fingerprint`, an extract-function `suggestion` with estimated savings, and a best-effort `suggested_name` (omitted when no confident name). Supports `mode`, `near`, `min_tokens`, `min_lines`, `min_occurrences`, `threshold`, `skip_local`, `cross_language`, `ignore_imports`. Use the same `near` value as the originating `find_dupes` call. Use to consolidate duplication when you need exact sibling locations and a refactor target |
|
|
52
52
|
<!-- generated:mcp-tools:end -->
|
|
53
53
|
|
|
54
|
+
Tool hints: `fix_apply` changes source files and declares `destructiveHint: true`. `analyze`, `check_changed`, `find_dupes` and `check_health` write a baseline, regression baseline or snapshot file when you pass `save_baseline`, `save_regression_baseline` or `save_snapshot`. They declare `readOnlyHint: false` and `destructiveHint: false`, so a host can ask for approval before it runs them. Every other tool declares `readOnlyHint: true`.
|
|
55
|
+
|
|
54
56
|
## Resource catalogue
|
|
55
57
|
|
|
56
58
|
Resources are the server's read-only reference channel: compile-time material an agent can list (`resources/list`, `resources/templates/list`) and read (`resources/read`) with no subprocess and no analysis run, cacheable by URI (your client reads them through its own resource tool). Each content item carries the server version in `_meta.fallow_version`; the payload itself is the plain document, so the schema resources are valid strict JSON Schema. Unknown URIs return a structured error whose `data` lists the known URIs (or the nearest issue types); the numeric code is `-32002` on protocol versions before 2026-07-28 and `-32602` from then on, so key on `data`. Every payload is JSON and carries `fallow_version`, so cache by URI and invalidate when the server version changes. The catalogue is static (no `subscribe`, no `listChanged`). Read `fallow://explain/{issue_type}` instead of calling `fallow_explain` when you only need the reference document; `issue_type` accepts the bare id (`unused-export`), the namespaced rule id (`security/sql-injection`), or the CLI filter spelling (`unused-exports`). An unknown URI or issue type returns a structured `resource_not_found` error listing the known URIs or the nearest issue types.
|
|
@@ -110,4 +112,4 @@ All JSON responses include structured `actions` arrays on every finding (dead co
|
|
|
110
112
|
|
|
111
113
|
`health.thresholdOverrides[]` lets projects keep known legacy functions visible as configured local ceilings instead of hiding them with suppressions. Each entry has `files` globs, optional exact `functions`, one or more of `maxCyclomatic`, `maxCognitive`, `maxCrap`, or `maxUnitSize`, and optional `reason`. Health JSON may include top-level `threshold_overrides[]` entries with `active`, `stale`, `insufficient`, or `no_match` status, and complexity findings that use an override carry `effective_thresholds` plus `threshold_source: "override"`. Each entry also names its `dimension` (`complexity` or `crap`), so one configured override yields one entry per dimension it participates in: group on `override_index` to count configured overrides. `insufficient` means the raised ceiling is still exceeded. An entry's `outstanding[]` lists every dimension the matched unit still breaches after the override applied, which is how an `active` override can sit next to a surviving finding.
|
|
112
114
|
|
|
113
|
-
`dead-code`, `health`, `dupes`, bare `fallow`, and `audit` JSON output also carry a top-level `next_steps` array of read-only follow-up commands computed from the run's findings: each entry is `{ id, command, reason }`. The `command` is runnable as-is (never a placeholder, never `fix` or any other mutating command); the stable kebab-case `id` (`setup`, `impact-report`, `trace-unused-export`, `trace-clone`, `complexity-breakdown`, `scope-workspaces`, `audit-changed`) maps to a verification step you should run BEFORE acting, for example tracing an export before deleting it. A leading `setup` step (command: `fallow schema`) appears only on unconfigured, non-CI projects with findings and doubles as an onboarding trigger; it disappears after setup or `fallow init --decline`. An at-most-weekly `impact-report` step (command: `fallow impact`) carries the local value digest when impact tracking has non-zero results; it may ride a clean run. When running via MCP, dispatch on the `id` to the matching tool / `code_execute` host call (`trace_export`, `trace_clone`, `check_health` with `complexity_breakdown: true`, `audit`) rather than shelling out the CLI string. The array is deduplicated, capped at three, and omitted when empty; set `FALLOW_SUGGESTIONS=off` to suppress it.
|
|
115
|
+
`dead-code`, `health`, `dupes`, bare `fallow`, and `audit` JSON output also carry a top-level `next_steps` array of read-only follow-up commands computed from the run's findings: each entry is `{ id, command, reason }`. The `command` is runnable as-is (never a placeholder, never `fix` or any other mutating command); the stable kebab-case `id` (`setup`, `impact-report`, `trace-unused-export`, `trace-deprecated-export`, `trace-clone`, `complexity-breakdown`, `scope-workspaces`, `audit-changed`) maps to a verification step you should run BEFORE acting, for example tracing an export before deleting it. A leading `setup` step (command: `fallow schema`) appears only on unconfigured, non-CI projects with findings and doubles as an onboarding trigger; it disappears after setup or `fallow init --decline`. An at-most-weekly `impact-report` step (command: `fallow impact`) carries the local value digest when impact tracking has non-zero results; it may ride a clean run. When running via MCP, dispatch on the `id` to the matching tool / `code_execute` host call (`trace_export`, `trace_clone`, `check_health` with `complexity_breakdown: true`, `audit`) rather than shelling out the CLI string. The array is deduplicated, capped at three, and omitted when empty; set `FALLOW_SUGGESTIONS=off` to suppress it.
|