fallow-type-aware 3.15.0 → 3.17.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
@@ -25,6 +25,13 @@ and SHA-256 declaration guard, but Fallow owns the final decision and fix
25
25
  policy. The sidecar does not emit TypeScript compiler diagnostics as Fallow
26
26
  findings and does not implement generic typed lint rules.
27
27
 
28
+ The raw TypeScript-Go host cannot currently expose Svelte virtual-module named
29
+ exports. If source code imports or re-exports such a name and checker resolution
30
+ has no declaration target, the sidecar returns
31
+ `svelte-virtual-module-exports` instead of claiming complete evidence. Run
32
+ `svelte-check` for framework diagnostics; Fallow stays fail-closed until a
33
+ supported host seam can preserve virtual source identity and mappings.
34
+
28
35
  ## Run locally
29
36
 
30
37
  ```sh
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { run } from "./src/cli.mjs";
3
+ import { installWindowsChildProcessPolicy } from "./src/windows-child-process.mjs";
4
+
5
+ installWindowsChildProcessPolicy();
4
6
 
5
7
  try {
8
+ const { assertTypescriptBackendResolvable } = await import("./src/backend-preflight.mjs");
9
+ assertTypescriptBackendResolvable();
10
+ const { run } = await import("./src/cli.mjs");
6
11
  await run({ input: process.stdin, output: process.stdout, args: process.argv.slice(2) });
7
12
  } catch (error) {
8
13
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow-type-aware",
3
- "version": "3.15.0",
3
+ "version": "3.17.0",
4
4
  "description": "Optional TypeScript-Go semantic refinement sidecar for Fallow",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@codspeed/tinybench-plugin": "5.7.1",
35
- "tinybench": "6.1.2"
35
+ "tinybench": "6.1.2",
36
+ "zod": "4.4.3"
36
37
  }
37
38
  }
@@ -0,0 +1,42 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ import { BACKEND_VERSION } from "./generated-protocol.mjs";
5
+
6
+ const REQUIRED_MAJOR = Number.parseInt(BACKEND_VERSION, 10);
7
+ const INSTALL_HINT =
8
+ "run `npm ci` in tools/type-aware-sidecar so the sidecar resolves its own typescript install";
9
+
10
+ /**
11
+ * Verify that module resolution hands the sidecar a typescript install that
12
+ * provides the `typescript/unstable/sync` backend entry point.
13
+ *
14
+ * The semantic modules import that entry point statically, so an incompatible
15
+ * resolution (for example a typescript 6 hoisted into an ancestor
16
+ * `node_modules`) fails at link time with a bare module path instead of the
17
+ * version conflict that caused it. This preflight runs before those imports
18
+ * and names the resolved version, its location, and the fix. Exact
19
+ * backend-version parity stays enforced in `protocol.mjs`.
20
+ *
21
+ * @param {string | URL} resolveFrom Module URL or file path to resolve from;
22
+ * defaults to this module so the check mirrors the semantic imports.
23
+ */
24
+ export const assertTypescriptBackendResolvable = (resolveFrom = import.meta.url) => {
25
+ const sidecarRequire = createRequire(resolveFrom);
26
+ let manifestPath;
27
+ try {
28
+ manifestPath = sidecarRequire.resolve("typescript/package.json");
29
+ } catch {
30
+ throw new Error(`typescript is not installed; ${INSTALL_HINT}`);
31
+ }
32
+ const { version } = sidecarRequire(manifestPath);
33
+ const major = Number.parseInt(version, 10);
34
+ if (Number.isFinite(major) && major >= REQUIRED_MAJOR) {
35
+ return;
36
+ }
37
+ throw new Error(
38
+ `resolved typescript ${version} from ${path.dirname(manifestPath)}, which lacks the ` +
39
+ `typescript/unstable/sync backend; fallow-type-aware needs typescript ${BACKEND_VERSION}; ` +
40
+ INSTALL_HINT,
41
+ );
42
+ };
@@ -1,8 +1,8 @@
1
1
  // Generated from crates/api/type-aware-protocol.json. Do not edit.
2
2
  export const TYPE_AWARE_PROTOCOL = Object.freeze({
3
3
  schema_version: 1,
4
- wire_protocol_version: 6,
5
- semantic_schema_version: 2,
4
+ wire_protocol_version: 7,
5
+ semantic_schema_version: 3,
6
6
  analysis_operation: "semantic-queries",
7
7
  status_operation: "status",
8
8
  query_operations: ["symbol-use", "symbol-trace", "api-surface", "symbol-impact", "type-coupling"],
@@ -2,18 +2,107 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
 
5
+ import { SymbolFlags } from "typescript/unstable/sync";
6
+ import {
7
+ isExportDeclaration,
8
+ isImportDeclaration,
9
+ isNamedExports,
10
+ isNamedImports,
11
+ } from "typescript/unstable/ast/is";
12
+
5
13
  import { canonicalFileIdentity } from "./file-identity.mjs";
6
- import { relativePath } from "./semantic-identity.mjs";
14
+ import { projectSourceFiles, relativePath } from "./semantic-identity.mjs";
7
15
 
8
16
  const INFERRED_PROJECT = "<inferred>";
9
17
  const compareText = (left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right));
10
18
  const slash = (value) => value.split(path.sep).join("/");
11
19
 
12
- const blockingDiagnosticCount = (project) =>
13
- project.program.getConfigFileParsingDiagnostics().length +
14
- project.program.getProgramDiagnostics().length +
15
- project.program.getSyntacticDiagnostics().length +
16
- project.program.getBindDiagnostics().length;
20
+ const structuralDiagnostics = (project) => [
21
+ ...project.program.getConfigFileParsingDiagnostics(),
22
+ ...project.program.getProgramDiagnostics(),
23
+ ...project.program.getSyntacticDiagnostics(),
24
+ ...project.program.getBindDiagnostics(),
25
+ ];
26
+
27
+ const isProjectLocalDiagnostic = (project, diagnostic) => {
28
+ if (!diagnostic.fileName) return true;
29
+ const sourceFile = project.program.getSourceFile(diagnostic.fileName);
30
+ if (!sourceFile) return true;
31
+ return (
32
+ !project.program.isSourceFileDefaultLibrary(sourceFile) &&
33
+ !project.program.isSourceFileFromExternalLibrary(sourceFile)
34
+ );
35
+ };
36
+
37
+ const isSvelteSpecifier = (node) =>
38
+ typeof node.moduleSpecifier?.text === "string" && node.moduleSpecifier.text.endsWith(".svelte");
39
+
40
+ const isUnknownAlias = (checker, specifier) => {
41
+ const symbol = checker.getSymbolAtLocation(specifier.name);
42
+ if (!symbol) return true;
43
+ return checker.isUnknownSymbol(checker.getAliasedSymbol(symbol));
44
+ };
45
+
46
+ const concreteExportSymbol = (checker, symbol) => {
47
+ const target =
48
+ (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : checker.getAliasedSymbol(symbol);
49
+ return !checker.isUnknownSymbol(target) && (target.declarations?.length ?? 0) > 0;
50
+ };
51
+
52
+ const hasProvableNamedExports = (project, declaration) => {
53
+ const moduleSymbol = project.checker.getSymbolAtLocation(declaration.moduleSpecifier);
54
+ if (!moduleSymbol) return false;
55
+ const hasConcreteModuleDeclaration = moduleSymbol.declarations?.some((moduleDeclaration) => {
56
+ const declarationPath =
57
+ moduleDeclaration.path ??
58
+ moduleDeclaration.fileName ??
59
+ moduleDeclaration.getSourceFile?.().fileName ??
60
+ "";
61
+ return declarationPath.endsWith(".d.svelte") || declarationPath.endsWith(".d.svelte.ts");
62
+ });
63
+ if (!hasConcreteModuleDeclaration) return false;
64
+ const namedExports = project.checker
65
+ .getExportsOfModule(moduleSymbol)
66
+ .filter((symbol) => symbol.name !== "default");
67
+ return namedExports.every((symbol) => concreteExportSymbol(project.checker, symbol));
68
+ };
69
+
70
+ const svelteDeclarationHasGap = (project, node) => {
71
+ if (!isSvelteSpecifier(node)) return false;
72
+ if (isImportDeclaration(node)) {
73
+ const bindings = node.importClause?.namedBindings;
74
+ return (
75
+ bindings !== undefined &&
76
+ isNamedImports(bindings) &&
77
+ bindings.elements.some((item) => isUnknownAlias(project.checker, item))
78
+ );
79
+ }
80
+ if (!isExportDeclaration(node)) return false;
81
+ if (!node.exportClause) return !hasProvableNamedExports(project, node);
82
+ if (!isNamedExports(node.exportClause)) return !hasProvableNamedExports(project, node);
83
+ return node.exportClause.elements.some((item) => isUnknownAlias(project.checker, item));
84
+ };
85
+
86
+ const sourceHasSvelteVirtualModuleGap = (project, sourceFile) => {
87
+ let gap = false;
88
+ const visit = (node) => {
89
+ if (gap) return;
90
+ gap = svelteDeclarationHasGap(project, node);
91
+ if (gap) return;
92
+ node.forEachChild((child) => {
93
+ visit(child);
94
+ return undefined;
95
+ });
96
+ };
97
+ visit(sourceFile);
98
+ return gap;
99
+ };
100
+
101
+ const hasSvelteVirtualModuleGap = (project) =>
102
+ projectSourceFiles(project).some(
103
+ (sourceFile) =>
104
+ sourceFile.text.includes(".svelte") && sourceHasSvelteVirtualModuleGap(project, sourceFile),
105
+ );
17
106
 
18
107
  const configPath = (root, project) => {
19
108
  const normalized = slash(project.configFileName);
@@ -173,15 +262,27 @@ const effectiveProjectConfigHash = (root, project) => {
173
262
  };
174
263
 
175
264
  export const projectState = (root, project, source) => {
176
- const diagnosticCount = blockingDiagnosticCount(project);
265
+ const diagnostics = structuralDiagnostics(project);
266
+ const localDiagnosticCount = diagnostics.filter((diagnostic) =>
267
+ isProjectLocalDiagnostic(project, diagnostic),
268
+ ).length;
269
+ const hasLocalDiagnostics = localDiagnosticCount > 0;
270
+ const svelteVirtualModuleGap = !hasLocalDiagnostics && hasSvelteVirtualModuleGap(project);
271
+ const reasonCode = hasLocalDiagnostics
272
+ ? "blocking-diagnostics"
273
+ : svelteVirtualModuleGap
274
+ ? "svelte-virtual-module-exports"
275
+ : diagnostics.length > 0
276
+ ? "blocking-diagnostics"
277
+ : null;
177
278
  return {
178
279
  project,
179
280
  config: configPath(root, project),
180
281
  effective_config_hash: effectiveProjectConfigHash(root, project),
181
282
  source,
182
- status: diagnosticCount === 0 ? "complete" : "unavailable",
183
- reason_code: diagnosticCount === 0 ? null : "blocking-diagnostics",
184
- blocking_diagnostic_count: diagnosticCount,
283
+ status: reasonCode === null ? "complete" : "unavailable",
284
+ reason_code: reasonCode,
285
+ blocking_diagnostic_count: reasonCode === "blocking-diagnostics" ? diagnostics.length : 0,
185
286
  source_file_count: project.program.getSourceFileNames().length,
186
287
  program_reused: false,
187
288
  candidate_count: 0,
@@ -12,9 +12,11 @@ import {
12
12
  isMethodDeclaration,
13
13
  isMethodSignatureDeclaration,
14
14
  isModuleDeclaration,
15
+ isNamespaceExport,
15
16
  isPropertyDeclaration,
16
17
  isPropertySignatureDeclaration,
17
18
  isSetAccessorDeclaration,
19
+ isSourceFile,
18
20
  isTypeAliasDeclaration,
19
21
  isVariableDeclaration,
20
22
  } from "typescript/unstable/ast/is";
@@ -45,7 +47,12 @@ const TYPE_ONLY_DECLARATIONS = [
45
47
  isTypeAliasDeclaration,
46
48
  isPropertySignatureDeclaration,
47
49
  ];
48
- const DUAL_NAMESPACE_DECLARATIONS = [isClassDeclaration, isClassExpression, isEnumDeclaration];
50
+ const DUAL_NAMESPACE_DECLARATIONS = [
51
+ isClassDeclaration,
52
+ isClassExpression,
53
+ isEnumDeclaration,
54
+ isSourceFile,
55
+ ];
49
56
  const DECLARATION_OWNER_NODES = [isClassDeclaration, isClassExpression, isInterfaceDeclaration];
50
57
 
51
58
  const slash = (value) => value.split(path.sep).join("/");
@@ -101,6 +108,8 @@ export const ownerDeclaration = (node) => {
101
108
 
102
109
  export const isDeclaration = (node) => declarationKind(node) !== undefined;
103
110
 
111
+ export const isExportAnchor = (node) => isExportSpecifier(node) || isNamespaceExport(node);
112
+
104
113
  const visit = (node, callback) => {
105
114
  callback(node);
106
115
  node.forEachChild((child) => {
@@ -151,6 +160,16 @@ const indexExportSpecifier = (index, node) => {
151
160
  );
152
161
  };
153
162
 
163
+ const indexNamespaceExport = (index, node) => {
164
+ const exportedName = nodeText(node.name);
165
+ if (!exportedName) return;
166
+ [node.parent, node]
167
+ .flatMap((candidate) => positions(candidate))
168
+ .forEach((position) =>
169
+ indexExportPosition(index, node, { localName: exportedName, exportedName }, position),
170
+ );
171
+ };
172
+
154
173
  const defaultModifierFor = (node) =>
155
174
  node.modifiers?.find((modifier) => modifier.getText(node.getSourceFile()) === "default");
156
175
 
@@ -197,6 +216,10 @@ const declarationIndex = (sourceFile) => {
197
216
  indexExportSpecifier(index, node);
198
217
  return;
199
218
  }
219
+ if (isNamespaceExport(node)) {
220
+ indexNamespaceExport(index, node);
221
+ return;
222
+ }
200
223
  indexDeclarationNode(index, node);
201
224
  });
202
225
  declarationIndexes.set(sourceFile, index);
@@ -205,7 +228,7 @@ const declarationIndex = (sourceFile) => {
205
228
 
206
229
  export const findDeclaration = (sourceFile, identity) => {
207
230
  const anchor = declarationIndex(sourceFile).get(anchorKey(identity));
208
- if (!anchor || isExportSpecifier(anchor)) return anchor;
231
+ if (!anchor || isExportAnchor(anchor)) return anchor;
209
232
  return declarationNamespaces(anchor).has(identity.namespace) ? anchor : undefined;
210
233
  };
211
234
 
package/src/semantic.mjs CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  declarationsForSymbol,
36
36
  findDeclaration,
37
37
  isDeclaration,
38
+ isExportAnchor,
38
39
  isProjectSource,
39
40
  nodeText,
40
41
  ownerDeclaration,
@@ -156,6 +157,10 @@ const REASON_ACTIONS = new Map([
156
157
  "blocking-diagnostics",
157
158
  "Repair structural TypeScript diagnostics in every selected project and retry.",
158
159
  ],
160
+ [
161
+ "svelte-virtual-module-exports",
162
+ "Run svelte-check for framework diagnostics. See https://docs.fallow.tools/analysis/type-aware#svelte-virtual-module-exports for supported Svelte project setup.",
163
+ ],
159
164
  [
160
165
  "unknown-entry-point",
161
166
  "Refresh the package entry points or pass project-relative source entry points.",
@@ -165,21 +170,22 @@ const DEFAULT_REASON_ACTION =
165
170
  "Narrow the query to a specific symbol, entry point, or healthy TypeScript project and retry.";
166
171
  const REASON_PRIORITY = new Map([
167
172
  ["blocking-diagnostics", 0],
168
- ["incomplete-project-coverage", 1],
169
- ["framework-contract-provenance", 2],
170
- ["ambiguous-project", 3],
171
- ["unknown-symbol", 4],
172
- ["unknown-entry-point", 5],
173
- ["decorated-declaration", 6],
174
- ["optional-contract", 7],
175
- ["accessor-pair", 8],
176
- ["overload-set", 9],
177
- ["attached-comment", 10],
178
- ["abstract-declaration", 11],
179
- ["dynamic-member-access", 12],
180
- ["virtual-dispatch", 13],
181
- ["dynamic-behavior", 14],
182
- ["evidence-limit", 15],
173
+ ["svelte-virtual-module-exports", 1],
174
+ ["incomplete-project-coverage", 2],
175
+ ["framework-contract-provenance", 3],
176
+ ["ambiguous-project", 4],
177
+ ["unknown-symbol", 5],
178
+ ["unknown-entry-point", 6],
179
+ ["decorated-declaration", 7],
180
+ ["optional-contract", 8],
181
+ ["accessor-pair", 9],
182
+ ["overload-set", 10],
183
+ ["attached-comment", 11],
184
+ ["abstract-declaration", 12],
185
+ ["dynamic-member-access", 13],
186
+ ["virtual-dispatch", 14],
187
+ ["dynamic-behavior", 15],
188
+ ["evidence-limit", 16],
183
189
  ]);
184
190
 
185
191
  const actionForReason = (reasonCode) => REASON_ACTIONS.get(reasonCode) ?? DEFAULT_REASON_ACTION;
@@ -200,6 +206,22 @@ const combineOmissions = (omissions) => {
200
206
  return [...counts].map(([reason_code, count]) => ({ reason_code, count }));
201
207
  };
202
208
 
209
+ const projectStateReason = (state) => state.reason_code ?? "blocking-diagnostics";
210
+
211
+ const unavailableProjectOmissions = (states) =>
212
+ combineOmissions(
213
+ states
214
+ .filter((state) => state.status !== "complete")
215
+ .map((state) => ({ reason_code: projectStateReason(state), count: 1 })),
216
+ );
217
+
218
+ const unavailableProjectReason = (states) => {
219
+ if (states.length === 0) return "no-project";
220
+ return (
221
+ unavailableProjectOmissions(states).toSorted(compareOmissions)[0]?.reason_code ?? "no-project"
222
+ );
223
+ };
224
+
203
225
  const resultStatus = (partial) => (partial ? "partial" : "complete");
204
226
  const resultReason = (omissions) => (omissions.length > 0 ? omissions[0].reason_code : null);
205
227
  const resultActions = (omissions) =>
@@ -680,8 +702,6 @@ const symbolResolutionError = (query, reasonCode, action) => ({
680
702
  });
681
703
 
682
704
  const isCompleteProjectState = (state) => state?.status === "complete";
683
- const selectedProjectName = (state) => state?.config ?? "the selected project";
684
-
685
705
  const owningProjectContexts = (statesByProject, absolutePath) =>
686
706
  [...statesByProject.entries()]
687
707
  .filter(([project]) => project.program.getSourceFile(absolutePath))
@@ -721,13 +741,16 @@ const selectSymbolContext = (
721
741
  }
722
742
  const completeOwners = owners.filter(({ state }) => isCompleteProjectState(state));
723
743
  if (completeOwners.length === 0) {
724
- return {
725
- ...owners[0],
726
- ...symbolResolutionError(
727
- query,
728
- "blocking-diagnostics",
729
- `Repair structural diagnostics in ${selectedProjectName(owners[0].state)} and retry.`,
744
+ const selectedOwner = [...owners].toSorted((left, right) =>
745
+ compareOmissions(
746
+ { reason_code: projectStateReason(left.state) },
747
+ { reason_code: projectStateReason(right.state) },
730
748
  ),
749
+ )[0];
750
+ const reasonCode = projectStateReason(selectedOwner.state);
751
+ return {
752
+ ...selectedOwner,
753
+ ...symbolResolutionError(query, reasonCode, actionForReason(reasonCode)),
731
754
  };
732
755
  }
733
756
  return { owners, completeOwners };
@@ -735,7 +758,7 @@ const selectSymbolContext = (
735
758
 
736
759
  const resolvedAnchorTarget = (project, anchor, requestedSymbol) => {
737
760
  if (!anchor) return undefined;
738
- if (!isExportSpecifier(anchor)) {
761
+ if (!isExportAnchor(anchor)) {
739
762
  return declarationNamespaces(anchor).has(requestedSymbol.namespace)
740
763
  ? { declaration: anchor, namespace: requestedSymbol.namespace }
741
764
  : undefined;
@@ -1376,6 +1399,13 @@ const signatureTypes = (project, type, anchor) =>
1376
1399
  );
1377
1400
 
1378
1401
  const checkerTypeChildren = (project, type, anchor, hasNamedDeclaration) => {
1402
+ if (type.isTypeParameter()) {
1403
+ const constraint = safeCheckerValue(
1404
+ () => project.checker.getConstraintOfTypeParameter(type),
1405
+ undefined,
1406
+ );
1407
+ return constraint ? [constraint] : [];
1408
+ }
1379
1409
  const structural = [
1380
1410
  ...(type.getTypes() ?? []),
1381
1411
  ...safeCheckerValue(() => type.getAliasTypeArguments(), []),
@@ -1546,12 +1576,9 @@ const graphProjects = (snapshot, explicitProjects) =>
1546
1576
  const readyProjectStates = (query, states) => {
1547
1577
  const ready = states.filter((state) => state.status === "complete");
1548
1578
  if (ready.length > 0) return { ready };
1579
+ const reasonCode = unavailableProjectReason(states);
1549
1580
  return {
1550
- error: unavailable(
1551
- query,
1552
- states.length === 0 ? "no-project" : "blocking-diagnostics",
1553
- "Pass a healthy tsconfig containing the package entry points and retry.",
1554
- ),
1581
+ error: unavailable(query, reasonCode, actionForReason(reasonCode)),
1555
1582
  };
1556
1583
  };
1557
1584
 
@@ -1683,7 +1710,7 @@ const analyzeApiSurface = (root, query, states, evidenceLimit) => {
1683
1710
  reason_code: "evidence-limit",
1684
1711
  count: omissionCount,
1685
1712
  },
1686
- { reason_code: "blocking-diagnostics", count: unavailableProjectCount },
1713
+ ...unavailableProjectOmissions(states),
1687
1714
  { reason_code: "unknown-entry-point", count: missingEntryPointCount },
1688
1715
  ],
1689
1716
  });
@@ -1762,6 +1789,7 @@ const analyzeGraphSemanticQuery = (root, query, graphStates, evidenceLimit) => {
1762
1789
  missingEntryPoints,
1763
1790
  publicApiGraph,
1764
1791
  readyProjectStates,
1792
+ unavailableProjectOmissions,
1765
1793
  uniqueSorted,
1766
1794
  },
1767
1795
  );
@@ -1841,6 +1869,7 @@ const recordAbstainedProjectOutcome = (state, result) => {
1841
1869
  "no-project",
1842
1870
  "ambiguous-project",
1843
1871
  "blocking-diagnostics",
1872
+ "svelte-virtual-module-exports",
1844
1873
  "unknown-symbol",
1845
1874
  "incomplete-project-coverage",
1846
1875
  ]);
@@ -118,7 +118,6 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
118
118
  const highCouplingThreshold = percentile(degrees, 0.9);
119
119
  const topContributors = topCouplingContributors(perFile);
120
120
  const cycles = query.includeCycles ? findCycles(edges) : [];
121
- const unavailableProjectCount = states.length - readiness.ready.length;
122
121
  const missingEntryPointCount = services.missingEntryPoints(query, resolvedEntryPoints);
123
122
  const nestedFileOmissionCount = nestedCouplingOmissionCount(perFile, evidenceLimit);
124
123
  const boundFile = (entry) => boundCoupledFile(entry, evidenceLimit);
@@ -159,7 +158,7 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
159
158
  Math.max(0, cycles.length - evidenceLimit) +
160
159
  nestedFileOmissionCount,
161
160
  },
162
- { reason_code: "blocking-diagnostics", count: unavailableProjectCount },
161
+ ...services.unavailableProjectOmissions(states),
163
162
  { reason_code: "unknown-entry-point", count: missingEntryPointCount },
164
163
  ],
165
164
  });
@@ -0,0 +1,25 @@
1
+ import childProcess from "node:child_process";
2
+ import { syncBuiltinESMExports } from "node:module";
3
+
4
+ const INSTALL_MARKER = Symbol.for("fallow.type-aware.windows-child-process-policy");
5
+
6
+ const hiddenOptions = (options) => ({ ...options, windowsHide: true });
7
+
8
+ /** Keep TypeScript-Go child processes hidden when the sidecar runs on Windows. */
9
+ export const installWindowsChildProcessPolicy = ({
10
+ childProcess: processApi = childProcess,
11
+ platform = process.platform,
12
+ syncBuiltinESMExports: syncExports = syncBuiltinESMExports,
13
+ } = {}) => {
14
+ if (platform !== "win32" || processApi[INSTALL_MARKER] === true) return;
15
+
16
+ const originalSpawn = processApi.spawn;
17
+ processApi.spawn = function spawnHidden(command, args, options) {
18
+ if (Array.isArray(args)) {
19
+ return originalSpawn.call(this, command, args, hiddenOptions(options));
20
+ }
21
+ return originalSpawn.call(this, command, hiddenOptions(args));
22
+ };
23
+ Object.defineProperty(processApi, INSTALL_MARKER, { value: true });
24
+ syncExports();
25
+ };