@veewo/gitnexus 1.3.10 → 1.3.11
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 +3 -3
- package/dist/benchmark/analyze-memory-sampler.d.ts +10 -0
- package/dist/benchmark/analyze-memory-sampler.js +12 -0
- package/dist/benchmark/analyze-memory-sampler.test.d.ts +1 -0
- package/dist/benchmark/analyze-memory-sampler.test.js +12 -0
- package/dist/benchmark/io.test.js +48 -5
- package/dist/benchmark/u2-e2e/config.d.ts +1 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.js +25 -3
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +44 -1
- package/dist/benchmark/unity-lazy-context-sampler.d.ts +58 -0
- package/dist/benchmark/unity-lazy-context-sampler.js +217 -0
- package/dist/benchmark/unity-lazy-context-sampler.test.d.ts +1 -0
- package/dist/benchmark/unity-lazy-context-sampler.test.js +32 -0
- package/dist/cli/analyze-close-policy.d.ts +5 -0
- package/dist/cli/analyze-close-policy.js +9 -0
- package/dist/cli/analyze-close-policy.test.d.ts +1 -0
- package/dist/cli/analyze-close-policy.test.js +12 -0
- package/dist/cli/analyze-runtime-summary.d.ts +2 -0
- package/dist/cli/analyze-runtime-summary.js +9 -0
- package/dist/cli/analyze-runtime-summary.test.d.ts +1 -0
- package/dist/cli/analyze-runtime-summary.test.js +14 -0
- package/dist/cli/analyze.js +42 -15
- package/dist/cli/eval-server.js +3 -0
- package/dist/cli/exit-code.d.ts +13 -0
- package/dist/cli/exit-code.js +25 -0
- package/dist/cli/exit-code.test.d.ts +1 -0
- package/dist/cli/exit-code.test.js +28 -0
- package/dist/cli/index.js +8 -2
- package/dist/cli/mcp.js +3 -0
- package/dist/cli/setup.js +3 -2
- package/dist/cli/setup.test.js +67 -0
- package/dist/cli/tool.d.ts +3 -1
- package/dist/cli/tool.js +2 -0
- package/dist/core/graph/types.d.ts +1 -1
- package/dist/core/ingestion/filesystem-walker.d.ts +6 -0
- package/dist/core/ingestion/filesystem-walker.js +17 -0
- package/dist/core/ingestion/filesystem-walker.test.d.ts +1 -0
- package/dist/core/ingestion/filesystem-walker.test.js +51 -0
- package/dist/core/ingestion/pipeline.js +4 -3
- package/dist/core/ingestion/unity-parity-seed.d.ts +9 -0
- package/dist/core/ingestion/unity-parity-seed.js +69 -0
- package/dist/core/ingestion/unity-parity-seed.test.d.ts +1 -0
- package/dist/core/ingestion/unity-parity-seed.test.js +35 -0
- package/dist/core/ingestion/unity-resource-processor.d.ts +2 -0
- package/dist/core/ingestion/unity-resource-processor.js +87 -53
- package/dist/core/ingestion/unity-resource-processor.test.js +37 -39
- package/dist/core/kuzu/csv-generator.d.ts +20 -1
- package/dist/core/kuzu/csv-generator.js +92 -25
- package/dist/core/kuzu/csv-generator.test.d.ts +1 -0
- package/dist/core/kuzu/csv-generator.test.js +28 -0
- package/dist/core/kuzu/kuzu-adapter.js +35 -54
- package/dist/core/kuzu/relationship-pair-buckets.d.ts +17 -0
- package/dist/core/kuzu/relationship-pair-buckets.js +79 -0
- package/dist/core/kuzu/relationship-pair-buckets.test.d.ts +1 -0
- package/dist/core/kuzu/relationship-pair-buckets.test.js +10 -0
- package/dist/core/kuzu/schema.d.ts +1 -1
- package/dist/core/kuzu/schema.js +1 -0
- package/dist/core/unity/options.d.ts +2 -0
- package/dist/core/unity/options.js +9 -0
- package/dist/core/unity/options.test.js +8 -1
- package/dist/core/unity/resolver.d.ts +3 -0
- package/dist/core/unity/resolver.js +56 -2
- package/dist/core/unity/resolver.test.js +46 -0
- package/dist/core/unity/scan-context.d.ts +5 -0
- package/dist/core/unity/scan-context.js +133 -44
- package/dist/core/unity/scan-context.test.js +41 -2
- package/dist/core/unity/serialized-type-index.d.ts +5 -0
- package/dist/core/unity/serialized-type-index.js +44 -13
- package/dist/core/unity/serialized-type-index.test.js +9 -1
- package/dist/mcp/local/local-backend.d.ts +16 -0
- package/dist/mcp/local/local-backend.js +320 -4
- package/dist/mcp/local/local-backend.unity-merge.test.d.ts +1 -0
- package/dist/mcp/local/local-backend.unity-merge.test.js +261 -0
- package/dist/mcp/local/unity-enrichment.d.ts +15 -0
- package/dist/mcp/local/unity-enrichment.js +69 -5
- package/dist/mcp/local/unity-enrichment.test.js +69 -1
- package/dist/mcp/local/unity-lazy-config.d.ts +6 -0
- package/dist/mcp/local/unity-lazy-config.js +7 -0
- package/dist/mcp/local/unity-lazy-config.test.d.ts +1 -0
- package/dist/mcp/local/unity-lazy-config.test.js +9 -0
- package/dist/mcp/local/unity-lazy-hydrator.d.ts +15 -0
- package/dist/mcp/local/unity-lazy-hydrator.js +43 -0
- package/dist/mcp/local/unity-lazy-hydrator.test.d.ts +1 -0
- package/dist/mcp/local/unity-lazy-hydrator.test.js +66 -0
- package/dist/mcp/local/unity-lazy-overlay.d.ts +3 -0
- package/dist/mcp/local/unity-lazy-overlay.js +89 -0
- package/dist/mcp/local/unity-lazy-overlay.test.d.ts +1 -0
- package/dist/mcp/local/unity-lazy-overlay.test.js +83 -0
- package/dist/mcp/local/unity-parity-cache.d.ts +7 -0
- package/dist/mcp/local/unity-parity-cache.js +88 -0
- package/dist/mcp/local/unity-parity-cache.test.d.ts +1 -0
- package/dist/mcp/local/unity-parity-cache.test.js +143 -0
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +2 -0
- package/dist/mcp/local/unity-parity-seed-loader.js +30 -0
- package/dist/mcp/local/unity-parity-seed-loader.test.d.ts +1 -0
- package/dist/mcp/local/unity-parity-seed-loader.test.js +25 -0
- package/dist/mcp/local/unity-parity-warmup-queue.d.ts +6 -0
- package/dist/mcp/local/unity-parity-warmup-queue.js +28 -0
- package/dist/mcp/local/unity-parity-warmup-queue.test.d.ts +1 -0
- package/dist/mcp/local/unity-parity-warmup-queue.test.js +15 -0
- package/dist/mcp/tools.js +24 -2
- package/dist/types/pipeline.d.ts +7 -0
- package/package.json +4 -1
- package/skills/gitnexus-cli.md +18 -0
- package/skills/gitnexus-debugging.md +16 -2
- package/skills/gitnexus-exploring.md +15 -1
- package/skills/gitnexus-guide.md +15 -0
- package/skills/gitnexus-impact-analysis.md +2 -0
- package/skills/gitnexus-refactoring.md +5 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
3
5
|
import { glob } from 'glob';
|
|
4
6
|
import { buildAssetMetaIndex, buildMetaIndex } from './meta-index.js';
|
|
5
|
-
import {
|
|
7
|
+
import { buildSerializableTypeIndexFromFiles } from './serialized-type-index.js';
|
|
6
8
|
const DECLARATION_PATTERN = /\b(?:class|struct|interface)\s+([A-Za-z_][A-Za-z0-9_]*)\b/g;
|
|
7
|
-
const
|
|
8
|
-
const RESOURCE_HIT_SCAN_CONCURRENCY =
|
|
9
|
+
const SCRIPT_GUID_IN_LINE_PATTERN = /\bm_Script\s*:\s*\{[^}]*\bguid\s*:\s*([0-9a-f]{32})\b/gi;
|
|
10
|
+
const RESOURCE_HIT_SCAN_CONCURRENCY = 4;
|
|
9
11
|
export async function buildUnityScanContext(input) {
|
|
10
12
|
const scriptFiles = input.symbolDeclarations && input.symbolDeclarations.length > 0
|
|
11
13
|
? resolveScriptFilesFromSymbolDeclarations(input.repoRoot, input.symbolDeclarations, input.scopedPaths)
|
|
@@ -13,8 +15,13 @@ export async function buildUnityScanContext(input) {
|
|
|
13
15
|
const symbolToScriptPaths = input.symbolDeclarations && input.symbolDeclarations.length > 0
|
|
14
16
|
? buildSymbolScriptPathIndexFromDeclarations(input.repoRoot, input.symbolDeclarations, input.scopedPaths)
|
|
15
17
|
: await buildSymbolScriptPathIndex(input.repoRoot, scriptFiles);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
+
const serializableTypeIndex = await buildSerializableTypeIndexFromFiles(scriptFiles.map((scriptPath) => {
|
|
19
|
+
const normalizedPath = normalizeSlashes(scriptPath);
|
|
20
|
+
return {
|
|
21
|
+
filePath: normalizedPath,
|
|
22
|
+
read: async () => fs.readFile(path.join(input.repoRoot, normalizedPath), 'utf-8'),
|
|
23
|
+
};
|
|
24
|
+
}));
|
|
18
25
|
const metaFiles = scriptFiles.map((scriptPath) => `${scriptPath}.meta`);
|
|
19
26
|
const guidToScriptPath = await buildMetaIndex(input.repoRoot, { metaFiles });
|
|
20
27
|
const scriptPathToGuid = new Map();
|
|
@@ -39,21 +46,91 @@ export async function buildUnityScanContext(input) {
|
|
|
39
46
|
resourceDocCache: new Map(),
|
|
40
47
|
};
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
49
|
+
export function buildUnityScanContextFromSeed(input) {
|
|
50
|
+
const seed = input.seed;
|
|
51
|
+
const requestedSymbols = new Set((input.symbolDeclarations || [])
|
|
52
|
+
.map((entry) => String(entry.symbol || '').trim())
|
|
53
|
+
.filter((value) => value.length > 0));
|
|
54
|
+
const requestedScripts = new Set((input.symbolDeclarations || [])
|
|
55
|
+
.map((entry) => normalizeSlashes(String(entry.scriptPath || '').trim()))
|
|
56
|
+
.filter((value) => value.length > 0));
|
|
57
|
+
const symbolToScriptPath = new Map();
|
|
58
|
+
for (const [symbol, scriptPath] of Object.entries(seed.symbolToScriptPath || {})) {
|
|
45
59
|
const normalizedPath = normalizeSlashes(scriptPath);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
if (!normalizedPath)
|
|
61
|
+
continue;
|
|
62
|
+
if (requestedSymbols.size > 0 && !requestedSymbols.has(symbol))
|
|
63
|
+
continue;
|
|
64
|
+
symbolToScriptPath.set(symbol, normalizedPath);
|
|
65
|
+
requestedScripts.add(normalizedPath);
|
|
66
|
+
}
|
|
67
|
+
if (symbolToScriptPath.size === 0 && requestedSymbols.size > 0) {
|
|
68
|
+
for (const declaration of input.symbolDeclarations || []) {
|
|
69
|
+
const symbol = String(declaration.symbol || '').trim();
|
|
70
|
+
const scriptPath = normalizeSlashes(String(declaration.scriptPath || '').trim());
|
|
71
|
+
if (!symbol || !scriptPath)
|
|
72
|
+
continue;
|
|
73
|
+
symbolToScriptPath.set(symbol, scriptPath);
|
|
74
|
+
requestedScripts.add(scriptPath);
|
|
49
75
|
}
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
}
|
|
77
|
+
const symbolToCanonicalScriptPath = new Map(symbolToScriptPath);
|
|
78
|
+
const symbolToScriptPaths = new Map();
|
|
79
|
+
for (const [symbol, scriptPath] of symbolToScriptPath.entries()) {
|
|
80
|
+
symbolToScriptPaths.set(symbol, [scriptPath]);
|
|
81
|
+
}
|
|
82
|
+
const scriptPathToGuid = new Map();
|
|
83
|
+
const normalizedScriptPathToGuidEntries = Object.entries(seed.scriptPathToGuid || {})
|
|
84
|
+
.map(([scriptPath, guid]) => [normalizeSlashes(scriptPath), String(guid || '').trim()])
|
|
85
|
+
.filter(([scriptPath, guid]) => scriptPath.length > 0 && guid.length > 0);
|
|
86
|
+
for (const [scriptPath, guid] of normalizedScriptPathToGuidEntries) {
|
|
87
|
+
if (requestedScripts.size > 0 && !requestedScripts.has(scriptPath))
|
|
88
|
+
continue;
|
|
89
|
+
scriptPathToGuid.set(scriptPath, guid);
|
|
90
|
+
}
|
|
91
|
+
if (scriptPathToGuid.size === 0 && requestedScripts.size > 0) {
|
|
92
|
+
for (const [scriptPath, guid] of normalizedScriptPathToGuidEntries) {
|
|
93
|
+
if (!requestedScripts.has(scriptPath))
|
|
52
94
|
continue;
|
|
53
|
-
|
|
95
|
+
scriptPathToGuid.set(scriptPath, guid);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const selectedGuids = new Set(scriptPathToGuid.values());
|
|
99
|
+
const guidToResourceHits = new Map();
|
|
100
|
+
for (const [guid, resourcePaths] of Object.entries(seed.guidToResourcePaths || {})) {
|
|
101
|
+
if (selectedGuids.size > 0 && !selectedGuids.has(guid))
|
|
102
|
+
continue;
|
|
103
|
+
const hits = (resourcePaths || [])
|
|
104
|
+
.map((resourcePathRaw) => normalizeSlashes(String(resourcePathRaw || '').trim()))
|
|
105
|
+
.filter((resourcePath) => resourcePath.length > 0)
|
|
106
|
+
.map((resourcePath) => ({
|
|
107
|
+
resourcePath,
|
|
108
|
+
resourceType: inferResourceType(resourcePath),
|
|
109
|
+
line: 0,
|
|
110
|
+
lineText: 'seed',
|
|
111
|
+
}));
|
|
112
|
+
if (hits.length > 0) {
|
|
113
|
+
guidToResourceHits.set(guid, hits);
|
|
54
114
|
}
|
|
55
115
|
}
|
|
56
|
-
|
|
116
|
+
const assetGuidToPath = new Map();
|
|
117
|
+
for (const [guid, assetPath] of Object.entries(seed.assetGuidToPath || {})) {
|
|
118
|
+
const normalizedPath = normalizeSlashes(String(assetPath || '').trim());
|
|
119
|
+
if (!guid || !normalizedPath)
|
|
120
|
+
continue;
|
|
121
|
+
assetGuidToPath.set(guid, normalizedPath);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
symbolToScriptPaths,
|
|
125
|
+
symbolToCanonicalScriptPath,
|
|
126
|
+
symbolToScriptPath,
|
|
127
|
+
scriptPathToGuid,
|
|
128
|
+
guidToResourceHits,
|
|
129
|
+
serializableSymbols: new Set(),
|
|
130
|
+
hostFieldTypeHints: new Map(),
|
|
131
|
+
assetGuidToPath,
|
|
132
|
+
resourceDocCache: new Map(),
|
|
133
|
+
};
|
|
57
134
|
}
|
|
58
135
|
async function buildSymbolScriptPathIndex(repoRoot, scriptFiles) {
|
|
59
136
|
const candidates = new Map();
|
|
@@ -93,53 +170,65 @@ async function buildGuidHitIndex(repoRoot, scriptPathToGuid, resourceFiles) {
|
|
|
93
170
|
}
|
|
94
171
|
const perResourceHits = await mapWithConcurrency(resourceFiles, RESOURCE_HIT_SCAN_CONCURRENCY, async (resourcePathRaw) => {
|
|
95
172
|
const resourcePath = normalizeSlashes(resourcePathRaw);
|
|
96
|
-
const absolutePath = path.join(repoRoot, resourcePath);
|
|
97
|
-
let content = '';
|
|
98
|
-
try {
|
|
99
|
-
content = await fs.readFile(absolutePath, 'utf-8');
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
const code = error.code;
|
|
103
|
-
if (code === 'ENOENT' || code === 'EISDIR') {
|
|
104
|
-
return new Map();
|
|
105
|
-
}
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
173
|
const resourceType = inferResourceType(resourcePath);
|
|
109
|
-
const lines = content.split(/\r?\n/);
|
|
110
174
|
const hits = new Map();
|
|
111
|
-
|
|
112
|
-
|
|
175
|
+
await scanGuidHitsInResourceFile(repoRoot, resourcePath, resourceType, guidLookup, hits);
|
|
176
|
+
return hits;
|
|
177
|
+
});
|
|
178
|
+
const guidToResourceHits = new Map();
|
|
179
|
+
for (const hitMap of perResourceHits) {
|
|
180
|
+
for (const [guid, hits] of hitMap.entries()) {
|
|
181
|
+
const existing = guidToResourceHits.get(guid) || [];
|
|
182
|
+
existing.push(...hits);
|
|
183
|
+
guidToResourceHits.set(guid, existing);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return guidToResourceHits;
|
|
187
|
+
}
|
|
188
|
+
async function scanGuidHitsInResourceFile(repoRoot, resourcePath, resourceType, guidLookup, hits) {
|
|
189
|
+
const absolutePath = path.join(repoRoot, resourcePath);
|
|
190
|
+
const stream = createReadStream(absolutePath, { encoding: 'utf-8' });
|
|
191
|
+
const reader = createInterface({
|
|
192
|
+
input: stream,
|
|
193
|
+
crlfDelay: Infinity,
|
|
194
|
+
});
|
|
195
|
+
const seenGuidInResource = new Set();
|
|
196
|
+
let lineNumber = 0;
|
|
197
|
+
try {
|
|
198
|
+
for await (const line of reader) {
|
|
199
|
+
lineNumber += 1;
|
|
113
200
|
const seenCanonical = new Set();
|
|
114
|
-
|
|
115
|
-
let match =
|
|
201
|
+
SCRIPT_GUID_IN_LINE_PATTERN.lastIndex = 0;
|
|
202
|
+
let match = SCRIPT_GUID_IN_LINE_PATTERN.exec(line);
|
|
116
203
|
while (match) {
|
|
117
204
|
const canonicalGuid = guidLookup.get(match[1].toLowerCase());
|
|
118
|
-
if (canonicalGuid && !seenCanonical.has(canonicalGuid)) {
|
|
205
|
+
if (canonicalGuid && !seenCanonical.has(canonicalGuid) && !seenGuidInResource.has(canonicalGuid)) {
|
|
119
206
|
seenCanonical.add(canonicalGuid);
|
|
207
|
+
seenGuidInResource.add(canonicalGuid);
|
|
120
208
|
const existing = hits.get(canonicalGuid) || [];
|
|
121
209
|
existing.push({
|
|
122
210
|
resourcePath,
|
|
123
211
|
resourceType,
|
|
124
|
-
line:
|
|
212
|
+
line: lineNumber,
|
|
125
213
|
lineText: line,
|
|
126
214
|
});
|
|
127
215
|
hits.set(canonicalGuid, existing);
|
|
128
216
|
}
|
|
129
|
-
match =
|
|
217
|
+
match = SCRIPT_GUID_IN_LINE_PATTERN.exec(line);
|
|
130
218
|
}
|
|
131
219
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const existing = guidToResourceHits.get(guid) || [];
|
|
138
|
-
existing.push(...hits);
|
|
139
|
-
guidToResourceHits.set(guid, existing);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
const code = error.code;
|
|
223
|
+
if (code === 'ENOENT' || code === 'EISDIR') {
|
|
224
|
+
return;
|
|
140
225
|
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
reader.close();
|
|
230
|
+
stream.destroy();
|
|
141
231
|
}
|
|
142
|
-
return guidToResourceHits;
|
|
143
232
|
}
|
|
144
233
|
async function resolveScriptFiles(repoRoot, scopedPaths) {
|
|
145
234
|
if (!scopedPaths || scopedPaths.length === 0) {
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { buildUnityScanContext } from './scan-context.js';
|
|
7
|
+
import { buildUnityScanContext, buildUnityScanContextFromSeed } from './scan-context.js';
|
|
8
8
|
import { resolveUnityBindings } from './resolver.js';
|
|
9
9
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const fixtureRoot = path.resolve(here, '../../../src/core/unity/__fixtures__/mini-unity');
|
|
@@ -74,7 +74,7 @@ test('buildUnityScanContext selects canonical script for duplicated symbol decla
|
|
|
74
74
|
await fs.writeFile(path.join(scriptsDir, 'PlayerActor.Visual.cs'), 'public partial class PlayerActor {}', 'utf-8');
|
|
75
75
|
await fs.writeFile(path.join(scriptsDir, 'PlayerActor.cs.meta'), 'guid: 11111111111111111111111111111111\n', 'utf-8');
|
|
76
76
|
await fs.writeFile(path.join(scriptsDir, 'PlayerActor.Visual.cs.meta'), 'guid: 22222222222222222222222222222222\n', 'utf-8');
|
|
77
|
-
await fs.writeFile(path.join(sceneDir, 'Test.unity'), '--- !u!
|
|
77
|
+
await fs.writeFile(path.join(sceneDir, 'Test.unity'), '--- !u!114 &1\nMonoBehaviour:\n m_Script: {fileID: 11500000, guid: 11111111111111111111111111111111, type: 3}\n', 'utf-8');
|
|
78
78
|
const context = await buildUnityScanContext({
|
|
79
79
|
repoRoot: tempRoot,
|
|
80
80
|
symbolDeclarations: [
|
|
@@ -116,3 +116,42 @@ test('buildUnityScanContext exposes serializable symbol index and host field typ
|
|
|
116
116
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
|
+
test('buildUnityScanContext builds serializable index from files without preloading source array', async () => {
|
|
120
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-serializable-streaming-'));
|
|
121
|
+
const scriptsDir = path.join(tempRoot, 'Assets/Scripts');
|
|
122
|
+
await fs.mkdir(scriptsDir, { recursive: true });
|
|
123
|
+
try {
|
|
124
|
+
await fs.writeFile(path.join(scriptsDir, 'AssetRef.cs'), '[Serializable] class AssetRef {}', 'utf-8');
|
|
125
|
+
await fs.writeFile(path.join(scriptsDir, 'Host.cs'), 'class Host { AssetRef icon; }', 'utf-8');
|
|
126
|
+
const context = await buildUnityScanContext({ repoRoot: tempRoot });
|
|
127
|
+
assert.equal(context.serializableSymbols.has('AssetRef'), true);
|
|
128
|
+
assert.equal(context.hostFieldTypeHints.get('Host')?.get('icon'), 'AssetRef');
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
test('buildUnityScanContextFromSeed reconstructs lookup maps for resolver fast path', async () => {
|
|
135
|
+
const context = buildUnityScanContextFromSeed({
|
|
136
|
+
seed: {
|
|
137
|
+
version: 1,
|
|
138
|
+
symbolToScriptPath: {
|
|
139
|
+
MainUIManager: 'Assets/Scripts/MainUIManager.cs',
|
|
140
|
+
},
|
|
141
|
+
scriptPathToGuid: {
|
|
142
|
+
'Assets/Scripts/MainUIManager.cs': '11111111111111111111111111111111',
|
|
143
|
+
},
|
|
144
|
+
guidToResourcePaths: {
|
|
145
|
+
'11111111111111111111111111111111': ['Assets/Scene/MainUIManager.unity'],
|
|
146
|
+
},
|
|
147
|
+
assetGuidToPath: {
|
|
148
|
+
'44444444444444444444444444444444': 'Assets/Config/MainUIDocument.asset',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
symbolDeclarations: [{ symbol: 'MainUIManager', scriptPath: 'Assets/Scripts/MainUIManager.cs' }],
|
|
152
|
+
});
|
|
153
|
+
assert.equal(context.symbolToScriptPath.get('MainUIManager'), 'Assets/Scripts/MainUIManager.cs');
|
|
154
|
+
assert.equal(context.scriptPathToGuid.get('Assets/Scripts/MainUIManager.cs'), '11111111111111111111111111111111');
|
|
155
|
+
assert.equal(context.guidToResourceHits.get('11111111111111111111111111111111')?.length, 1);
|
|
156
|
+
assert.equal(context.assetGuidToPath?.get('44444444444444444444444444444444'), 'Assets/Config/MainUIDocument.asset');
|
|
157
|
+
});
|
|
@@ -6,5 +6,10 @@ interface SourceFile {
|
|
|
6
6
|
filePath: string;
|
|
7
7
|
content: string;
|
|
8
8
|
}
|
|
9
|
+
interface SourceFileReader {
|
|
10
|
+
filePath: string;
|
|
11
|
+
read: () => Promise<string>;
|
|
12
|
+
}
|
|
9
13
|
export declare function buildSerializableTypeIndexFromSources(sources: SourceFile[]): SerializableTypeIndex;
|
|
14
|
+
export declare function buildSerializableTypeIndexFromFiles(files: SourceFileReader[]): Promise<SerializableTypeIndex>;
|
|
10
15
|
export {};
|
|
@@ -4,25 +4,56 @@ const FIELD_DECLARATION_PATTERN = /(?:\[[^\]]+\]\s*)*(?:(?:public|private|protec
|
|
|
4
4
|
export function buildSerializableTypeIndexFromSources(sources) {
|
|
5
5
|
const serializableSymbols = new Set();
|
|
6
6
|
for (const source of sources) {
|
|
7
|
-
|
|
8
|
-
let match = SERIALIZABLE_DECLARATION_PATTERN.exec(source.content);
|
|
9
|
-
while (match) {
|
|
10
|
-
serializableSymbols.add(match[1]);
|
|
11
|
-
match = SERIALIZABLE_DECLARATION_PATTERN.exec(source.content);
|
|
12
|
-
}
|
|
7
|
+
collectSerializableDeclarations(source.content, serializableSymbols);
|
|
13
8
|
}
|
|
14
9
|
const hostFieldTypeHints = new Map();
|
|
15
10
|
for (const source of sources) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
collectHostFieldTypeHints(source.content, serializableSymbols, hostFieldTypeHints);
|
|
12
|
+
}
|
|
13
|
+
return { serializableSymbols, hostFieldTypeHints };
|
|
14
|
+
}
|
|
15
|
+
export async function buildSerializableTypeIndexFromFiles(files) {
|
|
16
|
+
const serializableSymbols = new Set();
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const content = await safeReadFileContent(file);
|
|
19
|
+
collectSerializableDeclarations(content, serializableSymbols);
|
|
20
|
+
}
|
|
21
|
+
const hostFieldTypeHints = new Map();
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const content = await safeReadFileContent(file);
|
|
24
|
+
collectHostFieldTypeHints(content, serializableSymbols, hostFieldTypeHints);
|
|
23
25
|
}
|
|
24
26
|
return { serializableSymbols, hostFieldTypeHints };
|
|
25
27
|
}
|
|
28
|
+
function collectSerializableDeclarations(content, serializableSymbols) {
|
|
29
|
+
SERIALIZABLE_DECLARATION_PATTERN.lastIndex = 0;
|
|
30
|
+
let match = SERIALIZABLE_DECLARATION_PATTERN.exec(content);
|
|
31
|
+
while (match) {
|
|
32
|
+
serializableSymbols.add(match[1]);
|
|
33
|
+
match = SERIALIZABLE_DECLARATION_PATTERN.exec(content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function collectHostFieldTypeHints(content, serializableSymbols, hostFieldTypeHints) {
|
|
37
|
+
const classBodies = extractClassBodies(content);
|
|
38
|
+
for (const classBody of classBodies) {
|
|
39
|
+
const fieldHints = extractHostFieldHints(classBody.body, serializableSymbols);
|
|
40
|
+
if (fieldHints.size > 0) {
|
|
41
|
+
hostFieldTypeHints.set(classBody.name, fieldHints);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function safeReadFileContent(file) {
|
|
46
|
+
try {
|
|
47
|
+
return await file.read();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const code = error.code;
|
|
51
|
+
if (code === 'ENOENT' || code === 'EISDIR') {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
26
57
|
function extractClassBodies(content) {
|
|
27
58
|
const result = [];
|
|
28
59
|
CLASS_DECLARATION_PATTERN.lastIndex = 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { buildSerializableTypeIndexFromSources } from './serialized-type-index.js';
|
|
3
|
+
import { buildSerializableTypeIndexFromFiles, buildSerializableTypeIndexFromSources } from './serialized-type-index.js';
|
|
4
4
|
test('buildSerializableTypeIndex extracts serializable symbols and host field declared types', () => {
|
|
5
5
|
const index = buildSerializableTypeIndexFromSources([
|
|
6
6
|
{
|
|
@@ -32,3 +32,11 @@ test('buildSerializableTypeIndex extracts serializable symbols and host field de
|
|
|
32
32
|
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.get('iconVariants'), 'AssetRef');
|
|
33
33
|
assert.equal(index.hostFieldTypeHints.get('InventoryConfig')?.has('ignored'), false);
|
|
34
34
|
});
|
|
35
|
+
test('buildSerializableTypeIndexFromFiles does not require preloaded source array', async () => {
|
|
36
|
+
const out = await buildSerializableTypeIndexFromFiles([
|
|
37
|
+
{ filePath: 'Assets/A.cs', read: async () => '[Serializable] class AssetRef {}' },
|
|
38
|
+
{ filePath: 'Assets/B.cs', read: async () => 'class Host { AssetRef icon; }' },
|
|
39
|
+
]);
|
|
40
|
+
assert.equal(out.serializableSymbols.has('AssetRef'), true);
|
|
41
|
+
assert.equal(out.hostFieldTypeHints.get('Host')?.get('icon'), 'AssetRef');
|
|
42
|
+
});
|
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
* Supports multiple indexed repositories via a global registry.
|
|
6
6
|
* KuzuDB connections are opened lazily per repo on first query.
|
|
7
7
|
*/
|
|
8
|
+
import { type ResolvedUnityBinding } from '../../core/unity/resolver.js';
|
|
9
|
+
import { type UnityContextPayload, type UnityHydrationMeta } from './unity-enrichment.js';
|
|
8
10
|
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
11
|
+
export declare function mergeUnityBindings(baseBindings: ResolvedUnityBinding[], resolvedByPath: Map<string, ResolvedUnityBinding[]>): ResolvedUnityBinding[];
|
|
12
|
+
export declare function mergeParityUnityBindings(baseNonLightweightBindings: ResolvedUnityBinding[], resolvedBindings: ResolvedUnityBinding[]): ResolvedUnityBinding[];
|
|
13
|
+
export declare function attachUnityHydrationMeta(payload: UnityContextPayload, input: Pick<UnityHydrationMeta, 'requestedMode' | 'effectiveMode' | 'elapsedMs' | 'fallbackToCompact'> & {
|
|
14
|
+
hasExpandableBindings: boolean;
|
|
15
|
+
}): UnityContextPayload;
|
|
9
16
|
export interface CodebaseContext {
|
|
10
17
|
projectName: string;
|
|
11
18
|
stats: {
|
|
@@ -119,6 +126,15 @@ export declare class LocalBackend {
|
|
|
119
126
|
* Routes cluster/process types to direct graph queries.
|
|
120
127
|
*/
|
|
121
128
|
private explore;
|
|
129
|
+
private hydrateUnityContext;
|
|
130
|
+
private buildParityWarmupKey;
|
|
131
|
+
private scheduleParityWarmup;
|
|
132
|
+
private shouldEnableParityWarmup;
|
|
133
|
+
private getOrRunParityHydration;
|
|
134
|
+
private computeParityPayload;
|
|
135
|
+
private hydrateUnityContextParity;
|
|
136
|
+
private hydrateUnityContextCompact;
|
|
137
|
+
private toUnityContextPayload;
|
|
122
138
|
/**
|
|
123
139
|
* Detect changes — git-diff based impact analysis.
|
|
124
140
|
* Maps changed lines to indexed symbols, then finds affected processes.
|