fallow 3.3.0 → 3.5.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 +12 -2
- package/bin/fallow-lsp +4 -1
- package/bin/fallow-mcp +4 -1
- package/capabilities.json +70 -3
- package/package.json +10 -10
- package/schema.json +1 -1
- package/scripts/lazy-verify.js +4 -1
- package/scripts/lazy-verify.test.js +6 -11
- package/scripts/run-binary.js +10 -2
- package/scripts/verify-binary.js +9 -5
- package/scripts/verify-binary.test.js +16 -22
- package/skills/fallow/SKILL.md +2 -0
- package/skills/fallow/references/cli-reference.md +18 -13
- package/skills/fallow/references/mcp.md +2 -1
- package/skills/fallow/references/patterns.md +6 -6
- package/types/output-contract.d.ts +372 -138
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Static analysis is open source. An optional runtime layer adds production execut
|
|
|
20
20
|
npm install --save-dev fallow # or: pnpm add -D fallow / yarn add -D fallow / bun add -d fallow
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Installs the `fallow` CLI
|
|
23
|
+
Installs the `fallow` CLI in your project, along with the `fallow-lsp` and `fallow-mcp` launchers that start the LSP and MCP servers from the same binary.
|
|
24
24
|
|
|
25
25
|
The package also ships a version-matched Agent Skill under `skills/fallow`.
|
|
26
26
|
For tools that need CLI and issue-surface metadata without spawning the binary,
|
|
@@ -119,7 +119,7 @@ Create a config file in your project root, or run `fallow init`:
|
|
|
119
119
|
```jsonc
|
|
120
120
|
// .fallowrc.json
|
|
121
121
|
{
|
|
122
|
-
"$schema": "
|
|
122
|
+
"$schema": "./node_modules/fallow/schema.json",
|
|
123
123
|
"entry": ["src/workers/*.ts", "scripts/*.ts"],
|
|
124
124
|
"ignorePatterns": ["**/*.generated.ts"],
|
|
125
125
|
"rules": {
|
|
@@ -130,6 +130,16 @@ Create a config file in your project root, or run `fallow init`:
|
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
`$schema` gives editors autocomplete and validation and has no effect on
|
|
134
|
+
analysis. As an npm package, `fallow` always ships a version-aligned schema at
|
|
135
|
+
`./node_modules/fallow/schema.json`, which `fallow init` and
|
|
136
|
+
`fallow recommend` point at by default: offline, no editor trust prompt. If
|
|
137
|
+
you install fallow another way (cargo, homebrew, a bare binary) with no
|
|
138
|
+
`node_modules/fallow` to point at, use the remote fallback instead:
|
|
139
|
+
`https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json`. Some
|
|
140
|
+
editors, including VS Code, refuse to load a remote schema URL until you
|
|
141
|
+
explicitly trust the domain.
|
|
142
|
+
|
|
133
143
|
Also supports TOML (`fallow init --toml` creates `fallow.toml`).
|
|
134
144
|
|
|
135
145
|
## Documentation
|
package/bin/fallow-lsp
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Launcher shim: the platform packages ship a single multicall `fallow`
|
|
4
|
+
// binary, so `fallow-lsp` spawns `fallow lsp-server` with the same stdio,
|
|
5
|
+
// signal, and exit-code forwarding as a bare `fallow` invocation.
|
|
3
6
|
const { runBinary } = require('../scripts/run-binary');
|
|
4
|
-
runBinary('fallow-
|
|
7
|
+
runBinary('fallow', { prependArgs: ['lsp-server'] });
|
package/bin/fallow-mcp
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Launcher shim: the platform packages ship a single multicall `fallow`
|
|
4
|
+
// binary, so `fallow-mcp` spawns `fallow mcp-server` with the same stdio,
|
|
5
|
+
// signal, and exit-code forwarding as a bare `fallow` invocation.
|
|
3
6
|
const { runBinary } = require('../scripts/run-binary');
|
|
4
|
-
runBinary('fallow-
|
|
7
|
+
runBinary('fallow', { prependArgs: ['mcp-server'] });
|
package/capabilities.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"manifest_version": "1",
|
|
5
5
|
"description": "Codebase analyzer for TypeScript/JavaScript: unused code, circular dependencies, code duplication, complexity hotspots, and architecture boundary violations",
|
|
6
6
|
"global_flags": [
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
"description": "Path to config file (.fallowrc.json, .fallowrc.jsonc, fallow.toml, or .fallow.toml)",
|
|
19
19
|
"short": "-c"
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
"name": "--allow-remote-extends",
|
|
23
|
+
"type": "bool",
|
|
24
|
+
"required": false,
|
|
25
|
+
"description": "Allow trusted config files to extend HTTPS URLs",
|
|
26
|
+
"possible_values": [
|
|
27
|
+
"true",
|
|
28
|
+
"false"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
21
31
|
{
|
|
22
32
|
"name": "--format",
|
|
23
33
|
"type": "string",
|
|
@@ -36,7 +46,9 @@
|
|
|
36
46
|
"pr-comment-gitlab",
|
|
37
47
|
"review-github",
|
|
38
48
|
"review-gitlab",
|
|
39
|
-
"badge"
|
|
49
|
+
"badge",
|
|
50
|
+
"github-annotations",
|
|
51
|
+
"github-summary"
|
|
40
52
|
]
|
|
41
53
|
},
|
|
42
54
|
{
|
|
@@ -266,6 +278,12 @@
|
|
|
266
278
|
"description": "Write the report to a file instead of stdout, for any --format (no ANSI codes). Useful on large projects where the terminal scrollback truncates the top. Progress and the confirmation stay on stderr",
|
|
267
279
|
"short": "-o"
|
|
268
280
|
},
|
|
281
|
+
{
|
|
282
|
+
"name": "--report-path-prefix",
|
|
283
|
+
"type": "string",
|
|
284
|
+
"required": false,
|
|
285
|
+
"description": "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`"
|
|
286
|
+
},
|
|
269
287
|
{
|
|
270
288
|
"name": "--fail-on-regression",
|
|
271
289
|
"type": "bool",
|
|
@@ -836,6 +854,16 @@
|
|
|
836
854
|
"true",
|
|
837
855
|
"false"
|
|
838
856
|
]
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
"name": "--churn",
|
|
860
|
+
"type": "bool",
|
|
861
|
+
"required": false,
|
|
862
|
+
"description": "OPT-IN: attach target-level git churn evidence from the health hotspot subsystem. Default off to avoid git-history latency",
|
|
863
|
+
"possible_values": [
|
|
864
|
+
"true",
|
|
865
|
+
"false"
|
|
866
|
+
]
|
|
839
867
|
}
|
|
840
868
|
]
|
|
841
869
|
},
|
|
@@ -1448,6 +1476,18 @@
|
|
|
1448
1476
|
}
|
|
1449
1477
|
]
|
|
1450
1478
|
},
|
|
1479
|
+
{
|
|
1480
|
+
"name": "suppressions",
|
|
1481
|
+
"description": "List active fallow-ignore suppression markers (read-only inventory)",
|
|
1482
|
+
"flags": [
|
|
1483
|
+
{
|
|
1484
|
+
"name": "--file",
|
|
1485
|
+
"type": "string",
|
|
1486
|
+
"required": false,
|
|
1487
|
+
"description": "Only list suppressions in the specified files. Accepts multiple values"
|
|
1488
|
+
}
|
|
1489
|
+
]
|
|
1490
|
+
},
|
|
1451
1491
|
{
|
|
1452
1492
|
"name": "explain",
|
|
1453
1493
|
"description": "Explain one fallow issue type without running an analysis",
|
|
@@ -1753,6 +1793,18 @@
|
|
|
1753
1793
|
}
|
|
1754
1794
|
]
|
|
1755
1795
|
},
|
|
1796
|
+
{
|
|
1797
|
+
"name": "report",
|
|
1798
|
+
"description": "Render a saved `--format json` results file in another format without re-running analysis (analyze once, render annotations and the job summary from the same file). v1 renders the GitHub-native formats only: `--format github-annotations` or `--format github-summary`",
|
|
1799
|
+
"flags": [
|
|
1800
|
+
{
|
|
1801
|
+
"name": "--from",
|
|
1802
|
+
"type": "string",
|
|
1803
|
+
"required": true,
|
|
1804
|
+
"description": "Path to a fallow JSON results file produced by `--format json` (dead-code, dupes, health, audit, security, or bare combined)"
|
|
1805
|
+
}
|
|
1806
|
+
]
|
|
1807
|
+
},
|
|
1756
1808
|
{
|
|
1757
1809
|
"name": "schema",
|
|
1758
1810
|
"description": "Dump fallow's capability manifest (CLI commands and flags, issue types, MCP tools, framework plugins, env vars) as machine-readable JSON for agent introspection. Always JSON, regardless of --format",
|
|
@@ -6338,7 +6390,8 @@
|
|
|
6338
6390
|
"cli_command": "fallow inspect --format json --quiet",
|
|
6339
6391
|
"key_params": [
|
|
6340
6392
|
"target",
|
|
6341
|
-
"production"
|
|
6393
|
+
"production",
|
|
6394
|
+
"include_churn"
|
|
6342
6395
|
],
|
|
6343
6396
|
"license": "free",
|
|
6344
6397
|
"license_note": null,
|
|
@@ -6588,6 +6641,20 @@
|
|
|
6588
6641
|
"license_note": null,
|
|
6589
6642
|
"read_only": true
|
|
6590
6643
|
},
|
|
6644
|
+
{
|
|
6645
|
+
"name": "list_suppressions",
|
|
6646
|
+
"kind": "analysis",
|
|
6647
|
+
"description": "List active fallow-ignore suppression markers grouped per file (line, kind, level, reason, and a stale cross-reference); a read-only governance inventory that always exits 0",
|
|
6648
|
+
"cli_command": "fallow suppressions --format json --quiet",
|
|
6649
|
+
"key_params": [
|
|
6650
|
+
"workspace",
|
|
6651
|
+
"changed_since",
|
|
6652
|
+
"file"
|
|
6653
|
+
],
|
|
6654
|
+
"license": "free",
|
|
6655
|
+
"license_note": null,
|
|
6656
|
+
"read_only": true
|
|
6657
|
+
},
|
|
6591
6658
|
{
|
|
6592
6659
|
"name": "impact",
|
|
6593
6660
|
"kind": "introspection",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"tanstack-intent"
|
|
37
37
|
],
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=22"
|
|
40
40
|
},
|
|
41
41
|
"bin": {
|
|
42
42
|
"fallow": "bin/fallow",
|
|
@@ -87,13 +87,13 @@
|
|
|
87
87
|
"@tanstack/intent": "0.3.2"
|
|
88
88
|
},
|
|
89
89
|
"optionalDependencies": {
|
|
90
|
-
"@fallow-cli/darwin-arm64": "3.
|
|
91
|
-
"@fallow-cli/darwin-x64": "3.
|
|
92
|
-
"@fallow-cli/linux-x64-gnu": "3.
|
|
93
|
-
"@fallow-cli/linux-arm64-gnu": "3.
|
|
94
|
-
"@fallow-cli/linux-x64-musl": "3.
|
|
95
|
-
"@fallow-cli/linux-arm64-musl": "3.
|
|
96
|
-
"@fallow-cli/win32-arm64-msvc": "3.
|
|
97
|
-
"@fallow-cli/win32-x64-msvc": "3.
|
|
90
|
+
"@fallow-cli/darwin-arm64": "3.5.0",
|
|
91
|
+
"@fallow-cli/darwin-x64": "3.5.0",
|
|
92
|
+
"@fallow-cli/linux-x64-gnu": "3.5.0",
|
|
93
|
+
"@fallow-cli/linux-arm64-gnu": "3.5.0",
|
|
94
|
+
"@fallow-cli/linux-x64-musl": "3.5.0",
|
|
95
|
+
"@fallow-cli/linux-arm64-musl": "3.5.0",
|
|
96
|
+
"@fallow-cli/win32-arm64-msvc": "3.5.0",
|
|
97
|
+
"@fallow-cli/win32-x64-msvc": "3.5.0"
|
|
98
98
|
}
|
|
99
99
|
}
|
package/schema.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
6
|
"$schema": {
|
|
7
|
-
"description": "A string pointing at fallow's JSON Schema URL, used only by editors for autocomplete and validation of the config file; it has no effect on analysis and is stripped before serialization (serde skip_serializing, writeOnly in the schema). Set it to `https://
|
|
7
|
+
"description": "A string pointing at fallow's JSON Schema URL, used only by editors for autocomplete and validation of the config file; it has no effect on analysis and is stripped before serialization (serde skip_serializing, writeOnly in the schema). Set it to `./node_modules/fallow/schema.json` for npm installs (version-aligned, offline, avoids VS Code's untrusted-remote-schema prompt), or `https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json` for non-npm installs; any other value is ignored by fallow.",
|
|
8
8
|
"type": [
|
|
9
9
|
"string",
|
|
10
10
|
"null"
|
package/scripts/lazy-verify.js
CHANGED
|
@@ -66,8 +66,11 @@ function emitVerifyLog(env, payload) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function binaryTargetsForPlatform(platform) {
|
|
69
|
+
// Platform packages ship one binary: the multicall `fallow`. The bundled
|
|
70
|
+
// lsp/mcp launchers spawn `fallow lsp-server` / `fallow mcp-server`, so there
|
|
71
|
+
// is only one binary to sentinel-track and verify.
|
|
69
72
|
const ext = platform === "win32" ? ".exe" : "";
|
|
70
|
-
return [`fallow${ext}
|
|
73
|
+
return [`fallow${ext}`];
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function statMtimeMs(absPath) {
|
|
@@ -28,7 +28,8 @@ function ext() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function binaryNames() {
|
|
31
|
-
|
|
31
|
+
// Platform packages ship a single multicall `fallow` binary.
|
|
32
|
+
return [`fallow${ext()}`];
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function computeDigestsForDir(dir) {
|
|
@@ -116,7 +117,7 @@ test("ensureVerified verifies on cache miss and writes the sentinel", (t) => {
|
|
|
116
117
|
assert.equal(sentinel.schemaVersion, SENTINEL_SCHEMA_VERSION);
|
|
117
118
|
assert.equal(sentinel.packageVersion, "2.81.0");
|
|
118
119
|
assert.equal(sentinel.packageName, "@fallow-cli/test-platform");
|
|
119
|
-
assert.equal(Object.keys(sentinel.binaries).length,
|
|
120
|
+
assert.equal(Object.keys(sentinel.binaries).length, 1);
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
test("ensureVerified returns cached:true on a valid sentinel", (t) => {
|
|
@@ -164,7 +165,7 @@ test("ensureVerified invalidates sentinel on mtime drift", (t) => {
|
|
|
164
165
|
);
|
|
165
166
|
assert.equal(result.ok, true);
|
|
166
167
|
assert.equal(result.cached, false);
|
|
167
|
-
assert.equal(verifyCallCount,
|
|
168
|
+
assert.equal(verifyCallCount, 1, "verify should rerun for the single binary");
|
|
168
169
|
});
|
|
169
170
|
|
|
170
171
|
test("ensureVerified invalidates sentinel on packageVersion drift", (t) => {
|
|
@@ -215,12 +216,6 @@ test("ensureVerified invalidates sentinel on packageName drift", (t) => {
|
|
|
215
216
|
packageName: "@fallow-cli/wrong-name",
|
|
216
217
|
binaries: {
|
|
217
218
|
[`fallow${ext()}`]: { mtimeMs: fs.statSync(path.join(dir, `fallow${ext()}`)).mtimeMs },
|
|
218
|
-
[`fallow-lsp${ext()}`]: {
|
|
219
|
-
mtimeMs: fs.statSync(path.join(dir, `fallow-lsp${ext()}`)).mtimeMs,
|
|
220
|
-
},
|
|
221
|
-
[`fallow-mcp${ext()}`]: {
|
|
222
|
-
mtimeMs: fs.statSync(path.join(dir, `fallow-mcp${ext()}`)).mtimeMs,
|
|
223
|
-
},
|
|
224
219
|
},
|
|
225
220
|
}),
|
|
226
221
|
);
|
|
@@ -266,13 +261,13 @@ test("ensureVerified invalidates sentinel on schemaVersion drift", (t) => {
|
|
|
266
261
|
test("ensureVerified returns sig-invalid on a tampered signature", (t) => {
|
|
267
262
|
_resetWarningState();
|
|
268
263
|
const { privateKey, rawPub } = makeKeypair();
|
|
269
|
-
const dir = mkPlatformDir(privateKey, { corruptSigFor: `fallow
|
|
264
|
+
const dir = mkPlatformDir(privateKey, { corruptSigFor: `fallow${ext()}` });
|
|
270
265
|
t.after(() => cleanup(dir));
|
|
271
266
|
|
|
272
267
|
const result = ensureVerified(baseInput(dir, (p) => _verifyWithKey(p, rawPub)));
|
|
273
268
|
assert.equal(result.ok, false);
|
|
274
269
|
assert.equal(result.code, "sig-invalid");
|
|
275
|
-
assert.match(result.binary, /fallow
|
|
270
|
+
assert.match(result.binary, /fallow/);
|
|
276
271
|
// Sentinel must NOT have been written on failure
|
|
277
272
|
assert.equal(fs.existsSync(path.join(dir, SENTINEL_FILENAME)), false);
|
|
278
273
|
});
|
package/scripts/run-binary.js
CHANGED
|
@@ -146,7 +146,15 @@ function guardBrokenStdout() {
|
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
// Run the resolved platform binary. `options.prependArgs` (default none) is a
|
|
150
|
+
// list of leading arguments inserted before the process argv, used by the
|
|
151
|
+
// `fallow-lsp` / `fallow-mcp` launcher shims to spawn the multicall binary as
|
|
152
|
+
// `fallow lsp-server` / `fallow mcp-server`. Because the platform packages ship
|
|
153
|
+
// a single `fallow` binary, those shims pass `binaryBaseName = "fallow"` and
|
|
154
|
+
// the subcommand via `prependArgs`; signal, exit-code, and version-line
|
|
155
|
+
// handling stay identical to a bare `fallow` invocation.
|
|
156
|
+
function runBinary(binaryBaseName, options = {}) {
|
|
157
|
+
const prependArgs = Array.isArray(options.prependArgs) ? options.prependArgs : [];
|
|
150
158
|
guardBrokenStdout();
|
|
151
159
|
const { pkg, manifestPath, platformPkgDir } = resolvePlatformPaths();
|
|
152
160
|
const resolvedVersion = readResolvedVersion(manifestPath);
|
|
@@ -166,7 +174,7 @@ function runBinary(binaryBaseName) {
|
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
try {
|
|
169
|
-
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
|
177
|
+
execFileSync(binaryPath, [...prependArgs, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
170
178
|
} catch (e) {
|
|
171
179
|
if (e.status === undefined) throw e;
|
|
172
180
|
if (e.status === null) {
|
package/scripts/verify-binary.js
CHANGED
|
@@ -293,11 +293,15 @@ function binaryTargetsForPlatform(platformId) {
|
|
|
293
293
|
// and tests can synthesize a Windows verify without running on Windows.
|
|
294
294
|
const isWindows = typeof platformId === "string" && platformId.startsWith("win32");
|
|
295
295
|
const ext = isWindows ? ".exe" : "";
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
296
|
+
// Platform packages ship ONE binary now: the multicall `fallow` (CLI + LSP +
|
|
297
|
+
// MCP in one engine). The bundled `fallow-lsp` / `fallow-mcp` launchers live
|
|
298
|
+
// in the wrapper and spawn `fallow lsp-server` / `fallow mcp-server`. The
|
|
299
|
+
// authoritative digest is the `fallowDigests.fallow` field the release
|
|
300
|
+
// pipeline embeds for every bundled package; the `asset` below is only the
|
|
301
|
+
// legacy GitHub-release fallback for pre-embedded-digest packages, which
|
|
302
|
+
// never shipped this bundled binary, so it is inert for bundled installs and
|
|
303
|
+
// fails closed (digest-mismatch) in the impossible case it is ever reached.
|
|
304
|
+
return [{ binary: `fallow${ext}`, asset: `fallow-${platformId}${ext}` }];
|
|
301
305
|
}
|
|
302
306
|
|
|
303
307
|
function isSkipRequested() {
|
|
@@ -243,7 +243,7 @@ function makePlatformDir(privateKey, options) {
|
|
|
243
243
|
const opts = options || {};
|
|
244
244
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-"));
|
|
245
245
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
246
|
-
for (const base of ["fallow"
|
|
246
|
+
for (const base of ["fallow"]) {
|
|
247
247
|
const binaryPath = path.join(dir, `${base}${ext}`);
|
|
248
248
|
const content = Buffer.from(`mock ${base} contents`);
|
|
249
249
|
fs.writeFileSync(binaryPath, content);
|
|
@@ -349,7 +349,7 @@ test("verifyInstalled resolves a global npm install from the fallow package dire
|
|
|
349
349
|
);
|
|
350
350
|
|
|
351
351
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
352
|
-
for (const base of ["fallow"
|
|
352
|
+
for (const base of ["fallow"]) {
|
|
353
353
|
const binaryPath = path.join(platformDir, `${base}${ext}`);
|
|
354
354
|
const content = Buffer.from(`global install ${base}`);
|
|
355
355
|
fs.writeFileSync(binaryPath, content);
|
|
@@ -367,9 +367,9 @@ test("verifyInstalled resolves a global npm install from the fallow package dire
|
|
|
367
367
|
assert.equal(result.version, "9.9.9");
|
|
368
368
|
});
|
|
369
369
|
|
|
370
|
-
test("verifyInstalled with dirOverride fails
|
|
370
|
+
test("verifyInstalled with dirOverride fails on a bad signature", async (t) => {
|
|
371
371
|
const { privateKey, rawPub } = makeKeypair();
|
|
372
|
-
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow
|
|
372
|
+
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow" });
|
|
373
373
|
t.after(() => cleanup(dir));
|
|
374
374
|
const result = await verifyInstalled({
|
|
375
375
|
dirOverride: dir,
|
|
@@ -378,12 +378,12 @@ test("verifyInstalled with dirOverride fails fast on the first bad signature", a
|
|
|
378
378
|
});
|
|
379
379
|
assert.equal(result.ok, false);
|
|
380
380
|
assert.equal(result.code, "sig-invalid");
|
|
381
|
-
assert.match(result.binary, /fallow
|
|
381
|
+
assert.match(result.binary, /fallow/);
|
|
382
382
|
});
|
|
383
383
|
|
|
384
384
|
test("verifyInstalled with dirOverride reports sig-missing when a .sig is absent", async (t) => {
|
|
385
385
|
const { privateKey, rawPub } = makeKeypair();
|
|
386
|
-
const dir = makePlatformDir(privateKey, { skipSigFor: "fallow
|
|
386
|
+
const dir = makePlatformDir(privateKey, { skipSigFor: "fallow" });
|
|
387
387
|
t.after(() => cleanup(dir));
|
|
388
388
|
const result = await verifyInstalled({
|
|
389
389
|
dirOverride: dir,
|
|
@@ -392,13 +392,13 @@ test("verifyInstalled with dirOverride reports sig-missing when a .sig is absent
|
|
|
392
392
|
});
|
|
393
393
|
assert.equal(result.ok, false);
|
|
394
394
|
assert.equal(result.code, "sig-missing");
|
|
395
|
-
assert.match(result.binary, /fallow
|
|
395
|
+
assert.match(result.binary, /fallow/);
|
|
396
396
|
});
|
|
397
397
|
|
|
398
398
|
test("verifyInstalled threads the resolved version into the sig-missing message", async (t) => {
|
|
399
399
|
const { privateKey, rawPub } = makeKeypair();
|
|
400
|
-
const preDir = makePlatformDir(privateKey, { skipSigFor: "fallow
|
|
401
|
-
const eraDir = makePlatformDir(privateKey, { skipSigFor: "fallow
|
|
400
|
+
const preDir = makePlatformDir(privateKey, { skipSigFor: "fallow" });
|
|
401
|
+
const eraDir = makePlatformDir(privateKey, { skipSigFor: "fallow" });
|
|
402
402
|
t.after(() => {
|
|
403
403
|
cleanup(preDir);
|
|
404
404
|
cleanup(eraDir);
|
|
@@ -463,7 +463,7 @@ test("verifyInstalled honors FALLOW_SKIP_BINARY_VERIFY", async (t) => {
|
|
|
463
463
|
function computeDigestsForDir(dir) {
|
|
464
464
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
465
465
|
const out = {};
|
|
466
|
-
for (const base of ["fallow"
|
|
466
|
+
for (const base of ["fallow"]) {
|
|
467
467
|
const fileName = `${base}${ext}`;
|
|
468
468
|
const full = path.join(dir, fileName);
|
|
469
469
|
out[fileName] =
|
|
@@ -549,7 +549,7 @@ test("verifyInstalled falls back to the provider when the embedded digest is mis
|
|
|
549
549
|
},
|
|
550
550
|
});
|
|
551
551
|
assert.equal(result.ok, true);
|
|
552
|
-
assert.equal(providerCalls,
|
|
552
|
+
assert.equal(providerCalls, 1);
|
|
553
553
|
});
|
|
554
554
|
|
|
555
555
|
test("verifyInstalled falls back to the provider when fallowDigests is partial / malformed", async (t) => {
|
|
@@ -573,8 +573,8 @@ test("verifyInstalled falls back to the provider when fallowDigests is partial /
|
|
|
573
573
|
},
|
|
574
574
|
});
|
|
575
575
|
assert.equal(result.ok, true);
|
|
576
|
-
// One call
|
|
577
|
-
assert.equal(providerCalls,
|
|
576
|
+
// One call for the single shipped binary because its entry fails to normalize.
|
|
577
|
+
assert.equal(providerCalls, 1);
|
|
578
578
|
});
|
|
579
579
|
|
|
580
580
|
test("verifyInstalled returns digest-mismatch when the embedded digest disagrees with the binary", async (t) => {
|
|
@@ -587,8 +587,6 @@ test("verifyInstalled returns digest-mismatch when the embedded digest disagrees
|
|
|
587
587
|
version: "1.0.0",
|
|
588
588
|
fallowDigests: {
|
|
589
589
|
[`fallow${ext}`]: "sha256:" + "a".repeat(64),
|
|
590
|
-
[`fallow-lsp${ext}`]: "sha256:" + "a".repeat(64),
|
|
591
|
-
[`fallow-mcp${ext}`]: "sha256:" + "a".repeat(64),
|
|
592
590
|
},
|
|
593
591
|
});
|
|
594
592
|
const result = await verifyInstalled({
|
|
@@ -644,9 +642,9 @@ test("verifyInstalledSync with embedded digests returns ok end-to-end", (t) => {
|
|
|
644
642
|
assert.equal(result.package, "<override>");
|
|
645
643
|
});
|
|
646
644
|
|
|
647
|
-
test("verifyInstalledSync fails
|
|
645
|
+
test("verifyInstalledSync fails on a bad signature", (t) => {
|
|
648
646
|
const { privateKey, rawPub } = makeKeypair();
|
|
649
|
-
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow
|
|
647
|
+
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow" });
|
|
650
648
|
t.after(() => cleanup(dir));
|
|
651
649
|
writeManifest(dir, {
|
|
652
650
|
name: "@fallow-cli/x",
|
|
@@ -659,7 +657,7 @@ test("verifyInstalledSync fails fast on first bad signature", (t) => {
|
|
|
659
657
|
});
|
|
660
658
|
assert.equal(result.ok, false);
|
|
661
659
|
assert.equal(result.code, "sig-invalid");
|
|
662
|
-
assert.match(result.binary, /fallow
|
|
660
|
+
assert.match(result.binary, /fallow/);
|
|
663
661
|
});
|
|
664
662
|
|
|
665
663
|
test("verifyInstalledSync reports digest-mismatch when bytes diverge from embedded digest", (t) => {
|
|
@@ -671,11 +669,7 @@ test("verifyInstalledSync reports digest-mismatch when bytes diverge from embedd
|
|
|
671
669
|
version: "9.9.9",
|
|
672
670
|
fallowDigests: {
|
|
673
671
|
fallow: "sha256:" + "a".repeat(64),
|
|
674
|
-
"fallow-lsp": "sha256:" + "a".repeat(64),
|
|
675
|
-
"fallow-mcp": "sha256:" + "a".repeat(64),
|
|
676
672
|
"fallow.exe": "sha256:" + "a".repeat(64),
|
|
677
|
-
"fallow-lsp.exe": "sha256:" + "a".repeat(64),
|
|
678
|
-
"fallow-mcp.exe": "sha256:" + "a".repeat(64),
|
|
679
673
|
},
|
|
680
674
|
});
|
|
681
675
|
const result = verifyInstalledSync({
|
package/skills/fallow/SKILL.md
CHANGED
|
@@ -106,11 +106,13 @@ Route by intent before reaching for the big analysis commands. Same matrix as `f
|
|
|
106
106
|
| `dupes` | Code duplication detection | `--mode`, `--threshold`, `--top`, `--changed-since`, `--workspace`, `--changed-workspaces`, `--skip-local`, `--cross-language`, `--ignore-imports`, `--no-ignore-imports`, `--explain-skipped`, `--fail-on-regression`, `--tolerance`, `--regression-baseline`, `--save-regression-baseline` |
|
|
107
107
|
| `health` | Function complexity analysis (also covers Angular templates as synthetic `<template>` findings: external `.html` files via `templateUrl` AND inline `@Component({ template: \`...\` })` literals; suppress external with `<!-- fallow-ignore-file complexity -->` at the top of the `.html` file, suppress inline with `// fallow-ignore-next-line complexity` directly above the `@Component` decorator) | `--complexity`, `--max-cyclomatic`, `--max-cognitive`, `--max-crap`, `--top`, `--sort`, `--file-scores`, `--hotspots`, `--ownership`, `--ownership-emails`, `--targets`, `--effort`, `--score`, `--min-score`, `--since`, `--min-commits`, `--save-snapshot`, `--trend`, `--coverage-gaps`, `--coverage`, `--coverage-root`, `--runtime-coverage`, `--min-invocations-hot`, `--min-observation-volume`, `--low-traffic-threshold`, `--css`, `--complexity-breakdown`, `--min-severity`, `--report-only`, `--workspace`, `--changed-workspaces`, `--baseline`, `--save-baseline` |
|
|
108
108
|
| `flags` | Detect feature flag patterns (env vars, SDK calls, config objects) | `--top` |
|
|
109
|
+
| `suppressions` | List active fallow-ignore suppression markers (read-only inventory) | `--file` |
|
|
109
110
|
| `explain` | Explain one issue type without running analysis | `<issue-type>`, `--format json` |
|
|
110
111
|
| `audit` | Combined dead-code + complexity + duplication + styling for changed files, returns a verdict; `fallow review` is an alias for `fallow audit --brief` (advisory orientation brief, always exits 0) | `--base`, `--gate`, `--brief`, `--max-decisions`, `--walkthrough-guide`, `--walkthrough-file`, `--show-deprioritized`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--explain`, `--explain-skipped`, `--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`, `--max-crap`, `--coverage`, `--coverage-root`, `--no-css`, `--css-deep`, `--no-css-deep`, `--include-entry-exports` |
|
|
111
112
|
| `decision-surface` | Surface the consequential structural DECISIONS a change embeds (the apex of the review brief), each framed as a judgment question with the routed expert to ask | `--max-decisions` |
|
|
112
113
|
| `impact` | Show what fallow has done for you: how many issues it is surfacing, the trend since the last recorded run, and how many commits it contained at the pre-commit gate | `--all`, `--sort`, `--limit` |
|
|
113
114
|
| `security` | Surface opt-in local security candidates for agent verification (not confirmed vulnerabilities). Rule families include the graph rule `client-server-leak`, a data-driven `tainted-sink` catalogue, and the include-required `hardcoded-secret` category for provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers. Most catalogue rows require non-literal input; narrowly literal-aware rows flag deterministic unsafe literals. Rules default off; suppress a file with `// fallow-ignore-file security-sink`; scope categories with `security.categories`. Add project-local request object names with `security.requestReceivers`; it extends the built-in `req` / `request` / `ctx` / `context` / `event` allowlist for HTTP `query`, `params`, and `body` reads. `hardcoded-secret` runs only when listed in `security.categories.include`. | `--format human\|json\|sarif`, `--changed-since`, `--file`, `--diff-file`, `--workspace`, `--changed-workspaces`, `--surface`, `--ci`, `--fail-on-issues`, `--sarif-file`, `--summary` |
|
|
115
|
+
| `report` | Render a saved `--format json` results file in another format without re-running analysis (analyze once, render annotations and the job summary from the same file). | `--from` |
|
|
114
116
|
| `schema` | Dump CLI definition as JSON | |
|
|
115
117
|
| `ci-template` | Print or vendor CI integration templates | |
|
|
116
118
|
| `migrate` | Convert knip/jscpd config | `--dry-run`, `--from PATH` |
|
|
@@ -509,7 +509,7 @@ fallow health --format json --quiet --trend
|
|
|
509
509
|
{
|
|
510
510
|
"kind": "health",
|
|
511
511
|
"schema_version": 7,
|
|
512
|
-
"version": "3.
|
|
512
|
+
"version": "3.5.0",
|
|
513
513
|
"elapsed_ms": 32,
|
|
514
514
|
"summary": {
|
|
515
515
|
"files_analyzed": 482,
|
|
@@ -907,7 +907,7 @@ fallow audit \
|
|
|
907
907
|
{
|
|
908
908
|
"kind": "audit",
|
|
909
909
|
"schema_version": 7,
|
|
910
|
-
"version": "3.
|
|
910
|
+
"version": "3.5.0",
|
|
911
911
|
"command": "audit",
|
|
912
912
|
"verdict": "fail",
|
|
913
913
|
"changed_files_count": 12,
|
|
@@ -982,7 +982,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
982
982
|
```json
|
|
983
983
|
{
|
|
984
984
|
"schema_version": 7,
|
|
985
|
-
"version": "3.
|
|
985
|
+
"version": "3.5.0",
|
|
986
986
|
"elapsed_ms": 116,
|
|
987
987
|
"feature_flags": [],
|
|
988
988
|
"total_flags": 0
|
|
@@ -1083,7 +1083,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1083
1083
|
{
|
|
1084
1084
|
"kind": "security",
|
|
1085
1085
|
"schema_version": "4",
|
|
1086
|
-
"version": "3.
|
|
1086
|
+
"version": "3.5.0",
|
|
1087
1087
|
"elapsed_ms": 42,
|
|
1088
1088
|
"config": {
|
|
1089
1089
|
"rules": {
|
|
@@ -1112,7 +1112,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1112
1112
|
{
|
|
1113
1113
|
"kind": "security",
|
|
1114
1114
|
"schema_version": "4",
|
|
1115
|
-
"version": "3.
|
|
1115
|
+
"version": "3.5.0",
|
|
1116
1116
|
"elapsed_ms": 42,
|
|
1117
1117
|
"config": {
|
|
1118
1118
|
"rules": {
|
|
@@ -1185,6 +1185,7 @@ Compose one evidence bundle before editing a file or exported symbol. This is th
|
|
|
1185
1185
|
```bash
|
|
1186
1186
|
fallow inspect --file src/api.ts --format json --quiet
|
|
1187
1187
|
fallow inspect --symbol src/api.ts:fetchUser --format json --quiet
|
|
1188
|
+
fallow inspect --file src/api.ts --churn --format json --quiet
|
|
1188
1189
|
```
|
|
1189
1190
|
|
|
1190
1191
|
### Target Flags
|
|
@@ -1193,6 +1194,7 @@ fallow inspect --symbol src/api.ts:fetchUser --format json --quiet
|
|
|
1193
1194
|
|------|-------------|
|
|
1194
1195
|
| `--file <PATH>` | Inspect one project-relative file |
|
|
1195
1196
|
| `--symbol <FILE:EXPORT>` | Inspect one exported symbol. Supporting dead-code, duplication, complexity, and security evidence is file-scoped in the first version |
|
|
1197
|
+
| `--churn` | Add target-level git churn evidence. Off by default; missing git history is reported as `unavailable` |
|
|
1196
1198
|
|
|
1197
1199
|
Common global flags: `--format`, `--quiet`, `--root`, `--config`, `--workspace`, `--production`, `--no-cache`, `--threads`.
|
|
1198
1200
|
|
|
@@ -1215,13 +1217,14 @@ Common global flags: `--format`, `--quiet`, `--root`, `--config`, `--workspace`,
|
|
|
1215
1217
|
"dead_code": { "status": "ok", "scope": "file", "data": {} },
|
|
1216
1218
|
"duplication": { "status": "ok", "scope": "project_filtered_to_file", "data": {} },
|
|
1217
1219
|
"complexity": { "status": "ok", "scope": "project_filtered_to_file", "data": {} },
|
|
1218
|
-
"security": { "status": "ok", "scope": "file", "data": {} }
|
|
1220
|
+
"security": { "status": "ok", "scope": "file", "data": {} },
|
|
1221
|
+
"churn": { "status": "ok", "scope": "project_filtered_to_file", "data": {} }
|
|
1219
1222
|
},
|
|
1220
1223
|
"warnings": []
|
|
1221
1224
|
}
|
|
1222
1225
|
```
|
|
1223
1226
|
|
|
1224
|
-
Each evidence section carries `status` and `scope`. Non-fatal
|
|
1227
|
+
Each evidence section carries `status` and `scope`. The optional churn section can report `ok`, `unavailable`, or `error`. Non-fatal analysis failures become section-level errors and warnings, so callers can still use the remaining evidence.
|
|
1225
1228
|
|
|
1226
1229
|
---
|
|
1227
1230
|
|
|
@@ -1632,7 +1635,8 @@ Available on all commands:
|
|
|
1632
1635
|
|---|---|---|---|
|
|
1633
1636
|
| `-r, --root` | `string` | - | Project root directory |
|
|
1634
1637
|
| `-c, --config` | `string` | - | Config file path |
|
|
1635
|
-
|
|
|
1638
|
+
| `--allow-remote-extends` | `bool` | `false` | Allow trusted config files to extend HTTPS URLs |
|
|
1639
|
+
| `-f, --format` | `human\|json\|sarif\|compact\|markdown\|codeclimate\|pr-comment-github\|pr-comment-gitlab\|review-github\|review-gitlab\|badge\|github-annotations\|github-summary` | `human` | Output format (alias: --output) |
|
|
1636
1640
|
| `-q, --quiet` | `bool` | `false` | Suppress progress output |
|
|
1637
1641
|
| `--no-cache` | `bool` | `false` | Disable incremental caching |
|
|
1638
1642
|
| `--threads` | `string` | - | Number of parser threads |
|
|
@@ -1660,6 +1664,7 @@ Available on all commands:
|
|
|
1660
1664
|
| `--fail-on-issues` | `bool` | `false` | Exit 1 if any issues found (promotes `warn` to `error`) |
|
|
1661
1665
|
| `--sarif-file` | `string` | - | Write SARIF output to a file instead of stdout |
|
|
1662
1666
|
| `-o, --output-file` | `string` | - | Write the report to a file instead of stdout, for any --format (no ANSI codes). Useful on large projects where the terminal scrollback truncates the top. Progress and the confirmation stay on stderr |
|
|
1667
|
+
| `--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` |
|
|
1663
1668
|
| `--fail-on-regression` | `bool` | `false` | Fail if issue count increased beyond tolerance vs a regression baseline |
|
|
1664
1669
|
| `--tolerance` | `string` | `0` | Allowed increase: `"2%"` (percentage) or `"5"` (absolute). Default: `"0"` |
|
|
1665
1670
|
| `--regression-baseline` | `string` | - | Path to regression baseline file (default: `.fallow/regression-baseline.json`) |
|
|
@@ -1796,7 +1801,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1796
1801
|
|
|
1797
1802
|
## CI Integration
|
|
1798
1803
|
|
|
1799
|
-
- **GitHub Actions**: `uses: fallow-rs/fallow@
|
|
1804
|
+
- **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
|
|
1800
1805
|
- **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
|
|
1801
1806
|
- **Any CI**: `npx fallow --ci` - equivalent to `--format sarif --fail-on-issues --quiet`
|
|
1802
1807
|
|
|
@@ -1831,7 +1836,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1831
1836
|
{
|
|
1832
1837
|
"kind": "dead-code",
|
|
1833
1838
|
"schema_version": 7,
|
|
1834
|
-
"version": "3.
|
|
1839
|
+
"version": "3.5.0",
|
|
1835
1840
|
"elapsed_ms": 45,
|
|
1836
1841
|
"total_issues": 12,
|
|
1837
1842
|
"entry_points": {
|
|
@@ -1991,7 +1996,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1991
1996
|
{
|
|
1992
1997
|
"kind": "dupes",
|
|
1993
1998
|
"schema_version": 7,
|
|
1994
|
-
"version": "3.
|
|
1999
|
+
"version": "3.5.0",
|
|
1995
2000
|
"elapsed_ms": 82,
|
|
1996
2001
|
"total_clones": 15,
|
|
1997
2002
|
"total_lines_duplicated": 230,
|
|
@@ -2035,11 +2040,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
2035
2040
|
{
|
|
2036
2041
|
"kind": "combined",
|
|
2037
2042
|
"schema_version": 7,
|
|
2038
|
-
"version": "3.
|
|
2043
|
+
"version": "3.5.0",
|
|
2039
2044
|
"elapsed_ms": 159,
|
|
2040
2045
|
"check": {
|
|
2041
2046
|
"schema_version": 7,
|
|
2042
|
-
"version": "3.
|
|
2047
|
+
"version": "3.5.0",
|
|
2043
2048
|
"elapsed_ms": 45,
|
|
2044
2049
|
"total_issues": 12,
|
|
2045
2050
|
"unused_files": [],
|