@veewo/gitnexus 1.3.8 → 1.3.10
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 +5 -0
- package/dist/benchmark/runner.test.js +1 -1
- package/dist/benchmark/u2-e2e/analyze-parser.d.ts +22 -0
- package/dist/benchmark/u2-e2e/analyze-parser.js +89 -0
- package/dist/benchmark/u2-e2e/analyze-parser.test.js +13 -0
- package/dist/benchmark/u2-e2e/characterlist-assetref.d.ts +19 -0
- package/dist/benchmark/u2-e2e/characterlist-assetref.js +80 -0
- package/dist/benchmark/u2-e2e/characterlist-assetref.test.js +108 -0
- package/dist/benchmark/u2-e2e/config.d.ts +25 -0
- package/dist/benchmark/u2-e2e/config.js +86 -0
- package/dist/benchmark/u2-e2e/config.test.js +29 -0
- package/dist/benchmark/u2-e2e/metrics.d.ts +20 -0
- package/dist/benchmark/u2-e2e/metrics.js +34 -0
- package/dist/benchmark/u2-e2e/metrics.test.js +13 -0
- package/dist/benchmark/u2-e2e/neonspark-full-e2e.d.ts +33 -0
- package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +439 -0
- package/dist/benchmark/u2-e2e/neonspark-full-e2e.test.js +40 -0
- package/dist/benchmark/u2-e2e/report.d.ts +58 -0
- package/dist/benchmark/u2-e2e/report.js +130 -0
- package/dist/benchmark/u2-e2e/report.test.js +58 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +21 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.js +166 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +145 -0
- package/dist/benchmark/u2-performance-sampler.d.ts +33 -0
- package/dist/benchmark/u2-performance-sampler.js +178 -0
- package/dist/benchmark/u2-performance-sampler.test.d.ts +1 -0
- package/dist/benchmark/u2-performance-sampler.test.js +34 -0
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze-multi-scope-regression.test.js +10 -0
- package/dist/cli/analyze-options.d.ts +19 -0
- package/dist/cli/analyze-options.js +35 -0
- package/dist/cli/analyze-options.test.js +42 -1
- package/dist/cli/analyze-summary.d.ts +7 -0
- package/dist/cli/analyze-summary.js +37 -0
- package/dist/cli/analyze-summary.test.d.ts +1 -0
- package/dist/cli/analyze-summary.test.js +58 -0
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +64 -32
- package/dist/cli/benchmark-u2-e2e.d.ts +9 -0
- package/dist/cli/benchmark-u2-e2e.js +35 -0
- package/dist/cli/benchmark-u2-e2e.test.d.ts +1 -0
- package/dist/cli/benchmark-u2-e2e.test.js +7 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/repo-manager-alias.test.js +24 -1
- package/dist/cli/tool.d.ts +3 -0
- package/dist/cli/tool.js +2 -0
- package/dist/cli/unity-bindings.d.ts +8 -0
- package/dist/cli/unity-bindings.js +33 -0
- package/dist/cli/unity-bindings.test.d.ts +1 -0
- package/dist/cli/unity-bindings.test.js +24 -0
- package/dist/core/graph/types.d.ts +1 -1
- package/dist/core/ingestion/pipeline.d.ts +2 -4
- package/dist/core/ingestion/pipeline.js +12 -0
- package/dist/core/ingestion/unity-resource-processor.d.ts +26 -0
- package/dist/core/ingestion/unity-resource-processor.js +363 -0
- package/dist/core/ingestion/unity-resource-processor.test.d.ts +1 -0
- package/dist/core/ingestion/unity-resource-processor.test.js +599 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +6 -0
- package/dist/core/kuzu/kuzu-adapter.js +18 -7
- package/dist/core/kuzu/schema.d.ts +2 -2
- package/dist/core/kuzu/schema.js +22 -1
- package/dist/core/kuzu/schema.test.d.ts +1 -0
- package/dist/core/kuzu/schema.test.js +17 -0
- package/dist/core/unity/meta-index.d.ts +5 -0
- package/dist/core/unity/meta-index.js +113 -0
- package/dist/core/unity/meta-index.test.d.ts +1 -0
- package/dist/core/unity/meta-index.test.js +11 -0
- package/dist/core/unity/options.d.ts +2 -0
- package/dist/core/unity/options.js +9 -0
- package/dist/core/unity/options.test.d.ts +1 -0
- package/dist/core/unity/options.test.js +10 -0
- package/dist/core/unity/override-merger.d.ts +27 -0
- package/dist/core/unity/override-merger.js +35 -0
- package/dist/core/unity/override-merger.test.d.ts +1 -0
- package/dist/core/unity/override-merger.test.js +47 -0
- package/dist/core/unity/resolver.d.ts +79 -0
- package/dist/core/unity/resolver.js +384 -0
- package/dist/core/unity/resolver.test.d.ts +1 -0
- package/dist/core/unity/resolver.test.js +244 -0
- package/dist/core/unity/resource-hit-scanner.d.ts +10 -0
- package/dist/core/unity/resource-hit-scanner.js +60 -0
- package/dist/core/unity/resource-hit-scanner.test.d.ts +1 -0
- package/dist/core/unity/resource-hit-scanner.test.js +20 -0
- package/dist/core/unity/scan-context.d.ts +23 -0
- package/dist/core/unity/scan-context.js +318 -0
- package/dist/core/unity/scan-context.test.d.ts +1 -0
- package/dist/core/unity/scan-context.test.js +118 -0
- package/dist/core/unity/serialized-type-index.d.ts +10 -0
- package/dist/core/unity/serialized-type-index.js +105 -0
- package/dist/core/unity/serialized-type-index.test.d.ts +1 -0
- package/dist/core/unity/serialized-type-index.test.js +34 -0
- package/dist/core/unity/u2-thresholds.test.d.ts +1 -0
- package/dist/core/unity/u2-thresholds.test.js +71 -0
- package/dist/core/unity/yaml-object-graph.d.ts +9 -0
- package/dist/core/unity/yaml-object-graph.js +92 -0
- package/dist/core/unity/yaml-object-graph.test.d.ts +1 -0
- package/dist/core/unity/yaml-object-graph.test.js +49 -0
- package/dist/mcp/local/local-backend.js +12 -1
- package/dist/mcp/local/unity-enrichment.d.ts +6 -0
- package/dist/mcp/local/unity-enrichment.js +91 -0
- package/dist/mcp/local/unity-enrichment.test.d.ts +1 -0
- package/dist/mcp/local/unity-enrichment.test.js +130 -0
- package/dist/mcp/resources.js +1 -1
- package/dist/mcp/staleness.js +1 -1
- package/dist/mcp/tools.js +12 -0
- package/dist/storage/repo-manager.d.ts +6 -0
- package/dist/types/pipeline.d.ts +7 -0
- package/dist/types/pipeline.js +2 -0
- package/hooks/check-release-path-hygiene.mjs +108 -0
- package/package.json +16 -9
- package/dist/cli/analyze-custom-modules-regression.test.js +0 -75
- package/dist/cli/analyze-modules-diagnostics.test.js +0 -36
- package/dist/core/ingestion/modules/assignment-engine.d.ts +0 -33
- package/dist/core/ingestion/modules/assignment-engine.js +0 -179
- package/dist/core/ingestion/modules/assignment-engine.test.js +0 -111
- package/dist/core/ingestion/modules/config-loader.d.ts +0 -2
- package/dist/core/ingestion/modules/config-loader.js +0 -186
- package/dist/core/ingestion/modules/config-loader.test.js +0 -57
- package/dist/core/ingestion/modules/rule-matcher.d.ts +0 -12
- package/dist/core/ingestion/modules/rule-matcher.js +0 -63
- package/dist/core/ingestion/modules/rule-matcher.test.js +0 -58
- package/dist/core/ingestion/modules/types.d.ts +0 -44
- package/dist/core/ingestion/modules/types.js +0 -2
- package/dist/mcp/local/cluster-aggregation.d.ts +0 -20
- package/dist/mcp/local/cluster-aggregation.js +0 -48
- package/dist/mcp/local/cluster-aggregation.test.js +0 -22
- /package/dist/{cli/analyze-custom-modules-regression.test.d.ts → benchmark/u2-e2e/analyze-parser.test.d.ts} +0 -0
- /package/dist/{cli/analyze-modules-diagnostics.test.d.ts → benchmark/u2-e2e/characterlist-assetref.test.d.ts} +0 -0
- /package/dist/{core/ingestion/modules/assignment-engine.test.d.ts → benchmark/u2-e2e/config.test.d.ts} +0 -0
- /package/dist/{core/ingestion/modules/config-loader.test.d.ts → benchmark/u2-e2e/metrics.test.d.ts} +0 -0
- /package/dist/{core/ingestion/modules/rule-matcher.test.d.ts → benchmark/u2-e2e/neonspark-full-e2e.test.d.ts} +0 -0
- /package/dist/{mcp/local/cluster-aggregation.test.d.ts → benchmark/u2-e2e/report.test.d.ts} +0 -0
|
@@ -20,7 +20,7 @@ export type NodeProperties = {
|
|
|
20
20
|
entryPointScore?: number;
|
|
21
21
|
entryPointReason?: string;
|
|
22
22
|
};
|
|
23
|
-
export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS';
|
|
23
|
+
export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS' | 'UNITY_COMPONENT_IN' | 'UNITY_COMPONENT_INSTANCE' | 'UNITY_SERIALIZED_TYPE_IN';
|
|
24
24
|
export interface GraphNode {
|
|
25
25
|
id: string;
|
|
26
26
|
label: NodeLabel;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { PipelineProgress, PipelineResult } from '../../types/pipeline.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
scopeRules?: string[];
|
|
5
|
-
}) => Promise<PipelineResult>;
|
|
2
|
+
import type { PipelineRunOptions } from '../../types/pipeline.js';
|
|
3
|
+
export declare const runPipelineFromRepo: (repoPath: string, onProgress: (progress: PipelineProgress) => void, options?: PipelineRunOptions) => Promise<PipelineResult>;
|
|
@@ -6,6 +6,7 @@ import { processCalls, processCallsFromExtracted } from './call-processor.js';
|
|
|
6
6
|
import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
|
|
7
7
|
import { processCommunities } from './community-processor.js';
|
|
8
8
|
import { processProcesses } from './process-processor.js';
|
|
9
|
+
import { processUnityResources } from './unity-resource-processor.js';
|
|
9
10
|
import { createSymbolTable } from './symbol-table.js';
|
|
10
11
|
import { createASTCache } from './ast-cache.js';
|
|
11
12
|
import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
|
|
@@ -74,6 +75,9 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
74
75
|
stats: { filesProcessed: 0, totalFiles, nodesCreated: graph.nodeCount },
|
|
75
76
|
});
|
|
76
77
|
const allPaths = extensionFiltered.map(f => f.path);
|
|
78
|
+
const unityScopedPaths = (options?.scopeRules && options.scopeRules.length > 0)
|
|
79
|
+
? scopedFiles.map(f => f.path)
|
|
80
|
+
: allPaths;
|
|
77
81
|
processStructure(graph, allPaths);
|
|
78
82
|
onProgress({
|
|
79
83
|
phase: 'structure',
|
|
@@ -296,6 +300,13 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
296
300
|
step: step.step,
|
|
297
301
|
});
|
|
298
302
|
});
|
|
303
|
+
onProgress({
|
|
304
|
+
phase: 'enriching',
|
|
305
|
+
percent: 99,
|
|
306
|
+
message: 'Extracting Unity resource bindings...',
|
|
307
|
+
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
308
|
+
});
|
|
309
|
+
const unityResult = await processUnityResources(graph, { repoPath, scopedPaths: unityScopedPaths });
|
|
299
310
|
onProgress({
|
|
300
311
|
phase: 'complete',
|
|
301
312
|
percent: 100,
|
|
@@ -313,6 +324,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
313
324
|
totalFileCount: totalFiles,
|
|
314
325
|
communityResult,
|
|
315
326
|
processResult,
|
|
327
|
+
unityResult,
|
|
316
328
|
scopeDiagnostics: scopeSelection.diagnostics,
|
|
317
329
|
};
|
|
318
330
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
2
|
+
import { buildUnityScanContext } from '../unity/scan-context.js';
|
|
3
|
+
import { resolveUnityBindings } from '../unity/resolver.js';
|
|
4
|
+
export interface UnityResourceProcessingResult {
|
|
5
|
+
processedSymbols: number;
|
|
6
|
+
bindingCount: number;
|
|
7
|
+
componentCount: number;
|
|
8
|
+
diagnostics: string[];
|
|
9
|
+
timingsMs: {
|
|
10
|
+
scanContext: number;
|
|
11
|
+
resolve: number;
|
|
12
|
+
graphWrite: number;
|
|
13
|
+
total: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export type UnityPayloadMode = 'compact' | 'full';
|
|
17
|
+
export interface UnityResourceProcessingOptions {
|
|
18
|
+
repoPath: string;
|
|
19
|
+
scopedPaths?: string[];
|
|
20
|
+
payloadMode?: UnityPayloadMode;
|
|
21
|
+
}
|
|
22
|
+
export interface UnityResourceProcessingDeps {
|
|
23
|
+
buildScanContext?: typeof buildUnityScanContext;
|
|
24
|
+
resolveBindings?: typeof resolveUnityBindings;
|
|
25
|
+
}
|
|
26
|
+
export declare function processUnityResources(graph: KnowledgeGraph, options: UnityResourceProcessingOptions, deps?: UnityResourceProcessingDeps): Promise<UnityResourceProcessingResult>;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { generateId } from '../../lib/utils.js';
|
|
4
|
+
import { buildUnityScanContext } from '../unity/scan-context.js';
|
|
5
|
+
import { resolveUnityBindings } from '../unity/resolver.js';
|
|
6
|
+
const UNITY_DIAGNOSTIC_SAMPLE_LIMIT = 3;
|
|
7
|
+
export async function processUnityResources(graph, options, deps) {
|
|
8
|
+
const tStart = performance.now();
|
|
9
|
+
const buildScanContextFn = deps?.buildScanContext || buildUnityScanContext;
|
|
10
|
+
const resolveBindingsFn = deps?.resolveBindings || resolveUnityBindings;
|
|
11
|
+
const payloadMode = resolveUnityPayloadMode(options.payloadMode);
|
|
12
|
+
const classNodes = [...graph.iterNodes()].filter((node) => node.label === 'Class' && String(node.properties.filePath || '').endsWith('.cs'));
|
|
13
|
+
const symbolDeclarations = classNodes
|
|
14
|
+
.map((node) => ({
|
|
15
|
+
symbol: String(node.properties.name || '').trim(),
|
|
16
|
+
scriptPath: String(node.properties.filePath || '').trim(),
|
|
17
|
+
}))
|
|
18
|
+
.filter((entry) => entry.symbol.length > 0 && entry.scriptPath.length > 0);
|
|
19
|
+
let processedSymbols = 0;
|
|
20
|
+
let bindingCount = 0;
|
|
21
|
+
let componentCount = 0;
|
|
22
|
+
const diagnostics = [];
|
|
23
|
+
const issueDiagnostics = [];
|
|
24
|
+
let scanContext;
|
|
25
|
+
let symbolsWithResourceHits = new Set();
|
|
26
|
+
let skippedNoGuidHit = 0;
|
|
27
|
+
let skippedMissingCanonical = 0;
|
|
28
|
+
let skippedNonCanonical = 0;
|
|
29
|
+
let canonicalSelected = 0;
|
|
30
|
+
let serializedTypeEdgeCount = 0;
|
|
31
|
+
let serializedTypeMissCount = 0;
|
|
32
|
+
const serializedTypeSymbols = new Set();
|
|
33
|
+
const resolvedBySymbol = new Map();
|
|
34
|
+
const resolveErrorBySymbol = new Map();
|
|
35
|
+
let scanContextMs = 0;
|
|
36
|
+
let resolveMs = 0;
|
|
37
|
+
let graphWriteMs = 0;
|
|
38
|
+
try {
|
|
39
|
+
const tScanContextStart = performance.now();
|
|
40
|
+
scanContext = await buildScanContextFn({
|
|
41
|
+
repoRoot: options.repoPath,
|
|
42
|
+
scopedPaths: options.scopedPaths,
|
|
43
|
+
symbolDeclarations,
|
|
44
|
+
});
|
|
45
|
+
scanContextMs += performance.now() - tScanContextStart;
|
|
46
|
+
const uniqueResourcePaths = new Set();
|
|
47
|
+
for (const hits of scanContext.guidToResourceHits.values()) {
|
|
48
|
+
for (const hit of hits) {
|
|
49
|
+
uniqueResourcePaths.add(hit.resourcePath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
diagnostics.push(`scanContext: scripts=${scanContext.symbolToScriptPath.size}, guids=${scanContext.scriptPathToGuid.size}, resources=${uniqueResourcePaths.size}`);
|
|
53
|
+
symbolsWithResourceHits = collectSymbolsWithResourceHits(scanContext);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (scanContextMs === 0) {
|
|
57
|
+
scanContextMs = performance.now() - tStart;
|
|
58
|
+
}
|
|
59
|
+
diagnostics.push(error instanceof Error ? error.message : String(error));
|
|
60
|
+
}
|
|
61
|
+
const canonicalClassNodeBySymbol = buildCanonicalClassNodeIndex(classNodes, scanContext);
|
|
62
|
+
for (const classNode of classNodes) {
|
|
63
|
+
const symbol = String(classNode.properties.name || '').trim();
|
|
64
|
+
if (!symbol)
|
|
65
|
+
continue;
|
|
66
|
+
if (scanContext) {
|
|
67
|
+
const canonicalScriptPath = getCanonicalScriptPath(scanContext, symbol);
|
|
68
|
+
if (!canonicalScriptPath) {
|
|
69
|
+
skippedMissingCanonical += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const classNodePath = normalizePath(String(classNode.properties.filePath || '').trim());
|
|
73
|
+
if (classNodePath !== canonicalScriptPath) {
|
|
74
|
+
skippedNonCanonical += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
canonicalSelected += 1;
|
|
78
|
+
if (!symbolsWithResourceHits.has(symbol)) {
|
|
79
|
+
skippedNoGuidHit += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const resolveError = resolveErrorBySymbol.get(symbol);
|
|
85
|
+
if (resolveError) {
|
|
86
|
+
issueDiagnostics.push(resolveError);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let resolved = resolvedBySymbol.get(symbol);
|
|
90
|
+
if (!resolved) {
|
|
91
|
+
const tResolveStart = performance.now();
|
|
92
|
+
resolved = await resolveBindingsFn({ repoRoot: options.repoPath, symbol, scanContext });
|
|
93
|
+
resolveMs += performance.now() - tResolveStart;
|
|
94
|
+
resolvedBySymbol.set(symbol, resolved);
|
|
95
|
+
}
|
|
96
|
+
issueDiagnostics.push(...resolved.unityDiagnostics);
|
|
97
|
+
if (resolved.resourceBindings.length === 0) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
processedSymbols += 1;
|
|
101
|
+
for (const binding of resolved.resourceBindings) {
|
|
102
|
+
const tWriteStart = performance.now();
|
|
103
|
+
bindingCount += 1;
|
|
104
|
+
componentCount += 1;
|
|
105
|
+
const resourceFileNode = ensureResourceFileNode(graph, binding.resourcePath);
|
|
106
|
+
const componentNode = createComponentNode(symbol, binding, payloadMode);
|
|
107
|
+
graph.addNode(componentNode);
|
|
108
|
+
graph.addRelationship({
|
|
109
|
+
id: generateId('UNITY_COMPONENT_IN', `${classNode.id}:${binding.componentObjectId}->${resourceFileNode.id}`),
|
|
110
|
+
type: 'UNITY_COMPONENT_IN',
|
|
111
|
+
sourceId: classNode.id,
|
|
112
|
+
targetId: resourceFileNode.id,
|
|
113
|
+
confidence: 1.0,
|
|
114
|
+
reason: binding.bindingKind,
|
|
115
|
+
});
|
|
116
|
+
graph.addRelationship({
|
|
117
|
+
id: generateId('UNITY_COMPONENT_INSTANCE', `${classNode.id}->${componentNode.id}`),
|
|
118
|
+
type: 'UNITY_COMPONENT_INSTANCE',
|
|
119
|
+
sourceId: classNode.id,
|
|
120
|
+
targetId: componentNode.id,
|
|
121
|
+
confidence: 1.0,
|
|
122
|
+
reason: binding.bindingKind,
|
|
123
|
+
});
|
|
124
|
+
const serializableTypeLinking = linkSerializableTypeEdges(graph, componentNode, symbol, binding, scanContext, canonicalClassNodeBySymbol);
|
|
125
|
+
serializedTypeEdgeCount += serializableTypeLinking.edgeCount;
|
|
126
|
+
serializedTypeMissCount += serializableTypeLinking.missCount;
|
|
127
|
+
for (const hitSymbol of serializableTypeLinking.symbols) {
|
|
128
|
+
serializedTypeSymbols.add(hitSymbol);
|
|
129
|
+
}
|
|
130
|
+
graphWriteMs += performance.now() - tWriteStart;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
resolveErrorBySymbol.set(symbol, message);
|
|
136
|
+
issueDiagnostics.push(message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (skippedNoGuidHit > 0) {
|
|
140
|
+
diagnostics.push(`prefilter: skipped ${skippedNoGuidHit} symbol(s) without guid resource hits`);
|
|
141
|
+
}
|
|
142
|
+
diagnostics.push(`canonical: selected=${canonicalSelected}, skip-non-canonical=${skippedNonCanonical}, missing-canonical=${skippedMissingCanonical}`);
|
|
143
|
+
diagnostics.push(`serialized-type: edges=${serializedTypeEdgeCount}, symbols=${serializedTypeSymbols.size}, misses=${serializedTypeMissCount}`);
|
|
144
|
+
if (skippedMissingCanonical > 0) {
|
|
145
|
+
diagnostics.push(`prefilter: skipped ${skippedMissingCanonical} symbol(s) missing canonical script mapping`);
|
|
146
|
+
}
|
|
147
|
+
diagnostics.push(...aggregateUnityDiagnostics(issueDiagnostics));
|
|
148
|
+
return {
|
|
149
|
+
processedSymbols,
|
|
150
|
+
bindingCount,
|
|
151
|
+
componentCount,
|
|
152
|
+
diagnostics,
|
|
153
|
+
timingsMs: {
|
|
154
|
+
scanContext: roundMs(scanContextMs),
|
|
155
|
+
resolve: roundMs(resolveMs),
|
|
156
|
+
graphWrite: roundMs(graphWriteMs),
|
|
157
|
+
total: roundMs(performance.now() - tStart),
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function collectSymbolsWithResourceHits(scanContext) {
|
|
162
|
+
const symbols = new Set();
|
|
163
|
+
const canonicalEntries = scanContext.symbolToCanonicalScriptPath?.entries() || scanContext.symbolToScriptPath.entries();
|
|
164
|
+
for (const [symbol, scriptPath] of canonicalEntries) {
|
|
165
|
+
const guid = scanContext.scriptPathToGuid.get(scriptPath);
|
|
166
|
+
if (!guid)
|
|
167
|
+
continue;
|
|
168
|
+
if ((scanContext.guidToResourceHits.get(guid) || []).length === 0)
|
|
169
|
+
continue;
|
|
170
|
+
symbols.add(symbol);
|
|
171
|
+
}
|
|
172
|
+
return symbols;
|
|
173
|
+
}
|
|
174
|
+
function getCanonicalScriptPath(scanContext, symbol) {
|
|
175
|
+
const canonicalPath = scanContext.symbolToCanonicalScriptPath?.get(symbol);
|
|
176
|
+
if (canonicalPath) {
|
|
177
|
+
return normalizePath(canonicalPath);
|
|
178
|
+
}
|
|
179
|
+
const fallbackPath = scanContext.symbolToScriptPath.get(symbol);
|
|
180
|
+
if (fallbackPath) {
|
|
181
|
+
return normalizePath(fallbackPath);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
function normalizePath(filePath) {
|
|
186
|
+
return filePath.replace(/\\/g, '/');
|
|
187
|
+
}
|
|
188
|
+
function resolveUnityPayloadMode(explicit) {
|
|
189
|
+
if (explicit)
|
|
190
|
+
return explicit;
|
|
191
|
+
const envMode = String(process.env.GITNEXUS_UNITY_PAYLOAD_MODE || '').trim().toLowerCase();
|
|
192
|
+
if (envMode === 'full')
|
|
193
|
+
return 'full';
|
|
194
|
+
return 'compact';
|
|
195
|
+
}
|
|
196
|
+
function ensureResourceFileNode(graph, resourcePath) {
|
|
197
|
+
const normalizedResourcePath = resourcePath.replace(/\\/g, '/');
|
|
198
|
+
const fileId = generateId('File', normalizedResourcePath);
|
|
199
|
+
const existing = graph.getNode(fileId);
|
|
200
|
+
if (existing) {
|
|
201
|
+
return existing;
|
|
202
|
+
}
|
|
203
|
+
const node = {
|
|
204
|
+
id: fileId,
|
|
205
|
+
label: 'File',
|
|
206
|
+
properties: {
|
|
207
|
+
name: path.basename(normalizedResourcePath),
|
|
208
|
+
filePath: normalizedResourcePath,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
graph.addNode(node);
|
|
212
|
+
return node;
|
|
213
|
+
}
|
|
214
|
+
function createComponentNode(symbol, binding, payloadMode) {
|
|
215
|
+
const payload = buildUnityPayload(binding, payloadMode);
|
|
216
|
+
return {
|
|
217
|
+
id: generateId('CodeElement', `${binding.resourcePath}:${binding.componentObjectId}`),
|
|
218
|
+
label: 'CodeElement',
|
|
219
|
+
properties: {
|
|
220
|
+
name: `${symbol}@${binding.componentObjectId}`,
|
|
221
|
+
filePath: binding.resourcePath,
|
|
222
|
+
startLine: binding.evidence.line,
|
|
223
|
+
endLine: binding.evidence.line,
|
|
224
|
+
description: JSON.stringify(payload),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function buildUnityPayload(binding, mode) {
|
|
229
|
+
const payload = {
|
|
230
|
+
bindingKind: binding.bindingKind,
|
|
231
|
+
componentObjectId: binding.componentObjectId,
|
|
232
|
+
};
|
|
233
|
+
if (binding.serializedFields.scalarFields.length > 0 || binding.serializedFields.referenceFields.length > 0) {
|
|
234
|
+
payload.serializedFields = binding.serializedFields;
|
|
235
|
+
}
|
|
236
|
+
if (binding.resolvedReferences && binding.resolvedReferences.length > 0) {
|
|
237
|
+
payload.resolvedReferences = binding.resolvedReferences;
|
|
238
|
+
}
|
|
239
|
+
if (binding.assetRefPaths && binding.assetRefPaths.length > 0) {
|
|
240
|
+
payload.assetRefPaths = binding.assetRefPaths;
|
|
241
|
+
}
|
|
242
|
+
if (mode === 'full') {
|
|
243
|
+
payload.resourcePath = binding.resourcePath;
|
|
244
|
+
payload.resourceType = binding.resourceType;
|
|
245
|
+
payload.evidence = binding.evidence;
|
|
246
|
+
}
|
|
247
|
+
return payload;
|
|
248
|
+
}
|
|
249
|
+
function buildCanonicalClassNodeIndex(classNodes, scanContext) {
|
|
250
|
+
const index = new Map();
|
|
251
|
+
for (const classNode of classNodes) {
|
|
252
|
+
const symbol = String(classNode.properties.name || '').trim();
|
|
253
|
+
if (!symbol || index.has(symbol))
|
|
254
|
+
continue;
|
|
255
|
+
const classPath = normalizePath(String(classNode.properties.filePath || '').trim());
|
|
256
|
+
const canonicalPath = scanContext ? getCanonicalScriptPath(scanContext, symbol) : undefined;
|
|
257
|
+
if (canonicalPath && classPath !== canonicalPath) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
index.set(symbol, classNode);
|
|
261
|
+
}
|
|
262
|
+
return index;
|
|
263
|
+
}
|
|
264
|
+
function linkSerializableTypeEdges(graph, componentNode, hostSymbol, binding, scanContext, canonicalClassNodeBySymbol) {
|
|
265
|
+
const stats = {
|
|
266
|
+
edgeCount: 0,
|
|
267
|
+
missCount: 0,
|
|
268
|
+
symbols: new Set(),
|
|
269
|
+
};
|
|
270
|
+
if (!scanContext)
|
|
271
|
+
return stats;
|
|
272
|
+
const serializableSymbols = scanContext.serializableSymbols;
|
|
273
|
+
const hostFieldTypeHints = scanContext.hostFieldTypeHints;
|
|
274
|
+
if (!serializableSymbols || !hostFieldTypeHints)
|
|
275
|
+
return stats;
|
|
276
|
+
const hostHints = hostFieldTypeHints.get(hostSymbol);
|
|
277
|
+
if (!hostHints || hostHints.size === 0)
|
|
278
|
+
return stats;
|
|
279
|
+
const fieldSourceLayer = collectBindingFieldSources(binding);
|
|
280
|
+
if (fieldSourceLayer.size === 0)
|
|
281
|
+
return stats;
|
|
282
|
+
for (const [fieldName, declaredType] of hostHints.entries()) {
|
|
283
|
+
if (!serializableSymbols.has(declaredType))
|
|
284
|
+
continue;
|
|
285
|
+
const sourceLayer = fieldSourceLayer.get(fieldName);
|
|
286
|
+
if (!sourceLayer)
|
|
287
|
+
continue;
|
|
288
|
+
const serializableNode = canonicalClassNodeBySymbol.get(declaredType);
|
|
289
|
+
if (!serializableNode) {
|
|
290
|
+
stats.missCount += 1;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
graph.addRelationship({
|
|
294
|
+
id: generateId('UNITY_SERIALIZED_TYPE_IN', `${serializableNode.id}->${componentNode.id}:${fieldName}`),
|
|
295
|
+
type: 'UNITY_SERIALIZED_TYPE_IN',
|
|
296
|
+
sourceId: serializableNode.id,
|
|
297
|
+
targetId: componentNode.id,
|
|
298
|
+
confidence: 1.0,
|
|
299
|
+
reason: JSON.stringify({ hostSymbol, fieldName, declaredType, sourceLayer }),
|
|
300
|
+
});
|
|
301
|
+
stats.edgeCount += 1;
|
|
302
|
+
stats.symbols.add(declaredType);
|
|
303
|
+
}
|
|
304
|
+
return stats;
|
|
305
|
+
}
|
|
306
|
+
function collectBindingFieldSources(binding) {
|
|
307
|
+
const fieldSources = new Map();
|
|
308
|
+
for (const field of binding.serializedFields.scalarFields) {
|
|
309
|
+
if (!fieldSources.has(field.name)) {
|
|
310
|
+
fieldSources.set(field.name, field.sourceLayer || 'unknown');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (const field of binding.serializedFields.referenceFields) {
|
|
314
|
+
if (!fieldSources.has(field.name)) {
|
|
315
|
+
fieldSources.set(field.name, field.sourceLayer || 'unknown');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return fieldSources;
|
|
319
|
+
}
|
|
320
|
+
function roundMs(value) {
|
|
321
|
+
return Number(value.toFixed(1));
|
|
322
|
+
}
|
|
323
|
+
function aggregateUnityDiagnostics(messages) {
|
|
324
|
+
if (messages.length === 0) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const buckets = new Map();
|
|
328
|
+
for (const message of messages) {
|
|
329
|
+
const category = classifyUnityDiagnostic(message);
|
|
330
|
+
const bucket = buckets.get(category) || { count: 0, samples: [] };
|
|
331
|
+
bucket.count += 1;
|
|
332
|
+
if (bucket.samples.length < UNITY_DIAGNOSTIC_SAMPLE_LIMIT && !bucket.samples.includes(message)) {
|
|
333
|
+
bucket.samples.push(message);
|
|
334
|
+
}
|
|
335
|
+
buckets.set(category, bucket);
|
|
336
|
+
}
|
|
337
|
+
const ordered = [...buckets.entries()].sort((left, right) => right[1].count - left[1].count);
|
|
338
|
+
const lines = [
|
|
339
|
+
`diagnostics: aggregated ${messages.length} issue(s) across ${ordered.length} category(ies); sampleLimit=${UNITY_DIAGNOSTIC_SAMPLE_LIMIT}`,
|
|
340
|
+
];
|
|
341
|
+
for (const [category, bucket] of ordered) {
|
|
342
|
+
lines.push(`diagnostics: category=${category} count=${bucket.count} sampleCount=${bucket.samples.length}`);
|
|
343
|
+
for (const sample of bucket.samples) {
|
|
344
|
+
lines.push(`diagnostics: sample[${category}] ${sample}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return lines;
|
|
348
|
+
}
|
|
349
|
+
function classifyUnityDiagnostic(message) {
|
|
350
|
+
if (message.startsWith('No MonoBehaviour block matched script guid ')) {
|
|
351
|
+
return 'no-monobehaviour-match';
|
|
352
|
+
}
|
|
353
|
+
if (message.startsWith('Unity symbol "') && message.endsWith('" is ambiguous.')) {
|
|
354
|
+
return 'ambiguous-symbol';
|
|
355
|
+
}
|
|
356
|
+
if (message.startsWith('Unity symbol "') && message.includes('" was not found under ')) {
|
|
357
|
+
return 'symbol-not-found';
|
|
358
|
+
}
|
|
359
|
+
if (message.startsWith('No .meta guid found for ')) {
|
|
360
|
+
return 'missing-meta-guid';
|
|
361
|
+
}
|
|
362
|
+
return 'other';
|
|
363
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|