@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { toPipelineRuntimeSummary } from './analyze-runtime-summary.js';
|
|
4
|
+
test('toPipelineRuntimeSummary drops graph reference and preserves reporting fields', () => {
|
|
5
|
+
const out = toPipelineRuntimeSummary({
|
|
6
|
+
totalFileCount: 12,
|
|
7
|
+
communityResult: { stats: { totalCommunities: 3 } },
|
|
8
|
+
processResult: { stats: { totalProcesses: 2 } },
|
|
9
|
+
unityResult: { diagnostics: ['scanContext: scripts=1'] },
|
|
10
|
+
});
|
|
11
|
+
assert.equal('graph' in out, false);
|
|
12
|
+
assert.equal(out.totalFileCount, 12);
|
|
13
|
+
assert.equal(out.communityResult?.stats.totalCommunities, 3);
|
|
14
|
+
});
|
package/dist/cli/analyze.js
CHANGED
|
@@ -20,6 +20,9 @@ import fs from 'fs/promises';
|
|
|
20
20
|
import { registerClaudeHook } from './claude-hooks.js';
|
|
21
21
|
import { resolveEffectiveAnalyzeOptions } from './analyze-options.js';
|
|
22
22
|
import { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
|
|
23
|
+
import { resolveChildProcessExit } from './exit-code.js';
|
|
24
|
+
import { shouldCloseKuzuOnAnalyzeExit } from './analyze-close-policy.js';
|
|
25
|
+
import { toPipelineRuntimeSummary } from './analyze-runtime-summary.js';
|
|
23
26
|
const HEAP_MB = 8192;
|
|
24
27
|
const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
|
|
25
28
|
/** Re-exec the process with an 8GB heap if we're currently below that. */
|
|
@@ -37,7 +40,11 @@ function ensureHeap() {
|
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
catch (e) {
|
|
40
|
-
|
|
43
|
+
const resolved = resolveChildProcessExit(e, 1);
|
|
44
|
+
if (resolved.bySignal && resolved.signal) {
|
|
45
|
+
console.error(` analyze subprocess terminated by signal ${resolved.signal}`);
|
|
46
|
+
}
|
|
47
|
+
process.exitCode = resolved.code;
|
|
41
48
|
}
|
|
42
49
|
return true;
|
|
43
50
|
}
|
|
@@ -149,6 +156,10 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
149
156
|
aborted = true;
|
|
150
157
|
bar.stop();
|
|
151
158
|
console.log('\n Interrupted — cleaning up...');
|
|
159
|
+
if (!shouldCloseKuzuOnAnalyzeExit()) {
|
|
160
|
+
process.exit(130);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
152
163
|
closeKuzu().catch(() => { }).finally(() => process.exit(130));
|
|
153
164
|
};
|
|
154
165
|
process.on('SIGINT', sigintHandler);
|
|
@@ -246,6 +257,8 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
246
257
|
const progress = Math.min(84, 60 + Math.round((kuzuMsgCount / (kuzuMsgCount + 10)) * 24));
|
|
247
258
|
updateBar(progress, msg);
|
|
248
259
|
});
|
|
260
|
+
const pipelineRuntime = toPipelineRuntimeSummary(pipelineResult);
|
|
261
|
+
pipelineResult = undefined;
|
|
249
262
|
const kuzuTime = ((Date.now() - t0Kuzu) / 1000).toFixed(1);
|
|
250
263
|
const kuzuWarnings = kuzuResult.warnings;
|
|
251
264
|
// ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
|
|
@@ -312,38 +325,41 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
312
325
|
embeddings: embeddingsEnabled,
|
|
313
326
|
},
|
|
314
327
|
stats: {
|
|
315
|
-
files:
|
|
328
|
+
files: pipelineRuntime.totalFileCount,
|
|
316
329
|
nodes: stats.nodes,
|
|
317
330
|
edges: stats.edges,
|
|
318
|
-
communities:
|
|
319
|
-
processes:
|
|
331
|
+
communities: pipelineRuntime.communityResult?.stats.totalCommunities,
|
|
332
|
+
processes: pipelineRuntime.processResult?.stats.totalProcesses,
|
|
320
333
|
},
|
|
321
334
|
};
|
|
322
335
|
await saveMeta(storagePath, meta);
|
|
336
|
+
await persistUnityParitySeed(storagePath, pipelineRuntime.unityResult?.paritySeed);
|
|
323
337
|
const registeredRepo = await registerRepo(repoPath, meta, { repoAlias });
|
|
324
338
|
await addToGitignore(repoPath);
|
|
325
339
|
const hookResult = await registerClaudeHook();
|
|
326
340
|
const projectName = path.basename(repoPath);
|
|
327
341
|
let aggregatedClusterCount = 0;
|
|
328
|
-
if (
|
|
342
|
+
if (pipelineRuntime.communityResult?.communities) {
|
|
329
343
|
const groups = new Map();
|
|
330
|
-
for (const c of
|
|
344
|
+
for (const c of pipelineRuntime.communityResult.communities) {
|
|
331
345
|
const label = c.heuristicLabel || c.label || 'Unknown';
|
|
332
346
|
groups.set(label, (groups.get(label) || 0) + c.symbolCount);
|
|
333
347
|
}
|
|
334
348
|
aggregatedClusterCount = Array.from(groups.values()).filter(count => count >= 5).length;
|
|
335
349
|
}
|
|
336
350
|
const aiContext = await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
337
|
-
files:
|
|
351
|
+
files: pipelineRuntime.totalFileCount,
|
|
338
352
|
nodes: stats.nodes,
|
|
339
353
|
edges: stats.edges,
|
|
340
|
-
communities:
|
|
354
|
+
communities: pipelineRuntime.communityResult?.stats.totalCommunities,
|
|
341
355
|
clusters: aggregatedClusterCount,
|
|
342
|
-
processes:
|
|
356
|
+
processes: pipelineRuntime.processResult?.stats.totalProcesses,
|
|
343
357
|
}, {
|
|
344
358
|
skillScope: ((await loadCLIConfig()).setupScope === 'global') ? 'global' : 'project',
|
|
345
359
|
});
|
|
346
|
-
|
|
360
|
+
if (shouldCloseKuzuOnAnalyzeExit()) {
|
|
361
|
+
await closeKuzu();
|
|
362
|
+
}
|
|
347
363
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
348
364
|
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
|
|
349
365
|
// Since the process exits immediately after, Node.js reclaims everything.
|
|
@@ -361,9 +377,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
361
377
|
console.log(` Repo Name: ${registeredRepo.name}`);
|
|
362
378
|
console.log(` Repo Alias: ${registeredRepo.alias || 'none'}`);
|
|
363
379
|
console.log(` Scope Rules: ${scopeRules.length}`);
|
|
364
|
-
console.log(` Scoped Files: ${
|
|
365
|
-
if (scopeRules.length > 0 &&
|
|
366
|
-
const diagnostics =
|
|
380
|
+
console.log(` Scoped Files: ${pipelineRuntime.totalFileCount}`);
|
|
381
|
+
if (scopeRules.length > 0 && pipelineRuntime.scopeDiagnostics) {
|
|
382
|
+
const diagnostics = pipelineRuntime.scopeDiagnostics;
|
|
367
383
|
console.log(` Scope Overlap Files: ${diagnostics.overlapFiles} (${diagnostics.dedupedMatchCount} duplicate matches removed)`);
|
|
368
384
|
if (diagnostics.normalizedCollisions.length === 0) {
|
|
369
385
|
console.log(' Scope Collisions: none');
|
|
@@ -378,11 +394,11 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
378
394
|
}
|
|
379
395
|
}
|
|
380
396
|
}
|
|
381
|
-
const unitySummaryLines = formatUnityDiagnosticsSummary(
|
|
397
|
+
const unitySummaryLines = formatUnityDiagnosticsSummary(pipelineRuntime.unityResult?.diagnostics);
|
|
382
398
|
for (const line of unitySummaryLines) {
|
|
383
399
|
console.log(` ${line}`);
|
|
384
400
|
}
|
|
385
|
-
console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${
|
|
401
|
+
console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineRuntime.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineRuntime.processResult?.stats.totalProcesses || 0} flows`);
|
|
386
402
|
console.log(` KuzuDB ${kuzuTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
|
|
387
403
|
if (includeExtensions.length > 0) {
|
|
388
404
|
console.log(` File filter: ${includeExtensions.join(', ')}`);
|
|
@@ -416,3 +432,14 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
416
432
|
process.exit(0);
|
|
417
433
|
}
|
|
418
434
|
};
|
|
435
|
+
async function persistUnityParitySeed(storagePath, seed) {
|
|
436
|
+
const seedPath = path.join(storagePath, 'unity-parity-seed.json');
|
|
437
|
+
if (!seed) {
|
|
438
|
+
try {
|
|
439
|
+
await fs.rm(seedPath, { force: true });
|
|
440
|
+
}
|
|
441
|
+
catch { }
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
await fs.writeFile(seedPath, JSON.stringify(seed), 'utf-8');
|
|
445
|
+
}
|
package/dist/cli/eval-server.js
CHANGED
|
@@ -253,6 +253,9 @@ function getNextStepHint(toolName) {
|
|
|
253
253
|
}
|
|
254
254
|
// ─── Server ───────────────────────────────────────────────────────────
|
|
255
255
|
export async function evalServerCommand(options) {
|
|
256
|
+
if (!process.env.GITNEXUS_UNITY_PARITY_WARMUP) {
|
|
257
|
+
process.env.GITNEXUS_UNITY_PARITY_WARMUP = '1';
|
|
258
|
+
}
|
|
256
259
|
const port = parseInt(options?.port || '4848');
|
|
257
260
|
const idleTimeoutSec = parseInt(options?.idleTimeout || '0');
|
|
258
261
|
const backend = new LocalBackend();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ChildExitResolution {
|
|
2
|
+
code: number;
|
|
3
|
+
signal?: string;
|
|
4
|
+
bySignal: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Convert a signal name to a conventional shell exit code (128 + signal number).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSignalExitCode(signal?: string | null): number | null;
|
|
10
|
+
/**
|
|
11
|
+
* Normalize child-process termination details into a stable numeric exit code.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveChildProcessExit(error: any, fallbackCode?: number): ChildExitResolution;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { constants as osConstants } from 'node:os';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a signal name to a conventional shell exit code (128 + signal number).
|
|
4
|
+
*/
|
|
5
|
+
export function getSignalExitCode(signal) {
|
|
6
|
+
if (!signal)
|
|
7
|
+
return null;
|
|
8
|
+
const signalNumber = osConstants.signals[signal];
|
|
9
|
+
if (typeof signalNumber !== 'number')
|
|
10
|
+
return null;
|
|
11
|
+
return 128 + signalNumber;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Normalize child-process termination details into a stable numeric exit code.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveChildProcessExit(error, fallbackCode = 1) {
|
|
17
|
+
if (typeof error?.status === 'number') {
|
|
18
|
+
return { code: error.status, bySignal: false };
|
|
19
|
+
}
|
|
20
|
+
if (typeof error?.signal === 'string') {
|
|
21
|
+
const code = getSignalExitCode(error.signal) ?? fallbackCode;
|
|
22
|
+
return { code, signal: error.signal, bySignal: true };
|
|
23
|
+
}
|
|
24
|
+
return { code: fallbackCode, bySignal: false };
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { constants as osConstants } from 'node:os';
|
|
4
|
+
import { getSignalExitCode, resolveChildProcessExit } from './exit-code.js';
|
|
5
|
+
test('getSignalExitCode maps signal number using 128+N convention', () => {
|
|
6
|
+
const signalNumber = osConstants.signals.SIGSEGV;
|
|
7
|
+
const expected = typeof signalNumber === 'number' ? 128 + signalNumber : null;
|
|
8
|
+
assert.equal(getSignalExitCode('SIGSEGV'), expected);
|
|
9
|
+
});
|
|
10
|
+
test('resolveChildProcessExit prefers explicit status when present', () => {
|
|
11
|
+
const resolved = resolveChildProcessExit({ status: 42, signal: 'SIGSEGV' }, 1);
|
|
12
|
+
assert.equal(resolved.code, 42);
|
|
13
|
+
assert.equal(resolved.bySignal, false);
|
|
14
|
+
assert.equal(resolved.signal, undefined);
|
|
15
|
+
});
|
|
16
|
+
test('resolveChildProcessExit maps signal-based termination', () => {
|
|
17
|
+
const signalNumber = osConstants.signals.SIGSEGV;
|
|
18
|
+
const expected = typeof signalNumber === 'number' ? 128 + signalNumber : 1;
|
|
19
|
+
const resolved = resolveChildProcessExit({ signal: 'SIGSEGV' }, 1);
|
|
20
|
+
assert.equal(resolved.code, expected);
|
|
21
|
+
assert.equal(resolved.bySignal, true);
|
|
22
|
+
assert.equal(resolved.signal, 'SIGSEGV');
|
|
23
|
+
});
|
|
24
|
+
test('resolveChildProcessExit falls back to default code for unknown errors', () => {
|
|
25
|
+
const resolved = resolveChildProcessExit({ message: 'boom' }, 7);
|
|
26
|
+
assert.equal(resolved.code, 7);
|
|
27
|
+
assert.equal(resolved.bySignal, false);
|
|
28
|
+
});
|
package/dist/cli/index.js
CHANGED
|
@@ -14,8 +14,11 @@ if (!process.env.NODE_OPTIONS?.includes('--max-old-space-size')) {
|
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
16
16
|
catch (e) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const resolved = resolveChildProcessExit(e, 1);
|
|
18
|
+
if (resolved.bySignal && resolved.signal) {
|
|
19
|
+
process.stderr.write(`gitnexus: child process terminated by signal ${resolved.signal}\n`);
|
|
20
|
+
}
|
|
21
|
+
process.exit(resolved.code);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -38,6 +41,7 @@ import { benchmarkUnityCommand } from './benchmark-unity.js';
|
|
|
38
41
|
import { benchmarkAgentContextCommand } from './benchmark-agent-context.js';
|
|
39
42
|
import { unityBindingsCommand } from './unity-bindings.js';
|
|
40
43
|
import { benchmarkU2E2ECommand } from './benchmark-u2-e2e.js';
|
|
44
|
+
import { resolveChildProcessExit } from './exit-code.js';
|
|
41
45
|
function resolveCliVersion() {
|
|
42
46
|
try {
|
|
43
47
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -125,6 +129,7 @@ program
|
|
|
125
129
|
.option('-l, --limit <n>', 'Max processes to return (default: 5)')
|
|
126
130
|
.option('--content', 'Include full symbol source code')
|
|
127
131
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
132
|
+
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
128
133
|
.action(queryCommand);
|
|
129
134
|
program
|
|
130
135
|
.command('context [name]')
|
|
@@ -134,6 +139,7 @@ program
|
|
|
134
139
|
.option('-f, --file <path>', 'File path to disambiguate common names')
|
|
135
140
|
.option('--content', 'Include full symbol source code')
|
|
136
141
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
142
|
+
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
137
143
|
.action(contextCommand);
|
|
138
144
|
program
|
|
139
145
|
.command('unity-bindings <symbol>')
|
package/dist/cli/mcp.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import { startMCPServer } from '../mcp/server.js';
|
|
9
9
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
10
10
|
export const mcpCommand = async () => {
|
|
11
|
+
if (!process.env.GITNEXUS_UNITY_PARITY_WARMUP) {
|
|
12
|
+
process.env.GITNEXUS_UNITY_PARITY_WARMUP = '1';
|
|
13
|
+
}
|
|
11
14
|
// Prevent unhandled errors from crashing the MCP server process.
|
|
12
15
|
// KuzuDB lock conflicts and transient errors should degrade gracefully.
|
|
13
16
|
process.on('uncaughtException', (err) => {
|
package/dist/cli/setup.js
CHANGED
|
@@ -164,9 +164,10 @@ function buildCodexMcpTable() {
|
|
|
164
164
|
function mergeCodexConfig(existingRaw) {
|
|
165
165
|
const table = buildCodexMcpTable();
|
|
166
166
|
const normalized = existingRaw.replace(/\r\n/g, '\n');
|
|
167
|
-
const tablePattern =
|
|
167
|
+
const tablePattern = /^\[mcp_servers\.gitnexus\][\s\S]*?(?=^\[[^\]]+\]|(?![\s\S]))/m;
|
|
168
168
|
if (tablePattern.test(normalized)) {
|
|
169
|
-
|
|
169
|
+
// Keep exactly one table by replacing the whole previous section block.
|
|
170
|
+
return normalized.replace(tablePattern, `${table}\n\n`).trimEnd() + '\n';
|
|
170
171
|
}
|
|
171
172
|
const trimmed = normalized.trimEnd();
|
|
172
173
|
if (trimmed.length === 0)
|
package/dist/cli/setup.test.js
CHANGED
|
@@ -12,6 +12,7 @@ const packageRoot = path.resolve(here, '..', '..');
|
|
|
12
12
|
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
13
13
|
const packageName = JSON.parse(await fs.readFile(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
14
14
|
const expectedMcpPackage = `${packageName.name || 'gitnexus'}@latest`;
|
|
15
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15
16
|
async function runSetup(args, env, cwd = packageRoot) {
|
|
16
17
|
return execFileAsync(process.execPath, [cliPath, 'setup', ...args], { cwd, env });
|
|
17
18
|
}
|
|
@@ -235,6 +236,72 @@ test('setup --scope project --agent codex writes only .codex/config.toml', async
|
|
|
235
236
|
await fs.rm(fakeRepo, { recursive: true, force: true });
|
|
236
237
|
}
|
|
237
238
|
});
|
|
239
|
+
test('setup --scope project --agent codex replaces existing gitnexus table without duplicate keys', async () => {
|
|
240
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
241
|
+
const fakeRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-repo-'));
|
|
242
|
+
try {
|
|
243
|
+
await execFileAsync('git', ['init'], { cwd: fakeRepo });
|
|
244
|
+
const codexConfigPath = path.join(fakeRepo, '.codex', 'config.toml');
|
|
245
|
+
await fs.mkdir(path.dirname(codexConfigPath), { recursive: true });
|
|
246
|
+
await fs.writeFile(codexConfigPath, [
|
|
247
|
+
'[mcp_servers.gitnexus]',
|
|
248
|
+
'command = "npx"',
|
|
249
|
+
'args = ["-y", "oldpkg@latest", "mcp"]',
|
|
250
|
+
'',
|
|
251
|
+
'[profiles.default]',
|
|
252
|
+
'model = "gpt-5"',
|
|
253
|
+
'',
|
|
254
|
+
].join('\n'), 'utf-8');
|
|
255
|
+
await runSetup(['--scope', 'project', '--agent', 'codex'], {
|
|
256
|
+
...process.env,
|
|
257
|
+
HOME: fakeHome,
|
|
258
|
+
USERPROFILE: fakeHome,
|
|
259
|
+
}, fakeRepo);
|
|
260
|
+
const codexConfigRaw = await fs.readFile(codexConfigPath, 'utf-8');
|
|
261
|
+
const tableMatches = codexConfigRaw.match(/^\[mcp_servers\.gitnexus\]$/gm) || [];
|
|
262
|
+
assert.equal(tableMatches.length, 1);
|
|
263
|
+
const gitnexusTableMatch = codexConfigRaw.match(/^\[mcp_servers\.gitnexus\][\s\S]*?(?=^\[[^\]]+\]|(?![\s\S]))/m);
|
|
264
|
+
assert.ok(gitnexusTableMatch, 'expected [mcp_servers.gitnexus] table');
|
|
265
|
+
const gitnexusTable = gitnexusTableMatch[0];
|
|
266
|
+
assert.equal((gitnexusTable.match(/^command\s*=/gm) || []).length, 1);
|
|
267
|
+
assert.equal((gitnexusTable.match(/^args\s*=/gm) || []).length, 1);
|
|
268
|
+
assert.match(gitnexusTable, new RegExp(escapeRegExp(expectedMcpPackage)));
|
|
269
|
+
assert.doesNotMatch(gitnexusTable, /oldpkg@latest/);
|
|
270
|
+
assert.match(codexConfigRaw, /^\[profiles\.default\]$/m);
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
274
|
+
await fs.rm(fakeRepo, { recursive: true, force: true });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
test('setup --scope project --agent codex is idempotent across repeated runs', async () => {
|
|
278
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
279
|
+
const fakeRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-repo-'));
|
|
280
|
+
try {
|
|
281
|
+
await execFileAsync('git', ['init'], { cwd: fakeRepo });
|
|
282
|
+
const env = {
|
|
283
|
+
...process.env,
|
|
284
|
+
HOME: fakeHome,
|
|
285
|
+
USERPROFILE: fakeHome,
|
|
286
|
+
};
|
|
287
|
+
await runSetup(['--scope', 'project', '--agent', 'codex'], env, fakeRepo);
|
|
288
|
+
await runSetup(['--scope', 'project', '--agent', 'codex'], env, fakeRepo);
|
|
289
|
+
const codexConfigPath = path.join(fakeRepo, '.codex', 'config.toml');
|
|
290
|
+
const codexConfigRaw = await fs.readFile(codexConfigPath, 'utf-8');
|
|
291
|
+
const tableMatches = codexConfigRaw.match(/^\[mcp_servers\.gitnexus\]$/gm) || [];
|
|
292
|
+
assert.equal(tableMatches.length, 1);
|
|
293
|
+
const gitnexusTableMatch = codexConfigRaw.match(/^\[mcp_servers\.gitnexus\][\s\S]*?(?=^\[[^\]]+\]|(?![\s\S]))/m);
|
|
294
|
+
assert.ok(gitnexusTableMatch, 'expected [mcp_servers.gitnexus] table');
|
|
295
|
+
const gitnexusTable = gitnexusTableMatch[0];
|
|
296
|
+
assert.equal((gitnexusTable.match(/^command\s*=/gm) || []).length, 1);
|
|
297
|
+
assert.equal((gitnexusTable.match(/^args\s*=/gm) || []).length, 1);
|
|
298
|
+
assert.match(gitnexusTable, new RegExp(escapeRegExp(expectedMcpPackage)));
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
302
|
+
await fs.rm(fakeRepo, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
238
305
|
test('setup --scope project --agent opencode writes only opencode.json', async () => {
|
|
239
306
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
240
307
|
const fakeRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-repo-'));
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Note: Output goes to stderr because KuzuDB's native module captures stdout
|
|
14
14
|
* at the OS level during init. This is consistent with augment.ts.
|
|
15
15
|
*/
|
|
16
|
-
import type { UnityResourcesMode } from '../core/unity/options.js';
|
|
16
|
+
import type { UnityHydrationMode, UnityResourcesMode } from '../core/unity/options.js';
|
|
17
17
|
export declare function queryCommand(queryText: string, options?: {
|
|
18
18
|
repo?: string;
|
|
19
19
|
context?: string;
|
|
@@ -21,6 +21,7 @@ export declare function queryCommand(queryText: string, options?: {
|
|
|
21
21
|
limit?: string;
|
|
22
22
|
content?: boolean;
|
|
23
23
|
unityResources?: UnityResourcesMode;
|
|
24
|
+
unityHydration?: UnityHydrationMode;
|
|
24
25
|
}): Promise<void>;
|
|
25
26
|
export declare function contextCommand(name: string, options?: {
|
|
26
27
|
repo?: string;
|
|
@@ -28,6 +29,7 @@ export declare function contextCommand(name: string, options?: {
|
|
|
28
29
|
uid?: string;
|
|
29
30
|
content?: boolean;
|
|
30
31
|
unityResources?: UnityResourcesMode;
|
|
32
|
+
unityHydration?: UnityHydrationMode;
|
|
31
33
|
}): Promise<void>;
|
|
32
34
|
export declare function impactCommand(target: string, options?: {
|
|
33
35
|
direction?: string;
|
package/dist/cli/tool.js
CHANGED
|
@@ -44,6 +44,7 @@ export async function queryCommand(queryText, options) {
|
|
|
44
44
|
limit: options?.limit ? parseInt(options.limit) : undefined,
|
|
45
45
|
include_content: options?.content ?? false,
|
|
46
46
|
unity_resources: options?.unityResources,
|
|
47
|
+
unity_hydration_mode: options?.unityHydration,
|
|
47
48
|
repo: options?.repo,
|
|
48
49
|
});
|
|
49
50
|
output(result);
|
|
@@ -60,6 +61,7 @@ export async function contextCommand(name, options) {
|
|
|
60
61
|
file_path: options?.file,
|
|
61
62
|
include_content: options?.content ?? false,
|
|
62
63
|
unity_resources: options?.unityResources,
|
|
64
|
+
unity_hydration_mode: options?.unityHydration,
|
|
63
65
|
repo: options?.repo,
|
|
64
66
|
});
|
|
65
67
|
output(result);
|
|
@@ -20,7 +20,7 @@ export type NodeProperties = {
|
|
|
20
20
|
entryPointScore?: number;
|
|
21
21
|
entryPointReason?: string;
|
|
22
22
|
};
|
|
23
|
-
export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS' | 'UNITY_COMPONENT_IN' | 'UNITY_COMPONENT_INSTANCE' | 'UNITY_SERIALIZED_TYPE_IN';
|
|
23
|
+
export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS' | 'UNITY_COMPONENT_IN' | 'UNITY_COMPONENT_INSTANCE' | 'UNITY_RESOURCE_SUMMARY' | 'UNITY_SERIALIZED_TYPE_IN';
|
|
24
24
|
export interface GraphNode {
|
|
25
25
|
id: string;
|
|
26
26
|
label: NodeLabel;
|
|
@@ -16,6 +16,12 @@ export interface FilePath {
|
|
|
16
16
|
* Memory: ~10MB for 100K files vs ~1GB+ with content.
|
|
17
17
|
*/
|
|
18
18
|
export declare const walkRepositoryPaths: (repoPath: string, onProgress?: (current: number, total: number, filePath: string) => void) => Promise<ScannedFile[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Scan Unity resource files used by binding enrichment.
|
|
21
|
+
* This path scan intentionally does not apply the 512KB source-size cap,
|
|
22
|
+
* because bindings often live in large serialized assets.
|
|
23
|
+
*/
|
|
24
|
+
export declare const walkUnityResourcePaths: (repoPath: string) => Promise<string[]>;
|
|
19
25
|
/**
|
|
20
26
|
* Phase 2: Read file contents for a specific set of relative paths.
|
|
21
27
|
* Returns a Map for O(1) lookup. Silently skips files that fail to read.
|
|
@@ -5,6 +5,7 @@ import { shouldIgnorePath } from '../../config/ignore-service.js';
|
|
|
5
5
|
const READ_CONCURRENCY = 32;
|
|
6
6
|
/** Skip files larger than 512KB — they're usually generated/vendored and crash tree-sitter */
|
|
7
7
|
const MAX_FILE_SIZE = 512 * 1024;
|
|
8
|
+
const UNITY_RESOURCE_GLOBS = ['**/*.prefab', '**/*.unity', '**/*.asset'];
|
|
8
9
|
/**
|
|
9
10
|
* Phase 1: Scan repository — stat files to get paths + sizes, no content loaded.
|
|
10
11
|
* Memory: ~10MB for 100K files vs ~1GB+ with content.
|
|
@@ -46,6 +47,22 @@ export const walkRepositoryPaths = async (repoPath, onProgress) => {
|
|
|
46
47
|
}
|
|
47
48
|
return entries;
|
|
48
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Scan Unity resource files used by binding enrichment.
|
|
52
|
+
* This path scan intentionally does not apply the 512KB source-size cap,
|
|
53
|
+
* because bindings often live in large serialized assets.
|
|
54
|
+
*/
|
|
55
|
+
export const walkUnityResourcePaths = async (repoPath) => {
|
|
56
|
+
const files = await glob(UNITY_RESOURCE_GLOBS, {
|
|
57
|
+
cwd: repoPath,
|
|
58
|
+
nodir: true,
|
|
59
|
+
dot: false,
|
|
60
|
+
});
|
|
61
|
+
return files
|
|
62
|
+
.filter(file => !shouldIgnorePath(file))
|
|
63
|
+
.map(file => file.replace(/\\/g, '/'))
|
|
64
|
+
.sort((left, right) => left.localeCompare(right));
|
|
65
|
+
};
|
|
49
66
|
/**
|
|
50
67
|
* Phase 2: Read file contents for a specific set of relative paths.
|
|
51
68
|
* Returns a Map for O(1) lookup. Silently skips files that fail to read.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import { walkRepositoryPaths, walkUnityResourcePaths } from './filesystem-walker.js';
|
|
7
|
+
test('walkUnityResourcePaths includes large Unity resources while walkRepositoryPaths skips them', async () => {
|
|
8
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-fswalker-'));
|
|
9
|
+
const assetsDir = path.join(repoRoot, 'Assets/Scene');
|
|
10
|
+
await fs.mkdir(assetsDir, { recursive: true });
|
|
11
|
+
const largePrefab = 'Assets/Scene/Large.prefab';
|
|
12
|
+
const smallPrefab = 'Assets/Scene/Small.prefab';
|
|
13
|
+
const scriptFile = 'Assets/Scene/Test.cs';
|
|
14
|
+
try {
|
|
15
|
+
await fs.writeFile(path.join(repoRoot, largePrefab), 'x'.repeat(600 * 1024), 'utf-8');
|
|
16
|
+
await fs.writeFile(path.join(repoRoot, smallPrefab), 'small', 'utf-8');
|
|
17
|
+
await fs.writeFile(path.join(repoRoot, scriptFile), 'public class Test {}', 'utf-8');
|
|
18
|
+
const scanned = await walkRepositoryPaths(repoRoot);
|
|
19
|
+
const scannedPaths = new Set(scanned.map((entry) => entry.path));
|
|
20
|
+
assert.equal(scannedPaths.has(largePrefab), false);
|
|
21
|
+
assert.equal(scannedPaths.has(smallPrefab), true);
|
|
22
|
+
assert.equal(scannedPaths.has(scriptFile), true);
|
|
23
|
+
const unityPaths = await walkUnityResourcePaths(repoRoot);
|
|
24
|
+
assert.equal(unityPaths.includes(largePrefab), true);
|
|
25
|
+
assert.equal(unityPaths.includes(smallPrefab), true);
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test('walkUnityResourcePaths only returns prefab/unity/asset files and still honors ignore rules', async () => {
|
|
32
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-fswalker-'));
|
|
33
|
+
await fs.mkdir(path.join(repoRoot, 'Assets/Scene'), { recursive: true });
|
|
34
|
+
await fs.mkdir(path.join(repoRoot, 'node_modules/pkg'), { recursive: true });
|
|
35
|
+
try {
|
|
36
|
+
await fs.writeFile(path.join(repoRoot, 'Assets/Scene/Keep.prefab'), 'prefab', 'utf-8');
|
|
37
|
+
await fs.writeFile(path.join(repoRoot, 'Assets/Scene/Keep.unity'), 'scene', 'utf-8');
|
|
38
|
+
await fs.writeFile(path.join(repoRoot, 'Assets/Scene/Keep.asset'), 'asset', 'utf-8');
|
|
39
|
+
await fs.writeFile(path.join(repoRoot, 'Assets/Scene/Ignore.cs'), 'public class Ignore {}', 'utf-8');
|
|
40
|
+
await fs.writeFile(path.join(repoRoot, 'node_modules/pkg/Hidden.prefab'), 'hidden', 'utf-8');
|
|
41
|
+
const unityPaths = await walkUnityResourcePaths(repoRoot);
|
|
42
|
+
assert.deepEqual(unityPaths, [
|
|
43
|
+
'Assets/Scene/Keep.asset',
|
|
44
|
+
'Assets/Scene/Keep.prefab',
|
|
45
|
+
'Assets/Scene/Keep.unity',
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -9,7 +9,7 @@ import { processProcesses } from './process-processor.js';
|
|
|
9
9
|
import { processUnityResources } from './unity-resource-processor.js';
|
|
10
10
|
import { createSymbolTable } from './symbol-table.js';
|
|
11
11
|
import { createASTCache } from './ast-cache.js';
|
|
12
|
-
import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
|
|
12
|
+
import { walkRepositoryPaths, readFileContents, walkUnityResourcePaths } from './filesystem-walker.js';
|
|
13
13
|
import { getLanguageFromFilename } from './utils.js';
|
|
14
14
|
import { createWorkerPool } from './workers/worker-pool.js';
|
|
15
15
|
import { selectEntriesByScopeRules } from './scope-filter.js';
|
|
@@ -75,9 +75,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
75
75
|
stats: { filesProcessed: 0, totalFiles, nodesCreated: graph.nodeCount },
|
|
76
76
|
});
|
|
77
77
|
const allPaths = extensionFiltered.map(f => f.path);
|
|
78
|
+
const unityCandidates = await walkUnityResourcePaths(repoPath);
|
|
78
79
|
const unityScopedPaths = (options?.scopeRules && options.scopeRules.length > 0)
|
|
79
|
-
?
|
|
80
|
-
:
|
|
80
|
+
? selectEntriesByScopeRules(unityCandidates.map(path => ({ path })), options.scopeRules).selected.map(entry => entry.path)
|
|
81
|
+
: unityCandidates;
|
|
81
82
|
processStructure(graph, allPaths);
|
|
82
83
|
onProgress({
|
|
83
84
|
phase: 'structure',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnityScanContext } from '../unity/scan-context.js';
|
|
2
|
+
export interface UnityParitySeed {
|
|
3
|
+
version: 1;
|
|
4
|
+
symbolToScriptPath: Record<string, string>;
|
|
5
|
+
scriptPathToGuid: Record<string, string>;
|
|
6
|
+
guidToResourcePaths: Record<string, string[]>;
|
|
7
|
+
assetGuidToPath?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildUnityParitySeed(scanContext: UnityScanContext): UnityParitySeed;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export function buildUnityParitySeed(scanContext) {
|
|
2
|
+
const canonicalMap = scanContext.symbolToCanonicalScriptPath instanceof Map
|
|
3
|
+
? scanContext.symbolToCanonicalScriptPath
|
|
4
|
+
: new Map();
|
|
5
|
+
const fallbackMap = scanContext.symbolToScriptPath instanceof Map
|
|
6
|
+
? scanContext.symbolToScriptPath
|
|
7
|
+
: new Map();
|
|
8
|
+
const canonical = canonicalMap.size > 0 ? canonicalMap : fallbackMap;
|
|
9
|
+
const symbolToScriptPath = {};
|
|
10
|
+
for (const [symbol, scriptPath] of canonical.entries()) {
|
|
11
|
+
const key = String(symbol || '').trim();
|
|
12
|
+
const value = normalizePath(scriptPath);
|
|
13
|
+
if (!key || !value)
|
|
14
|
+
continue;
|
|
15
|
+
symbolToScriptPath[key] = value;
|
|
16
|
+
}
|
|
17
|
+
const scriptPathToGuid = {};
|
|
18
|
+
const scriptPathToGuidMap = scanContext.scriptPathToGuid instanceof Map
|
|
19
|
+
? scanContext.scriptPathToGuid
|
|
20
|
+
: new Map();
|
|
21
|
+
for (const [scriptPath, guid] of scriptPathToGuidMap.entries()) {
|
|
22
|
+
const key = normalizePath(scriptPath);
|
|
23
|
+
const value = String(guid || '').trim();
|
|
24
|
+
if (!key || !value)
|
|
25
|
+
continue;
|
|
26
|
+
scriptPathToGuid[key] = value;
|
|
27
|
+
}
|
|
28
|
+
const guidToResourcePaths = {};
|
|
29
|
+
const guidToResourceHitsMap = scanContext.guidToResourceHits instanceof Map
|
|
30
|
+
? scanContext.guidToResourceHits
|
|
31
|
+
: new Map();
|
|
32
|
+
for (const [guid, hits] of guidToResourceHitsMap.entries()) {
|
|
33
|
+
const key = String(guid || '').trim();
|
|
34
|
+
if (!key)
|
|
35
|
+
continue;
|
|
36
|
+
const uniquePaths = new Set();
|
|
37
|
+
for (const hit of hits || []) {
|
|
38
|
+
const resourcePath = normalizePath(hit?.resourcePath || '');
|
|
39
|
+
if (resourcePath)
|
|
40
|
+
uniquePaths.add(resourcePath);
|
|
41
|
+
}
|
|
42
|
+
if (uniquePaths.size > 0) {
|
|
43
|
+
guidToResourcePaths[key] = [...uniquePaths].sort((left, right) => left.localeCompare(right));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const assetGuidToPath = {};
|
|
47
|
+
if (scanContext.assetGuidToPath instanceof Map) {
|
|
48
|
+
for (const [guid, assetPath] of scanContext.assetGuidToPath.entries()) {
|
|
49
|
+
const key = String(guid || '').trim();
|
|
50
|
+
const value = normalizePath(assetPath);
|
|
51
|
+
if (!key || !value)
|
|
52
|
+
continue;
|
|
53
|
+
assetGuidToPath[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
version: 1,
|
|
58
|
+
symbolToScriptPath: sortRecord(symbolToScriptPath),
|
|
59
|
+
scriptPathToGuid: sortRecord(scriptPathToGuid),
|
|
60
|
+
guidToResourcePaths: sortRecord(guidToResourcePaths),
|
|
61
|
+
assetGuidToPath: Object.keys(assetGuidToPath).length > 0 ? sortRecord(assetGuidToPath) : undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function normalizePath(input) {
|
|
65
|
+
return String(input || '').replace(/\\/g, '/').trim();
|
|
66
|
+
}
|
|
67
|
+
function sortRecord(input) {
|
|
68
|
+
return Object.fromEntries(Object.entries(input).sort(([left], [right]) => left.localeCompare(right)));
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|