fallow 3.4.2 → 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 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 plus the companion `fallow-lsp` and `fallow-mcp` binaries in your project.
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,
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-lsp');
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-mcp');
7
+ runBinary('fallow', { prependArgs: ['mcp-server'] });
package/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.4.2",
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": [
@@ -854,6 +854,16 @@
854
854
  "true",
855
855
  "false"
856
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
+ ]
857
867
  }
858
868
  ]
859
869
  },
@@ -6380,7 +6390,8 @@
6380
6390
  "cli_command": "fallow inspect --format json --quiet",
6381
6391
  "key_params": [
6382
6392
  "target",
6383
- "production"
6393
+ "production",
6394
+ "include_churn"
6384
6395
  ],
6385
6396
  "license": "free",
6386
6397
  "license_note": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.4.2",
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": {
@@ -87,13 +87,13 @@
87
87
  "@tanstack/intent": "0.3.2"
88
88
  },
89
89
  "optionalDependencies": {
90
- "@fallow-cli/darwin-arm64": "3.4.2",
91
- "@fallow-cli/darwin-x64": "3.4.2",
92
- "@fallow-cli/linux-x64-gnu": "3.4.2",
93
- "@fallow-cli/linux-arm64-gnu": "3.4.2",
94
- "@fallow-cli/linux-x64-musl": "3.4.2",
95
- "@fallow-cli/linux-arm64-musl": "3.4.2",
96
- "@fallow-cli/win32-arm64-msvc": "3.4.2",
97
- "@fallow-cli/win32-x64-msvc": "3.4.2"
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
  }
@@ -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}`, `fallow-lsp${ext}`, `fallow-mcp${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
- return [`fallow${ext()}`, `fallow-lsp${ext()}`, `fallow-mcp${ext()}`];
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, 3);
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, 3, "verify should rerun for all three binaries");
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-lsp${ext()}` });
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-lsp/);
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
  });
@@ -146,7 +146,15 @@ function guardBrokenStdout() {
146
146
  });
147
147
  }
148
148
 
149
- function runBinary(binaryBaseName) {
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) {
@@ -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
- return [
297
- { binary: `fallow${ext}`, asset: `fallow-${platformId}${ext}` },
298
- { binary: `fallow-lsp${ext}`, asset: `fallow-lsp-${platformId}${ext}` },
299
- { binary: `fallow-mcp${ext}`, asset: `fallow-mcp-${platformId}${ext}` },
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", "fallow-lsp", "fallow-mcp"]) {
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", "fallow-lsp", "fallow-mcp"]) {
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 fast on the first bad signature", async (t) => {
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-lsp" });
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-lsp/);
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-mcp" });
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-mcp/);
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-mcp" });
401
- const eraDir = makePlatformDir(privateKey, { skipSigFor: "fallow-mcp" });
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", "fallow-lsp", "fallow-mcp"]) {
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, 3);
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 per binary because all three entries fail to normalize.
577
- assert.equal(providerCalls, 3);
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 fast on first bad signature", (t) => {
645
+ test("verifyInstalledSync fails on a bad signature", (t) => {
648
646
  const { privateKey, rawPub } = makeKeypair();
649
- const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow-lsp" });
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-lsp/);
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({
@@ -509,7 +509,7 @@ fallow health --format json --quiet --trend
509
509
  {
510
510
  "kind": "health",
511
511
  "schema_version": 7,
512
- "version": "3.4.2",
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.4.2",
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.4.2",
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.4.2",
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.4.2",
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 child-analysis failures become section-level errors and warnings, so callers can still use the remaining evidence.
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
 
@@ -1798,7 +1801,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1798
1801
 
1799
1802
  ## CI Integration
1800
1803
 
1801
- - **GitHub Actions**: `uses: fallow-rs/fallow@v2` - 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
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
1802
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
1803
1806
  - **Any CI**: `npx fallow --ci` - equivalent to `--format sarif --fail-on-issues --quiet`
1804
1807
 
@@ -1833,7 +1836,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1833
1836
  {
1834
1837
  "kind": "dead-code",
1835
1838
  "schema_version": 7,
1836
- "version": "3.4.2",
1839
+ "version": "3.5.0",
1837
1840
  "elapsed_ms": 45,
1838
1841
  "total_issues": 12,
1839
1842
  "entry_points": {
@@ -1993,7 +1996,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
1993
1996
  {
1994
1997
  "kind": "dupes",
1995
1998
  "schema_version": 7,
1996
- "version": "3.4.2",
1999
+ "version": "3.5.0",
1997
2000
  "elapsed_ms": 82,
1998
2001
  "total_clones": 15,
1999
2002
  "total_lines_duplicated": 230,
@@ -2037,11 +2040,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2037
2040
  {
2038
2041
  "kind": "combined",
2039
2042
  "schema_version": 7,
2040
- "version": "3.4.2",
2043
+ "version": "3.5.0",
2041
2044
  "elapsed_ms": 159,
2042
2045
  "check": {
2043
2046
  "schema_version": 7,
2044
- "version": "3.4.2",
2047
+ "version": "3.5.0",
2045
2048
  "elapsed_ms": 45,
2046
2049
  "total_issues": 12,
2047
2050
  "unused_files": [],
@@ -15,7 +15,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
15
15
  | `analyze` | analysis | free | `fallow dead-code --format json --quiet` | `issue_types`, `production`, `workspace`, `baseline`, `group_by`, `file` | Full dead code analysis (unused files/exports/types/dependencies/members + circular dependencies + re-export cycles (barrel files that form a structural loop, silently breaking re-exports) + 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 |
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
- | `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Supports `root`, `config`, `production`, `workspace`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
18
+ | `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production`, `include_churn` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Set `include_churn: true` to add target-level git churn from the health hotspot subsystem. Churn is off by default, and unavailable git history or analysis failures remain explicit section states. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Supports `root`, `config`, `production`, `workspace`, `include_churn`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
19
19
  | `guard` | introspection | free | `fallow guard <file> --format json --quiet` | `files` | Report the architecture rules that apply to given files before editing them: boundary zone, allowed import zones, forbidden calls, and rule-pack policies |
20
20
  | `find_dupes` | analysis | free | `fallow dupes --format json --quiet` | `mode`, `min_tokens`, `min_occurrences`, `top`, `threshold` | Code duplication detection. Set `changed_since` to scope to changed files since a git ref. Set `min_occurrences` (≥ 2, default 2) to hide pair-only clones and focus on widespread copy-paste; JSON gains `stats.clone_groups_below_min_occurrences` when the filter hides anything. Each `clone_groups[]` entry carries a stable `fingerprint`, usually `dup:<8hex>` and widened only on rare report collisions; pass it to `trace_clone` to deep-dive that group |
21
21
  | `check_health` | analysis | free | `fallow health --format json --quiet` | `score`, `css`, `file_scores`, `hotspots`, `targets`, `coverage`, `runtime_coverage`, `max_crap`, `group_by` | Complexity metrics, health scores, hotspots, and refactoring targets. Set `complexity_breakdown: true` to add a per-decision-point `contributions[]` array to each complexity finding (each `else-if`, nested `if`, boolean operator, loop, `case`, etc. with its source line and cyclomatic/cognitive weight) so you can explain WHY a function scored high and pinpoint refactor targets. Optional `runtime_coverage` merges a V8 or Istanbul dump; tune it with `min_invocations_hot` (default 100), `min_observation_volume` (default 5000), and `low_traffic_threshold` (default 0.001). When runtime evidence combines with static usage, test coverage, CRAP/complexity, ownership, or change scope, read `coverage_intelligence` for stable `fallow:coverage-intel:<hash>` recommendations. Set `group_by` to `owner`, `directory`, `package`, or `section` for per-group `vital_signs` / `health_score`; SARIF results gain `properties.group`, CodeClimate issues gain a top-level `group` field |
@@ -111,7 +111,7 @@ Parse the JSON to list specific files and exports that became unused.
111
111
  ### GitHub Actions: Using the Official Action
112
112
 
113
113
  ```yaml
114
- - uses: fallow-rs/fallow@v2
114
+ - uses: fallow-rs/fallow@v3
115
115
  with:
116
116
  command: dead-code
117
117
  fail-on-issues: true
@@ -123,7 +123,7 @@ Parse the JSON to list specific files and exports that became unused.
123
123
  Fail a PR only when it introduces new security candidates (or makes existing ones newly reachable). Gated failures exit with code 8; the `issues` output counts only matching gate candidates. PR comment and review renderers skip security envelopes.
124
124
 
125
125
  ```yaml
126
- - uses: fallow-rs/fallow@v2
126
+ - uses: fallow-rs/fallow@v3
127
127
  with:
128
128
  command: security
129
129
  security-gate: new # or newly-reachable (needs a base ref via changed-since or PR auto-scoping)
@@ -134,7 +134,7 @@ GitLab equivalent: `FALLOW_COMMAND: "security"` with `FALLOW_SECURITY_GATE: "new
134
134
  ### GitHub Actions: With Health Score
135
135
 
136
136
  ```yaml
137
- - uses: fallow-rs/fallow@v2
137
+ - uses: fallow-rs/fallow@v3
138
138
  with:
139
139
  score: true
140
140
  changed-since: main
@@ -145,7 +145,7 @@ Computes a health score (0-100 with letter grade) in combined mode and enables t
145
145
  ### GitHub Actions: Severity-Aware PR Quality Gate (Audit)
146
146
 
147
147
  ```yaml
148
- - uses: fallow-rs/fallow@v2
148
+ - uses: fallow-rs/fallow@v3
149
149
  with:
150
150
  command: audit
151
151
  gate: new-only # default; fails only on findings introduced by this PR
@@ -157,7 +157,7 @@ Runs `fallow audit` to combine dead-code + complexity + duplication scoped to ch
157
157
  The action exposes `outputs.verdict` (`pass`/`warn`/`fail`) and `outputs.gate` for downstream conditionals; `outputs.issues` holds the introduced count under `gate: new-only` and the total count under `gate: all`.
158
158
 
159
159
  ```yaml
160
- - uses: fallow-rs/fallow@v2
160
+ - uses: fallow-rs/fallow@v3
161
161
  id: fallow
162
162
  with:
163
163
  command: audit
@@ -174,7 +174,7 @@ Three additional outputs surface silent failures in the action's PR comment / re
174
174
  The official action supports inline PR annotations via GitHub workflow commands. This does not require Advanced Security (unlike SARIF upload) and works on any GitHub plan.
175
175
 
176
176
  ```yaml
177
- - uses: fallow-rs/fallow@v2
177
+ - uses: fallow-rs/fallow@v3
178
178
  with:
179
179
  command: dead-code
180
180
  changed-since: main
@@ -24,9 +24,9 @@
24
24
 
25
25
 
26
26
  /**
27
- * Schemas for the JSON output of fallow commands. Object-shaped envelopes covered by the `FallowOutput` contract carry a top-level `kind` discriminator. Current kind values: `audit`, `explain`, `inspect_target`, `trace`, `review-envelope`, `review-reconcile`, `coverage-setup`, `coverage-analyze`, `list-boundaries`, `list-workspaces`, `health`, `dupes`, `dead-code-grouped`, `impact`, `impact-cross-repo`, `security`, `security-survivors`, `security-blind-spots`, `dead-code`, `combined`, `feature-flags`, `audit-brief`, `decision-surface`, `review-walkthrough-guide`, `review-walkthrough-validation`, `suppression-inventory`. Consumers should branch on `kind` instead of probing for unique field presence. `CodeClimateOutput` is a bare JSON array (per the Code Climate / GitLab Code Quality spec) and stays a sibling root branch discriminated by checking whether the document root is an array.
27
+ * Schemas for the JSON output of fallow commands. Object-shaped envelopes covered by the `FallowOutput` contract carry a top-level `kind` discriminator. Current kind values: `audit`, `explain`, `inspect_target`, `trace`, `review-envelope`, `review-reconcile`, `coverage-setup`, `coverage-analyze`, `list-boundaries`, `list-workspaces`, `health`, `dupes`, `dead-code-grouped`, `impact`, `impact-cross-repo`, `security`, `security-survivors`, `security-blind-spots`, `dead-code`, `combined`, `feature-flags`, `audit-brief`, `decision-surface`, `review-walkthrough-guide`, `review-walkthrough-validation`, `suppression-inventory`. Consumers should branch on `kind` instead of probing for unique field presence. `CodeClimateOutput` is a bare JSON array (per the Code Climate / GitLab Code Quality spec) and stays a sibling root branch discriminated by checking whether the document root is an array. `ErrorOutput` is the `--format json` failure document, emitted on stdout with a non-zero exit; it carries no `kind` and is discriminated by the `error: true` field.
28
28
  */
29
- export type FallowJsonOutput = (FallowOutput | CodeClimateOutput)
29
+ export type FallowJsonOutput = (FallowOutput | CodeClimateOutput | ErrorOutput)
30
30
  /**
31
31
  * Typed root of every fallow JSON envelope shape that serializes as a JSON
32
32
  * object and participates in the documented `FallowOutput` contract. The
@@ -86,7 +86,7 @@ kind: "dead-code"
86
86
  kind: "combined"
87
87
  }) | (FeatureFlagsOutput & {
88
88
  kind: "feature-flags"
89
- }) | (ReviewBriefOutput & {
89
+ }) | (ReviewBriefWireOutput & {
90
90
  kind: "audit-brief"
91
91
  }) | (DecisionSurfaceOutput & {
92
92
  kind: "decision-surface"
@@ -651,7 +651,7 @@ export_name: string
651
651
  type: "symbol"
652
652
  })
653
653
  export type InspectIdentity = (InspectFileIdentity | InspectSymbolIdentity)
654
- export type InspectSectionStatus = ("ok" | "error")
654
+ export type InspectSectionStatus = ("ok" | "unavailable" | "error")
655
655
  export type InspectEvidenceScope = ("symbol" | "file" | "project_filtered_to_file")
656
656
  /**
657
657
  * Best-effort classification of why a callee did not resolve to an edge.
@@ -835,6 +835,12 @@ export type FeatureFlagActionType = ("investigate-flag" | "suppress-line")
835
835
  * Serializes as the integer `REVIEW_BRIEF_SCHEMA_VERSION`.
836
836
  */
837
837
  export type ReviewBriefSchemaVersion = number
838
+ /**
839
+ * The exactly-three shippable decision categories (the SOLID-3). No cut category
840
+ * (abstraction / deletion / convention / irreversibility) is representable: this
841
+ * enum is the structural guarantee that confirmed-noise categories never ship.
842
+ */
843
+ export type DecisionCategory = ("coupling-boundary" | "public-api-contract" | "dependency")
838
844
  /**
839
845
  * Coarse risk classification for a changeset, a pure function of the change
840
846
  * size (file count plus, once threaded, net lines).
@@ -864,12 +870,6 @@ export type ConfidenceFlag = ("dynamic-dispatch" | "re-export-indirection")
864
870
  * The category of a single weakening signal.
865
871
  */
866
872
  export type WeakeningKind = ("test-weakened" | "threshold-lowered" | "suppression-added" | "security-check-removed")
867
- /**
868
- * The exactly-three shippable decision categories (the SOLID-3). No cut category
869
- * (abstraction / deletion / convention / irreversibility) is representable: this
870
- * enum is the structural guarantee that confirmed-noise categories never ship.
871
- */
872
- export type DecisionCategory = ("coupling-boundary" | "public-api-contract" | "dependency")
873
873
  /**
874
874
  * Independently-versioned wire-version newtype. Serializes as the integer
875
875
  * [`DECISION_SURFACE_SCHEMA_VERSION`].
@@ -7206,6 +7206,11 @@ duplication: InspectEvidenceSection
7206
7206
  complexity: InspectEvidenceSection
7207
7207
  security: InspectEvidenceSection
7208
7208
  impact_closure: InspectEvidenceSection
7209
+ /**
7210
+ * OPT-IN target-level git churn. Omitted unless historical evidence was
7211
+ * explicitly requested by the caller.
7212
+ */
7213
+ churn?: (InspectEvidenceSection | null)
7209
7214
  /**
7210
7215
  * OPT-IN symbol-level call chain. Present only when `--symbol-chain` was
7211
7216
  * requested AND the target is a SYMBOL (best-effort, syntactic, OFF the
@@ -9347,10 +9352,22 @@ dead_export_count: number
9347
9352
  dead_exports: string[]
9348
9353
  }
9349
9354
  /**
9350
- * `_meta.feature_flags` details emitted with `--explain`.
9355
+ * Optional `_meta` block for [`FeatureFlagsOutput`]. Both fields are optional
9356
+ * because the two contributors are independent: `feature_flags` details are
9357
+ * present only with `--explain`, and `telemetry` is injected post-pass by
9358
+ * [`attach_telemetry_meta`] whenever an analysis run id is available (which is
9359
+ * the default path). Mirrors `Meta` / `CombinedMeta`, which also model
9360
+ * `telemetry` as an optional, never-required property.
9351
9361
  */
9352
9362
  export interface FeatureFlagsMeta {
9353
- feature_flags: FeatureFlagsMetaDetails
9363
+ /**
9364
+ * Feature-flag detection explanations, emitted only with `--explain`.
9365
+ */
9366
+ feature_flags?: (FeatureFlagsMetaDetails | null)
9367
+ /**
9368
+ * Local telemetry correlation metadata for agent follow-up runs.
9369
+ */
9370
+ telemetry?: (TelemetryMeta | null)
9354
9371
  }
9355
9372
  /**
9356
9373
  * Feature flag explanatory metadata.
@@ -9378,21 +9395,48 @@ medium: string
9378
9395
  low: string
9379
9396
  }
9380
9397
  /**
9381
- * The full `fallow audit --brief --format json` envelope. Carries the
9382
- * informational verdict, the triage and graph-facts orientation stages, plus
9383
- * the reused "subtract" section (the same dead-code / duplication / complexity
9384
- * payload `fallow audit --format json` emits).
9398
+ * Complete `fallow audit --brief --format json` wire envelope.
9399
+ *
9400
+ * This is distinct from [`ReviewBriefOutput`], which is the reusable review
9401
+ * digest embedded in walkthrough output. The wire envelope also carries audit
9402
+ * metadata, optional telemetry, and the subtract-style analysis subreports.
9385
9403
  */
9386
- export interface ReviewBriefOutput {
9404
+ export interface ReviewBriefWireOutput {
9387
9405
  schema_version: ReviewBriefSchemaVersion
9388
- /**
9389
- * Fallow CLI version that produced this output.
9390
- */
9391
- version: string
9406
+ version: ToolVersion
9392
9407
  /**
9393
9408
  * Command discriminator singleton: always `"audit-brief"`.
9394
9409
  */
9395
9410
  command: string
9411
+ verdict: AuditVerdict
9412
+ /**
9413
+ * Number of changed files in the audit scope.
9414
+ */
9415
+ changed_files_count: number
9416
+ /**
9417
+ * Base ref used to determine the changeset.
9418
+ */
9419
+ base_ref: string
9420
+ /**
9421
+ * Human-readable description of the resolved base, when available.
9422
+ */
9423
+ base_description?: (string | null)
9424
+ /**
9425
+ * Head commit SHA, when available.
9426
+ */
9427
+ head_sha?: (string | null)
9428
+ elapsed_ms: ElapsedMs
9429
+ /**
9430
+ * Whether base-snapshot analysis was skipped for this run.
9431
+ */
9432
+ base_snapshot_skipped?: (boolean | null)
9433
+ summary: AuditSummary
9434
+ attribution: AuditAttribution
9435
+ /**
9436
+ * Optional metric definitions and local telemetry correlation metadata.
9437
+ */
9438
+ _meta?: (Meta | null)
9439
+ decisions: DecisionSurface
9396
9440
  triage: DiffTriage
9397
9441
  graph_facts: GraphFacts
9398
9442
  partition: PartitionFacts
@@ -9400,20 +9444,130 @@ impact_closure: ImpactClosureFacts
9400
9444
  focus: FocusMap
9401
9445
  deltas: ReviewDeltas
9402
9446
  /**
9403
- * 6.F, headline: reviewer-private weakening signals (tests
9404
- * removed/skipped, thresholds lowered, suppressions added, security steps
9405
- * removed). Advisory, never gates, never auto-posted.
9447
+ * Reviewer-private weakening signals.
9406
9448
  */
9407
9449
  weakening: WeakeningSignal[]
9408
9450
  routing: RoutingFacts
9409
- decisions: DecisionSurface
9451
+ /**
9452
+ * Dead-code findings scoped to the audit changeset.
9453
+ */
9454
+ dead_code?: (CheckOutput | null)
9455
+ /**
9456
+ * Duplication findings scoped to the audit changeset.
9457
+ */
9458
+ duplication?: (DupesReportPayload | null)
9459
+ /**
9460
+ * Complexity findings scoped to the audit changeset.
9461
+ */
9462
+ complexity?: (HealthReport | null)
9463
+ }
9464
+ /**
9465
+ * The ranked, capped decision surface plus the set of signal_ids the
9466
+ * deterministic layer emitted (the anti-hallucination allowlist).
9467
+ */
9468
+ export interface DecisionSurface {
9469
+ /**
9470
+ * Ranked decisions, highest consequence first.
9471
+ */
9472
+ decisions: Decision[]
9473
+ /**
9474
+ * Present when more than the cap were extracted.
9475
+ */
9476
+ truncated?: (TruncationNote | null)
9477
+ /**
9478
+ * Every signal_id the deterministic layer emitted, INCLUDING those whose
9479
+ * decision was collapsed below the cap or suppressed. The anti-hallucination
9480
+ * allowlist: an agent decision whose id is absent is rejected.
9481
+ */
9482
+ emitted_signal_ids: string[]
9483
+ }
9484
+ /**
9485
+ * One consequential structural decision, framed as a judgment question for a
9486
+ * human with taste, anchored to a fallow-emitted signal.
9487
+ */
9488
+ export interface Decision {
9489
+ /**
9490
+ * Deterministic anchor to the fallow-emitted candidate this decision frames.
9491
+ * `accept_signal_id` rejects any id not in the emitted set.
9492
+ */
9493
+ signal_id: string
9494
+ category: DecisionCategory
9495
+ /**
9496
+ * The decision framed as a judgment question for the human.
9497
+ */
9498
+ question: string
9499
+ /**
9500
+ * Root-relative file the decision is anchored at.
9501
+ */
9502
+ anchor_file: string
9503
+ /**
9504
+ * 1-based anchor line, when the underlying signal carries one (0 = file head).
9505
+ */
9506
+ anchor_line: number
9507
+ /**
9508
+ * The raw fallow-emitted candidate key the `signal_id` hashes.
9509
+ */
9510
+ signal_key: string
9511
+ /**
9512
+ * The `signal_id` this decision WOULD have had before any rename in this
9513
+ * change (the anchor file's pre-rename path). Present only when the anchor was
9514
+ * renamed. A review-memory layer carries a dismissal across a `git mv`: if
9515
+ * `previous_signal_id` was dismissed in an earlier PR, treat this decision as
9516
+ * dismissed too. Keeps `signal_id` itself exact + deterministic.
9517
+ */
9518
+ previous_signal_id?: (string | null)
9519
+ /**
9520
+ * Blast radius: count of modules affected beyond the diff by this decision.
9521
+ */
9522
+ blast: number
9523
+ /**
9524
+ * `blast * reversibility_weight`: the rank key (sorted descending).
9525
+ */
9526
+ consequence: number
9527
+ /**
9528
+ * The routed expert(s) to ask, from ownership routing. Empty when no
9529
+ * ownership signal is available for the anchor file.
9530
+ */
9531
+ expert: string[]
9532
+ /**
9533
+ * Whether the anchor file's only qualified owner is one person.
9534
+ */
9535
+ bus_factor_one?: boolean
9536
+ /**
9537
+ * Honest per-decision count: in-repo modules OUTSIDE the diff that already
9538
+ * depend on this decision's anchor. This is the DISPLAY number (taste
9539
+ * ownership: the human reads reversibility from the count itself), distinct
9540
+ * from `blast` (the project-wide proxy used only for ranking). Never a door
9541
+ * label. Internal-only by construction, so it cannot see a published library's
9542
+ * external consumers; the public-API trade-off clause names that risk in prose.
9543
+ */
9544
+ internal_consumer_count: number
9545
+ /**
9546
+ * The named structural sacrifice this change makes, stated as a fact, never a
9547
+ * recommendation (e.g. "Couples `app` to `infra`; 4 in-repo modules already
9548
+ * depend on this anchor."). A sibling fact to `question`; it never tells the
9549
+ * human what to choose.
9550
+ */
9551
+ tradeoff: string
9552
+ }
9553
+ /**
9554
+ * A note for decisions collapsed below the cap.
9555
+ */
9556
+ export interface TruncationNote {
9557
+ /**
9558
+ * How many decisions were collapsed below the cap.
9559
+ */
9560
+ collapsed: number
9561
+ /**
9562
+ * Human-readable collapse reason.
9563
+ */
9564
+ reason: string
9410
9565
  }
9411
9566
  /**
9412
9567
  * Stage 0 of the brief: triage facts derived purely from the diff size.
9413
9568
  *
9414
- * `hunks` and `net_lines` are `None` in v1: the file-level audit does not yet
9415
- * thread a `DiffIndex` (from `report/ci/diff_filter.rs`). They populate later,
9416
- * on `--diff-file` / `--diff-stdin`, without a schema bump.
9569
+ * `hunks` and `net_lines` are populated when the caller supplies parsed diff
9570
+ * evidence. They remain absent when no diff is available.
9417
9571
  */
9418
9572
  export interface DiffTriage {
9419
9573
  /**
@@ -9421,11 +9575,11 @@ export interface DiffTriage {
9421
9575
  */
9422
9576
  files: number
9423
9577
  /**
9424
- * Number of diff hunks. `None` in v1 (no diff index threaded yet).
9578
+ * Number of diff hunks, or `None` when no diff evidence was supplied.
9425
9579
  */
9426
9580
  hunks?: (number | null)
9427
9581
  /**
9428
- * Net added-minus-removed lines. `None` in v1 (no diff index threaded yet).
9582
+ * Net added-minus-removed lines, or `None` without diff evidence.
9429
9583
  */
9430
9584
  net_lines?: (number | null)
9431
9585
  risk_class: RiskClass
@@ -9437,18 +9591,20 @@ review_effort: ReviewEffort
9437
9591
  * `boundaries_touched` is derived from the run's boundary-violation zones;
9438
9592
  * `reachable_from` is populated by the impact closure (the affected-not-shown
9439
9593
  * set: modules the changed code is reachable from / affects, none in the diff).
9440
- * `exports_added` / `api_width_delta` stay honestly stubbed (`0`) until the
9441
- * export-surface delta lands. The fields are present and correctly typed so
9442
- * values fill in later without a schema bump.
9594
+ * `exports_added` and `api_width_delta` both report the exports-aware public API
9595
+ * widening count. Removed exports are not represented in this widening-only
9596
+ * signal.
9443
9597
  */
9444
9598
  export interface GraphFacts {
9445
9599
  /**
9446
- * Number of exports added by the changeset. Stubbed to `0` in v1.
9600
+ * Number of public API exports added by the changeset. Zero means the
9601
+ * changeset adds no public API exports.
9447
9602
  */
9448
9603
  exports_added: number
9449
9604
  /**
9450
- * Change in public API width (added minus removed exports). Stubbed to `0`
9451
- * in v1.
9605
+ * Widening-only public API delta, currently equal to `exports_added`.
9606
+ * Removed exports are not represented, so zero means no public API exports
9607
+ * were added.
9452
9608
  */
9453
9609
  api_width_delta: number
9454
9610
  /**
@@ -9696,108 +9852,6 @@ expert: string[]
9696
9852
  */
9697
9853
  bus_factor_one?: boolean
9698
9854
  }
9699
- /**
9700
- * The ranked, capped decision surface plus the set of signal_ids the
9701
- * deterministic layer emitted (the anti-hallucination allowlist).
9702
- */
9703
- export interface DecisionSurface {
9704
- /**
9705
- * Ranked decisions, highest consequence first.
9706
- */
9707
- decisions: Decision[]
9708
- /**
9709
- * Present when more than the cap were extracted.
9710
- */
9711
- truncated?: (TruncationNote | null)
9712
- /**
9713
- * Every signal_id the deterministic layer emitted, INCLUDING those whose
9714
- * decision was collapsed below the cap or suppressed. The anti-hallucination
9715
- * allowlist: an agent decision whose id is absent is rejected.
9716
- */
9717
- emitted_signal_ids: string[]
9718
- }
9719
- /**
9720
- * One consequential structural decision, framed as a judgment question for a
9721
- * human with taste, anchored to a fallow-emitted signal.
9722
- */
9723
- export interface Decision {
9724
- /**
9725
- * Deterministic anchor to the fallow-emitted candidate this decision frames.
9726
- * `accept_signal_id` rejects any id not in the emitted set.
9727
- */
9728
- signal_id: string
9729
- category: DecisionCategory
9730
- /**
9731
- * The decision framed as a judgment question for the human.
9732
- */
9733
- question: string
9734
- /**
9735
- * Root-relative file the decision is anchored at.
9736
- */
9737
- anchor_file: string
9738
- /**
9739
- * 1-based anchor line, when the underlying signal carries one (0 = file head).
9740
- */
9741
- anchor_line: number
9742
- /**
9743
- * The raw fallow-emitted candidate key the `signal_id` hashes.
9744
- */
9745
- signal_key: string
9746
- /**
9747
- * The `signal_id` this decision WOULD have had before any rename in this
9748
- * change (the anchor file's pre-rename path). Present only when the anchor was
9749
- * renamed. A review-memory layer carries a dismissal across a `git mv`: if
9750
- * `previous_signal_id` was dismissed in an earlier PR, treat this decision as
9751
- * dismissed too. Keeps `signal_id` itself exact + deterministic.
9752
- */
9753
- previous_signal_id?: (string | null)
9754
- /**
9755
- * Blast radius: count of modules affected beyond the diff by this decision.
9756
- */
9757
- blast: number
9758
- /**
9759
- * `blast * reversibility_weight`: the rank key (sorted descending).
9760
- */
9761
- consequence: number
9762
- /**
9763
- * The routed expert(s) to ask, from ownership routing. Empty when no
9764
- * ownership signal is available for the anchor file.
9765
- */
9766
- expert: string[]
9767
- /**
9768
- * Whether the anchor file's only qualified owner is one person.
9769
- */
9770
- bus_factor_one?: boolean
9771
- /**
9772
- * Honest per-decision count: in-repo modules OUTSIDE the diff that already
9773
- * depend on this decision's anchor. This is the DISPLAY number (taste
9774
- * ownership: the human reads reversibility from the count itself), distinct
9775
- * from `blast` (the project-wide proxy used only for ranking). Never a door
9776
- * label. Internal-only by construction, so it cannot see a published library's
9777
- * external consumers; the public-API trade-off clause names that risk in prose.
9778
- */
9779
- internal_consumer_count: number
9780
- /**
9781
- * The named structural sacrifice this change makes, stated as a fact, never a
9782
- * recommendation (e.g. "Couples `app` to `infra`; 4 in-repo modules already
9783
- * depend on this anchor."). A sibling fact to `question`; it never tells the
9784
- * human what to choose.
9785
- */
9786
- tradeoff: string
9787
- }
9788
- /**
9789
- * A note for decisions collapsed below the cap.
9790
- */
9791
- export interface TruncationNote {
9792
- /**
9793
- * How many decisions were collapsed below the cap.
9794
- */
9795
- collapsed: number
9796
- /**
9797
- * Human-readable collapse reason.
9798
- */
9799
- reason: string
9800
- }
9801
9855
  /**
9802
9856
  * The separable `decision-surface` envelope: the single call that puts taste-
9803
9857
  * decisions in front of a human, callable WITHOUT the full pipeline (the
@@ -9954,6 +10008,37 @@ agent_schema: AgentSchema
9954
10008
  */
9955
10009
  injection_note: string
9956
10010
  }
10011
+ /**
10012
+ * The full `fallow audit --brief --format json` envelope. Carries the
10013
+ * informational verdict, the triage and graph-facts orientation stages, plus
10014
+ * the reused "subtract" section (the same dead-code / duplication / complexity
10015
+ * payload `fallow audit --format json` emits).
10016
+ */
10017
+ export interface ReviewBriefOutput {
10018
+ schema_version: ReviewBriefSchemaVersion
10019
+ /**
10020
+ * Fallow CLI version that produced this output.
10021
+ */
10022
+ version: string
10023
+ /**
10024
+ * Command discriminator singleton: always `"audit-brief"`.
10025
+ */
10026
+ command: string
10027
+ triage: DiffTriage
10028
+ graph_facts: GraphFacts
10029
+ partition: PartitionFacts
10030
+ impact_closure: ImpactClosureFacts
10031
+ focus: FocusMap
10032
+ deltas: ReviewDeltas
10033
+ /**
10034
+ * 6.F, headline: reviewer-private weakening signals (tests
10035
+ * removed/skipped, thresholds lowered, suppressions added, security steps
10036
+ * removed). Advisory, never gates, never auto-posted.
10037
+ */
10038
+ weakening: WeakeningSignal[]
10039
+ routing: RoutingFacts
10040
+ decisions: DecisionSurface
10041
+ }
9957
10042
  /**
9958
10043
  * The review direction artifact: the order to review in, the coherent units,
9959
10044
  * and per-unit concern lens + out-of-diff + expert. A minimal projection of the
@@ -10301,6 +10386,29 @@ export interface CodeClimateLines {
10301
10386
  */
10302
10387
  begin: number
10303
10388
  }
10389
+ /**
10390
+ * Structured JSON error emitted on stdout when `--format json` is active and a
10391
+ * command fails. It carries no `kind` discriminator: it is distinguished from
10392
+ * the kind-tagged success envelopes by the required `error: true` field, and is
10393
+ * a document-root branch alongside `FallowOutput` and `CodeClimateOutput` in
10394
+ * `docs/output-schema.json`. Agents that pass `--format json` and observe a
10395
+ * non-zero exit code parse this shape from stdout.
10396
+ */
10397
+ export interface ErrorOutput {
10398
+ /**
10399
+ * Always `true`. The discriminator that separates an error document from a
10400
+ * success envelope (which instead carries a `kind`).
10401
+ */
10402
+ error: boolean
10403
+ /**
10404
+ * Human-readable error message.
10405
+ */
10406
+ message: string
10407
+ /**
10408
+ * The process exit code the CLI returns alongside this document.
10409
+ */
10410
+ exit_code: number
10411
+ }
10304
10412
 
10305
10413
  /**
10306
10414
  * Inner complexity-violation payload, flattened into `HealthFinding`