@veewo/gitnexus 1.5.0-rc.4 → 1.5.1

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.
Files changed (175) hide show
  1. package/dist/benchmark/agent-context/runner.js +3 -0
  2. package/dist/benchmark/agent-context/runner.test.js +22 -0
  3. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
  4. package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
  5. package/dist/benchmark/agent-safe-query-context/io.js +86 -0
  6. package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
  7. package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
  8. package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
  9. package/dist/benchmark/agent-safe-query-context/report.js +159 -0
  10. package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
  11. package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
  12. package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
  13. package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
  14. package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
  15. package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
  16. package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
  17. package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
  18. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
  19. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
  20. package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
  21. package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
  22. package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
  23. package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
  24. package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
  25. package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
  26. package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
  27. package/dist/benchmark/agent-safe-query-context/types.js +8 -0
  28. package/dist/benchmark/analyze-runner.d.ts +1 -1
  29. package/dist/benchmark/analyze-runner.js +4 -3
  30. package/dist/benchmark/analyze-runner.test.js +7 -0
  31. package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
  32. package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
  33. package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
  34. package/dist/benchmark/runtime-poc/runner.js +163 -0
  35. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
  36. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
  37. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
  38. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
  39. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
  40. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
  41. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
  42. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
  43. package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
  44. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
  45. package/dist/cli/ai-context.d.ts +0 -1
  46. package/dist/cli/ai-context.js +5 -6
  47. package/dist/cli/ai-context.test.js +8 -0
  48. package/dist/cli/analyze-options.js +58 -34
  49. package/dist/cli/analyze-options.test.js +57 -0
  50. package/dist/cli/analyze-runtime-summary.js +2 -0
  51. package/dist/cli/analyze-runtime-summary.test.js +12 -0
  52. package/dist/cli/analyze-summary.d.ts +4 -0
  53. package/dist/cli/analyze-summary.js +43 -0
  54. package/dist/cli/analyze-summary.test.js +65 -1
  55. package/dist/cli/analyze.d.ts +11 -0
  56. package/dist/cli/analyze.js +34 -5
  57. package/dist/cli/analyze.test.d.ts +1 -0
  58. package/dist/cli/analyze.test.js +25 -0
  59. package/dist/cli/benchmark-agent-context.js +1 -1
  60. package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
  61. package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
  62. package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
  63. package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
  64. package/dist/cli/benchmark-unity.js +1 -1
  65. package/dist/cli/benchmark-unity.test.js +5 -1
  66. package/dist/cli/benchmark.d.ts +29 -0
  67. package/dist/cli/benchmark.js +55 -0
  68. package/dist/cli/index.js +27 -2
  69. package/dist/cli/rule-lab.d.ts +3 -7
  70. package/dist/cli/rule-lab.js +13 -22
  71. package/dist/cli/rule-lab.test.js +23 -3
  72. package/dist/cli/scope-manifest-config.d.ts +9 -0
  73. package/dist/cli/scope-manifest-config.js +37 -0
  74. package/dist/cli/setup.js +40 -41
  75. package/dist/cli/setup.test.js +14 -14
  76. package/dist/cli/sync-manifest.d.ts +27 -0
  77. package/dist/cli/sync-manifest.js +200 -0
  78. package/dist/cli/sync-manifest.test.d.ts +1 -0
  79. package/dist/cli/sync-manifest.test.js +88 -0
  80. package/dist/cli/tool.d.ts +2 -0
  81. package/dist/cli/tool.js +2 -0
  82. package/dist/core/config/unity-config.d.ts +1 -1
  83. package/dist/core/config/unity-config.js +1 -1
  84. package/dist/core/ingestion/call-processor.d.ts +2 -1
  85. package/dist/core/ingestion/call-processor.js +28 -6
  86. package/dist/core/ingestion/heritage-processor.d.ts +2 -1
  87. package/dist/core/ingestion/heritage-processor.js +30 -7
  88. package/dist/core/ingestion/import-processor.d.ts +2 -1
  89. package/dist/core/ingestion/import-processor.js +28 -6
  90. package/dist/core/ingestion/parsing-processor.d.ts +5 -3
  91. package/dist/core/ingestion/parsing-processor.js +46 -13
  92. package/dist/core/ingestion/pipeline.js +100 -19
  93. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
  94. package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
  95. package/dist/core/ingestion/unity-parity-seed.js +8 -0
  96. package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
  97. package/dist/core/ingestion/unity-resource-processor.js +102 -0
  98. package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
  99. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +16 -1
  100. package/dist/core/ingestion/unity-runtime-binding-rules.js +193 -42
  101. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
  102. package/dist/core/ingestion/workers/parse-worker.js +50 -6
  103. package/dist/core/lbug/csv-generator.test.js +2 -2
  104. package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
  105. package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
  106. package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
  107. package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
  108. package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
  109. package/dist/core/tree-sitter/parser-loader.js +19 -0
  110. package/dist/core/unity/doc-contract.test.d.ts +1 -0
  111. package/dist/core/unity/doc-contract.test.js +30 -0
  112. package/dist/core/unity/prefab-source-scan.d.ts +25 -0
  113. package/dist/core/unity/prefab-source-scan.js +152 -0
  114. package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
  115. package/dist/core/unity/prefab-source-scan.test.js +70 -0
  116. package/dist/core/unity/scan-context.d.ts +12 -0
  117. package/dist/core/unity/scan-context.js +50 -2
  118. package/dist/core/unity/scan-context.test.js +74 -0
  119. package/dist/mcp/local/agent-safe-response.d.ts +10 -0
  120. package/dist/mcp/local/agent-safe-response.js +639 -0
  121. package/dist/mcp/local/derived-process-reader.js +1 -1
  122. package/dist/mcp/local/local-backend.d.ts +18 -1
  123. package/dist/mcp/local/local-backend.js +319 -125
  124. package/dist/mcp/local/process-confidence.d.ts +1 -2
  125. package/dist/mcp/local/process-confidence.js +0 -3
  126. package/dist/mcp/local/process-confidence.test.js +4 -2
  127. package/dist/mcp/local/process-evidence.d.ts +1 -8
  128. package/dist/mcp/local/process-evidence.js +1 -23
  129. package/dist/mcp/local/process-evidence.test.js +2 -16
  130. package/dist/mcp/local/process-ref.d.ts +1 -1
  131. package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
  132. package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
  133. package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
  134. package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
  135. package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
  136. package/dist/mcp/local/runtime-chain-verify.js +149 -138
  137. package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
  138. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
  139. package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
  140. package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
  141. package/dist/mcp/local/runtime-claim.d.ts +11 -0
  142. package/dist/mcp/local/runtime-claim.js +28 -0
  143. package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
  144. package/dist/mcp/local/unity-evidence-view.js +1 -1
  145. package/dist/mcp/local/unity-evidence-view.test.js +22 -0
  146. package/dist/mcp/tools.js +51 -21
  147. package/dist/rule-lab/analyze.d.ts +2 -1
  148. package/dist/rule-lab/analyze.js +94 -59
  149. package/dist/rule-lab/analyze.test.js +238 -20
  150. package/dist/rule-lab/curate.d.ts +2 -1
  151. package/dist/rule-lab/curate.js +24 -3
  152. package/dist/rule-lab/curate.test.js +65 -0
  153. package/dist/rule-lab/curation-input-builder.d.ts +45 -0
  154. package/dist/rule-lab/curation-input-builder.js +133 -0
  155. package/dist/rule-lab/promote.js +80 -7
  156. package/dist/rule-lab/promote.test.js +150 -0
  157. package/dist/rule-lab/review-pack.d.ts +3 -0
  158. package/dist/rule-lab/review-pack.js +41 -1
  159. package/dist/rule-lab/review-pack.test.js +67 -0
  160. package/dist/rule-lab/types.d.ts +29 -0
  161. package/dist/types/pipeline.d.ts +16 -0
  162. package/package.json +14 -13
  163. package/scripts/check-sync-manifest-traceability.mjs +203 -0
  164. package/scripts/run-node-tests.mjs +61 -0
  165. package/scripts/tree-sitter-audit-classify.mjs +172 -0
  166. package/skills/_shared/unity-rule-authoring-contract.md +64 -0
  167. package/skills/_shared/unity-runtime-process-contract.md +16 -0
  168. package/skills/gitnexus-cli.md +44 -4
  169. package/skills/gitnexus-debugging.md +9 -0
  170. package/skills/gitnexus-exploring.md +66 -18
  171. package/skills/gitnexus-guide.md +42 -3
  172. package/skills/gitnexus-impact-analysis.md +8 -0
  173. package/skills/gitnexus-pr-review.md +8 -0
  174. package/skills/gitnexus-refactoring.md +8 -0
  175. package/skills/gitnexus-unity-rule-gen.md +66 -312
@@ -1,25 +1,30 @@
1
1
  import Parser from 'tree-sitter';
2
- import { loadParser, loadLanguage, isLanguageAvailable } from '../tree-sitter/parser-loader.js';
2
+ import { loadParser, loadLanguage, isLanguageAvailable, parseContent } from '../tree-sitter/parser-loader.js';
3
3
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
4
4
  import { generateId } from '../../lib/utils.js';
5
5
  import { getLanguageFromFilename, yieldToEventLoop, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature } from './utils.js';
6
6
  import { isNodeExported } from './export-detection.js';
7
7
  import { detectFrameworkFromAST } from './framework-detection.js';
8
8
  import { typeConfigs } from './type-extractors/index.js';
9
- import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from './constants.js';
9
+ import { TREE_SITTER_MAX_BUFFER } from './constants.js';
10
10
  // isNodeExported imported from ./export-detection.js (shared module)
11
11
  // Re-export for backward compatibility with any external consumers
12
12
  export { isNodeExported } from './export-detection.js';
13
13
  // ============================================================================
14
14
  // Worker-based parallel parsing
15
15
  // ============================================================================
16
- const processParsingWithWorkers = async (graph, files, symbolTable, astCache, workerPool, onFileProgress) => {
16
+ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, workerPool, onFileProgress, onRawFallbackParse) => {
17
17
  // Filter to parseable files only
18
18
  const parseableFiles = [];
19
19
  for (const file of files) {
20
20
  const lang = getLanguageFromFilename(file.path);
21
- if (lang)
22
- parseableFiles.push({ path: file.path, content: file.content });
21
+ if (lang) {
22
+ parseableFiles.push({
23
+ path: file.path,
24
+ content: file.content,
25
+ ...(file.rawContent ? { rawContent: file.rawContent } : {}),
26
+ });
27
+ }
23
28
  }
24
29
  if (parseableFiles.length === 0)
25
30
  return { imports: [], calls: [], heritage: [], routes: [], constructorBindings: [] };
@@ -34,6 +39,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
34
39
  const allHeritage = [];
35
40
  const allRoutes = [];
36
41
  const allConstructorBindings = [];
42
+ let rawFallbackCount = 0;
37
43
  for (const result of chunkResults) {
38
44
  for (const node of result.nodes) {
39
45
  graph.addNode({
@@ -57,7 +63,10 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
57
63
  allHeritage.push(...result.heritage);
58
64
  allRoutes.push(...result.routes);
59
65
  allConstructorBindings.push(...result.constructorBindings);
66
+ rawFallbackCount += result.csharpPreprocFallbackFiles;
60
67
  }
68
+ if (rawFallbackCount > 0)
69
+ onRawFallbackParse?.(rawFallbackCount);
61
70
  // Merge and log skipped languages from workers
62
71
  const skippedLanguages = new Map();
63
72
  for (const result of chunkResults) {
@@ -78,7 +87,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
78
87
  // ============================================================================
79
88
  // Sequential fallback (original implementation)
80
89
  // ============================================================================
81
- const processParsingSequential = async (graph, files, symbolTable, astCache, onFileProgress) => {
90
+ const processParsingSequential = async (graph, files, symbolTable, astCache, onFileProgress, onRawFallbackParse) => {
82
91
  const parser = await loadParser();
83
92
  const total = files.length;
84
93
  const skippedLanguages = new Map();
@@ -106,11 +115,35 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
106
115
  }
107
116
  let tree;
108
117
  try {
109
- tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
118
+ tree = parseContent(file.content);
110
119
  }
111
- catch (parseError) {
112
- console.warn(`Skipping unparseable file: ${file.path}`);
113
- continue;
120
+ catch {
121
+ if (file.rawContent && file.rawContent !== file.content) {
122
+ try {
123
+ tree = parseContent(file.rawContent);
124
+ onRawFallbackParse?.(1);
125
+ }
126
+ catch {
127
+ console.warn(`Skipping unparseable file: ${file.path}`);
128
+ continue;
129
+ }
130
+ }
131
+ else {
132
+ console.warn(`Skipping unparseable file: ${file.path}`);
133
+ continue;
134
+ }
135
+ }
136
+ if (file.rawContent && file.rawContent !== file.content && tree.rootNode?.hasError) {
137
+ try {
138
+ const rawTree = parseContent(file.rawContent);
139
+ if (!rawTree.rootNode?.hasError) {
140
+ tree = rawTree;
141
+ onRawFallbackParse?.(1);
142
+ }
143
+ }
144
+ catch {
145
+ // Keep normalized parse result when raw fallback fails
146
+ }
114
147
  }
115
148
  astCache.set(file.path, tree);
116
149
  const queryString = LANGUAGE_QUERIES[language];
@@ -274,16 +307,16 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
274
307
  // ============================================================================
275
308
  // Public API
276
309
  // ============================================================================
277
- export const processParsing = async (graph, files, symbolTable, astCache, onFileProgress, workerPool) => {
310
+ export const processParsing = async (graph, files, symbolTable, astCache, onFileProgress, workerPool, onRawFallbackParse) => {
278
311
  if (workerPool) {
279
312
  try {
280
- return await processParsingWithWorkers(graph, files, symbolTable, astCache, workerPool, onFileProgress);
313
+ return await processParsingWithWorkers(graph, files, symbolTable, astCache, workerPool, onFileProgress, onRawFallbackParse);
281
314
  }
282
315
  catch (err) {
283
316
  console.warn('Worker pool parsing failed, falling back to sequential:', err instanceof Error ? err.message : err);
284
317
  }
285
318
  }
286
319
  // Fallback: sequential parsing (no pre-extracted data)
287
- await processParsingSequential(graph, files, symbolTable, astCache, onFileProgress);
320
+ await processParsingSequential(graph, files, symbolTable, astCache, onFileProgress, onRawFallbackParse);
288
321
  return null;
289
322
  };
@@ -11,11 +11,14 @@ import { processUnityResources } from './unity-resource-processor.js';
11
11
  import { applyUnityLifecycleSyntheticCalls } from './unity-lifecycle-synthetic-calls.js';
12
12
  import { applyUnityRuntimeBindingRules } from './unity-runtime-binding-rules.js';
13
13
  import { resolveUnityConfig } from '../config/unity-config.js';
14
+ import { loadCSharpDefineProfileFromCsproj } from '../tree-sitter/csharp-define-profile.js';
15
+ import { normalizeCSharpPreprocessorBranches } from '../tree-sitter/csharp-preproc-normalizer.js';
14
16
  import { loadAnalyzeRules } from '../../mcp/local/runtime-claim-rule-registry.js';
15
17
  import { createResolutionContext } from './resolution-context.js';
16
18
  import { createASTCache } from './ast-cache.js';
17
19
  import { walkRepositoryPaths, readFileContents, walkUnityResourcePaths } from './filesystem-walker.js';
18
20
  import { getLanguageFromFilename } from './utils.js';
21
+ import { SupportedLanguages } from '../../config/supported-languages.js';
19
22
  import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
20
23
  import { createWorkerPool } from './workers/worker-pool.js';
21
24
  import { selectEntriesByScopeRules } from './scope-filter.js';
@@ -39,7 +42,24 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
39
42
  astCache.clear();
40
43
  ctx.clear();
41
44
  };
45
+ let csharpDefineSymbols;
46
+ const csharpUndefinedSymbols = new Set();
47
+ let csharpPreprocDiagnostics;
42
48
  try {
49
+ if (options?.csharpDefineCsproj) {
50
+ const defineProfile = await loadCSharpDefineProfileFromCsproj(options.csharpDefineCsproj);
51
+ csharpDefineSymbols = defineProfile.symbols;
52
+ csharpPreprocDiagnostics = {
53
+ enabled: true,
54
+ sourcePath: defineProfile.sourcePath,
55
+ defineSymbolCount: defineProfile.symbols.size,
56
+ normalizedFiles: 0,
57
+ fallbackFiles: 0,
58
+ skippedFiles: 0,
59
+ expressionErrors: 0,
60
+ undefinedSymbols: [],
61
+ };
62
+ }
43
63
  // ── Phase 1: Scan paths only (no content read) ─────────────────────
44
64
  onProgress({
45
65
  phase: 'extracting',
@@ -184,7 +204,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
184
204
  // Calls/heritage use the symbol table built so far (symbols from earlier chunks
185
205
  // are already registered). This trades ~5% cross-chunk resolution accuracy for
186
206
  // 200-400MB less memory — critical for Linux-kernel-scale repos.
187
- const sequentialChunkPaths = [];
207
+ const sequentialChunkFiles = [];
188
208
  try {
189
209
  for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
190
210
  const chunkPaths = chunks[chunkIdx];
@@ -192,7 +212,27 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
192
212
  const chunkContents = await readFileContents(repoPath, chunkPaths);
193
213
  const chunkFiles = chunkPaths
194
214
  .filter(p => chunkContents.has(p))
195
- .map(p => ({ path: p, content: chunkContents.get(p) }));
215
+ .map((p) => {
216
+ const originalContent = chunkContents.get(p);
217
+ if (!csharpDefineSymbols || getLanguageFromFilename(p) !== SupportedLanguages.CSharp) {
218
+ return { path: p, content: originalContent };
219
+ }
220
+ const normalized = normalizeCSharpPreprocessorBranches(originalContent, csharpDefineSymbols);
221
+ csharpPreprocDiagnostics.expressionErrors += normalized.diagnostics.expressionErrors;
222
+ for (const symbol of normalized.diagnostics.undefinedSymbols) {
223
+ csharpUndefinedSymbols.add(symbol);
224
+ }
225
+ if (!normalized.changed) {
226
+ csharpPreprocDiagnostics.skippedFiles += 1;
227
+ return { path: p, content: originalContent };
228
+ }
229
+ csharpPreprocDiagnostics.normalizedFiles += 1;
230
+ return {
231
+ path: p,
232
+ content: normalized.normalizedText,
233
+ rawContent: originalContent,
234
+ };
235
+ });
196
236
  // Parse this chunk (workers or sequential fallback)
197
237
  const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, (current, _total, filePath) => {
198
238
  const globalCurrent = filesParsedSoFar + current;
@@ -204,7 +244,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
204
244
  detail: filePath,
205
245
  stats: { filesProcessed: globalCurrent, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
206
246
  });
207
- }, workerPool);
247
+ }, workerPool, (count) => {
248
+ if (csharpPreprocDiagnostics)
249
+ csharpPreprocDiagnostics.fallbackFiles += count;
250
+ });
208
251
  const chunkBasePercent = 20 + ((filesParsedSoFar / totalParseable) * 62);
209
252
  if (chunkWorkerData) {
210
253
  // Imports
@@ -251,8 +294,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
251
294
  ]);
252
295
  }
253
296
  else {
254
- await processImports(graph, chunkFiles, astCache, ctx, undefined, repoPath, allPaths);
255
- sequentialChunkPaths.push(chunkPaths);
297
+ await processImports(graph, chunkFiles, astCache, ctx, undefined, repoPath, allPaths, (count) => {
298
+ if (csharpPreprocDiagnostics)
299
+ csharpPreprocDiagnostics.fallbackFiles += count;
300
+ });
301
+ sequentialChunkFiles.push(chunkFiles);
256
302
  }
257
303
  filesParsedSoFar += chunkFiles.length;
258
304
  // Clear AST cache between chunks to free memory
@@ -263,15 +309,17 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
263
309
  finally {
264
310
  await workerPool?.terminate();
265
311
  }
266
- // Sequential fallback chunks: re-read source for call/heritage resolution
267
- for (const chunkPaths of sequentialChunkPaths) {
268
- const chunkContents = await readFileContents(repoPath, chunkPaths);
269
- const chunkFiles = chunkPaths
270
- .filter(p => chunkContents.has(p))
271
- .map(p => ({ path: p, content: chunkContents.get(p) }));
312
+ // Sequential fallback chunks: use the same normalized-or-raw source for call/heritage resolution
313
+ for (const chunkFiles of sequentialChunkFiles) {
272
314
  astCache = createASTCache(chunkFiles.length);
273
- const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx);
274
- await processHeritage(graph, chunkFiles, astCache, ctx);
315
+ const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx, undefined, (count) => {
316
+ if (csharpPreprocDiagnostics)
317
+ csharpPreprocDiagnostics.fallbackFiles += count;
318
+ });
319
+ await processHeritage(graph, chunkFiles, astCache, ctx, undefined, (count) => {
320
+ if (csharpPreprocDiagnostics)
321
+ csharpPreprocDiagnostics.fallbackFiles += count;
322
+ });
275
323
  if (rubyHeritage.length > 0) {
276
324
  await processHeritageFromExtracted(graph, rubyHeritage, ctx);
277
325
  }
@@ -293,6 +341,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
293
341
  let communityResult;
294
342
  let processResult;
295
343
  let unityResult;
344
+ let unityRuleBindingResult;
296
345
  if (!options?.skipGraphPhases) {
297
346
  // ── Phase 4.5: Method Resolution Order ──────────────────────────────
298
347
  onProgress({
@@ -358,7 +407,9 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
358
407
  // ── Phase 5.6: Unity lifecycle synthetic calls (auto-detect) ────────
359
408
  const isUnityProject = allPaths.some(p => p.startsWith('Assets/') && p.endsWith('.cs'));
360
409
  const unityConfig = resolveUnityConfig();
361
- const persistLifecycleProcessMetadata = unityConfig.config.persistLifecycleProcessMetadata ?? false;
410
+ // Persistence is coupled to the Unity resource-binding indexing flow:
411
+ // if Unity project auto-detection is active, persist lifecycle metadata.
412
+ const persistLifecycleProcessMetadata = isUnityProject;
362
413
  const unityLifecycleSyntheticResult = isUnityProject
363
414
  ? applyUnityLifecycleSyntheticCalls(graph, {
364
415
  maxSyntheticEdgesPerClass: unityConfig.config.maxSyntheticEdgesPerClass,
@@ -374,16 +425,41 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
374
425
  // Phase 5.7: rule-driven binding injection (Phase 3)
375
426
  try {
376
427
  const analyzeRules = await loadAnalyzeRules(repoPath);
377
- if (analyzeRules.length > 0) {
378
- const bindingResult = applyUnityRuntimeBindingRules(graph, analyzeRules, unityConfig.config);
379
- if (isDev && bindingResult.edgesInjected > 0) {
380
- console.log(`[UnityRuleBinding] injected ${bindingResult.edgesInjected} edges from ${analyzeRules.length} rule(s)`);
381
- }
428
+ const bindingResult = applyUnityRuntimeBindingRules(graph, analyzeRules, unityConfig.config);
429
+ unityRuleBindingResult = bindingResult;
430
+ if (isDev && bindingResult.edgesInjected > 0) {
431
+ console.log(`[UnityRuleBinding] injected ${bindingResult.edgesInjected} edges from ${analyzeRules.length} rule(s)`);
382
432
  }
383
433
  }
384
434
  catch (err) {
385
435
  // rule catalog missing or invalid — skip silently
386
436
  console.warn(`[UnityRuleBinding] failed to load or apply analyze rules: ${err instanceof Error ? err.message : String(err)}`);
437
+ const reason = err instanceof Error ? err.message : String(err);
438
+ unityRuleBindingResult = {
439
+ edgesInjected: 0,
440
+ ruleResults: [],
441
+ diagnostics: {
442
+ rulesEvaluated: 0,
443
+ bindingsEvaluated: 0,
444
+ bindingsByKind: {},
445
+ methodLookupCalls: 0,
446
+ methodLookupCacheHits: 0,
447
+ sceneRuntimeTraversalCalls: 0,
448
+ sceneRuntimeTraversalCacheHits: 0,
449
+ sceneRuntimeResourcesVisited: 0,
450
+ anomalies: [`failed to load/apply analyze rules: ${reason}`],
451
+ shouldAgentReport: true,
452
+ agentReportReason: 'failed to load/apply analyze rules',
453
+ summary: [
454
+ 'rule_binding.summary: rules=0, bindings=0, edges=0',
455
+ 'rule_binding.bindings_by_kind: none',
456
+ 'rule_binding.lookup: method_calls=0, cache_hits=0',
457
+ 'rule_binding.scene_closure: traversals=0, cache_hits=0, visited_resources=0',
458
+ 'rule_binding.agent_report: should_report=true reason="failed to load/apply analyze rules"',
459
+ `rule_binding.anomaly: failed to load/apply analyze rules: ${reason}`,
460
+ ],
461
+ },
462
+ };
387
463
  }
388
464
  // ── Phase 6: Processes ─────────────────────────────────────────────
389
465
  onProgress({
@@ -459,6 +535,9 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
459
535
  },
460
536
  });
461
537
  astCache.clear();
538
+ if (csharpPreprocDiagnostics) {
539
+ csharpPreprocDiagnostics.undefinedSymbols = [...csharpUndefinedSymbols].sort();
540
+ }
462
541
  return {
463
542
  graph,
464
543
  repoPath,
@@ -466,7 +545,9 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
466
545
  communityResult,
467
546
  processResult,
468
547
  unityResult,
548
+ unityRuleBindingResult,
469
549
  scopeDiagnostics: scopeSelection.diagnostics,
550
+ csharpPreprocDiagnostics,
470
551
  };
471
552
  }
472
553
  catch (error) {
@@ -245,7 +245,7 @@ test('detects Unity hosts through transitive inheritance chains', () => {
245
245
  assert.equal(gunGraphHost.baseType, 'ScriptableObject');
246
246
  assert.ok(gunGraphHost.methods.length > 0);
247
247
  });
248
- test('prioritizes gameplay lifecycle hosts when synthetic edge budget is tight', () => {
248
+ test('respects global lifecycle edge budget without emitting loader bridges', () => {
249
249
  const graph = createKnowledgeGraph();
250
250
  for (let i = 0; i < 6; i += 1) {
251
251
  addClass(graph, {
@@ -275,12 +275,13 @@ test('prioritizes gameplay lifecycle hosts when synthetic edge budget is tight',
275
275
  maxSyntheticEdgesPerClass: 4,
276
276
  maxSyntheticEdgesTotal: 8,
277
277
  });
278
- const runtimeEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' &&
279
- (edge.reason === 'unity-lifecycle-synthetic' || edge.reason === 'unity-runtime-loader-synthetic'));
280
- const targets = new Set(runtimeEdges.map((edge) => edge.targetId));
278
+ const lifecycleEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-lifecycle-synthetic');
279
+ const loaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
281
280
  assert.equal(result.syntheticEdgeCount, 8);
282
- assert.equal([...targets].some((id) => id.includes('Assets/NEON/Code/Game/Core/GunGraphMB.cs')), true);
283
- assert.equal([...targets].some((id) => id.includes('Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadConfig.cs')), true);
281
+ assert.equal(result.lifecycleEdgeCount, 8);
282
+ assert.equal(result.loaderEdgeCount, 0);
283
+ assert.equal(lifecycleEdges.length, 8);
284
+ assert.equal(loaderEdges.length, 0);
284
285
  });
285
286
  test('resolves named class inheritance targets when direct target node lookup is ambiguous', () => {
286
287
  const graph = createKnowledgeGraph();
@@ -336,7 +337,7 @@ test('resolves named class inheritance targets when direct target node lookup is
336
337
  assert.ok(gunGraphHost);
337
338
  assert.equal(gunGraphHost.baseType, 'ScriptableObject');
338
339
  });
339
- test('emits deterministic runtime loader bridge chain after pre-bridge budget is exhausted', () => {
340
+ test('lifecycle phase does not emit runtime-loader bridge edges', () => {
340
341
  const graph = createKnowledgeGraph();
341
342
  addClass(graph, {
342
343
  className: 'GunGraphMB',
@@ -429,16 +430,15 @@ test('emits deterministic runtime loader bridge chain after pre-bridge budget is
429
430
  maxSyntheticEdgesPerClass: 12,
430
431
  maxSyntheticEdgesTotal: 10,
431
432
  });
432
- const syntheticEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
433
- const syntheticPairs = new Set(syntheticEdges.map((edge) => `${edge.sourceId}->${edge.targetId}`));
434
- assert.equal(result.syntheticEdgeCount, 10);
435
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Core/GunGraphMB.cs:GunGraphMB.RegisterGraphEvents')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.RegisterEvents')}`), true);
436
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.RegisterEvents')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.StartRoutineWithEvents')}`), true);
437
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.StartRoutineWithEvents')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:ReloadBase.GetValue')}`), true);
438
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:ReloadBase.GetValue')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:ReloadBase.CheckReload')}`), true);
439
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:ReloadBase.CheckReload')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs:Reload.ReloadRoutine')}`), true);
433
+ const syntheticLoaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
434
+ const lifecycleEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-lifecycle-synthetic');
435
+ assert.equal(result.syntheticEdgeCount, 3);
436
+ assert.equal(result.lifecycleEdgeCount, 3);
437
+ assert.equal(result.loaderEdgeCount, 0);
438
+ assert.equal(lifecycleEdges.length, 3);
439
+ assert.equal(syntheticLoaderEdges.length, 0);
440
440
  });
441
- test('preserves bridge budget when accepted host count exceeds synthetic edge cap', () => {
441
+ test('lifecycle edge cap does not rely on runtime-loader bridge injection', () => {
442
442
  const graph = createKnowledgeGraph();
443
443
  for (let i = 0; i < 20; i += 1) {
444
444
  addClass(graph, {
@@ -534,8 +534,6 @@ test('preserves bridge budget when accepted host count exceeds synthetic edge ca
534
534
  maxSyntheticEdgesPerClass: 12,
535
535
  maxSyntheticEdgesTotal: 12,
536
536
  });
537
- const syntheticPairs = new Set([...graph.iterRelationships()]
538
- .filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic')
539
- .map((edge) => `${edge.sourceId}->${edge.targetId}`));
540
- assert.equal(syntheticPairs.has(`${generateId('Method', 'Assets/NEON/Code/Game/Core/GunGraphMB.cs:GunGraphMB.RegisterGraphEvents')}->${generateId('Method', 'Assets/NEON/Code/Game/Graph/Graphs/GunGraph.cs:GunGraph.RegisterEvents')}`), true);
537
+ const loaderEdges = [...graph.iterRelationships()].filter((edge) => edge.type === 'CALLS' && edge.reason === 'unity-runtime-loader-synthetic');
538
+ assert.equal(loaderEdges.length, 0);
541
539
  });
@@ -1,9 +1,10 @@
1
- import type { UnityScanContext } from '../unity/scan-context.js';
1
+ import type { UnityPrefabSourceRef, UnityScanContext } from '../unity/scan-context.js';
2
2
  export interface UnityParitySeed {
3
3
  version: 1;
4
4
  symbolToScriptPath: Record<string, string>;
5
5
  scriptPathToGuid: Record<string, string>;
6
6
  guidToResourcePaths: Record<string, string[]>;
7
7
  assetGuidToPath?: Record<string, string>;
8
+ prefabSourceRefs?: UnityPrefabSourceRef[];
8
9
  }
9
10
  export declare function buildUnityParitySeed(scanContext: UnityScanContext): UnityParitySeed;
@@ -59,6 +59,14 @@ export function buildUnityParitySeed(scanContext) {
59
59
  scriptPathToGuid: sortRecord(scriptPathToGuid),
60
60
  guidToResourcePaths: sortRecord(guidToResourcePaths),
61
61
  assetGuidToPath: Object.keys(assetGuidToPath).length > 0 ? sortRecord(assetGuidToPath) : undefined,
62
+ prefabSourceRefs: scanContext.prefabSourceRefs?.map((row) => ({
63
+ sourceResourcePath: normalizePath(row.sourceResourcePath),
64
+ targetGuid: String(row.targetGuid || '').trim().toLowerCase(),
65
+ targetResourcePath: normalizePath(row.targetResourcePath || '') || undefined,
66
+ fileId: String(row.fileId || '').trim() || undefined,
67
+ fieldName: 'm_SourcePrefab',
68
+ sourceLayer: row.sourceLayer === 'scene' ? 'scene' : 'prefab',
69
+ })),
62
70
  };
63
71
  }
64
72
  function normalizePath(input) {
@@ -7,6 +7,7 @@ export interface UnityResourceProcessingResult {
7
7
  bindingCount: number;
8
8
  componentCount: number;
9
9
  diagnostics: string[];
10
+ prefabSourceStats: PrefabSourcePassStats;
10
11
  paritySeed?: UnityParitySeed;
11
12
  timingsMs: {
12
13
  scanContext: number;
@@ -25,4 +26,14 @@ export interface UnityResourceProcessingDeps {
25
26
  buildScanContext?: typeof buildUnityScanContext;
26
27
  resolveBindings?: typeof resolveUnityBindings;
27
28
  }
29
+ interface PrefabSourcePassStats {
30
+ rowsParsed: number;
31
+ rowsFilteredZeroGuid: number;
32
+ rowsFilteredPlaceholder: number;
33
+ rowsFilteredUnresolved: number;
34
+ rowsDeduped: number;
35
+ rowsEmitted: number;
36
+ fileErrors: number;
37
+ }
28
38
  export declare function processUnityResources(graph: KnowledgeGraph, options: UnityResourceProcessingOptions, deps?: UnityResourceProcessingDeps): Promise<UnityResourceProcessingResult>;
39
+ export {};
@@ -5,6 +5,7 @@ import { resolveUnityBindings } from '../unity/resolver.js';
5
5
  import { buildUnityParitySeed } from './unity-parity-seed.js';
6
6
  import { resolveUnityConfig } from '../config/unity-config.js';
7
7
  const UNITY_DIAGNOSTIC_SAMPLE_LIMIT = 3;
8
+ const PREFAB_SOURCE_PASS_DISABLE_ENV = 'GITNEXUS_DISABLE_PREFAB_SOURCE_PASS';
8
9
  export async function processUnityResources(graph, options, deps) {
9
10
  const tStart = performance.now();
10
11
  const buildScanContextFn = deps?.buildScanContext || buildUnityScanContext;
@@ -36,6 +37,7 @@ export async function processUnityResources(graph, options, deps) {
36
37
  let scanContextMs = 0;
37
38
  let resolveMs = 0;
38
39
  let graphWriteMs = 0;
40
+ let prefabSourceStats = initPrefabSourcePassStats();
39
41
  try {
40
42
  const tScanContextStart = performance.now();
41
43
  scanContext = await buildScanContextFn({
@@ -51,6 +53,22 @@ export async function processUnityResources(graph, options, deps) {
51
53
  }
52
54
  }
53
55
  diagnostics.push(`scanContext: scripts=${scanContext.symbolToScriptPath.size}, guids=${scanContext.scriptPathToGuid.size}, resources=${uniqueResourcePaths.size}`);
56
+ if (isPrefabSourcePassDisabledByEnv()) {
57
+ diagnostics.push(`prefab-source: skipped (env ${PREFAB_SOURCE_PASS_DISABLE_ENV}=1)`);
58
+ }
59
+ else {
60
+ const tPrefabSourceStart = performance.now();
61
+ prefabSourceStats = await emitPrefabSourceGuidRefsFromScanContext(graph, scanContext);
62
+ graphWriteMs += performance.now() - tPrefabSourceStart;
63
+ diagnostics.push(`prefab-source: emitted=${prefabSourceStats.rowsEmitted}`);
64
+ diagnostics.push(`prefab_source.rows_parsed=${prefabSourceStats.rowsParsed}`);
65
+ diagnostics.push(`prefab_source.rows_filtered_zero_guid=${prefabSourceStats.rowsFilteredZeroGuid}`);
66
+ diagnostics.push(`prefab_source.rows_filtered_placeholder=${prefabSourceStats.rowsFilteredPlaceholder}`);
67
+ diagnostics.push(`prefab_source.rows_filtered_unresolved=${prefabSourceStats.rowsFilteredUnresolved}`);
68
+ diagnostics.push(`prefab_source.rows_deduped=${prefabSourceStats.rowsDeduped}`);
69
+ diagnostics.push(`prefab_source.rows_emitted=${prefabSourceStats.rowsEmitted}`);
70
+ diagnostics.push(`prefab_source.file_errors=${prefabSourceStats.fileErrors}`);
71
+ }
54
72
  symbolsWithResourceHits = collectSymbolsWithResourceHits(scanContext);
55
73
  }
56
74
  catch (error) {
@@ -228,6 +246,7 @@ export async function processUnityResources(graph, options, deps) {
228
246
  bindingCount,
229
247
  componentCount,
230
248
  diagnostics,
249
+ prefabSourceStats,
231
250
  paritySeed: scanContext ? buildUnityParitySeed(scanContext) : undefined,
232
251
  timingsMs: {
233
252
  scanContext: roundMs(scanContextMs),
@@ -460,3 +479,86 @@ function classifyUnityDiagnostic(message) {
460
479
  }
461
480
  return 'other';
462
481
  }
482
+ async function emitPrefabSourceGuidRefsFromScanContext(graph, scanContext) {
483
+ const stats = initPrefabSourcePassStats();
484
+ const dedupeBySource = new Map();
485
+ const iterable = typeof scanContext.streamPrefabSourceRefs === 'function'
486
+ ? scanContext.streamPrefabSourceRefs({
487
+ hooks: {
488
+ onFileError: () => {
489
+ stats.fileErrors += 1;
490
+ },
491
+ },
492
+ })
493
+ : (async function* () {
494
+ for (const row of scanContext.prefabSourceRefs || []) {
495
+ yield row;
496
+ }
497
+ })();
498
+ for await (const row of iterable) {
499
+ stats.rowsParsed += 1;
500
+ const source = normalizePath(String(row.sourceResourcePath || '').trim());
501
+ const guid = String(row.targetGuid || '').trim().toLowerCase();
502
+ if (!guid || guid === '00000000000000000000000000000000') {
503
+ stats.rowsFilteredZeroGuid += 1;
504
+ continue;
505
+ }
506
+ const hintedTarget = normalizePath(String(row.targetResourcePath || '').trim());
507
+ if (hintedTarget === '__PLACEHOLDER__') {
508
+ stats.rowsFilteredPlaceholder += 1;
509
+ continue;
510
+ }
511
+ const resolvedTarget = hintedTarget
512
+ || normalizePath(scanContext.assetGuidToPath?.get(guid) || scanContext.assetGuidToPath?.get(guid.toLowerCase()) || '');
513
+ if (!resolvedTarget || !resolvedTarget.endsWith('.prefab')) {
514
+ stats.rowsFilteredUnresolved += 1;
515
+ continue;
516
+ }
517
+ if (!source) {
518
+ stats.rowsFilteredUnresolved += 1;
519
+ continue;
520
+ }
521
+ const perSourceDedupe = dedupeBySource.get(source) || new Set();
522
+ const dedupeKey = `${resolvedTarget}|m_SourcePrefab|${guid}|${String(row.fileId || '').trim()}|${row.sourceLayer === 'scene' ? 'scene' : 'prefab'}`;
523
+ if (perSourceDedupe.has(dedupeKey)) {
524
+ stats.rowsDeduped += 1;
525
+ continue;
526
+ }
527
+ perSourceDedupe.add(dedupeKey);
528
+ dedupeBySource.set(source, perSourceDedupe);
529
+ const sourceFileId = ensureResourceFileNode(graph, source);
530
+ const targetFileId = ensureResourceFileNode(graph, resolvedTarget);
531
+ graph.addRelationship({
532
+ id: generateId('UNITY_ASSET_GUID_REF', `${sourceFileId}->${targetFileId}:m_SourcePrefab:${guid}:${String(row.fileId || '')}`),
533
+ type: 'UNITY_ASSET_GUID_REF',
534
+ sourceId: sourceFileId,
535
+ targetId: targetFileId,
536
+ confidence: 1.0,
537
+ reason: JSON.stringify({
538
+ resourcePath: source,
539
+ targetResourcePath: resolvedTarget,
540
+ guid,
541
+ fileId: String(row.fileId || ''),
542
+ fieldName: 'm_SourcePrefab',
543
+ sourceLayer: row.sourceLayer === 'scene' ? 'scene' : 'prefab',
544
+ }),
545
+ });
546
+ stats.rowsEmitted += 1;
547
+ }
548
+ return stats;
549
+ }
550
+ function initPrefabSourcePassStats() {
551
+ return {
552
+ rowsParsed: 0,
553
+ rowsFilteredZeroGuid: 0,
554
+ rowsFilteredPlaceholder: 0,
555
+ rowsFilteredUnresolved: 0,
556
+ rowsDeduped: 0,
557
+ rowsEmitted: 0,
558
+ fileErrors: 0,
559
+ };
560
+ }
561
+ function isPrefabSourcePassDisabledByEnv() {
562
+ const value = String(process.env[PREFAB_SOURCE_PASS_DISABLE_ENV] || '').trim().toLowerCase();
563
+ return value === '1' || value === 'true' || value === 'yes' || value === 'on';
564
+ }