fallow 3.29.0 → 3.30.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/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.29.0",
3
+ "version": "3.30.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": [
@@ -1055,6 +1055,16 @@
1055
1055
  "required": false,
1056
1056
  "description": "Shortest import path between two modules, as two file paths (e.g. `--path src/app.ts src/db.ts`). Mutually exclusive with the symbol target and the call-chain flags"
1057
1057
  },
1058
+ {
1059
+ "name": "--eager-only",
1060
+ "type": "bool",
1061
+ "required": false,
1062
+ "description": "With `--path`, follow only static value imports, so the route explains why TO loads before FROM runs",
1063
+ "possible_values": [
1064
+ "true",
1065
+ "false"
1066
+ ]
1067
+ },
1058
1068
  {
1059
1069
  "name": "--callers",
1060
1070
  "type": "bool",
@@ -1264,7 +1274,7 @@
1264
1274
  },
1265
1275
  {
1266
1276
  "name": "list",
1267
- "description": "List discovered entry points, files, plugins, boundaries, and workspaces",
1277
+ "description": "List discovered entry points, files, plugins, boundaries, workspaces, and the startup import weight",
1268
1278
  "flags": [
1269
1279
  {
1270
1280
  "name": "--entry-points",
@@ -1316,6 +1326,16 @@
1316
1326
  "false"
1317
1327
  ]
1318
1328
  },
1329
+ {
1330
+ "name": "--entry-weight",
1331
+ "type": "bool",
1332
+ "required": false,
1333
+ "description": "Show the startup import weight of each runtime entry point, in source bytes (not bundle size)",
1334
+ "possible_values": [
1335
+ "true",
1336
+ "false"
1337
+ ]
1338
+ },
1319
1339
  {
1320
1340
  "name": "path",
1321
1341
  "type": "string",
@@ -1722,6 +1742,77 @@
1722
1742
  "type": "string",
1723
1743
  "required": false,
1724
1744
  "description": "Show only the top N flags"
1745
+ },
1746
+ {
1747
+ "name": "--retirement",
1748
+ "type": "bool",
1749
+ "required": false,
1750
+ "description": "Add a retirement report: one row per flag, with the reasons the flag can be retired. Advisory only; nothing is removed",
1751
+ "possible_values": [
1752
+ "true",
1753
+ "false"
1754
+ ]
1755
+ },
1756
+ {
1757
+ "name": "--reason",
1758
+ "type": "string",
1759
+ "required": false,
1760
+ "description": "Keep only retirement rows with this reason (repeatable)",
1761
+ "possible_values": [
1762
+ "single-read-site",
1763
+ "test-only",
1764
+ "literal-constant",
1765
+ "identical-branches",
1766
+ "empty-branch",
1767
+ "guards-dead-code",
1768
+ "defined-never-read",
1769
+ "fully-rolled-out",
1770
+ "archived-in-vendor",
1771
+ "missing-in-vendor",
1772
+ "vendor-only"
1773
+ ]
1774
+ },
1775
+ {
1776
+ "name": "--sort",
1777
+ "type": "string",
1778
+ "required": false,
1779
+ "description": "Order of the retirement rows",
1780
+ "default": "age",
1781
+ "possible_values": [
1782
+ "age",
1783
+ "sites",
1784
+ "name"
1785
+ ]
1786
+ },
1787
+ {
1788
+ "name": "--flag-age",
1789
+ "type": "string",
1790
+ "required": false,
1791
+ "description": "How to measure flag age: blame (lower bound), pickaxe (first commit with the name, slower) or off",
1792
+ "default": "blame",
1793
+ "possible_values": [
1794
+ "blame",
1795
+ "pickaxe",
1796
+ "off"
1797
+ ]
1798
+ },
1799
+ {
1800
+ "name": "--min-age",
1801
+ "type": "string",
1802
+ "required": false,
1803
+ "description": "Keep only retirement rows at least this many days old"
1804
+ },
1805
+ {
1806
+ "name": "--flag-state",
1807
+ "type": "string",
1808
+ "required": false,
1809
+ "description": "Vendor flag export (JSON, read offline) that adds the fully-rolled-out, archived-in-vendor, missing-in-vendor and vendor-only reasons"
1810
+ },
1811
+ {
1812
+ "name": "--max-flag-age",
1813
+ "type": "string",
1814
+ "required": false,
1815
+ "description": "Exit with code 1 when a flag in scope is older than this many days. Opt-in; needs a flag age"
1725
1816
  }
1726
1817
  ]
1727
1818
  },
@@ -4672,6 +4763,36 @@
4672
4763
  "license_note": null,
4673
4764
  "docs_url": "https://docs.fallow.tools/cli/flags"
4674
4765
  },
4766
+ {
4767
+ "id": "flag-retirement-candidate",
4768
+ "rule_id": "fallow/flag-retirement-candidate",
4769
+ "command": "flags",
4770
+ "category": "Flags",
4771
+ "description": "Feature flag is a retirement candidate",
4772
+ "label": null,
4773
+ "config_key": null,
4774
+ "registry_index": null,
4775
+ "aliases": [],
4776
+ "lsp": false,
4777
+ "filter_flag": null,
4778
+ "result_key": null,
4779
+ "summary_label": null,
4780
+ "summary_docs_anchor": null,
4781
+ "sarif_rule_ids": null,
4782
+ "codeclimate_check_names": null,
4783
+ "ts_alias": null,
4784
+ "counts_in_total": false,
4785
+ "fixable": false,
4786
+ "suppressible": false,
4787
+ "suppress_comment": null,
4788
+ "default_severity": null,
4789
+ "opt_in": null,
4790
+ "frameworks": [],
4791
+ "note": null,
4792
+ "license": "free",
4793
+ "license_note": null,
4794
+ "docs_url": "https://docs.fallow.tools/cli/flags#retirement-report"
4795
+ },
4675
4796
  {
4676
4797
  "id": "tainted-sink",
4677
4798
  "rule_id": "security/tainted-sink",
@@ -6201,8 +6322,10 @@
6201
6322
  "FALLOW_TIMEOUT_SECS": "MCP server: per-tool-call CLI subprocess timeout in seconds (default 120). Raise for long runs like production coverage on large dumps.",
6202
6323
  "FALLOW_DIFF_FILE": "MCP server: path to a unified diff that scopes all findings by changed line.",
6203
6324
  "FALLOW_CHANGED_SINCE": "MCP server: git ref that scopes file discovery for analysis tools.",
6325
+ "FALLOW_MCP_WARM_SESSION": "MCP server: set to 0, false, off or no to stop typed tool calls from keeping parsed modules in memory between calls (default on).",
6204
6326
  "FALLOW_INTEGRATION_SURFACE": "Telemetry integration_surface override for non-CLI surfaces (mcp/lsp/vscode/napi/programmatic). Set by the MCP server on the CLI it spawns.",
6205
- "FALLOW_MCP_TOOL": "Telemetry mcp_tool dimension, validated against the MCP tool-name allowlist. Set by the MCP server alongside FALLOW_INTEGRATION_SURFACE=mcp."
6327
+ "FALLOW_MCP_TOOL": "Telemetry mcp_tool dimension, validated against the MCP tool-name allowlist. Set by the MCP server alongside FALLOW_INTEGRATION_SURFACE=mcp.",
6328
+ "FALLOW_LSP_REUSE_SESSION": "Language server: set to 0/false/off/no to load a new project session on each analysis run. By default the server keeps one session per project root between saves and parses only the changed files."
6206
6329
  },
6207
6330
  "severity_levels": [
6208
6331
  "error",
@@ -7331,7 +7454,7 @@
7331
7454
  ]
7332
7455
  },
7333
7456
  "plugins": {
7334
- "count": 127,
7457
+ "count": 128,
7335
7458
  "note": "Built-in framework plugins, auto-activated when their enabler dependency is present; run fallow list --plugins for the set active in a specific project",
7336
7459
  "names": [
7337
7460
  "nextjs",
@@ -7345,6 +7468,7 @@
7345
7468
  "react-router",
7346
7469
  "redwoodsdk",
7347
7470
  "tanstack-router",
7471
+ "waku",
7348
7472
  "react-native",
7349
7473
  "expo",
7350
7474
  "expo-router",
@@ -2402,6 +2402,36 @@
2402
2402
  "license_note": null,
2403
2403
  "docs_url": "https://docs.fallow.tools/cli/security"
2404
2404
  },
2405
+ {
2406
+ "id": "flag-retirement-candidate",
2407
+ "rule_id": "fallow/flag-retirement-candidate",
2408
+ "command": "flags",
2409
+ "category": "Flags",
2410
+ "description": "Feature flag is a retirement candidate",
2411
+ "label": null,
2412
+ "config_key": null,
2413
+ "registry_index": null,
2414
+ "aliases": [],
2415
+ "lsp": false,
2416
+ "filter_flag": null,
2417
+ "result_key": null,
2418
+ "summary_label": null,
2419
+ "summary_docs_anchor": null,
2420
+ "sarif_rule_ids": null,
2421
+ "codeclimate_check_names": null,
2422
+ "ts_alias": null,
2423
+ "counts_in_total": false,
2424
+ "fixable": false,
2425
+ "suppressible": false,
2426
+ "suppress_comment": null,
2427
+ "default_severity": null,
2428
+ "opt_in": null,
2429
+ "frameworks": [],
2430
+ "note": null,
2431
+ "license": "free",
2432
+ "license_note": null,
2433
+ "docs_url": "https://docs.fallow.tools/cli/flags#retirement-report"
2434
+ },
2405
2435
  {
2406
2436
  "id": "hardcoded-secret",
2407
2437
  "rule_id": "security/hardcoded-secret",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.29.0",
3
+ "version": "3.30.0",
4
4
  "mcpName": "io.github.fallow-rs/fallow",
5
5
  "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.",
6
6
  "license": "MIT",
@@ -88,14 +88,14 @@
88
88
  "@tanstack/intent": "0.4.0"
89
89
  },
90
90
  "optionalDependencies": {
91
- "@fallow-cli/darwin-arm64": "3.29.0",
92
- "@fallow-cli/darwin-x64": "3.29.0",
93
- "@fallow-cli/linux-x64-gnu": "3.29.0",
94
- "@fallow-cli/linux-arm64-gnu": "3.29.0",
95
- "@fallow-cli/linux-x64-musl": "3.29.0",
96
- "@fallow-cli/linux-arm64-musl": "3.29.0",
97
- "@fallow-cli/win32-arm64-msvc": "3.29.0",
98
- "@fallow-cli/win32-x64-msvc": "3.29.0",
99
- "fallow-type-aware": "3.29.0"
91
+ "@fallow-cli/darwin-arm64": "3.30.0",
92
+ "@fallow-cli/darwin-x64": "3.30.0",
93
+ "@fallow-cli/linux-x64-gnu": "3.30.0",
94
+ "@fallow-cli/linux-arm64-gnu": "3.30.0",
95
+ "@fallow-cli/linux-x64-musl": "3.30.0",
96
+ "@fallow-cli/linux-arm64-musl": "3.30.0",
97
+ "@fallow-cli/win32-arm64-msvc": "3.30.0",
98
+ "@fallow-cli/win32-x64-msvc": "3.30.0",
99
+ "fallow-type-aware": "3.30.0"
100
100
  }
101
101
  }
package/schema.json CHANGED
@@ -265,7 +265,7 @@
265
265
  }
266
266
  },
267
267
  "flags": {
268
- "description": "Configures feature-flag detection: `sdkPatterns` (custom flag-evaluating call signatures, each `{ function, nameArg (zero-based arg index of the flag name, default 0), provider? }`, merged with built-ins for LaunchDarkly, Statsig, Unleash, GrowthBook, Split, PostHog, Vercel Flags, ConfigCat, Flagsmith, Optimizely, and Eppo), `envPrefixes` (env-var prefixes marking `process.env.*` accesses as flags, merged with built-ins), and `configObjectHeuristics` (default false; when true, property accesses on objects whose name contains `feature`/`flag`/`toggle` are reported as low-confidence flags). Set `sdkPatterns`/`envPrefixes` to teach fallow a proprietary flag SDK or naming convention, or enable `configObjectHeuristics` for projects that read flags off config objects (higher false-positive rate). Feature-flag findings surface only when the `feature-flags` rule is enabled (default `off`).",
268
+ "description": "Configures feature-flag detection: `sdkPatterns` (custom flag-evaluating call signatures, each `{ function, nameArg (zero-based arg index of the flag name, default 0), provider? }`, merged with built-ins for LaunchDarkly, Statsig, Unleash, GrowthBook, Split, PostHog, Vercel Flags, ConfigCat, Flagsmith, Optimizely, and Eppo), `envPrefixes` (env-var prefixes marking `process.env.*` and `import.meta.env.*` accesses as flags, merged with built-ins), and `configObjectHeuristics` (default false; when true, property accesses on objects whose name contains `feature`/`flag`/`toggle` are reported as low-confidence flags). Set `sdkPatterns`/`envPrefixes` to teach fallow a proprietary flag SDK or naming convention, or enable `configObjectHeuristics` for projects that read flags off config objects (higher false-positive rate). Feature-flag findings surface only when the `feature-flags` rule is enabled (default `off`).",
269
269
  "$ref": "#/$defs/FlagsConfig",
270
270
  "default": {
271
271
  "configObjectHeuristics": false
@@ -1824,7 +1824,7 @@
1824
1824
  }
1825
1825
  },
1826
1826
  "envPrefixes": {
1827
- "description": "Environment variable prefixes that indicate feature flags.\nMerged with built-in prefixes. Only `process.env.*` accesses matching\nthese prefixes are reported as feature flags.",
1827
+ "description": "Environment variable prefixes that indicate feature flags.\nMerged with built-in prefixes. Only `process.env.*` and\n`import.meta.env.*` accesses matching these prefixes are reported as\nfeature flags.",
1828
1828
  "type": "array",
1829
1829
  "items": {
1830
1830
  "type": "string"
@@ -1834,6 +1834,13 @@
1834
1834
  "description": "Enable config object heuristic detection.\nWhen true, property accesses on objects whose name contains \"feature\",\n\"flag\", or \"toggle\" are reported as low-confidence feature flags.\nDefault: false (opt-in due to higher false positive rate).",
1835
1835
  "type": "boolean",
1836
1836
  "default": false
1837
+ },
1838
+ "vendorKeyPrefix": {
1839
+ "description": "Prefix to remove from each key of a `--flag-state` vendor export\nbefore the key is compared with the flag names in the code. For\nexample, `\"web.\"` makes the vendor key `web.new-checkout` match the\ncode flag `new-checkout`. A key without the prefix is compared as it\nis.",
1840
+ "type": [
1841
+ "string",
1842
+ "null"
1843
+ ]
1837
1844
  }
1838
1845
  }
1839
1846
  },
@@ -192,9 +192,10 @@ Reports unused exports in entry files (package.json `main`/`exports`, framework
192
192
  ```bash
193
193
  fallow flags --format json --quiet
194
194
  fallow flags --format json --quiet --top 20
195
+ fallow flags --retirement --format json --quiet
195
196
  ```
196
197
 
197
- Reports environment-variable gates (`process.env.FEATURE_*`), SDK calls from common flag providers, and config-object patterns, with flag locations, detection confidence, and a cross-reference against dead code. Only `--top N` is command-specific.
198
+ Reports environment-variable gates (`process.env.FEATURE_*`), SDK calls from common flag providers, and config-object patterns, with flag locations, detection confidence, and a cross-reference against dead code. `--top N` limits the list. `--retirement` adds a `retirement` object with one row per flag, the reasons it can be retired (`single-read-site`, `test-only`, `literal-constant`, `identical-branches`, `empty-branch`, `guards-dead-code`, `defined-never-read`), and its age from git (`--flag-age blame|pickaxe|off`; blame gives a lower bound). Filter with `--reason <CODE>` and `--min-age <DAYS>`, order with `--sort age|sites|name`. `--flag-state <FILE>` reads an offline vendor export in one vendor-neutral schema and adds `fully-rolled-out`, `archived-in-vendor`, `missing-in-vendor` and `vendor-only`. With `--retirement`, `--save-regression-baseline <PATH>` and `--fail-on-regression --regression-baseline <PATH>` gate on `distinct_flags` (plus each `--reason` count), and the opt-in `--max-flag-age <DAYS>` fails on old flags. Every format works: compact prints `flag-retire:<reason>:<path>:<line>:<name>`, SARIF adds the rule `fallow/flag-retirement-candidate`, CodeClimate adds `fallow/flag-retirement`. The report is advisory: every action has `auto_fixable: false`, and a person decides what to remove.
198
199
 
199
200
  ### Surface security candidates for verification
200
201
  ```bash
@@ -260,7 +261,7 @@ fallow dead-code --format json --quiet --save-baseline .fallow/snapshot.json
260
261
  fallow dead-code --format json --quiet --baseline .fallow/snapshot.json
261
262
  ```
262
263
 
263
- `--save-regression-baseline` / `--regression-baseline` / `--fail-on-regression` / `--tolerance` are count-based gates for `dead-code` and bare combined mode. `--save-baseline` / `--baseline` are identity-based (track finding identity, fail on new). `audit` rejects the global baseline flags and uses `--dead-code-baseline` / `--health-baseline` / `--dupes-baseline` instead.
264
+ `--save-regression-baseline` / `--regression-baseline` / `--fail-on-regression` / `--tolerance` are count-based gates for `dead-code`, bare combined mode, and `flags --retirement` (a flags baseline needs a PATH; without `--retirement` the options have no effect on `flags` and it warns). `--save-baseline` / `--baseline` are identity-based (track finding identity, fail on new). `audit` rejects the global baseline flags and uses `--dead-code-baseline` / `--health-baseline` / `--dupes-baseline` instead.
264
265
 
265
266
  With no path, `--save-regression-baseline` updates `regression.baseline` in the discovered fallow config, or creates `.fallowrc.json` when none exists. Pass a path only when a standalone baseline file is preferred.
266
267
 
@@ -52,7 +52,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
52
52
  | `doctor` | Diagnose project readiness without analysis or mutation | |
53
53
  | `similar-code` | Find semantically similar functions with a pinned local model (opt-in) | `--threshold`, `--min-lines`, `--top`, `--file` |
54
54
  | `inspect` | Compose one evidence bundle for a file or exported symbol | `--file <path>`, `--symbol <file>:<export>` |
55
- | `trace` | Trace a symbol's call chain (best-effort, syntactic; OFF the ranked path) | `symbol`, `--callers`, `--callees`, `--depth` |
55
+ | `trace` | Trace a symbol's call chain (best-effort, syntactic; OFF the ranked path) | `symbol`, `--callers`, `--callees`, `--depth`, `--path`, `--eager-only` |
56
56
  | `trace-error` | Resolve a runtime stack trace's frames to the definitions they name (best-effort, syntactic; OFF the ranked path) | `trace_file` |
57
57
  | `fix` | Auto-remove unused exports/deps | `--dry-run`, `--yes` (required in non-TTY) |
58
58
  | `init` | Generate config file, AGENTS.md agent guide, or pre-commit hook | `--toml`, `--agents`, `--hooks`, `--branch` |
@@ -68,11 +68,11 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
68
68
  | `guard` | Show which architecture rules apply to files before changing them | `files` |
69
69
  | `config` | Show the loaded config path and resolved config (verifies which `.fallowrc.json` is in effect) | `--path` |
70
70
  | `recommend` | Recommend a project-tailored config for an agent to author | |
71
- | `list` | Inspect project structure | `--files`, `--entry-points`, `--plugins`, `--boundaries`, `--workspaces` |
71
+ | `list` | Inspect project structure | `--files`, `--entry-points`, `--plugins`, `--boundaries`, `--workspaces`, `--entry-weight` |
72
72
  | `workspaces` | Inspect monorepo workspaces + discovery diagnostics (shorthand for `list --workspaces`) | (no flags) |
73
73
  | `dupes` | Code duplication detection | `--mode`, `--near`, `--threshold`, `--top`, `--changed-since`, `--workspace`, `--changed-workspaces`, `--skip-local`, `--cross-language`, `--ignore-imports`, `--explain-skipped`, `--fail-on-regression`, `--tolerance`, `--regression-baseline`, `--save-regression-baseline` |
74
74
  | `health` | Function complexity analysis (also covers component templates as synthetic `<template>` findings: Angular external `.html` files via `templateUrl` AND inline `@Component({ template: \`...\` })` literals, plus Vue, Svelte and Astro single-file components; suppress an Angular external template with `<!-- fallow-ignore-file complexity -->` at the top of the `.html` file, an Angular inline template with `// fallow-ignore-next-line complexity` directly above the `@Component` decorator, and a `.svelte` / `.vue` / `.astro` template with `<!-- fallow-ignore-next-line complexity -->` on the line immediately above the reported line) | `--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` |
75
- | `flags` | Detect feature flag patterns (env vars, SDK calls, config objects) | `--top` |
75
+ | `flags` | Detect feature flag patterns (env vars, SDK calls, config objects) | `--top`, `--retirement`, `--reason`, `--min-age`, `--flag-state`, `--max-flag-age` |
76
76
  | `suppressions` | List active fallow-ignore suppression markers (read-only inventory) | `--file` |
77
77
  | `explain` | Explain one issue type without running analysis | `<issue-type>`, `--format json` |
78
78
  | `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` |
@@ -347,6 +347,7 @@ Inspect discovered files, entry points, detected frameworks, and architecture bo
347
347
  | `--plugins` | `bool` | `false` | List active framework plugins |
348
348
  | `--boundaries` | `bool` | `false` | Show architecture boundary zones, rules, per-zone file counts, and `logical_groups[]` for `autoDiscover` parents |
349
349
  | `--workspaces` | `bool` | `false` | Show discovered monorepo workspaces plus any workspace-discovery diagnostics (malformed `package.json`, unreachable glob matches, missing tsconfig references). Available as the `fallow workspaces` alias too. |
350
+ | `--entry-weight` | `bool` | `false` | Show the startup import weight of each runtime entry point, in source bytes (not bundle size): eager, deferred and out-of-thread modules, eager packages, and the imports that keep the most bytes eager |
350
351
 
351
352
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags).
352
353
  <!-- generated:flags:list:end -->
@@ -358,9 +359,14 @@ fallow list --entry-points --format json --quiet
358
359
  fallow list --plugins --format json --quiet
359
360
  fallow list --boundaries --format json --quiet
360
361
  fallow list --workspaces --format json --quiet
362
+ fallow list --entry-weight --format json --quiet
361
363
  fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
362
364
  ```
363
365
 
366
+ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per runtime entry point, heaviest first. Each row has `eager_modules` and `eager_bytes` (the project modules and source bytes that load before the entry runs; `import type` and a declaration file do not count), `eager_css_bytes`, `deferred_modules` and `deferred_bytes` (reached only through `import()` or a lazy glob), `out_of_thread_modules` and `out_of_thread_bytes` (reached only through a `new URL(..., import.meta.url)` reference such as a worker URL, `child_process.fork`, a pino transport or a `module.register` hook), `eager_packages[]` with the specifiers as written, and `dominating_imports[]`. A dominating import is one import that alone keeps `exclusive_bytes` on the startup path. The unit is `source_bytes`: types and comments count, and tree shaking does not apply, so the value is not a bundle size. An import without the `type` keyword counts as eager, even when it brings in only types that TypeScript removes, so `eager_bytes` can be too high. Treat a dominating import as evidence for a review, not as a fix: a lazy load of code that the first screen needs can make startup slower.
367
+
368
+ To gate eager growth in CI, save a baseline file on the main branch with `fallow list --entry-weight --save-regression-baseline <PATH>`. A later run with `--regression-baseline <PATH>` adds `entry_weight.regression`: per-entry `baseline_eager_bytes`, `current_eager_bytes`, `new_eager_packages` and `exceeded`. The comparison is report-only until you add `--fail-on-regression`; then an entry that grew more than `--tolerance` (bytes, or a percentage such as `5%`) exits 1. A new entry never fails the gate.
369
+
364
370
  The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (`undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `bun-lockb-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The two analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues.
365
371
 
366
372
  The `--boundaries` JSON output carries `boundaries.logical_groups[]` alongside the existing `zones[]` / `rules[]` arrays. Each logical-group entry surfaces a user-authored `autoDiscover` parent zone (which expansion otherwise flattens into per-child zones like `features/auth` / `features/billing`): `name`, `children`, `auto_discover` (verbatim user strings), `status` (`ok` / `empty` / `invalid_path`), `source_zone_index`, summed `file_count`, optional `authored_rule` (the pre-expansion `{ allow, allowTypeOnly }` keyed on the parent), optional `fallback_zone` cross-reference when the parent also kept its own `patterns` (Bulletproof case), optional `merged_from` (parent zone indices when the user declared the same parent name twice; surfaces the duplicate in JSON instead of only in `tracing::warn!`), optional `original_zone_root` (echo of the parent's `root` subtree scope for monorepo patchers), and optional `child_source_indices` (parallel to `children`, attributing each child to a specific `auto_discover` entry when multiple paths were authored). The full shape is documented in `docs/output-schema.json` under `ListBoundariesOutput`.
@@ -447,7 +453,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
447
453
  {
448
454
  "kind": "agent-install",
449
455
  "schema_version": 1,
450
- "fallow_version": "3.29.0",
456
+ "fallow_version": "3.30.0",
451
457
  "root": "/abs/path",
452
458
  "mode": "install",
453
459
  "dry_run": false,
@@ -651,7 +657,7 @@ fallow health --format json --quiet --trend
651
657
  {
652
658
  "kind": "health",
653
659
  "schema_version": 7,
654
- "version": "3.29.0",
660
+ "version": "3.30.0",
655
661
  "elapsed_ms": 32,
656
662
  "summary": {
657
663
  "files_analyzed": 482,
@@ -1054,7 +1060,7 @@ fallow audit \
1054
1060
  {
1055
1061
  "kind": "audit",
1056
1062
  "schema_version": 7,
1057
- "version": "3.29.0",
1063
+ "version": "3.30.0",
1058
1064
  "command": "audit",
1059
1065
  "verdict": "fail",
1060
1066
  "changed_files_count": 12,
@@ -1110,6 +1116,13 @@ Detects feature flag patterns in the codebase. Identifies environment variable f
1110
1116
  | Flag | Type | Default | Description |
1111
1117
  |---|---|---|---|
1112
1118
  | `--top` | `string` | - | Show only the top N flags |
1119
+ | `--retirement` | `bool` | `false` | Add a retirement report: one row per flag, with the reasons the flag can be retired. Advisory only; nothing is removed |
1120
+ | `--reason` | `single-read-site\|test-only\|literal-constant\|identical-branches\|empty-branch\|guards-dead-code\|defined-never-read\|fully-rolled-out\|archived-in-vendor\|missing-in-vendor\|vendor-only` | - | Keep only retirement rows with this reason (repeatable) |
1121
+ | `--sort` | `age\|sites\|name` | `age` | Order of the retirement rows |
1122
+ | `--flag-age` | `blame\|pickaxe\|off` | `blame` | How to measure flag age: blame (lower bound), pickaxe (first commit with the name, slower) or off |
1123
+ | `--min-age` | `string` | - | Keep only retirement rows at least this many days old |
1124
+ | `--flag-state` | `string` | - | Vendor flag export (JSON, read offline) that adds the fully-rolled-out, archived-in-vendor, missing-in-vendor and vendor-only reasons |
1125
+ | `--max-flag-age` | `string` | - | Exit with code 1 when a flag in scope is older than this many days. Opt-in; needs a flag age |
1113
1126
 
1114
1127
  Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#global-flags), [`--changed-since`](#global-flags), [`--workspace`](#global-flags).
1115
1128
  <!-- generated:flags:flags:end -->
@@ -1122,6 +1135,10 @@ fallow flags --format json --quiet
1122
1135
  # Top 10 flags
1123
1136
  fallow flags --format json --quiet --top 10
1124
1137
 
1138
+ # Flags at least 90 days old, oldest first. A row with an empty
1139
+ # `reasons` array is not a retirement candidate.
1140
+ fallow flags --retirement --min-age 90 --format json --quiet
1141
+
1125
1142
  # Single workspace package
1126
1143
  fallow flags --format json --quiet --workspace my-package
1127
1144
  ```
@@ -1131,7 +1148,7 @@ fallow flags --format json --quiet --workspace my-package
1131
1148
  ```json
1132
1149
  {
1133
1150
  "schema_version": 7,
1134
- "version": "3.29.0",
1151
+ "version": "3.30.0",
1135
1152
  "elapsed_ms": 116,
1136
1153
  "feature_flags": [],
1137
1154
  "total_flags": 0
@@ -1232,7 +1249,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1232
1249
  {
1233
1250
  "kind": "security",
1234
1251
  "schema_version": "4",
1235
- "version": "3.29.0",
1252
+ "version": "3.30.0",
1236
1253
  "elapsed_ms": 42,
1237
1254
  "config": {
1238
1255
  "rules": {
@@ -1261,7 +1278,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1261
1278
  {
1262
1279
  "kind": "security",
1263
1280
  "schema_version": "4",
1264
- "version": "3.29.0",
1281
+ "version": "3.30.0",
1265
1282
  "elapsed_ms": 42,
1266
1283
  "config": {
1267
1284
  "rules": {
@@ -1386,12 +1403,17 @@ The target is a positional argument, formatted as `FILE:SYMBOL` (for example `sr
1386
1403
  ```bash
1387
1404
  fallow trace src/utils.ts:formatDate
1388
1405
  fallow trace src/utils.ts:formatDate --callers --depth 3
1406
+ fallow trace --path src/main.ts src/chart.ts --format json --quiet
1407
+ fallow trace --path src/main.ts src/chart.ts --eager-only --format json --quiet
1389
1408
  ```
1390
1409
 
1410
+ Each `--path` hop carries `type_only` and `dynamic`. A `dynamic` hop loads its target only on demand (`import()`, a lazy glob) or on another thread (a worker, a fork). `--eager-only` follows static value imports only, so its route explains why a module is in the `--entry-weight` eager set of `fallow list`.
1411
+
1391
1412
  <!-- generated:flags:trace:start -->
1392
1413
  | Flag | Type | Default | Description |
1393
1414
  |---|---|---|---|
1394
1415
  | `--path` | `string` | - | Shortest import path between two modules, as two file paths (e.g. `--path src/app.ts src/db.ts`). Mutually exclusive with the symbol target and the call-chain flags |
1416
+ | `--eager-only` | `bool` | `false` | With `--path`, follow only static value imports, so the route explains why TO loads before FROM runs. `import()`, lazy globs, worker loads and `import type` do not qualify |
1395
1417
  | `--callers` | `bool` | `false` | Walk UP to callers (modules that import the symbol). When neither `--callers` nor `--callees` is set, both directions are walked |
1396
1418
  | `--callees` | `bool` | `false` | Walk DOWN to callees (the symbol's module's import-symbol edges plus unresolved call sites). When neither flag is set, both are walked |
1397
1419
  | `--depth` | `string` | - | Chain depth bound for both directions (default 2). Symbol-level is best-effort, so a shallow bound keeps the trace legible |
@@ -2034,7 +2056,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2034
2056
  {
2035
2057
  "kind": "dead-code",
2036
2058
  "schema_version": 7,
2037
- "version": "3.29.0",
2059
+ "version": "3.30.0",
2038
2060
  "elapsed_ms": 45,
2039
2061
  "total_issues": 12,
2040
2062
  "entry_points": {
@@ -2194,7 +2216,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2194
2216
  {
2195
2217
  "kind": "dupes",
2196
2218
  "schema_version": 7,
2197
- "version": "3.29.0",
2219
+ "version": "3.30.0",
2198
2220
  "elapsed_ms": 82,
2199
2221
  "total_clones": 15,
2200
2222
  "total_lines_duplicated": 230,
@@ -2238,11 +2260,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2238
2260
  {
2239
2261
  "kind": "combined",
2240
2262
  "schema_version": 7,
2241
- "version": "3.29.0",
2263
+ "version": "3.30.0",
2242
2264
  "elapsed_ms": 159,
2243
2265
  "check": {
2244
2266
  "schema_version": 7,
2245
- "version": "3.29.0",
2267
+ "version": "3.30.0",
2246
2268
  "elapsed_ms": 45,
2247
2269
  "total_issues": 12,
2248
2270
  "unused_files": [],
@@ -72,6 +72,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
72
72
  | `untested-export` | `--coverage-gaps` | - | `// fallow-ignore-file coverage-gaps` | Runtime-reachable export has no test dependency path |
73
73
  | `code-duplication` | - | - | `// fallow-ignore-next-line code-duplication` | Duplicated code block; Reported by fallow dupes (and bare fallow / fallow audit) |
74
74
  | `feature-flag` | - | - | `// fallow-ignore-next-line feature-flag` | Detected feature flag pattern; Reported by fallow flags |
75
+ | `flag-retirement-candidate` | - | - | - | Feature flag is a retirement candidate |
75
76
  | `tainted-sink` | - | - | `// fallow-ignore-next-line security-sink` | Syntactic security sink candidates require verification |
76
77
  | `client-server-leak` | - | - | `// fallow-ignore-file security-client-server-leak` | Client-bound code reaches a non-public env read |
77
78
  | `hardcoded-secret` | - | - | `// fallow-ignore-next-line security-sink` | Provider-prefixed or contextual secret literals require verification; Include-required category: enable via security.categories.include |
@@ -36,7 +36,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
36
36
  | `project_info` | introspection | free | `fallow list --files --entry-points --plugins --format json --quiet` | `entry_points`, `files`, `plugins`, `boundaries` | Project metadata. Set `entry_points`, `files`, `plugins`, or `boundaries` to `true` to request specific sections |
37
37
  | `recommend` | introspection | free | `fallow recommend --format json --quiet` | `root` | Recommend a project-tailored config from framework/workspace/tooling detection: a loader-validated proposed_config and three-valued auto/default/taste decisions for cold-start onboarding |
38
38
  | `list_boundaries` | introspection | free | `fallow list --boundaries --format json --quiet` | - | Architecture boundary zones, access rules, and pre-expansion `autoDiscover` `logical_groups[]` (user-authored parent name, verbatim paths, discovered children, `status` enum, summed `file_count`). Returns `{"configured": false}` if no boundaries configured |
39
- | `feature_flags` | analysis | free | `fallow flags --format json --quiet` | `workspace`, `production` | Detect feature flag patterns (env vars, SDK calls, config objects). Set `top` to limit results |
39
+ | `feature_flags` | analysis | free | `fallow flags --format json --quiet` | `workspace`, `production` | Detect feature flag patterns (env vars, SDK calls, config objects). Set `top` to limit results. `retirement: true` adds the retirement rows; `flag_state` and `flag_age` tune them |
40
40
  | `list_suppressions` | analysis | free | `fallow suppressions --format json --quiet` | `workspace`, `changed_since`, `file` | 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 |
41
41
  | `impact` | introspection | free | `fallow impact --format json --quiet` | `root` | Read the local, opt-in Fallow Impact value report (`fallow impact --format json`). Runs no analysis: current surfacing counts, trend since the last recorded run, pre-commit gate containment, and (on impact v1.5+) resolved/suppressed attribution. History is read from a per-project file in the user's private config dir (never inside the repo). Read-only and `root`-only; the mutating `enable` / `disable` / `default` lifecycle is not exposed. A never-enabled project returns a populated `{"enabled": false, ...}` report (never `{}`); branch on `enabled` and `enabled_source` (`project` / `user` / `default`) then `record_count`, recommending `fallow impact enable` only when `explicit_decision` is `false` (never asked) and staying silent when `true` (deliberately disabled here). Local-developer signal: fallow never records in CI, so empty there and not a CI metric |
42
42
  | `impact_all` | introspection | free | `fallow impact --all --format json --quiet` | `sort`, `limit` | Roll every tracked fallow project on this machine into one cross-repo value report (hashed keys plus basename labels, never paths; local-dev only) |
@@ -731,6 +731,15 @@ reason: string
731
731
  kind: "plugin-effect-not-modeled"
732
732
  } | {
733
733
  kind: "coverage-auto-detected"
734
+ } | {
735
+ kind: "flag-age-shallow-clone"
736
+ } | {
737
+ /**
738
+ * Why no history is available, as a kebab-case token:
739
+ * `not-a-repository` or `no-commits`. The set is open.
740
+ */
741
+ cause: string
742
+ kind: "flag-age-unavailable"
734
743
  })
735
744
  /**
736
745
  * Discriminant for [`CloneGroupAction::kind`]. Mirrors the action types
@@ -905,6 +914,11 @@ export type RuntimeCoverageVerdict = ("safe_to_delete" | "review_required" | "co
905
914
  * Confidence level for a runtime coverage finding.
906
915
  */
907
916
  export type RuntimeCoverageConfidence = ("very_high" | "high" | "medium" | "low" | "none" | "unknown")
917
+ /**
918
+ * The per-call cost that `optimization_target.cost_score` multiplies with
919
+ * `invocations`.
920
+ */
921
+ export type RuntimeCoverageCostBasis = ("inner_iterations" | "cognitive")
908
922
  /**
909
923
  * Blast-radius risk band. The current thresholds are high at >=20 static
910
924
  * callers or >=1,000,000 traffic-weighted caller reach; medium at >=5 callers
@@ -1301,6 +1315,30 @@ export type FeatureFlagConfidence = ("high" | "medium" | "low")
1301
1315
  * Feature flag action discriminants.
1302
1316
  */
1303
1317
  export type FeatureFlagActionType = ("investigate-flag" | "suppress-line")
1318
+ /**
1319
+ * How the report measures the age of a flag.
1320
+ */
1321
+ export type FlagAgeMode = ("blame" | "pickaxe" | "off")
1322
+ /**
1323
+ * How a retirement row's flag was detected.
1324
+ */
1325
+ export type RetirementFlagKind = ("environment_variable" | "sdk_call" | "config_object" | "constant" | "vendor_export")
1326
+ /**
1327
+ * What a site does with the flag.
1328
+ */
1329
+ export type FlagSiteRole = ("read" | "definition")
1330
+ /**
1331
+ * Why a flag is a retirement candidate.
1332
+ */
1333
+ export type RetirementReason = ("single-read-site" | "test-only" | "literal-constant" | "identical-branches" | "empty-branch" | "guards-dead-code" | "defined-never-read" | "fully-rolled-out" | "archived-in-vendor" | "missing-in-vendor" | "vendor-only")
1334
+ /**
1335
+ * Action discriminants for a retirement row.
1336
+ */
1337
+ export type RetirementActionType = "review-retirement"
1338
+ /**
1339
+ * State of a flag in a `--flag-state` vendor export.
1340
+ */
1341
+ export type VendorFlagState = ("on" | "off" | "rolled_out" | "archived" | "experiment")
1304
1342
  /**
1305
1343
  * Independently-versioned wire-version newtype for the brief envelope.
1306
1344
  * Serializes as the integer `REVIEW_BRIEF_SCHEMA_VERSION`.
@@ -8362,6 +8400,48 @@ percentile: number
8362
8400
  * when empty.
8363
8401
  */
8364
8402
  actions?: RuntimeCoverageAction[]
8403
+ /**
8404
+ * Per-call cost inputs and the speed-work score for this hot function.
8405
+ * Omitted when the hot path has no `stable_id` or no static counterpart
8406
+ * in this checkout.
8407
+ */
8408
+ optimization_target?: (RuntimeCoverageOptimizationTarget | null)
8409
+ }
8410
+ /**
8411
+ * Speed-work inputs for one hot function: how often it runs and how much
8412
+ * work each call does. `importance` ranks the risk of a change; this block
8413
+ * ranks where speed work gives the largest gain.
8414
+ */
8415
+ export interface RuntimeCoverageOptimizationTarget {
8416
+ /**
8417
+ * `invocations` multiplied by the per-call cost that `cost_basis` names.
8418
+ * Uncapped integer. Compare it only between hot paths with the same
8419
+ * `cost_basis`: sort by `cost_basis` first, then by `cost_score`
8420
+ * descending. On the `cognitive` basis the per-call cost is at least 1.
8421
+ */
8422
+ cost_score: number
8423
+ cost_basis: RuntimeCoverageCostBasis
8424
+ /**
8425
+ * Static cognitive complexity of the function. A static proxy for the
8426
+ * work per call, not a measurement.
8427
+ */
8428
+ cognitive: number
8429
+ /**
8430
+ * Static cyclomatic complexity of the function.
8431
+ */
8432
+ cyclomatic: number
8433
+ /**
8434
+ * Number of lines in the function body.
8435
+ */
8436
+ line_count: number
8437
+ /**
8438
+ * Peak executions of one block inside the function per call, from V8
8439
+ * block coverage. `1.0` means no block ran more than once per call. A
8440
+ * loop body that runs 3 times per call gives `3.0`. Calls to other
8441
+ * functions do not change the value.
8442
+ * Omitted when the coverage input has no block counts for the function.
8443
+ */
8444
+ inner_iterations_per_call?: (number | null)
8365
8445
  }
8366
8446
  /**
8367
8447
  * One blast-radius entry in `runtime_coverage.blast_radius`: how far a
@@ -10925,6 +11005,13 @@ to: string
10925
11005
  * chain is a real compile-time coupling.
10926
11006
  */
10927
11007
  type_only: boolean
11008
+ /**
11009
+ * Whether the edge carries a runtime value but no static one: the target
11010
+ * loads only on demand (`import()`, a lazy glob or template pattern) or
11011
+ * on another thread (a worker URL, `child_process.fork`). False for a
11012
+ * static hop and for a type-only hop.
11013
+ */
11014
+ dynamic: boolean
10928
11015
  /**
10929
11016
  * 1-based line in `from` of the imported binding that creates this edge:
10930
11017
  * the first value-carrying symbol on the import, or the first symbol when
@@ -14063,6 +14150,15 @@ total_flags: number
14063
14150
  * change.
14064
14151
  */
14065
14152
  workspace_diagnostics?: WorkspaceDiagnostic[]
14153
+ /**
14154
+ * One row per flag with the reasons the flag can be retired.
14155
+ *
14156
+ * Present only with `--retirement`. Without that option the key is
14157
+ * omitted, so the envelope stays byte-identical and `schema_version`
14158
+ * does not move. The per-site `feature_flags[]` array is the same with
14159
+ * and without the option.
14160
+ */
14161
+ retirement?: (FlagRetirementReport | null)
14066
14162
  /**
14067
14163
  * `_meta` block; see [`FeatureFlagsMeta`].
14068
14164
  */
@@ -14138,6 +14234,347 @@ dead_export_count: number
14138
14234
  */
14139
14235
  dead_exports: string[]
14140
14236
  }
14237
+ /**
14238
+ * The `retirement` block of `fallow flags --retirement --format json`.
14239
+ */
14240
+ export interface FlagRetirementReport {
14241
+ /**
14242
+ * The analysis clock that ages count from, as an RFC 3339 UTC
14243
+ * timestamp. `null` when the age mode is `off`, and also when no git
14244
+ * history is available: outside a repository, on a branch without
14245
+ * commits, or in a shallow clone. A `workspace_diagnostics` entry then
14246
+ * gives the reason.
14247
+ */
14248
+ generated_at_clock?: (string | null)
14249
+ age_mode: FlagAgeMode
14250
+ /**
14251
+ * The vendor export that the report read. Present only with
14252
+ * `--flag-state`.
14253
+ */
14254
+ vendor_state?: (RetirementVendorState | null)
14255
+ summary: RetirementSummary
14256
+ /**
14257
+ * Verdict of `--fail-on-regression` against a flags regression
14258
+ * baseline. Present only when the gate ran.
14259
+ */
14260
+ regression?: (FlagRegressionResult | null)
14261
+ /**
14262
+ * Verdict of `--max-flag-age`. Present only with that option.
14263
+ */
14264
+ max_flag_age?: (FlagAgeGate | null)
14265
+ /**
14266
+ * One row per flag after the `--min-age`, `--reason`, `--sort` and
14267
+ * `--top` options. A row with an empty `reasons` array is not a
14268
+ * candidate.
14269
+ */
14270
+ flags: RetirementFlag[]
14271
+ }
14272
+ /**
14273
+ * The `--flag-state` vendor export that the report read.
14274
+ */
14275
+ export interface RetirementVendorState {
14276
+ /**
14277
+ * The vendor name from the export, for example `launchdarkly`.
14278
+ */
14279
+ source: string
14280
+ /**
14281
+ * When the export was made, as the export gives it.
14282
+ */
14283
+ exported_at: string
14284
+ /**
14285
+ * Days between `exported_at` and the analysis clock. `null` when the
14286
+ * date cannot be read.
14287
+ */
14288
+ export_age_days?: (number | null)
14289
+ /**
14290
+ * Number of flags in the export.
14291
+ */
14292
+ flags: number
14293
+ }
14294
+ /**
14295
+ * Totals of the retirement report.
14296
+ */
14297
+ export interface RetirementSummary {
14298
+ /**
14299
+ * Distinct flags in the code in scope, before `--min-age` and
14300
+ * `--reason`. The `vendor-only` rows of a `--flag-state` export do not
14301
+ * count here, so the count does not change when a key is added in the
14302
+ * vendor only. `by_reason` counts them.
14303
+ */
14304
+ distinct_flags: number
14305
+ /**
14306
+ * Rows in scope with at least one reason, `vendor-only` rows included.
14307
+ */
14308
+ candidates: number
14309
+ /**
14310
+ * Number of rows in scope per reason.
14311
+ */
14312
+ by_reason: {
14313
+ [k: string]: number
14314
+ }
14315
+ }
14316
+ /**
14317
+ * Verdict of the flags regression gate.
14318
+ */
14319
+ export interface FlagRegressionResult {
14320
+ status: RegressionStatus
14321
+ /**
14322
+ * The `--tolerance` value. Absent when the status is `skipped`.
14323
+ */
14324
+ tolerance?: (number | null)
14325
+ /**
14326
+ * How to read `tolerance`. Absent when the status is `skipped`.
14327
+ */
14328
+ tolerance_kind?: (RegressionToleranceKind | null)
14329
+ /**
14330
+ * The compared counts: `distinct_flags` first, then each `--reason`
14331
+ * code. Empty when the status is `skipped`.
14332
+ */
14333
+ metrics: FlagRegressionMetric[]
14334
+ /**
14335
+ * Whether one count grew more than the tolerance.
14336
+ */
14337
+ exceeded: boolean
14338
+ /**
14339
+ * Why the gate did not run. Present only when the status is `skipped`.
14340
+ */
14341
+ reason?: (string | null)
14342
+ }
14343
+ /**
14344
+ * One count that the flags regression gate compares.
14345
+ */
14346
+ export interface FlagRegressionMetric {
14347
+ /**
14348
+ * `distinct_flags`, or a reason code from `--reason`.
14349
+ */
14350
+ metric: string
14351
+ /**
14352
+ * The count in the baseline.
14353
+ */
14354
+ baseline: number
14355
+ /**
14356
+ * The count in this run.
14357
+ */
14358
+ current: number
14359
+ /**
14360
+ * `current - baseline`.
14361
+ */
14362
+ delta: number
14363
+ /**
14364
+ * Whether the growth is more than the tolerance.
14365
+ */
14366
+ exceeded: boolean
14367
+ }
14368
+ /**
14369
+ * Verdict of `--max-flag-age`.
14370
+ */
14371
+ export interface FlagAgeGate {
14372
+ status: RegressionStatus
14373
+ /**
14374
+ * The `--max-flag-age` value in days.
14375
+ */
14376
+ max_days: number
14377
+ /**
14378
+ * Whether one flag in scope is older than `max_days`.
14379
+ */
14380
+ exceeded: boolean
14381
+ /**
14382
+ * Flags in the code in scope without a measured age. The gate cannot
14383
+ * check these flags.
14384
+ */
14385
+ unmeasured: number
14386
+ /**
14387
+ * Why the gate did not run. Present only when the status is `skipped`.
14388
+ */
14389
+ reason?: (string | null)
14390
+ /**
14391
+ * The flags in scope that are older than `max_days`, oldest first.
14392
+ * The `--reason`, `--min-age` and `--top` options do not change this
14393
+ * list.
14394
+ */
14395
+ flags: FlagAgeGateEntry[]
14396
+ }
14397
+ /**
14398
+ * A flag that is older than `--max-flag-age`.
14399
+ */
14400
+ export interface FlagAgeGateEntry {
14401
+ /**
14402
+ * Flag identifier.
14403
+ */
14404
+ flag_name: string
14405
+ kind: RetirementFlagKind
14406
+ /**
14407
+ * Flag SDK, for SDK flags with a known provider.
14408
+ */
14409
+ sdk_name?: (string | null)
14410
+ /**
14411
+ * Workspace root of the flag, in a project with workspaces.
14412
+ */
14413
+ workspace?: (string | null)
14414
+ /**
14415
+ * Age of the flag in days.
14416
+ */
14417
+ age_days: number
14418
+ }
14419
+ /**
14420
+ * One flag in the retirement report.
14421
+ */
14422
+ export interface RetirementFlag {
14423
+ /**
14424
+ * Flag identifier.
14425
+ */
14426
+ flag_name: string
14427
+ kind: RetirementFlagKind
14428
+ /**
14429
+ * Flag SDK, for SDK flags with a known provider.
14430
+ */
14431
+ sdk_name?: (string | null)
14432
+ /**
14433
+ * Workspace root relative to the analysed root, when the project has
14434
+ * workspaces and the flag is inside one. Part of the flag identity.
14435
+ */
14436
+ workspace?: (string | null)
14437
+ /**
14438
+ * Every site of the flag, sorted by path, line and column.
14439
+ */
14440
+ sites: RetirementSite[]
14441
+ /**
14442
+ * Number of sites in this row that read the flag.
14443
+ */
14444
+ read_sites: number
14445
+ /**
14446
+ * Whether every read site is in a test, story or mock file. Read sites
14447
+ * of the same flag in other workspaces count too.
14448
+ */
14449
+ test_only: boolean
14450
+ /**
14451
+ * First commit that added the flag name. Set in `pickaxe` mode only.
14452
+ */
14453
+ first_seen?: (FlagCommit | null)
14454
+ /**
14455
+ * Oldest commit among the lines that still hold the flag.
14456
+ */
14457
+ oldest_surviving_site?: (FlagCommit | null)
14458
+ /**
14459
+ * Newest commit among the lines that still hold the flag.
14460
+ */
14461
+ last_touched?: (FlagCommit | null)
14462
+ /**
14463
+ * Days between the flag's oldest known commit and the analysis clock.
14464
+ * In `blame` mode this is a lower bound.
14465
+ */
14466
+ age_days?: (number | null)
14467
+ /**
14468
+ * Retirement reasons, in report order. Empty for a flag that is not a
14469
+ * candidate.
14470
+ */
14471
+ reasons: RetirementReason[]
14472
+ /**
14473
+ * Evidence for each reason.
14474
+ */
14475
+ evidence: RetirementEvidence[]
14476
+ /**
14477
+ * Follow-up actions. Empty for a flag that is not a candidate.
14478
+ */
14479
+ actions: RetirementAction[]
14480
+ /**
14481
+ * The vendor state of the flag. Present only with `--flag-state`, for
14482
+ * a flag whose key is in the export.
14483
+ */
14484
+ vendor?: (RetirementVendor | null)
14485
+ }
14486
+ /**
14487
+ * One site of a flag in the retirement report.
14488
+ */
14489
+ export interface RetirementSite {
14490
+ /**
14491
+ * File path relative to the analysed root.
14492
+ */
14493
+ path: string
14494
+ /**
14495
+ * 1-based line.
14496
+ */
14497
+ line: number
14498
+ /**
14499
+ * 0-based byte column.
14500
+ */
14501
+ col: number
14502
+ role: FlagSiteRole
14503
+ /**
14504
+ * Whether the file is a test, story or mock file.
14505
+ */
14506
+ in_test: boolean
14507
+ }
14508
+ /**
14509
+ * A commit that git history links to a flag.
14510
+ */
14511
+ export interface FlagCommit {
14512
+ /**
14513
+ * Abbreviated commit hash.
14514
+ */
14515
+ commit: string
14516
+ /**
14517
+ * Commit date in UTC, as `YYYY-MM-DD`.
14518
+ */
14519
+ date: string
14520
+ }
14521
+ /**
14522
+ * One piece of evidence for a retirement reason.
14523
+ */
14524
+ export interface RetirementEvidence {
14525
+ reason: RetirementReason
14526
+ /**
14527
+ * File path relative to the analysed root. For `vendor-only`, the path
14528
+ * of the `--flag-state` file: relative to the root when the file is
14529
+ * inside it, else as given.
14530
+ */
14531
+ path: string
14532
+ /**
14533
+ * 1-based line.
14534
+ */
14535
+ line: number
14536
+ /**
14537
+ * What the evidence shows.
14538
+ */
14539
+ detail: string
14540
+ }
14541
+ /**
14542
+ * A follow-up action for a retirement candidate.
14543
+ */
14544
+ export interface RetirementAction {
14545
+ type: RetirementActionType
14546
+ /**
14547
+ * Always `false`: Fallow never removes a flag.
14548
+ */
14549
+ auto_fixable: boolean
14550
+ /**
14551
+ * Human-readable action description.
14552
+ */
14553
+ description: string
14554
+ }
14555
+ /**
14556
+ * The vendor state of one flag in the retirement report.
14557
+ */
14558
+ export interface RetirementVendor {
14559
+ /**
14560
+ * The key in the vendor export, before `flags.vendorKeyPrefix` is
14561
+ * removed.
14562
+ */
14563
+ key: string
14564
+ state: VendorFlagState
14565
+ /**
14566
+ * Whether the flag serves one variation, when the export says so.
14567
+ */
14568
+ serves_single_variation?: (boolean | null)
14569
+ /**
14570
+ * When the vendor created the flag, as the export gives it.
14571
+ */
14572
+ created_at?: (string | null)
14573
+ /**
14574
+ * When the vendor last evaluated the flag, as the export gives it.
14575
+ */
14576
+ last_evaluated_at?: (string | null)
14577
+ }
14141
14578
  /**
14142
14579
  * Optional `_meta` block for [`FeatureFlagsOutput`]. Both fields are optional
14143
14580
  * because the two contributors are independent: `feature_flags` details are