@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
|
@@ -8,8 +8,16 @@
|
|
|
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 {
|
|
11
|
+
import { parseUnityHydrationMode, parseUnityResourcesMode } from '../../core/unity/options.js';
|
|
12
|
+
import { buildUnityScanContext, buildUnityScanContextFromSeed } from '../../core/unity/scan-context.js';
|
|
13
|
+
import { resolveUnityBindings } from '../../core/unity/resolver.js';
|
|
14
|
+
import { formatLazyHydrationBudgetDiagnostic, loadUnityContext, } from './unity-enrichment.js';
|
|
15
|
+
import { resolveUnityLazyConfig } from './unity-lazy-config.js';
|
|
16
|
+
import { hydrateLazyBindings } from './unity-lazy-hydrator.js';
|
|
17
|
+
import { readUnityOverlayBindings, upsertUnityOverlayBindings } from './unity-lazy-overlay.js';
|
|
18
|
+
import { readUnityParityCache, upsertUnityParityCache } from './unity-parity-cache.js';
|
|
19
|
+
import { createParityWarmupQueue } from './unity-parity-warmup-queue.js';
|
|
20
|
+
import { loadUnityParitySeed } from './unity-parity-seed-loader.js';
|
|
13
21
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
14
22
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
15
23
|
// git utilities available if needed
|
|
@@ -30,6 +38,91 @@ function isTestFilePath(filePath) {
|
|
|
30
38
|
p.endsWith('_test.go') || p.endsWith('_test.py') ||
|
|
31
39
|
p.includes('/test_') || p.includes('/conftest.'));
|
|
32
40
|
}
|
|
41
|
+
function normalizePath(filePath) {
|
|
42
|
+
return String(filePath || '').replace(/\\/g, '/');
|
|
43
|
+
}
|
|
44
|
+
function bindingIdentity(binding) {
|
|
45
|
+
return [
|
|
46
|
+
normalizePath(binding.resourcePath),
|
|
47
|
+
binding.bindingKind,
|
|
48
|
+
binding.componentObjectId,
|
|
49
|
+
].join('|');
|
|
50
|
+
}
|
|
51
|
+
export function mergeUnityBindings(baseBindings, resolvedByPath) {
|
|
52
|
+
const merged = [];
|
|
53
|
+
const expandedPaths = new Set();
|
|
54
|
+
for (const binding of baseBindings) {
|
|
55
|
+
const resourcePath = normalizePath(binding.resourcePath);
|
|
56
|
+
if (!binding.lightweight) {
|
|
57
|
+
merged.push(binding);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const expanded = resolvedByPath.get(resourcePath);
|
|
61
|
+
if (expanded && expanded.length > 0) {
|
|
62
|
+
if (!expandedPaths.has(resourcePath)) {
|
|
63
|
+
merged.push(...expanded.map((row) => ({ ...row, lightweight: false })));
|
|
64
|
+
expandedPaths.add(resourcePath);
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
merged.push(binding);
|
|
69
|
+
}
|
|
70
|
+
return merged;
|
|
71
|
+
}
|
|
72
|
+
export function mergeParityUnityBindings(baseNonLightweightBindings, resolvedBindings) {
|
|
73
|
+
const merged = [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const row of [...baseNonLightweightBindings, ...resolvedBindings]) {
|
|
76
|
+
const key = bindingIdentity(row);
|
|
77
|
+
if (seen.has(key))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(key);
|
|
80
|
+
merged.push({ ...row, lightweight: false });
|
|
81
|
+
}
|
|
82
|
+
return merged;
|
|
83
|
+
}
|
|
84
|
+
export function attachUnityHydrationMeta(payload, input) {
|
|
85
|
+
const { hasExpandableBindings, ...metaInput } = input;
|
|
86
|
+
const reasons = [];
|
|
87
|
+
if (metaInput.effectiveMode === 'compact' && hasExpandableBindings) {
|
|
88
|
+
reasons.push('mode_compact');
|
|
89
|
+
}
|
|
90
|
+
if (metaInput.fallbackToCompact) {
|
|
91
|
+
reasons.push('fallback_to_compact');
|
|
92
|
+
}
|
|
93
|
+
if (hasExpandableBindings) {
|
|
94
|
+
reasons.push('lightweight_bindings_remaining');
|
|
95
|
+
}
|
|
96
|
+
if (payload.unityDiagnostics.some((diag) => /budget exceeded/i.test(String(diag || '')))) {
|
|
97
|
+
reasons.push('budget_exceeded');
|
|
98
|
+
}
|
|
99
|
+
const isComplete = reasons.length === 0;
|
|
100
|
+
const needsParityRetry = !isComplete && metaInput.effectiveMode === 'compact';
|
|
101
|
+
return {
|
|
102
|
+
...payload,
|
|
103
|
+
hydrationMeta: {
|
|
104
|
+
...metaInput,
|
|
105
|
+
resourceBindingCount: payload.resourceBindings.length,
|
|
106
|
+
unityDiagnosticsCount: payload.unityDiagnostics.length,
|
|
107
|
+
isComplete,
|
|
108
|
+
completenessReason: reasons,
|
|
109
|
+
needsParityRetry,
|
|
110
|
+
...(needsParityRetry ? { retryHint: 'rerun_with_unity_hydration=parity' } : {}),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const inFlightParityHydration = new Map();
|
|
115
|
+
const parityWarmupQueue = createParityWarmupQueue({
|
|
116
|
+
maxParallel: resolveParityWarmupMaxParallel(process.env),
|
|
117
|
+
});
|
|
118
|
+
function resolveParityWarmupMaxParallel(env) {
|
|
119
|
+
const raw = String(env.GITNEXUS_UNITY_PARITY_WARMUP_MAX_PARALLEL || '').trim();
|
|
120
|
+
const parsed = Number.parseInt(raw, 10);
|
|
121
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
33
126
|
/** Valid KuzuDB node labels for safe Cypher query construction */
|
|
34
127
|
const VALID_NODE_LABELS = new Set([
|
|
35
128
|
'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement',
|
|
@@ -288,6 +381,7 @@ export class LocalBackend {
|
|
|
288
381
|
const maxSymbolsPerProcess = params.max_symbols || 10;
|
|
289
382
|
const includeContent = params.include_content ?? false;
|
|
290
383
|
const unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
|
|
384
|
+
const unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
|
|
291
385
|
const searchQuery = params.query.trim();
|
|
292
386
|
// Step 1: Run hybrid search to get matching symbols
|
|
293
387
|
const searchLimit = processLimit * maxSymbolsPerProcess; // fetch enough raw results
|
|
@@ -387,7 +481,13 @@ export class LocalBackend {
|
|
|
387
481
|
...(module ? { module } : {}),
|
|
388
482
|
...(includeContent && content ? { content } : {}),
|
|
389
483
|
...((unityResourcesMode !== 'off' && sym.nodeId && sym.type === 'Class')
|
|
390
|
-
? await
|
|
484
|
+
? await this.hydrateUnityContext(repo, {
|
|
485
|
+
symbolUid: sym.nodeId,
|
|
486
|
+
symbolName: sym.name || '',
|
|
487
|
+
symbolFilePath: sym.filePath || '',
|
|
488
|
+
payload: await loadUnityContext(repo.id, sym.nodeId, (query) => executeQuery(repo.id, query)),
|
|
489
|
+
hydrationMode: unityHydrationMode,
|
|
490
|
+
})
|
|
391
491
|
: {}),
|
|
392
492
|
};
|
|
393
493
|
if (processRows.length === 0) {
|
|
@@ -803,6 +903,7 @@ export class LocalBackend {
|
|
|
803
903
|
await this.ensureInitialized(repo.id);
|
|
804
904
|
const { name, uid, file_path, include_content } = params;
|
|
805
905
|
const unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
|
|
906
|
+
const unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
|
|
806
907
|
if (!name && !uid) {
|
|
807
908
|
return { error: 'Either "name" or "uid" parameter is required.' };
|
|
808
909
|
}
|
|
@@ -976,7 +1077,15 @@ export class LocalBackend {
|
|
|
976
1077
|
})),
|
|
977
1078
|
};
|
|
978
1079
|
if (unityResourcesMode !== 'off' && symNodeId && symKind === 'Class') {
|
|
979
|
-
|
|
1080
|
+
const unityContext = await loadUnityContext(repo.id, symNodeId, (query) => executeQuery(repo.id, query));
|
|
1081
|
+
const hydratedUnityContext = await this.hydrateUnityContext(repo, {
|
|
1082
|
+
symbolUid: symNodeId,
|
|
1083
|
+
symbolName: getRowValue(sym, 'name', 1) || '',
|
|
1084
|
+
symbolFilePath: symFilePath,
|
|
1085
|
+
payload: unityContext,
|
|
1086
|
+
hydrationMode: unityHydrationMode,
|
|
1087
|
+
});
|
|
1088
|
+
Object.assign(result, hydratedUnityContext);
|
|
980
1089
|
}
|
|
981
1090
|
return result;
|
|
982
1091
|
}
|
|
@@ -1063,6 +1172,213 @@ export class LocalBackend {
|
|
|
1063
1172
|
}
|
|
1064
1173
|
return { error: 'Invalid type. Use: symbol, cluster, or process' };
|
|
1065
1174
|
}
|
|
1175
|
+
async hydrateUnityContext(repo, input) {
|
|
1176
|
+
const startedAt = Date.now();
|
|
1177
|
+
if (input.hydrationMode === 'compact') {
|
|
1178
|
+
const compactPayload = await this.hydrateUnityContextCompact(repo, input);
|
|
1179
|
+
const withMeta = attachUnityHydrationMeta(compactPayload, {
|
|
1180
|
+
requestedMode: 'compact',
|
|
1181
|
+
effectiveMode: 'compact',
|
|
1182
|
+
elapsedMs: Date.now() - startedAt,
|
|
1183
|
+
fallbackToCompact: false,
|
|
1184
|
+
hasExpandableBindings: compactPayload.resourceBindings.some((binding) => binding.lightweight || binding.componentObjectId === 'summary'),
|
|
1185
|
+
});
|
|
1186
|
+
if (withMeta.hydrationMeta?.needsParityRetry) {
|
|
1187
|
+
this.scheduleParityWarmup(repo, input);
|
|
1188
|
+
}
|
|
1189
|
+
return withMeta;
|
|
1190
|
+
}
|
|
1191
|
+
const parityResult = await this.hydrateUnityContextParity(repo, input);
|
|
1192
|
+
return attachUnityHydrationMeta(parityResult.payload, {
|
|
1193
|
+
requestedMode: 'parity',
|
|
1194
|
+
effectiveMode: parityResult.effectiveMode,
|
|
1195
|
+
elapsedMs: Date.now() - startedAt,
|
|
1196
|
+
fallbackToCompact: parityResult.fallbackToCompact,
|
|
1197
|
+
hasExpandableBindings: parityResult.payload.resourceBindings.some((binding) => binding.lightweight || binding.componentObjectId === 'summary'),
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
buildParityWarmupKey(repo, symbolUid) {
|
|
1201
|
+
return `${repo.storagePath}::${repo.lastCommit}::${symbolUid}`;
|
|
1202
|
+
}
|
|
1203
|
+
scheduleParityWarmup(repo, input) {
|
|
1204
|
+
if (!this.shouldEnableParityWarmup()) {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (!input.symbolUid || !input.symbolName || !input.symbolFilePath) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
void parityWarmupQueue.run(() => this.getOrRunParityHydration(repo, input))
|
|
1211
|
+
.then(() => undefined)
|
|
1212
|
+
.catch(() => undefined);
|
|
1213
|
+
}
|
|
1214
|
+
shouldEnableParityWarmup() {
|
|
1215
|
+
const raw = String(process.env.GITNEXUS_UNITY_PARITY_WARMUP || '').trim().toLowerCase();
|
|
1216
|
+
return raw === '1' || raw === 'true' || raw === 'on';
|
|
1217
|
+
}
|
|
1218
|
+
async getOrRunParityHydration(repo, input) {
|
|
1219
|
+
const key = this.buildParityWarmupKey(repo, input.symbolUid);
|
|
1220
|
+
const existing = inFlightParityHydration.get(key);
|
|
1221
|
+
if (existing) {
|
|
1222
|
+
return existing;
|
|
1223
|
+
}
|
|
1224
|
+
const pending = (async () => {
|
|
1225
|
+
const cached = await readUnityParityCache(repo.storagePath, repo.lastCommit, input.symbolUid);
|
|
1226
|
+
if (cached) {
|
|
1227
|
+
return cached;
|
|
1228
|
+
}
|
|
1229
|
+
const payload = await this.computeParityPayload(repo, input);
|
|
1230
|
+
await upsertUnityParityCache(repo.storagePath, repo.lastCommit, input.symbolUid, payload);
|
|
1231
|
+
return payload;
|
|
1232
|
+
})().finally(() => {
|
|
1233
|
+
inFlightParityHydration.delete(key);
|
|
1234
|
+
});
|
|
1235
|
+
inFlightParityHydration.set(key, pending);
|
|
1236
|
+
return pending;
|
|
1237
|
+
}
|
|
1238
|
+
async computeParityPayload(repo, input) {
|
|
1239
|
+
const symbolDeclarations = [{ symbol: input.symbolName, scriptPath: input.symbolFilePath }];
|
|
1240
|
+
const paritySeed = await loadUnityParitySeed(repo.storagePath);
|
|
1241
|
+
const seededScanContext = paritySeed
|
|
1242
|
+
? buildUnityScanContextFromSeed({
|
|
1243
|
+
seed: paritySeed,
|
|
1244
|
+
symbolDeclarations,
|
|
1245
|
+
})
|
|
1246
|
+
: null;
|
|
1247
|
+
let resolved = await resolveUnityBindings({
|
|
1248
|
+
repoRoot: repo.repoPath,
|
|
1249
|
+
symbol: input.symbolName,
|
|
1250
|
+
scanContext: seededScanContext || await buildUnityScanContext({
|
|
1251
|
+
repoRoot: repo.repoPath,
|
|
1252
|
+
symbolDeclarations,
|
|
1253
|
+
}),
|
|
1254
|
+
deepParseLargeResources: true,
|
|
1255
|
+
});
|
|
1256
|
+
if (seededScanContext
|
|
1257
|
+
&& resolved.resourceBindings.length === 0
|
|
1258
|
+
&& input.payload.resourceBindings.length > 0) {
|
|
1259
|
+
const fallbackScanContext = await buildUnityScanContext({
|
|
1260
|
+
repoRoot: repo.repoPath,
|
|
1261
|
+
symbolDeclarations,
|
|
1262
|
+
});
|
|
1263
|
+
resolved = await resolveUnityBindings({
|
|
1264
|
+
repoRoot: repo.repoPath,
|
|
1265
|
+
symbol: input.symbolName,
|
|
1266
|
+
scanContext: fallbackScanContext,
|
|
1267
|
+
deepParseLargeResources: true,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
if (resolved.resourceBindings.length === 0 && input.payload.resourceBindings.length > 0) {
|
|
1271
|
+
throw new Error('parity-expand returned zero bindings');
|
|
1272
|
+
}
|
|
1273
|
+
const baseNonLightweight = input.payload.resourceBindings.filter((binding) => !binding.lightweight && binding.componentObjectId !== 'summary');
|
|
1274
|
+
const mergedBindings = mergeParityUnityBindings(baseNonLightweight, resolved.resourceBindings);
|
|
1275
|
+
return this.toUnityContextPayload(mergedBindings, [
|
|
1276
|
+
...input.payload.unityDiagnostics,
|
|
1277
|
+
...resolved.unityDiagnostics,
|
|
1278
|
+
]);
|
|
1279
|
+
}
|
|
1280
|
+
async hydrateUnityContextParity(repo, input) {
|
|
1281
|
+
try {
|
|
1282
|
+
return {
|
|
1283
|
+
payload: await this.getOrRunParityHydration(repo, input),
|
|
1284
|
+
effectiveMode: 'parity',
|
|
1285
|
+
fallbackToCompact: false,
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
catch (error) {
|
|
1289
|
+
const compactFallback = await this.hydrateUnityContextCompact(repo, input);
|
|
1290
|
+
const message = String(error instanceof Error ? error.message : error);
|
|
1291
|
+
return {
|
|
1292
|
+
payload: {
|
|
1293
|
+
...compactFallback,
|
|
1294
|
+
unityDiagnostics: [
|
|
1295
|
+
...compactFallback.unityDiagnostics,
|
|
1296
|
+
/parity-expand returned zero bindings/i.test(message)
|
|
1297
|
+
? 'parity-expand returned zero bindings; fell back to compact hydration'
|
|
1298
|
+
: `parity-expand failed: ${message}`,
|
|
1299
|
+
],
|
|
1300
|
+
},
|
|
1301
|
+
effectiveMode: 'compact',
|
|
1302
|
+
fallbackToCompact: true,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
async hydrateUnityContextCompact(repo, input) {
|
|
1307
|
+
const lightweightPaths = [...new Set(input.payload.resourceBindings
|
|
1308
|
+
.filter((binding) => binding.lightweight || binding.componentObjectId === 'summary')
|
|
1309
|
+
.map((binding) => normalizePath(binding.resourcePath))
|
|
1310
|
+
.filter((value) => value.length > 0))];
|
|
1311
|
+
if (lightweightPaths.length === 0) {
|
|
1312
|
+
return input.payload;
|
|
1313
|
+
}
|
|
1314
|
+
const overlayHits = await readUnityOverlayBindings(repo.storagePath, repo.lastCommit, input.symbolUid, lightweightPaths);
|
|
1315
|
+
const pendingPaths = lightweightPaths.filter((resourcePath) => !overlayHits.has(resourcePath));
|
|
1316
|
+
const resolvedByPath = new Map(overlayHits);
|
|
1317
|
+
const unityDiagnostics = [...input.payload.unityDiagnostics];
|
|
1318
|
+
if (pendingPaths.length > 0) {
|
|
1319
|
+
try {
|
|
1320
|
+
const cfg = resolveUnityLazyConfig(process.env);
|
|
1321
|
+
const hydration = await hydrateLazyBindings({
|
|
1322
|
+
pendingPaths,
|
|
1323
|
+
config: cfg,
|
|
1324
|
+
dedupeKey: `${input.symbolUid}::${pendingPaths.slice().sort().join('|')}`,
|
|
1325
|
+
resolveBatch: async (resourcePaths) => {
|
|
1326
|
+
const scopedPaths = [
|
|
1327
|
+
input.symbolFilePath,
|
|
1328
|
+
`${input.symbolFilePath}.meta`,
|
|
1329
|
+
...resourcePaths,
|
|
1330
|
+
...resourcePaths.map((resourcePath) => `${resourcePath}.meta`),
|
|
1331
|
+
].map(normalizePath);
|
|
1332
|
+
const scanContext = await buildUnityScanContext({
|
|
1333
|
+
repoRoot: repo.repoPath,
|
|
1334
|
+
scopedPaths,
|
|
1335
|
+
symbolDeclarations: [{ symbol: input.symbolName, scriptPath: input.symbolFilePath }],
|
|
1336
|
+
});
|
|
1337
|
+
const resolved = await resolveUnityBindings({
|
|
1338
|
+
repoRoot: repo.repoPath,
|
|
1339
|
+
symbol: input.symbolName,
|
|
1340
|
+
scanContext,
|
|
1341
|
+
resourcePathAllowlist: resourcePaths,
|
|
1342
|
+
deepParseLargeResources: true,
|
|
1343
|
+
});
|
|
1344
|
+
unityDiagnostics.push(...resolved.unityDiagnostics);
|
|
1345
|
+
const byPath = new Map();
|
|
1346
|
+
for (const resourcePath of resourcePaths) {
|
|
1347
|
+
byPath.set(resourcePath, resolved.resourceBindings.filter((binding) => normalizePath(binding.resourcePath) === normalizePath(resourcePath)));
|
|
1348
|
+
}
|
|
1349
|
+
return byPath;
|
|
1350
|
+
},
|
|
1351
|
+
});
|
|
1352
|
+
const freshByPath = hydration.resolvedByPath;
|
|
1353
|
+
if (hydration.timedOut) {
|
|
1354
|
+
unityDiagnostics.push(formatLazyHydrationBudgetDiagnostic(hydration.elapsedMs));
|
|
1355
|
+
}
|
|
1356
|
+
const hydrationExtras = hydration.diagnostics.filter((diag) => !/budget exceeded/i.test(diag));
|
|
1357
|
+
if (hydrationExtras.length > 0) {
|
|
1358
|
+
unityDiagnostics.push(...hydrationExtras);
|
|
1359
|
+
}
|
|
1360
|
+
await upsertUnityOverlayBindings(repo.storagePath, repo.lastCommit, input.symbolUid, freshByPath);
|
|
1361
|
+
for (const [resourcePath, bindings] of freshByPath.entries()) {
|
|
1362
|
+
resolvedByPath.set(resourcePath, bindings);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
catch (error) {
|
|
1366
|
+
unityDiagnostics.push(`lazy-expand failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const mergedBindings = mergeUnityBindings(input.payload.resourceBindings, resolvedByPath);
|
|
1370
|
+
return this.toUnityContextPayload(mergedBindings, unityDiagnostics);
|
|
1371
|
+
}
|
|
1372
|
+
toUnityContextPayload(resourceBindings, unityDiagnostics) {
|
|
1373
|
+
return {
|
|
1374
|
+
resourceBindings,
|
|
1375
|
+
serializedFields: {
|
|
1376
|
+
scalarFields: resourceBindings.flatMap((binding) => binding.serializedFields.scalarFields),
|
|
1377
|
+
referenceFields: resourceBindings.flatMap((binding) => binding.serializedFields.referenceFields),
|
|
1378
|
+
},
|
|
1379
|
+
unityDiagnostics,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1066
1382
|
/**
|
|
1067
1383
|
* Detect changes — git-diff based impact analysis.
|
|
1068
1384
|
* Maps changed lines to indexed symbols, then finds affected processes.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { projectUnityBindings } from './unity-enrichment.js';
|
|
4
|
+
import { hydrateLazyBindings } from './unity-lazy-hydrator.js';
|
|
5
|
+
import { attachUnityHydrationMeta, mergeParityUnityBindings, mergeUnityBindings } from './local-backend.js';
|
|
6
|
+
test('summary-only rows hydrate and merge into full bindings with preserved field coverage', async () => {
|
|
7
|
+
const projected = projectUnityBindings([
|
|
8
|
+
{
|
|
9
|
+
relationType: 'UNITY_RESOURCE_SUMMARY',
|
|
10
|
+
relationReason: JSON.stringify({ resourceType: 'prefab', bindingKinds: ['direct', 'nested'], lightweight: true }),
|
|
11
|
+
resourcePath: 'Assets/Doors/Door.prefab',
|
|
12
|
+
payload: '',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
relationType: 'UNITY_RESOURCE_SUMMARY',
|
|
16
|
+
relationReason: JSON.stringify({ resourceType: 'prefab', bindingKinds: ['prefab-instance'], lightweight: true }),
|
|
17
|
+
resourcePath: 'Assets/Doors/Boss.prefab',
|
|
18
|
+
payload: '',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
22
|
+
relationType: 'UNITY_COMPONENT_INSTANCE',
|
|
23
|
+
relationReason: 'scene-override',
|
|
24
|
+
payload: JSON.stringify({
|
|
25
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
26
|
+
resourceType: 'scene',
|
|
27
|
+
bindingKind: 'scene-override',
|
|
28
|
+
componentObjectId: '11400000',
|
|
29
|
+
evidence: { line: 9, lineText: 'm_Script: ...' },
|
|
30
|
+
serializedFields: {
|
|
31
|
+
scalarFields: [{ name: 'needPause', value: '1', valueType: 'number', sourceLayer: 'scene' }],
|
|
32
|
+
referenceFields: [],
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
const pendingPaths = [...new Set(projected.resourceBindings
|
|
38
|
+
.filter((binding) => binding.lightweight)
|
|
39
|
+
.map((binding) => binding.resourcePath))];
|
|
40
|
+
const hydration = await hydrateLazyBindings({
|
|
41
|
+
pendingPaths,
|
|
42
|
+
config: { maxPendingPathsPerRequest: 10, batchSize: 10, maxHydrationMs: 5000 },
|
|
43
|
+
resolveBatch: async () => new Map([
|
|
44
|
+
['Assets/Doors/Door.prefab', [
|
|
45
|
+
{
|
|
46
|
+
resourcePath: 'Assets/Doors/Door.prefab',
|
|
47
|
+
resourceType: 'prefab',
|
|
48
|
+
bindingKind: 'direct',
|
|
49
|
+
componentObjectId: '114',
|
|
50
|
+
lightweight: false,
|
|
51
|
+
evidence: { line: 12, lineText: 'm_Script: ...' },
|
|
52
|
+
serializedFields: {
|
|
53
|
+
scalarFields: [{ name: 'Shows', value: '1', valueType: 'number', sourceLayer: 'prefab' }],
|
|
54
|
+
referenceFields: [],
|
|
55
|
+
},
|
|
56
|
+
resolvedReferences: [],
|
|
57
|
+
assetRefPaths: [],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
resourcePath: 'Assets/Doors/Door.prefab',
|
|
61
|
+
resourceType: 'prefab',
|
|
62
|
+
bindingKind: 'nested',
|
|
63
|
+
componentObjectId: '115',
|
|
64
|
+
lightweight: false,
|
|
65
|
+
evidence: { line: 33, lineText: 'm_Script: ...' },
|
|
66
|
+
serializedFields: {
|
|
67
|
+
scalarFields: [{ name: 'ToSecretRoom', value: '0', valueType: 'number', sourceLayer: 'prefab' }],
|
|
68
|
+
referenceFields: [],
|
|
69
|
+
},
|
|
70
|
+
resolvedReferences: [],
|
|
71
|
+
assetRefPaths: [],
|
|
72
|
+
},
|
|
73
|
+
]],
|
|
74
|
+
['Assets/Doors/Boss.prefab', [
|
|
75
|
+
{
|
|
76
|
+
resourcePath: 'Assets/Doors/Boss.prefab',
|
|
77
|
+
resourceType: 'prefab',
|
|
78
|
+
bindingKind: 'prefab-instance',
|
|
79
|
+
componentObjectId: '210',
|
|
80
|
+
lightweight: false,
|
|
81
|
+
evidence: { line: 19, lineText: 'm_Script: ...' },
|
|
82
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
83
|
+
resolvedReferences: [],
|
|
84
|
+
assetRefPaths: [],
|
|
85
|
+
},
|
|
86
|
+
]],
|
|
87
|
+
]),
|
|
88
|
+
});
|
|
89
|
+
const merged = mergeUnityBindings(projected.resourceBindings, hydration.resolvedByPath);
|
|
90
|
+
assert.equal(merged.length, 4);
|
|
91
|
+
assert.equal(merged.filter((row) => row.resourcePath === 'Assets/Doors/Door.prefab').length, 2);
|
|
92
|
+
assert.equal(merged.filter((row) => row.resourcePath === 'Assets/Doors/Boss.prefab').length, 1);
|
|
93
|
+
assert.equal(merged.filter((row) => row.resourcePath === 'Assets/Scene/Test.unity').length, 1);
|
|
94
|
+
assert.equal(merged.some((row) => row.componentObjectId === 'summary'), false);
|
|
95
|
+
assert.equal(merged.some((row) => row.lightweight), false);
|
|
96
|
+
const scalarFieldNames = merged.flatMap((row) => row.serializedFields.scalarFields.map((field) => field.name));
|
|
97
|
+
assert.equal(scalarFieldNames.includes('Shows'), true);
|
|
98
|
+
assert.equal(scalarFieldNames.includes('ToSecretRoom'), true);
|
|
99
|
+
assert.equal(scalarFieldNames.includes('needPause'), true);
|
|
100
|
+
});
|
|
101
|
+
test('mergeUnityBindings keeps lightweight summaries when hydration has no expanded rows', async () => {
|
|
102
|
+
const projected = projectUnityBindings([
|
|
103
|
+
{
|
|
104
|
+
relationType: 'UNITY_RESOURCE_SUMMARY',
|
|
105
|
+
relationReason: JSON.stringify({ resourceType: 'prefab', bindingKinds: ['direct'], lightweight: true }),
|
|
106
|
+
resourcePath: 'Assets/Doors/Unresolved.prefab',
|
|
107
|
+
payload: '',
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
const merged = mergeUnityBindings(projected.resourceBindings, new Map());
|
|
111
|
+
assert.equal(merged.length, 1);
|
|
112
|
+
assert.equal(merged[0]?.resourcePath, 'Assets/Doors/Unresolved.prefab');
|
|
113
|
+
assert.equal(merged[0]?.lightweight, true);
|
|
114
|
+
assert.equal(merged[0]?.componentObjectId, 'summary');
|
|
115
|
+
});
|
|
116
|
+
test('mergeParityUnityBindings prefers full resolved rows and preserves non-lightweight base evidence', () => {
|
|
117
|
+
const merged = mergeParityUnityBindings([
|
|
118
|
+
{
|
|
119
|
+
resourcePath: 'Assets/Doors/Legacy.prefab',
|
|
120
|
+
resourceType: 'prefab',
|
|
121
|
+
bindingKind: 'direct',
|
|
122
|
+
componentObjectId: 'legacy-1',
|
|
123
|
+
lightweight: false,
|
|
124
|
+
evidence: { line: 1, lineText: 'legacy' },
|
|
125
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
126
|
+
resolvedReferences: [],
|
|
127
|
+
assetRefPaths: [],
|
|
128
|
+
},
|
|
129
|
+
], [
|
|
130
|
+
{
|
|
131
|
+
resourcePath: 'Assets/Doors/New.prefab',
|
|
132
|
+
resourceType: 'prefab',
|
|
133
|
+
bindingKind: 'direct',
|
|
134
|
+
componentObjectId: 'new-1',
|
|
135
|
+
lightweight: false,
|
|
136
|
+
evidence: { line: 2, lineText: 'new' },
|
|
137
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
138
|
+
resolvedReferences: [],
|
|
139
|
+
assetRefPaths: [],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
resourcePath: 'Assets/Doors/New.prefab',
|
|
143
|
+
resourceType: 'prefab',
|
|
144
|
+
bindingKind: 'direct',
|
|
145
|
+
componentObjectId: 'new-1',
|
|
146
|
+
lightweight: false,
|
|
147
|
+
evidence: { line: 2, lineText: 'new' },
|
|
148
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
149
|
+
resolvedReferences: [],
|
|
150
|
+
assetRefPaths: [],
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
assert.equal(merged.length, 2);
|
|
154
|
+
assert.equal(merged.some((row) => row.componentObjectId === 'legacy-1'), true);
|
|
155
|
+
assert.equal(merged.some((row) => row.componentObjectId === 'new-1'), true);
|
|
156
|
+
assert.equal(merged.some((row) => row.lightweight), false);
|
|
157
|
+
});
|
|
158
|
+
test('attachUnityHydrationMeta annotates payload with requested/effective mode and counts', () => {
|
|
159
|
+
const payload = projectUnityBindings([
|
|
160
|
+
{
|
|
161
|
+
relationType: 'UNITY_RESOURCE_SUMMARY',
|
|
162
|
+
relationReason: JSON.stringify({ resourceType: 'prefab', bindingKinds: ['direct'], lightweight: true }),
|
|
163
|
+
resourcePath: 'Assets/Doors/Door.prefab',
|
|
164
|
+
payload: '',
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
const out = attachUnityHydrationMeta(payload, {
|
|
168
|
+
requestedMode: 'parity',
|
|
169
|
+
effectiveMode: 'compact',
|
|
170
|
+
elapsedMs: 42,
|
|
171
|
+
fallbackToCompact: true,
|
|
172
|
+
hasExpandableBindings: true,
|
|
173
|
+
});
|
|
174
|
+
assert.equal(out.hydrationMeta?.requestedMode, 'parity');
|
|
175
|
+
assert.equal(out.hydrationMeta?.effectiveMode, 'compact');
|
|
176
|
+
assert.equal(out.hydrationMeta?.elapsedMs, 42);
|
|
177
|
+
assert.equal(out.hydrationMeta?.fallbackToCompact, true);
|
|
178
|
+
assert.equal(out.hydrationMeta?.resourceBindingCount, 1);
|
|
179
|
+
assert.equal(out.hydrationMeta?.unityDiagnosticsCount, 0);
|
|
180
|
+
assert.equal(out.hydrationMeta?.isComplete, false);
|
|
181
|
+
assert.equal(out.hydrationMeta?.needsParityRetry, true);
|
|
182
|
+
assert.equal(out.hydrationMeta?.retryHint, 'rerun_with_unity_hydration=parity');
|
|
183
|
+
assert.equal(out.hydrationMeta?.completenessReason.includes('mode_compact'), true);
|
|
184
|
+
assert.equal(out.hydrationMeta?.completenessReason.includes('fallback_to_compact'), true);
|
|
185
|
+
assert.equal(out.hydrationMeta?.completenessReason.includes('lightweight_bindings_remaining'), true);
|
|
186
|
+
});
|
|
187
|
+
test('attachUnityHydrationMeta marks parity payload complete when no fallback/diagnostics remain', () => {
|
|
188
|
+
const payload = projectUnityBindings([
|
|
189
|
+
{
|
|
190
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
191
|
+
relationType: 'UNITY_COMPONENT_INSTANCE',
|
|
192
|
+
relationReason: 'scene-override',
|
|
193
|
+
payload: JSON.stringify({
|
|
194
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
195
|
+
resourceType: 'scene',
|
|
196
|
+
bindingKind: 'scene-override',
|
|
197
|
+
componentObjectId: '11400000',
|
|
198
|
+
evidence: { line: 9, lineText: 'm_Script: ...' },
|
|
199
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
const out = attachUnityHydrationMeta(payload, {
|
|
204
|
+
requestedMode: 'parity',
|
|
205
|
+
effectiveMode: 'parity',
|
|
206
|
+
elapsedMs: 12,
|
|
207
|
+
fallbackToCompact: false,
|
|
208
|
+
hasExpandableBindings: false,
|
|
209
|
+
});
|
|
210
|
+
assert.equal(out.hydrationMeta?.isComplete, true);
|
|
211
|
+
assert.equal(out.hydrationMeta?.needsParityRetry, false);
|
|
212
|
+
assert.equal(out.hydrationMeta?.retryHint, undefined);
|
|
213
|
+
assert.deepEqual(out.hydrationMeta?.completenessReason, []);
|
|
214
|
+
});
|
|
215
|
+
test('attachUnityHydrationMeta marks compact payload complete when no expandable bindings remain', () => {
|
|
216
|
+
const payload = projectUnityBindings([
|
|
217
|
+
{
|
|
218
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
219
|
+
relationType: 'UNITY_COMPONENT_INSTANCE',
|
|
220
|
+
relationReason: 'scene-override',
|
|
221
|
+
payload: JSON.stringify({
|
|
222
|
+
resourcePath: 'Assets/Scene/Test.unity',
|
|
223
|
+
resourceType: 'scene',
|
|
224
|
+
bindingKind: 'scene-override',
|
|
225
|
+
componentObjectId: '11400000',
|
|
226
|
+
evidence: { line: 9, lineText: 'm_Script: ...' },
|
|
227
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
const out = attachUnityHydrationMeta(payload, {
|
|
232
|
+
requestedMode: 'compact',
|
|
233
|
+
effectiveMode: 'compact',
|
|
234
|
+
elapsedMs: 5,
|
|
235
|
+
fallbackToCompact: false,
|
|
236
|
+
hasExpandableBindings: false,
|
|
237
|
+
});
|
|
238
|
+
assert.equal(out.hydrationMeta?.isComplete, true);
|
|
239
|
+
assert.equal(out.hydrationMeta?.needsParityRetry, false);
|
|
240
|
+
assert.deepEqual(out.hydrationMeta?.completenessReason, []);
|
|
241
|
+
});
|
|
242
|
+
test('attachUnityHydrationMeta marks compact payload incomplete when expandable bindings remain', () => {
|
|
243
|
+
const payload = projectUnityBindings([
|
|
244
|
+
{
|
|
245
|
+
relationType: 'UNITY_RESOURCE_SUMMARY',
|
|
246
|
+
relationReason: JSON.stringify({ resourceType: 'prefab', bindingKinds: ['direct'], lightweight: true }),
|
|
247
|
+
resourcePath: 'Assets/Doors/Door.prefab',
|
|
248
|
+
payload: '',
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
const out = attachUnityHydrationMeta(payload, {
|
|
252
|
+
requestedMode: 'compact',
|
|
253
|
+
effectiveMode: 'compact',
|
|
254
|
+
elapsedMs: 5,
|
|
255
|
+
fallbackToCompact: false,
|
|
256
|
+
hasExpandableBindings: true,
|
|
257
|
+
});
|
|
258
|
+
assert.equal(out.hydrationMeta?.isComplete, false);
|
|
259
|
+
assert.equal(out.hydrationMeta?.needsParityRetry, true);
|
|
260
|
+
assert.equal(out.hydrationMeta?.completenessReason.includes('mode_compact'), true);
|
|
261
|
+
});
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { ResolveOutput } from '../../core/unity/resolver.js';
|
|
2
|
+
import type { UnityHydrationMode } from '../../core/unity/options.js';
|
|
3
|
+
export interface UnityHydrationMeta {
|
|
4
|
+
requestedMode: UnityHydrationMode;
|
|
5
|
+
effectiveMode: UnityHydrationMode;
|
|
6
|
+
elapsedMs: number;
|
|
7
|
+
fallbackToCompact: boolean;
|
|
8
|
+
resourceBindingCount: number;
|
|
9
|
+
unityDiagnosticsCount: number;
|
|
10
|
+
isComplete: boolean;
|
|
11
|
+
completenessReason: string[];
|
|
12
|
+
needsParityRetry: boolean;
|
|
13
|
+
retryHint?: string;
|
|
14
|
+
}
|
|
2
15
|
export interface UnityContextPayload extends Pick<ResolveOutput, 'resourceBindings' | 'serializedFields' | 'unityDiagnostics'> {
|
|
16
|
+
hydrationMeta?: UnityHydrationMeta;
|
|
3
17
|
}
|
|
4
18
|
export type ExecuteQuery = (query: string) => Promise<any[]>;
|
|
5
19
|
export declare function loadUnityContext(_repoId: string, symbolId: string, execute: ExecuteQuery): Promise<UnityContextPayload>;
|
|
20
|
+
export declare function formatLazyHydrationBudgetDiagnostic(elapsedMs: number): string;
|
|
6
21
|
export declare function projectUnityBindings(rows: any[]): UnityContextPayload;
|