@veewo/gitnexus 1.3.7 → 1.3.9
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 +9 -4
- 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.d.ts +1 -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.d.ts +1 -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.d.ts +1 -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.d.ts +1 -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.d.ts +1 -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.d.ts +1 -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/analyze-custom-modules-regression.test.d.ts +1 -0
- package/dist/cli/analyze-custom-modules-regression.test.js +75 -0
- package/dist/cli/analyze-modules-diagnostics.test.d.ts +1 -0
- package/dist/cli/analyze-modules-diagnostics.test.js +36 -0
- package/dist/cli/analyze-multi-scope-regression.test.js +10 -0
- 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.js +11 -6
- 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 +20 -0
- package/dist/cli/setup.js +24 -3
- package/dist/cli/setup.test.js +6 -4
- 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/modules/assignment-engine.d.ts +33 -0
- package/dist/core/ingestion/modules/assignment-engine.js +179 -0
- package/dist/core/ingestion/modules/assignment-engine.test.d.ts +1 -0
- package/dist/core/ingestion/modules/assignment-engine.test.js +111 -0
- package/dist/core/ingestion/modules/config-loader.d.ts +2 -0
- package/dist/core/ingestion/modules/config-loader.js +186 -0
- package/dist/core/ingestion/modules/config-loader.test.d.ts +1 -0
- package/dist/core/ingestion/modules/config-loader.test.js +57 -0
- package/dist/core/ingestion/modules/rule-matcher.d.ts +12 -0
- package/dist/core/ingestion/modules/rule-matcher.js +63 -0
- package/dist/core/ingestion/modules/rule-matcher.test.d.ts +1 -0
- package/dist/core/ingestion/modules/rule-matcher.test.js +58 -0
- package/dist/core/ingestion/modules/types.d.ts +44 -0
- package/dist/core/ingestion/modules/types.js +2 -0
- 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/cluster-aggregation.d.ts +20 -0
- package/dist/mcp/local/cluster-aggregation.js +48 -0
- package/dist/mcp/local/cluster-aggregation.test.d.ts +1 -0
- package/dist/mcp/local/cluster-aggregation.test.js +22 -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/tools.js +12 -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 +14 -7
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildSerializableTypeIndexFromSources } from './serialized-type-index.js';
|
|
4
|
+
test('buildSerializableTypeIndex extracts serializable symbols and host field declared types', () => {
|
|
5
|
+
const index = buildSerializableTypeIndexFromSources([
|
|
6
|
+
{
|
|
7
|
+
filePath: 'Assets/Scripts/AssetRef.cs',
|
|
8
|
+
content: `
|
|
9
|
+
[System.Serializable]
|
|
10
|
+
public class AssetRef { public string guid; }
|
|
11
|
+
`,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
filePath: 'Assets/Scripts/InventoryConfig.cs',
|
|
15
|
+
content: `
|
|
16
|
+
using UnityEngine;
|
|
17
|
+
using System.Collections.Generic;
|
|
18
|
+
public class InventoryConfig : ScriptableObject {
|
|
19
|
+
public AssetRef icon;
|
|
20
|
+
public AssetRef<GameObject> iconPrefab;
|
|
21
|
+
[SerializeField] private List<AssetRef> drops;
|
|
22
|
+
[SerializeField] private List<AssetRef<Sprite>> iconVariants;
|
|
23
|
+
[SerializeField] private int ignored;
|
|
24
|
+
}
|
|
25
|
+
`,
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
assert.equal(index.serializableSymbols.has('AssetRef'), true);
|
|
29
|
+
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.get('icon'), 'AssetRef');
|
|
30
|
+
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.get('iconPrefab'), 'AssetRef');
|
|
31
|
+
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.get('drops'), 'AssetRef');
|
|
32
|
+
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.get('iconVariants'), 'AssetRef');
|
|
33
|
+
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.has('ignored'), false);
|
|
34
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { resolveUnityBindings } from './resolver.js';
|
|
6
|
+
import { buildUnityScanContext } from './scan-context.js';
|
|
7
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const fixtureRoot = path.resolve(here, '../../../src/core/unity/__fixtures__/mini-unity');
|
|
9
|
+
const U2_REFERENCE_RESOLUTION_RATE_THRESHOLD = 0.8;
|
|
10
|
+
const U2_LIST_REFERENCE_PARSE_RATE_THRESHOLD = 1.0;
|
|
11
|
+
function countListCandidatesFromScalarFields(scalarFields) {
|
|
12
|
+
let total = 0;
|
|
13
|
+
for (const field of scalarFields) {
|
|
14
|
+
const lines = field.value.split(/\r?\n/);
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (/^\s*-\s*\{[^}]*fileID\s*:/.test(line)) {
|
|
17
|
+
total += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return total;
|
|
22
|
+
}
|
|
23
|
+
function countResolvedSuccesses(references) {
|
|
24
|
+
return references.filter((ref) => ref.resolution === 'local-object' || ref.resolution === 'external-asset').length;
|
|
25
|
+
}
|
|
26
|
+
function collectU2ReferenceStats(results) {
|
|
27
|
+
let candidateRefs = 0;
|
|
28
|
+
let resolvedRefs = 0;
|
|
29
|
+
let listCandidates = 0;
|
|
30
|
+
let parsedListRefs = 0;
|
|
31
|
+
for (const result of results) {
|
|
32
|
+
for (const binding of result.resourceBindings) {
|
|
33
|
+
const listCandidatesForBinding = countListCandidatesFromScalarFields(binding.serializedFields.scalarFields);
|
|
34
|
+
const directCandidatesForBinding = binding.serializedFields.referenceFields.length;
|
|
35
|
+
candidateRefs += directCandidatesForBinding + listCandidatesForBinding;
|
|
36
|
+
listCandidates += listCandidatesForBinding;
|
|
37
|
+
resolvedRefs += countResolvedSuccesses(binding.resolvedReferences);
|
|
38
|
+
parsedListRefs += binding.resolvedReferences.filter((ref) => ref.fromList).length;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { candidateRefs, resolvedRefs, listCandidates, parsedListRefs };
|
|
42
|
+
}
|
|
43
|
+
test('U2 threshold: reference_resolution_rate stays above baseline', async () => {
|
|
44
|
+
const scanContext = await buildUnityScanContext({
|
|
45
|
+
repoRoot: fixtureRoot,
|
|
46
|
+
scopedPaths: [
|
|
47
|
+
'Assets/Scripts/MainUIManager.cs',
|
|
48
|
+
'Assets/Scripts/MainUIManager.cs.meta',
|
|
49
|
+
'Assets/Scene/MainUIManager.unity',
|
|
50
|
+
'Assets/Config/MainUIDocument.asset.meta',
|
|
51
|
+
'Assets/Scripts/MenuScreenCarrier.cs',
|
|
52
|
+
'Assets/Scripts/MenuScreenCarrier.cs.meta',
|
|
53
|
+
'Assets/Prefabs/MenuScreenCarrier.prefab',
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
const [mainUIManager, menuScreenCarrier] = await Promise.all([
|
|
57
|
+
resolveUnityBindings({ repoRoot: fixtureRoot, symbol: 'MainUIManager', scanContext }),
|
|
58
|
+
resolveUnityBindings({ repoRoot: fixtureRoot, symbol: 'MenuScreenCarrier', scanContext }),
|
|
59
|
+
]);
|
|
60
|
+
const stats = collectU2ReferenceStats([mainUIManager, menuScreenCarrier]);
|
|
61
|
+
assert.ok(stats.candidateRefs > 0, 'candidate reference count must be > 0');
|
|
62
|
+
const referenceResolutionRate = stats.resolvedRefs / stats.candidateRefs;
|
|
63
|
+
assert.ok(referenceResolutionRate >= U2_REFERENCE_RESOLUTION_RATE_THRESHOLD, `reference_resolution_rate=${referenceResolutionRate.toFixed(3)} below threshold=${U2_REFERENCE_RESOLUTION_RATE_THRESHOLD.toFixed(3)} (resolved=${stats.resolvedRefs}, candidates=${stats.candidateRefs})`);
|
|
64
|
+
});
|
|
65
|
+
test('U2 threshold: list_reference_parse_rate stays above baseline', async () => {
|
|
66
|
+
const result = await resolveUnityBindings({ repoRoot: fixtureRoot, symbol: 'MenuScreenCarrier' });
|
|
67
|
+
const stats = collectU2ReferenceStats([result]);
|
|
68
|
+
assert.ok(stats.listCandidates > 0, 'list reference candidate count must be > 0');
|
|
69
|
+
const listReferenceParseRate = stats.parsedListRefs / stats.listCandidates;
|
|
70
|
+
assert.ok(listReferenceParseRate >= U2_LIST_REFERENCE_PARSE_RATE_THRESHOLD, `list_reference_parse_rate=${listReferenceParseRate.toFixed(3)} below threshold=${U2_LIST_REFERENCE_PARSE_RATE_THRESHOLD.toFixed(3)} (parsed=${stats.parsedListRefs}, candidates=${stats.listCandidates})`);
|
|
71
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type UnityObjectType = 'MonoBehaviour' | 'PrefabInstance' | 'GameObject';
|
|
2
|
+
export interface UnityObjectBlock {
|
|
3
|
+
objectId: string;
|
|
4
|
+
objectType: UnityObjectType;
|
|
5
|
+
stripped: boolean;
|
|
6
|
+
fields: Record<string, string>;
|
|
7
|
+
rawBody: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseUnityYamlObjects(text: string): UnityObjectBlock[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const SUPPORTED_TYPES = new Set(['MonoBehaviour', 'PrefabInstance', 'GameObject']);
|
|
2
|
+
export function parseUnityYamlObjects(text) {
|
|
3
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
4
|
+
const blocks = [];
|
|
5
|
+
let currentHeader = null;
|
|
6
|
+
let currentBody = [];
|
|
7
|
+
const flush = () => {
|
|
8
|
+
if (!currentHeader)
|
|
9
|
+
return;
|
|
10
|
+
const objectType = findObjectType(currentBody);
|
|
11
|
+
if (!objectType) {
|
|
12
|
+
currentHeader = null;
|
|
13
|
+
currentBody = [];
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
blocks.push({
|
|
17
|
+
objectId: currentHeader.objectId,
|
|
18
|
+
objectType,
|
|
19
|
+
stripped: currentHeader.stripped,
|
|
20
|
+
fields: parseFields(currentBody),
|
|
21
|
+
rawBody: currentBody.join('\n'),
|
|
22
|
+
});
|
|
23
|
+
currentHeader = null;
|
|
24
|
+
currentBody = [];
|
|
25
|
+
};
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const header = line.match(/^--- !u!\d+ &(\d+)(?:\s+(\w+))?\s*$/);
|
|
28
|
+
if (header) {
|
|
29
|
+
flush();
|
|
30
|
+
currentHeader = {
|
|
31
|
+
objectId: header[1],
|
|
32
|
+
stripped: header[2] === 'stripped',
|
|
33
|
+
};
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (currentHeader) {
|
|
37
|
+
currentBody.push(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
flush();
|
|
41
|
+
return blocks;
|
|
42
|
+
}
|
|
43
|
+
function findObjectType(lines) {
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const match = line.match(/^(MonoBehaviour|PrefabInstance|GameObject):\s*$/);
|
|
46
|
+
if (match && SUPPORTED_TYPES.has(match[1])) {
|
|
47
|
+
return match[1];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function parseFields(lines) {
|
|
53
|
+
const fields = {};
|
|
54
|
+
const rootIndex = lines.findIndex((line) => /^(MonoBehaviour|PrefabInstance|GameObject):\s*$/.test(line));
|
|
55
|
+
if (rootIndex === -1)
|
|
56
|
+
return fields;
|
|
57
|
+
let currentKey = null;
|
|
58
|
+
let currentValue = [];
|
|
59
|
+
const flush = () => {
|
|
60
|
+
if (!currentKey)
|
|
61
|
+
return;
|
|
62
|
+
fields[currentKey] = currentValue.join('\n').trim();
|
|
63
|
+
currentKey = null;
|
|
64
|
+
currentValue = [];
|
|
65
|
+
};
|
|
66
|
+
for (const line of lines.slice(rootIndex + 1)) {
|
|
67
|
+
if (!line.trim()) {
|
|
68
|
+
if (currentKey)
|
|
69
|
+
currentValue.push('');
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const topLevelMatch = line.match(/^ {2}(?!-)([^ ][^:]*):(.*)$/);
|
|
73
|
+
if (topLevelMatch) {
|
|
74
|
+
flush();
|
|
75
|
+
currentKey = topLevelMatch[1].trim();
|
|
76
|
+
const inlineValue = topLevelMatch[2].trim();
|
|
77
|
+
if (inlineValue)
|
|
78
|
+
currentValue.push(inlineValue);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (currentKey) {
|
|
82
|
+
currentValue.push(stripCommonIndent(line));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
flush();
|
|
86
|
+
return fields;
|
|
87
|
+
}
|
|
88
|
+
function stripCommonIndent(line) {
|
|
89
|
+
if (line.startsWith(' '))
|
|
90
|
+
return line.slice(4);
|
|
91
|
+
return line.trimStart();
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { parseUnityYamlObjects } from './yaml-object-graph.js';
|
|
4
|
+
const sampleYaml = `--- !u!1 &1000
|
|
5
|
+
GameObject:
|
|
6
|
+
m_Name: MainUIRoot
|
|
7
|
+
--- !u!114 &11400000 stripped
|
|
8
|
+
MonoBehaviour:
|
|
9
|
+
m_CorrespondingSourceObject: {fileID: 11400000, guid: abcdef0123456789abcdef0123456789, type: 3}
|
|
10
|
+
m_PrefabInstance: {fileID: 2000}
|
|
11
|
+
needPause: 0
|
|
12
|
+
mainUIDocument: {fileID: 11400000, guid: fedcba9876543210fedcba9876543210, type: 2}
|
|
13
|
+
--- !u!1001 &2000
|
|
14
|
+
PrefabInstance:
|
|
15
|
+
m_Modification:
|
|
16
|
+
m_Modifications:
|
|
17
|
+
- target: {fileID: 11400000}
|
|
18
|
+
propertyPath: needPause
|
|
19
|
+
value: 1
|
|
20
|
+
objectReference: {fileID: 0}
|
|
21
|
+
`;
|
|
22
|
+
test('parseUnityYamlObjects parses stripped MonoBehaviour and PrefabInstance blocks', () => {
|
|
23
|
+
const blocks = parseUnityYamlObjects(sampleYaml);
|
|
24
|
+
assert.equal(blocks.length, 3);
|
|
25
|
+
assert.deepEqual(blocks.map((block) => ({ id: block.objectId, type: block.objectType, stripped: block.stripped })), [
|
|
26
|
+
{ id: '1000', type: 'GameObject', stripped: false },
|
|
27
|
+
{ id: '11400000', type: 'MonoBehaviour', stripped: true },
|
|
28
|
+
{ id: '2000', type: 'PrefabInstance', stripped: false },
|
|
29
|
+
]);
|
|
30
|
+
assert.equal(blocks[1].fields.needPause, '0');
|
|
31
|
+
assert.match(blocks[1].fields.mainUIDocument, /fileID: 11400000/);
|
|
32
|
+
assert.match(blocks[2].fields.m_Modification, /propertyPath: needPause/);
|
|
33
|
+
});
|
|
34
|
+
test('parseUnityYamlObjects keeps inline list entries under their parent field', () => {
|
|
35
|
+
const yamlWithInlineList = `--- !u!114 &11400001
|
|
36
|
+
MonoBehaviour:
|
|
37
|
+
buttonMappings:
|
|
38
|
+
- {fileID: 11400000, guid: fedcba9876543210fedcba9876543210, type: 2}
|
|
39
|
+
- {fileID: 0}
|
|
40
|
+
needPause: 0
|
|
41
|
+
`;
|
|
42
|
+
const blocks = parseUnityYamlObjects(yamlWithInlineList);
|
|
43
|
+
assert.equal(blocks.length, 1);
|
|
44
|
+
const mono = blocks[0];
|
|
45
|
+
assert.equal(mono.objectType, 'MonoBehaviour');
|
|
46
|
+
assert.ok(mono.fields.buttonMappings.includes('- {fileID: 11400000'));
|
|
47
|
+
assert.equal(mono.fields['- {fileID'], undefined);
|
|
48
|
+
assert.equal(mono.fields.needPause, '0');
|
|
49
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RawCluster {
|
|
2
|
+
id: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
heuristicLabel?: string;
|
|
5
|
+
cohesion?: number;
|
|
6
|
+
symbolCount?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AggregatedCluster {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
heuristicLabel: string;
|
|
12
|
+
symbolCount: number;
|
|
13
|
+
cohesion: number;
|
|
14
|
+
subCommunities: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ClusterAggregationOptions {
|
|
17
|
+
minSymbolCount?: number;
|
|
18
|
+
configuredIdPrefix?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function aggregateClusters(clusters: RawCluster[], options?: ClusterAggregationOptions): AggregatedCluster[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const DEFAULT_MIN_SYMBOL_COUNT = 5;
|
|
2
|
+
const DEFAULT_CONFIGURED_ID_PREFIX = 'comm_cfg_';
|
|
3
|
+
export function aggregateClusters(clusters, options = {}) {
|
|
4
|
+
const minSymbolCount = options.minSymbolCount ?? DEFAULT_MIN_SYMBOL_COUNT;
|
|
5
|
+
const configuredIdPrefix = options.configuredIdPrefix ?? DEFAULT_CONFIGURED_ID_PREFIX;
|
|
6
|
+
const groups = new Map();
|
|
7
|
+
for (const cluster of clusters) {
|
|
8
|
+
const label = cluster.heuristicLabel || cluster.label || 'Unknown';
|
|
9
|
+
const symbols = cluster.symbolCount || 0;
|
|
10
|
+
const cohesion = cluster.cohesion || 0;
|
|
11
|
+
const isConfigured = cluster.id.startsWith(configuredIdPrefix);
|
|
12
|
+
const existing = groups.get(label);
|
|
13
|
+
if (!existing) {
|
|
14
|
+
groups.set(label, {
|
|
15
|
+
ids: [cluster.id],
|
|
16
|
+
totalSymbols: symbols,
|
|
17
|
+
weightedCohesion: cohesion * symbols,
|
|
18
|
+
largest: cluster,
|
|
19
|
+
hasConfigured: isConfigured,
|
|
20
|
+
});
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
existing.ids.push(cluster.id);
|
|
24
|
+
existing.totalSymbols += symbols;
|
|
25
|
+
existing.weightedCohesion += cohesion * symbols;
|
|
26
|
+
existing.hasConfigured = existing.hasConfigured || isConfigured;
|
|
27
|
+
if (symbols > (existing.largest.symbolCount || 0)) {
|
|
28
|
+
existing.largest = cluster;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return Array.from(groups.entries())
|
|
32
|
+
.map(([label, g]) => ({
|
|
33
|
+
id: g.largest.id,
|
|
34
|
+
label,
|
|
35
|
+
heuristicLabel: label,
|
|
36
|
+
symbolCount: g.totalSymbols,
|
|
37
|
+
cohesion: g.totalSymbols > 0 ? g.weightedCohesion / g.totalSymbols : 0,
|
|
38
|
+
subCommunities: g.ids.length,
|
|
39
|
+
hasConfigured: g.hasConfigured,
|
|
40
|
+
}))
|
|
41
|
+
.filter((c) => c.symbolCount >= minSymbolCount || c.hasConfigured)
|
|
42
|
+
.sort((a, b) => {
|
|
43
|
+
if (a.symbolCount !== b.symbolCount)
|
|
44
|
+
return b.symbolCount - a.symbolCount;
|
|
45
|
+
return a.heuristicLabel.localeCompare(b.heuristicLabel);
|
|
46
|
+
})
|
|
47
|
+
.map(({ hasConfigured: _hasConfigured, ...rest }) => rest);
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { aggregateClusters } from './cluster-aggregation.js';
|
|
4
|
+
test('keeps auto tiny clusters filtered but includes empty configured modules', () => {
|
|
5
|
+
const out = aggregateClusters([
|
|
6
|
+
{ id: 'comm_1', label: 'AutoTiny', heuristicLabel: 'AutoTiny', cohesion: 0.2, symbolCount: 1 },
|
|
7
|
+
{ id: 'comm_cfg_battle', label: 'Battle', heuristicLabel: 'Battle', cohesion: 0, symbolCount: 0 },
|
|
8
|
+
{ id: 'comm_2', label: 'Gameplay', heuristicLabel: 'Gameplay', cohesion: 0.8, symbolCount: 9 },
|
|
9
|
+
], { minSymbolCount: 5, configuredIdPrefix: 'comm_cfg_' });
|
|
10
|
+
assert.ok(out.some((c) => c.heuristicLabel === 'Battle' && c.symbolCount === 0));
|
|
11
|
+
assert.ok(!out.some((c) => c.heuristicLabel === 'AutoTiny'));
|
|
12
|
+
assert.ok(out.some((c) => c.heuristicLabel === 'Gameplay' && c.symbolCount === 9));
|
|
13
|
+
});
|
|
14
|
+
test('aggregates same-name clusters with weighted cohesion', () => {
|
|
15
|
+
const out = aggregateClusters([
|
|
16
|
+
{ id: 'comm_1', label: 'Gameplay', heuristicLabel: 'Gameplay', cohesion: 0.5, symbolCount: 4 },
|
|
17
|
+
{ id: 'comm_2', label: 'Gameplay', heuristicLabel: 'Gameplay', cohesion: 1.0, symbolCount: 6 },
|
|
18
|
+
], { minSymbolCount: 5, configuredIdPrefix: 'comm_cfg_' });
|
|
19
|
+
assert.equal(out.length, 1);
|
|
20
|
+
assert.equal(out[0].symbolCount, 10);
|
|
21
|
+
assert.equal(Math.round(out[0].cohesion * 100), 80);
|
|
22
|
+
});
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { initKuzu, executeQuery, closeKuzu, isKuzuReady } from '../core/kuzu-adapter.js';
|
|
11
|
+
import { parseUnityResourcesMode } from '../../core/unity/options.js';
|
|
12
|
+
import { loadUnityContext } from './unity-enrichment.js';
|
|
11
13
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
12
14
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
13
15
|
// git utilities available if needed
|
|
@@ -285,6 +287,7 @@ export class LocalBackend {
|
|
|
285
287
|
const processLimit = params.limit || 5;
|
|
286
288
|
const maxSymbolsPerProcess = params.max_symbols || 10;
|
|
287
289
|
const includeContent = params.include_content ?? false;
|
|
290
|
+
const unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
|
|
288
291
|
const searchQuery = params.query.trim();
|
|
289
292
|
// Step 1: Run hybrid search to get matching symbols
|
|
290
293
|
const searchLimit = processLimit * maxSymbolsPerProcess; // fetch enough raw results
|
|
@@ -383,6 +386,9 @@ export class LocalBackend {
|
|
|
383
386
|
endLine: sym.endLine,
|
|
384
387
|
...(module ? { module } : {}),
|
|
385
388
|
...(includeContent && content ? { content } : {}),
|
|
389
|
+
...((unityResourcesMode !== 'off' && sym.nodeId && sym.type === 'Class')
|
|
390
|
+
? await loadUnityContext(repo.id, sym.nodeId, (query) => executeQuery(repo.id, query))
|
|
391
|
+
: {}),
|
|
386
392
|
};
|
|
387
393
|
if (processRows.length === 0) {
|
|
388
394
|
// Symbol not in any process — goes to definitions
|
|
@@ -796,6 +802,7 @@ export class LocalBackend {
|
|
|
796
802
|
async context(repo, params) {
|
|
797
803
|
await this.ensureInitialized(repo.id);
|
|
798
804
|
const { name, uid, file_path, include_content } = params;
|
|
805
|
+
const unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
|
|
799
806
|
if (!name && !uid) {
|
|
800
807
|
return { error: 'Either "name" or "uid" parameter is required.' };
|
|
801
808
|
}
|
|
@@ -948,7 +955,7 @@ export class LocalBackend {
|
|
|
948
955
|
}
|
|
949
956
|
return cats;
|
|
950
957
|
};
|
|
951
|
-
|
|
958
|
+
const result = {
|
|
952
959
|
status: 'found',
|
|
953
960
|
symbol: {
|
|
954
961
|
uid: getRowValue(sym, 'id', 0),
|
|
@@ -968,6 +975,10 @@ export class LocalBackend {
|
|
|
968
975
|
step_count: getRowValue(r, 'stepCount', 3),
|
|
969
976
|
})),
|
|
970
977
|
};
|
|
978
|
+
if (unityResourcesMode !== 'off' && symNodeId && symKind === 'Class') {
|
|
979
|
+
Object.assign(result, await loadUnityContext(repo.id, symNodeId, (query) => executeQuery(repo.id, query)));
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
971
982
|
}
|
|
972
983
|
/**
|
|
973
984
|
* Legacy explore — kept for backwards compatibility with resources.ts.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ResolveOutput } from '../../core/unity/resolver.js';
|
|
2
|
+
export interface UnityContextPayload extends Pick<ResolveOutput, 'resourceBindings' | 'serializedFields' | 'unityDiagnostics'> {
|
|
3
|
+
}
|
|
4
|
+
export type ExecuteQuery = (query: string) => Promise<any[]>;
|
|
5
|
+
export declare function loadUnityContext(_repoId: string, symbolId: string, execute: ExecuteQuery): Promise<UnityContextPayload>;
|
|
6
|
+
export declare function projectUnityBindings(rows: any[]): UnityContextPayload;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { extractAssetRefPathReferences } from '../../core/unity/resolver.js';
|
|
2
|
+
export async function loadUnityContext(_repoId, symbolId, execute) {
|
|
3
|
+
const escapedSymbolId = symbolId.replace(/'/g, "''");
|
|
4
|
+
const rows = await execute(`
|
|
5
|
+
MATCH (symbol {id: '${escapedSymbolId}'})-[r:CodeRelation]->(component:CodeElement)
|
|
6
|
+
WHERE r.type IN ['UNITY_COMPONENT_INSTANCE', 'UNITY_SERIALIZED_TYPE_IN']
|
|
7
|
+
RETURN component.filePath AS resourcePath, component.description AS payload, r.type AS relationType, r.reason AS relationReason
|
|
8
|
+
ORDER BY component.filePath, component.id
|
|
9
|
+
`);
|
|
10
|
+
return projectUnityBindings(rows);
|
|
11
|
+
}
|
|
12
|
+
export function projectUnityBindings(rows) {
|
|
13
|
+
const resourceBindings = [];
|
|
14
|
+
const scalarFields = [];
|
|
15
|
+
const referenceFields = [];
|
|
16
|
+
const unityDiagnostics = [];
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const rawPayload = row?.payload ?? row?.description ?? row?.[1];
|
|
19
|
+
if (typeof rawPayload !== 'string' || rawPayload.length === 0) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(rawPayload);
|
|
24
|
+
const binding = {
|
|
25
|
+
resourcePath: parsed.resourcePath || row?.resourcePath || row?.[0] || '',
|
|
26
|
+
resourceType: parsed.resourceType || inferResourceType(parsed.resourcePath || row?.resourcePath || row?.[0] || ''),
|
|
27
|
+
bindingKind: parsed.bindingKind || 'direct',
|
|
28
|
+
componentObjectId: parsed.componentObjectId || '',
|
|
29
|
+
evidence: parsed.evidence || buildSyntheticEvidence(row),
|
|
30
|
+
serializedFields: parsed.serializedFields || { scalarFields: [], referenceFields: [] },
|
|
31
|
+
resolvedReferences: parsed.resolvedReferences || [],
|
|
32
|
+
assetRefPaths: normalizeAssetRefPaths(parsed.assetRefPaths) || extractAssetRefPathReferences(parsed.serializedFields || { scalarFields: [], referenceFields: [] }),
|
|
33
|
+
};
|
|
34
|
+
resourceBindings.push(binding);
|
|
35
|
+
scalarFields.push(...binding.serializedFields.scalarFields);
|
|
36
|
+
referenceFields.push(...binding.serializedFields.referenceFields);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
unityDiagnostics.push(error instanceof Error ? error.message : String(error));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
resourceBindings,
|
|
44
|
+
serializedFields: {
|
|
45
|
+
scalarFields,
|
|
46
|
+
referenceFields,
|
|
47
|
+
},
|
|
48
|
+
unityDiagnostics,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function inferResourceType(resourcePath) {
|
|
52
|
+
if (resourcePath.endsWith('.prefab'))
|
|
53
|
+
return 'prefab';
|
|
54
|
+
if (resourcePath.endsWith('.asset'))
|
|
55
|
+
return 'asset';
|
|
56
|
+
return 'scene';
|
|
57
|
+
}
|
|
58
|
+
function buildSyntheticEvidence(row) {
|
|
59
|
+
const relationType = String(row?.relationType || '').trim();
|
|
60
|
+
const relationReason = String(row?.relationReason || '').trim();
|
|
61
|
+
if (!relationType && !relationReason) {
|
|
62
|
+
return { line: 0, lineText: '' };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
line: 0,
|
|
66
|
+
lineText: relationReason ? `${relationType}:${relationReason}` : relationType,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function normalizeAssetRefPaths(input) {
|
|
70
|
+
if (!Array.isArray(input) || input.length === 0) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const rows = [];
|
|
74
|
+
for (const row of input) {
|
|
75
|
+
if (!row || typeof row !== 'object')
|
|
76
|
+
continue;
|
|
77
|
+
const parentFieldName = String(row.parentFieldName || '').trim();
|
|
78
|
+
const fieldName = String(row.fieldName || '').trim();
|
|
79
|
+
const relativePath = String(row.relativePath || '');
|
|
80
|
+
const sourceLayer = String(row.sourceLayer || 'unknown');
|
|
81
|
+
rows.push({
|
|
82
|
+
parentFieldName: parentFieldName || fieldName || 'unknown',
|
|
83
|
+
fieldName: fieldName || parentFieldName || 'unknown',
|
|
84
|
+
relativePath,
|
|
85
|
+
sourceLayer,
|
|
86
|
+
isEmpty: Boolean(row.isEmpty ?? relativePath.length === 0),
|
|
87
|
+
isSprite: Boolean(row.isSprite),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return rows.length > 0 ? rows : undefined;
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { loadUnityContext, projectUnityBindings } from './unity-enrichment.js';
|
|
4
|
+
test('projectUnityBindings restores graph-native Unity payload rows', () => {
|
|
5
|
+
const out = projectUnityBindings([
|
|
6
|
+
{
|
|
7
|
+
resourcePath: 'Assets/Scene/MainUIManager.unity',
|
|
8
|
+
payload: JSON.stringify({
|
|
9
|
+
resourcePath: 'Assets/Scene/MainUIManager.unity',
|
|
10
|
+
resourceType: 'scene',
|
|
11
|
+
bindingKind: 'nested',
|
|
12
|
+
componentObjectId: '11400000',
|
|
13
|
+
evidence: { line: 9, lineText: ' m_Script: {...}' },
|
|
14
|
+
serializedFields: {
|
|
15
|
+
scalarFields: [{ name: 'needPause', value: '1', valueType: 'number', sourceLayer: 'scene' }],
|
|
16
|
+
referenceFields: [{ name: 'mainUIDocument', guid: 'abc', sourceLayer: 'scene' }],
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
assert.equal(out.resourceBindings[0].bindingKind, 'nested');
|
|
22
|
+
assert.ok(out.serializedFields.scalarFields.length >= 1);
|
|
23
|
+
assert.ok(out.serializedFields.referenceFields.length >= 1);
|
|
24
|
+
});
|
|
25
|
+
test('loadUnityContext queries component payload rows and projects stable output', async () => {
|
|
26
|
+
const out = await loadUnityContext('repo-id', 'Class:Assets/Scripts/MainUIManager.cs:MainUIManager', async (query) => {
|
|
27
|
+
assert.match(query, /UNITY_COMPONENT_INSTANCE/);
|
|
28
|
+
assert.match(query, /UNITY_SERIALIZED_TYPE_IN/);
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
resourcePath: 'Assets/Scene/MainUIManager.unity',
|
|
32
|
+
payload: JSON.stringify({
|
|
33
|
+
resourcePath: 'Assets/Scene/MainUIManager.unity',
|
|
34
|
+
resourceType: 'scene',
|
|
35
|
+
bindingKind: 'scene-override',
|
|
36
|
+
componentObjectId: '11400000',
|
|
37
|
+
evidence: { line: 9, lineText: ' m_Script: {...}' },
|
|
38
|
+
serializedFields: {
|
|
39
|
+
scalarFields: [{ name: 'needPause', value: '1', valueType: 'number', sourceLayer: 'scene' }],
|
|
40
|
+
referenceFields: [],
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
});
|
|
46
|
+
assert.equal(out.resourceBindings[0]?.bindingKind, 'scene-override');
|
|
47
|
+
assert.equal(out.serializedFields.scalarFields[0]?.name, 'needPause');
|
|
48
|
+
assert.deepEqual(out.unityDiagnostics, []);
|
|
49
|
+
});
|
|
50
|
+
test('loadUnityContext returns resourceBindings for UNITY_SERIALIZED_TYPE_IN relations', async () => {
|
|
51
|
+
const out = await loadUnityContext('repo-id', 'Class:Assets/Scripts/AssetRef.cs:AssetRef', async () => [
|
|
52
|
+
{
|
|
53
|
+
relationType: 'UNITY_SERIALIZED_TYPE_IN',
|
|
54
|
+
relationReason: '{"hostSymbol":"InventoryConfig","fieldName":"icon","declaredType":"AssetRef"}',
|
|
55
|
+
resourcePath: 'Assets/Config/Inventory.asset',
|
|
56
|
+
payload: JSON.stringify({
|
|
57
|
+
resourceType: 'asset',
|
|
58
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
assert.equal(out.resourceBindings.length, 1);
|
|
63
|
+
assert.equal(out.resourceBindings[0]?.resourcePath, 'Assets/Config/Inventory.asset');
|
|
64
|
+
assert.equal(out.resourceBindings[0]?.resourceType, 'asset');
|
|
65
|
+
});
|
|
66
|
+
test('projectUnityBindings preserves structured assetRefPaths from payload', () => {
|
|
67
|
+
const out = projectUnityBindings([
|
|
68
|
+
{
|
|
69
|
+
resourcePath: 'Assets/NEON/DataAssets/CharacterList.asset',
|
|
70
|
+
payload: JSON.stringify({
|
|
71
|
+
resourcePath: 'Assets/NEON/DataAssets/CharacterList.asset',
|
|
72
|
+
resourceType: 'asset',
|
|
73
|
+
bindingKind: 'prefab-instance',
|
|
74
|
+
componentObjectId: '11400000',
|
|
75
|
+
assetRefPaths: [
|
|
76
|
+
{
|
|
77
|
+
parentFieldName: 'Values',
|
|
78
|
+
fieldName: '_Head_Ref',
|
|
79
|
+
relativePath: 'Assets/NEON/Art/Sprites/UI/0_pixle/ui_character_head/hero_head_Nik.png',
|
|
80
|
+
sourceLayer: 'asset',
|
|
81
|
+
isEmpty: false,
|
|
82
|
+
isSprite: true,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
serializedFields: {
|
|
86
|
+
scalarFields: [],
|
|
87
|
+
referenceFields: [],
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
assert.equal(out.resourceBindings.length, 1);
|
|
93
|
+
assert.equal(out.resourceBindings[0]?.assetRefPaths?.length, 1);
|
|
94
|
+
assert.equal(out.resourceBindings[0]?.assetRefPaths?.[0]?.fieldName, '_Head_Ref');
|
|
95
|
+
assert.equal(out.resourceBindings[0]?.assetRefPaths?.[0]?.isSprite, true);
|
|
96
|
+
});
|
|
97
|
+
test('projectUnityBindings derives assetRefPaths from serialized scalar fields when payload lacks structured rows', () => {
|
|
98
|
+
const out = projectUnityBindings([
|
|
99
|
+
{
|
|
100
|
+
resourcePath: 'Assets/NEON/DataAssets/CharacterList.asset',
|
|
101
|
+
payload: JSON.stringify({
|
|
102
|
+
resourcePath: 'Assets/NEON/DataAssets/CharacterList.asset',
|
|
103
|
+
resourceType: 'asset',
|
|
104
|
+
bindingKind: 'prefab-instance',
|
|
105
|
+
componentObjectId: '11400000',
|
|
106
|
+
serializedFields: {
|
|
107
|
+
scalarFields: [
|
|
108
|
+
{
|
|
109
|
+
name: 'Values',
|
|
110
|
+
sourceLayer: 'asset',
|
|
111
|
+
value: `
|
|
112
|
+
_Head_Ref:
|
|
113
|
+
_relativePath: Assets/NEON/Art/Sprites/UI/0_pixle/ui_character_head/hero_head_Nik.png
|
|
114
|
+
_actorPrefabRef:
|
|
115
|
+
_relativePath: Assets/ActorPrefab/Actor_Nik/V_Actor_Nik.prefab
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
referenceFields: [],
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
const refs = out.resourceBindings[0]?.assetRefPaths || [];
|
|
125
|
+
assert.equal(refs.length, 2);
|
|
126
|
+
assert.equal(refs[0]?.fieldName, '_Head_Ref');
|
|
127
|
+
assert.equal(refs[0]?.isSprite, true);
|
|
128
|
+
assert.equal(refs[1]?.fieldName, '_actorPrefabRef');
|
|
129
|
+
assert.equal(refs[1]?.isSprite, false);
|
|
130
|
+
});
|