fallow 2.82.0 → 2.83.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "2.82.0",
3
+ "version": "2.83.0",
4
4
  "description": "Deterministic codebase intelligence for TypeScript and JavaScript. Quality, risk, architecture, dependencies, duplication, and safe cleanup evidence for humans, CI, and agents. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, 96 framework plugins.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -83,13 +83,13 @@
83
83
  "@tanstack/intent": "0.0.41"
84
84
  },
85
85
  "optionalDependencies": {
86
- "@fallow-cli/darwin-arm64": "2.82.0",
87
- "@fallow-cli/darwin-x64": "2.82.0",
88
- "@fallow-cli/linux-x64-gnu": "2.82.0",
89
- "@fallow-cli/linux-arm64-gnu": "2.82.0",
90
- "@fallow-cli/linux-x64-musl": "2.82.0",
91
- "@fallow-cli/linux-arm64-musl": "2.82.0",
92
- "@fallow-cli/win32-arm64-msvc": "2.82.0",
93
- "@fallow-cli/win32-x64-msvc": "2.82.0"
86
+ "@fallow-cli/darwin-arm64": "2.83.0",
87
+ "@fallow-cli/darwin-x64": "2.83.0",
88
+ "@fallow-cli/linux-x64-gnu": "2.83.0",
89
+ "@fallow-cli/linux-arm64-gnu": "2.83.0",
90
+ "@fallow-cli/linux-x64-musl": "2.83.0",
91
+ "@fallow-cli/linux-arm64-musl": "2.83.0",
92
+ "@fallow-cli/win32-arm64-msvc": "2.83.0",
93
+ "@fallow-cli/win32-x64-msvc": "2.83.0"
94
94
  }
95
95
  }
package/schema.json CHANGED
@@ -64,6 +64,14 @@
64
64
  },
65
65
  "default": []
66
66
  },
67
+ "ignoreUnresolvedImports": {
68
+ "description": "Import specifier glob patterns whose `unresolved-import` findings are\nexpected and should be suppressed.\n\nMatching is against the raw import specifier string, not a filesystem\npath. Exact specifiers and subpaths must be listed separately when both\nshould be ignored, for example `[\"@example/icons\", \"@example/icons/**\"]`.\nBroad patterns such as `\"**\"` can hide real missing modules, so keep\nthis list focused on generated or runtime-provided specifiers.",
69
+ "type": "array",
70
+ "items": {
71
+ "type": "string"
72
+ },
73
+ "default": []
74
+ },
67
75
  "ignoreExports": {
68
76
  "description": "Export ignore rules.",
69
77
  "type": "array",
@@ -277,6 +285,11 @@
277
285
  "type": "boolean",
278
286
  "default": false
279
287
  },
288
+ "autoImports": {
289
+ "description": "Resolve framework convention auto-imports (Nuxt components) as real\nmodule-graph edges, and stop treating the covered convention directories\nas always-used entry points.\n\nWhen `false` (default), auto-import edges are still synthesized additively\n(so a component's default export consumed via a `<Card />` template tag is\ncredited under `includeEntryExports`), but the convention directories stay\nregistered as entry points, so genuinely-unreferenced components are never\nreported as `unused-file`.\n\nWhen `true`, the Nuxt plugin drops its component entry patterns\n(`components/**`, `app/components/**`) so an unreferenced component is\nreported as `unused-file`. This is opt-in because non-flat projects\n(custom `prefix` / `pathPrefix` / `dirs` in `nuxt.config`, dynamic\n`<component :is>`, `@nuxt/content` MDC) are not fully modeled yet and could\nproduce false positives. As a guard, if `nuxt.config` declares a\n`components:` key the entry patterns are kept regardless. Composable and\nutil entry patterns are unaffected until convention resolution covers\nthem. See issue #704.",
290
+ "type": "boolean",
291
+ "default": false
292
+ },
280
293
  "cache": {
281
294
  "description": "Incremental cache tuning. Today the only knob is `maxSizeMb`, which\ncaps the on-disk cache and triggers LRU eviction during save. See\n[`CacheConfig`].",
282
295
  "$ref": "#/$defs/CacheConfig"
@@ -85,7 +85,25 @@ function writeVerifiedLineIfVersionQuery(verifyResult) {
85
85
  }
86
86
  }
87
87
 
88
+ // Swallow EPIPE on stdout. When fallow's output is piped into a reader that
89
+ // closes early (e.g. `fallow --version | head`), the trailing `verified:`
90
+ // status line would otherwise surface as an unhandled EPIPE 'error' event and
91
+ // dump a Node stack trace. EPIPE arrives as an async 'error' event on the
92
+ // stdout stream, not as a throw, so a try/catch around the write cannot catch
93
+ // it. The child binary's primary output is already written via inherited
94
+ // stdio; the status line is best-effort, so exit cleanly once the reader is
95
+ // gone. Scoped to stdout so a genuine error write to stderr still sets exit 1.
96
+ function guardBrokenStdout() {
97
+ process.stdout.on("error", (err) => {
98
+ if (err && err.code === "EPIPE") {
99
+ process.exit(0);
100
+ }
101
+ throw err;
102
+ });
103
+ }
104
+
88
105
  function runBinary(binaryBaseName) {
106
+ guardBrokenStdout();
89
107
  const { pkg, manifestPath, platformPkgDir } = resolvePlatformPaths();
90
108
 
91
109
  const binaryName = process.platform === "win32" ? `${binaryBaseName}.exe` : binaryBaseName;
@@ -119,4 +137,5 @@ module.exports = {
119
137
  runBinary,
120
138
  describeVerified, // test-only
121
139
  isVersionQuery, // test-only
140
+ guardBrokenStdout, // test-only
122
141
  };
@@ -0,0 +1,39 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const { spawnSync } = require("node:child_process");
4
+ const path = require("node:path");
5
+
6
+ const RUN_BINARY = path.join(__dirname, "run-binary.js");
7
+
8
+ // Run a child that installs guardBrokenStdout, then emits a synthetic stdout
9
+ // 'error' with the given code. Node delivers a broken-pipe failure as exactly
10
+ // this event ("Emitted 'error' event on Socket instance"), so emitting it is a
11
+ // faithful reproduction of `fallow --version | head` without needing a live
12
+ // pipe or an installed @fallow-cli platform package. Requiring run-binary.js
13
+ // has no side effects beyond defining functions, so no binary is resolved.
14
+ function runGuardChild(errorCode) {
15
+ const script =
16
+ `const { guardBrokenStdout } = require(${JSON.stringify(RUN_BINARY)});` +
17
+ `guardBrokenStdout();` +
18
+ `process.stdout.emit("error", Object.assign(new Error("write ${errorCode}"), { code: "${errorCode}" }));` +
19
+ // Reached only if the guard neither exited (EPIPE) nor rethrew (other).
20
+ `process.exit(42);`;
21
+ return spawnSync(process.execPath, ["-e", script], { encoding: "utf8" });
22
+ }
23
+
24
+ test("guardBrokenStdout swallows EPIPE on stdout and exits 0", () => {
25
+ const res = runGuardChild("EPIPE");
26
+ assert.equal(res.status, 0, "EPIPE on stdout should exit 0 cleanly, not crash");
27
+ assert.doesNotMatch(res.stderr, /EPIPE/, "no EPIPE stack trace on stderr");
28
+ });
29
+
30
+ test("guardBrokenStdout rethrows non-EPIPE stdout errors (exit 1)", () => {
31
+ const res = runGuardChild("ENOSPC");
32
+ assert.equal(res.status, 1, "a non-EPIPE stdout error must surface, not be swallowed");
33
+ // Match the thrown error's header ("Error: write ENOSPC"), not just the
34
+ // message substring: a missing-guard TypeError would leak the script source
35
+ // (`new Error("write ENOSPC")`) into its code frame and match a looser regex,
36
+ // masking a regression. The colon-space header only appears on a real rethrow.
37
+ assert.match(res.stderr, /Error: write ENOSPC/, "the rethrown error reaches stderr");
38
+ assert.doesNotMatch(res.stderr, /is not a function/, "guard must be present, not absent");
39
+ });
@@ -1531,8 +1531,8 @@ introduced?: (AuditIntroduced | null)
1531
1531
  /**
1532
1532
  * Wire-shape envelope for an [`UnresolvedImport`] finding. Mirrors
1533
1533
  * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
1534
- * `actions` array (`resolve-import` primary plus `suppress-line`
1535
- * secondary).
1534
+ * `actions` array (`resolve-import` primary plus config and inline
1535
+ * suppression actions).
1536
1536
  */
1537
1537
  export interface UnresolvedImportFinding {
1538
1538
  /**
@@ -2533,8 +2533,8 @@ vital_signs?: (VitalSigns | null)
2533
2533
  health_score?: (HealthScore | null)
2534
2534
  /**
2535
2535
  * Per-file health scores. Only present when --file-scores is used. Sorted
2536
- * by maintainability_index ascending (worst first). Zero-function files
2537
- * (barrels) are excluded by default.
2536
+ * by risk-aware triage concern, combining low maintainability and high
2537
+ * CRAP risk. Zero-function files (barrels) are excluded by default.
2538
2538
  */
2539
2539
  file_scores?: FileHealthScore[]
2540
2540
  /**
@@ -3740,10 +3740,33 @@ untracked_ratio_percent: number
3740
3740
  }
3741
3741
  export interface RuntimeCoverageFinding {
3742
3742
  /**
3743
- * Stable content-hash ID of the form `fallow:prod:<hash>`, where `<hash>`
3744
- * is the first 8 hex characters of SHA-256(file + function + line + 'prod').
3743
+ * Per-finding suppression key of the form `fallow:prod:<hash>` (first 8 hex
3744
+ * of SHA-256(file + function + line + 'prod')). Hashes the current line, so
3745
+ * it changes when the function moves. Use this to suppress one finding.
3745
3746
  */
3746
3747
  id: string
3748
+ /**
3749
+ * Cross-surface join key of the form `fallow:fn:<hash>`
3750
+ * (`fallow_cov_protocol::function_identity_id`, hashes file + name +
3751
+ * start_line). The same function shares ONE value across findings, hot
3752
+ * paths, blast-radius, and importance entries (the per-finding `id` uses a
3753
+ * per-surface salt, so it differs by surface), and across V8, Istanbul,
3754
+ * and oxc producers (columns are excluded from the hash). Like `id`, it
3755
+ * changes when the function's file, name, or start line changes; it is a
3756
+ * cross-surface / cross-producer join key, not a line-move-immune one.
3757
+ * `null` when the producing surface (or an un-migrated cloud) supplied no
3758
+ * `FunctionIdentity`.
3759
+ */
3760
+ stable_id?: (string | null)
3761
+ /**
3762
+ * Content digest of the function's full-span source slice
3763
+ * (`fallow_cov_protocol::source_hash_for`: first 8 bytes of SHA-256 as 16
3764
+ * lowercase hex). Unlike `stable_id`, this is stable across line moves: a
3765
+ * moved-but-unedited function keeps the same value, so baselines can
3766
+ * suppress it after a pure line shift. `null` when the producing surface
3767
+ * supplied no `source_hash`.
3768
+ */
3769
+ source_hash?: (string | null)
3747
3770
  /**
3748
3771
  * File path relative to the project root.
3749
3772
  */
@@ -3825,6 +3848,12 @@ export interface RuntimeCoverageHotPath {
3825
3848
  * Stable content-hash ID of the form `fallow:hot:<hash>`.
3826
3849
  */
3827
3850
  id: string
3851
+ /**
3852
+ * Cross-surface join key (`fallow:fn:<hash>`) for the hot function. Stable
3853
+ * across line moves; shared with the same function's findings / blast /
3854
+ * importance entries. `null` when no `FunctionIdentity` was supplied.
3855
+ */
3856
+ stable_id?: (string | null)
3828
3857
  /**
3829
3858
  * File path relative to the project root.
3830
3859
  */
@@ -3865,6 +3894,12 @@ export interface RuntimeCoverageBlastRadiusEntry {
3865
3894
  * Stable content-hash ID of the form `fallow:blast:<hash>`.
3866
3895
  */
3867
3896
  id: string
3897
+ /**
3898
+ * Cross-surface join key (`fallow:fn:<hash>`) for the function. Stable
3899
+ * across line moves; shared with the same function's findings / hot-path /
3900
+ * importance entries. `null` when no `FunctionIdentity` was supplied.
3901
+ */
3902
+ stable_id?: (string | null)
3868
3903
  /**
3869
3904
  * File path relative to the project root.
3870
3905
  */
@@ -3897,6 +3932,12 @@ export interface RuntimeCoverageImportanceEntry {
3897
3932
  * Stable content-hash ID of the form `fallow:importance:<hash>`.
3898
3933
  */
3899
3934
  id: string
3935
+ /**
3936
+ * Cross-surface join key (`fallow:fn:<hash>`) for the function. Stable
3937
+ * across line moves; shared with the same function's findings / hot-path /
3938
+ * blast-radius entries. `null` when no `FunctionIdentity` was supplied.
3939
+ */
3940
+ stable_id?: (string | null)
3900
3941
  /**
3901
3942
  * File path relative to the project root.
3902
3943
  */
@@ -4939,8 +4980,8 @@ vital_signs?: (VitalSigns | null)
4939
4980
  health_score?: (HealthScore | null)
4940
4981
  /**
4941
4982
  * Per-file health scores. Only present when --file-scores is used. Sorted
4942
- * by maintainability_index ascending (worst first). Zero-function files
4943
- * (barrels) are excluded by default.
4983
+ * by risk-aware triage concern, combining low maintainability and high
4984
+ * CRAP risk. Zero-function files (barrels) are excluded by default.
4944
4985
  */
4945
4986
  file_scores?: FileHealthScore[]
4946
4987
  /**