@veewo/gitnexus 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +9 -4
  2. package/dist/benchmark/runner.test.js +1 -1
  3. package/dist/benchmark/u2-e2e/analyze-parser.d.ts +22 -0
  4. package/dist/benchmark/u2-e2e/analyze-parser.js +89 -0
  5. package/dist/benchmark/u2-e2e/analyze-parser.test.d.ts +1 -0
  6. package/dist/benchmark/u2-e2e/analyze-parser.test.js +13 -0
  7. package/dist/benchmark/u2-e2e/characterlist-assetref.d.ts +19 -0
  8. package/dist/benchmark/u2-e2e/characterlist-assetref.js +80 -0
  9. package/dist/benchmark/u2-e2e/characterlist-assetref.test.d.ts +1 -0
  10. package/dist/benchmark/u2-e2e/characterlist-assetref.test.js +108 -0
  11. package/dist/benchmark/u2-e2e/config.d.ts +25 -0
  12. package/dist/benchmark/u2-e2e/config.js +86 -0
  13. package/dist/benchmark/u2-e2e/config.test.d.ts +1 -0
  14. package/dist/benchmark/u2-e2e/config.test.js +29 -0
  15. package/dist/benchmark/u2-e2e/metrics.d.ts +20 -0
  16. package/dist/benchmark/u2-e2e/metrics.js +34 -0
  17. package/dist/benchmark/u2-e2e/metrics.test.d.ts +1 -0
  18. package/dist/benchmark/u2-e2e/metrics.test.js +13 -0
  19. package/dist/benchmark/u2-e2e/neonspark-full-e2e.d.ts +33 -0
  20. package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +439 -0
  21. package/dist/benchmark/u2-e2e/neonspark-full-e2e.test.d.ts +1 -0
  22. package/dist/benchmark/u2-e2e/neonspark-full-e2e.test.js +40 -0
  23. package/dist/benchmark/u2-e2e/report.d.ts +58 -0
  24. package/dist/benchmark/u2-e2e/report.js +130 -0
  25. package/dist/benchmark/u2-e2e/report.test.d.ts +1 -0
  26. package/dist/benchmark/u2-e2e/report.test.js +58 -0
  27. package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +21 -0
  28. package/dist/benchmark/u2-e2e/retrieval-runner.js +166 -0
  29. package/dist/benchmark/u2-e2e/retrieval-runner.test.d.ts +1 -0
  30. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +145 -0
  31. package/dist/benchmark/u2-performance-sampler.d.ts +33 -0
  32. package/dist/benchmark/u2-performance-sampler.js +178 -0
  33. package/dist/benchmark/u2-performance-sampler.test.d.ts +1 -0
  34. package/dist/benchmark/u2-performance-sampler.test.js +34 -0
  35. package/dist/cli/analyze-custom-modules-regression.test.d.ts +1 -0
  36. package/dist/cli/analyze-custom-modules-regression.test.js +75 -0
  37. package/dist/cli/analyze-modules-diagnostics.test.d.ts +1 -0
  38. package/dist/cli/analyze-modules-diagnostics.test.js +36 -0
  39. package/dist/cli/analyze-multi-scope-regression.test.js +10 -0
  40. package/dist/cli/analyze-summary.d.ts +7 -0
  41. package/dist/cli/analyze-summary.js +37 -0
  42. package/dist/cli/analyze-summary.test.d.ts +1 -0
  43. package/dist/cli/analyze-summary.test.js +58 -0
  44. package/dist/cli/analyze.js +11 -6
  45. package/dist/cli/benchmark-u2-e2e.d.ts +9 -0
  46. package/dist/cli/benchmark-u2-e2e.js +35 -0
  47. package/dist/cli/benchmark-u2-e2e.test.d.ts +1 -0
  48. package/dist/cli/benchmark-u2-e2e.test.js +7 -0
  49. package/dist/cli/index.js +20 -0
  50. package/dist/cli/setup.js +24 -3
  51. package/dist/cli/setup.test.js +6 -4
  52. package/dist/cli/tool.d.ts +3 -0
  53. package/dist/cli/tool.js +2 -0
  54. package/dist/cli/unity-bindings.d.ts +8 -0
  55. package/dist/cli/unity-bindings.js +33 -0
  56. package/dist/cli/unity-bindings.test.d.ts +1 -0
  57. package/dist/cli/unity-bindings.test.js +24 -0
  58. package/dist/core/graph/types.d.ts +1 -1
  59. package/dist/core/ingestion/modules/assignment-engine.d.ts +33 -0
  60. package/dist/core/ingestion/modules/assignment-engine.js +179 -0
  61. package/dist/core/ingestion/modules/assignment-engine.test.d.ts +1 -0
  62. package/dist/core/ingestion/modules/assignment-engine.test.js +111 -0
  63. package/dist/core/ingestion/modules/config-loader.d.ts +2 -0
  64. package/dist/core/ingestion/modules/config-loader.js +186 -0
  65. package/dist/core/ingestion/modules/config-loader.test.d.ts +1 -0
  66. package/dist/core/ingestion/modules/config-loader.test.js +57 -0
  67. package/dist/core/ingestion/modules/rule-matcher.d.ts +12 -0
  68. package/dist/core/ingestion/modules/rule-matcher.js +63 -0
  69. package/dist/core/ingestion/modules/rule-matcher.test.d.ts +1 -0
  70. package/dist/core/ingestion/modules/rule-matcher.test.js +58 -0
  71. package/dist/core/ingestion/modules/types.d.ts +44 -0
  72. package/dist/core/ingestion/modules/types.js +2 -0
  73. package/dist/core/ingestion/pipeline.d.ts +2 -4
  74. package/dist/core/ingestion/pipeline.js +12 -0
  75. package/dist/core/ingestion/unity-resource-processor.d.ts +26 -0
  76. package/dist/core/ingestion/unity-resource-processor.js +363 -0
  77. package/dist/core/ingestion/unity-resource-processor.test.d.ts +1 -0
  78. package/dist/core/ingestion/unity-resource-processor.test.js +599 -0
  79. package/dist/core/kuzu/kuzu-adapter.d.ts +6 -0
  80. package/dist/core/kuzu/kuzu-adapter.js +18 -7
  81. package/dist/core/kuzu/schema.d.ts +2 -2
  82. package/dist/core/kuzu/schema.js +22 -1
  83. package/dist/core/kuzu/schema.test.d.ts +1 -0
  84. package/dist/core/kuzu/schema.test.js +17 -0
  85. package/dist/core/unity/meta-index.d.ts +5 -0
  86. package/dist/core/unity/meta-index.js +113 -0
  87. package/dist/core/unity/meta-index.test.d.ts +1 -0
  88. package/dist/core/unity/meta-index.test.js +11 -0
  89. package/dist/core/unity/options.d.ts +2 -0
  90. package/dist/core/unity/options.js +9 -0
  91. package/dist/core/unity/options.test.d.ts +1 -0
  92. package/dist/core/unity/options.test.js +10 -0
  93. package/dist/core/unity/override-merger.d.ts +27 -0
  94. package/dist/core/unity/override-merger.js +35 -0
  95. package/dist/core/unity/override-merger.test.d.ts +1 -0
  96. package/dist/core/unity/override-merger.test.js +47 -0
  97. package/dist/core/unity/resolver.d.ts +79 -0
  98. package/dist/core/unity/resolver.js +384 -0
  99. package/dist/core/unity/resolver.test.d.ts +1 -0
  100. package/dist/core/unity/resolver.test.js +244 -0
  101. package/dist/core/unity/resource-hit-scanner.d.ts +10 -0
  102. package/dist/core/unity/resource-hit-scanner.js +60 -0
  103. package/dist/core/unity/resource-hit-scanner.test.d.ts +1 -0
  104. package/dist/core/unity/resource-hit-scanner.test.js +20 -0
  105. package/dist/core/unity/scan-context.d.ts +23 -0
  106. package/dist/core/unity/scan-context.js +318 -0
  107. package/dist/core/unity/scan-context.test.d.ts +1 -0
  108. package/dist/core/unity/scan-context.test.js +118 -0
  109. package/dist/core/unity/serialized-type-index.d.ts +10 -0
  110. package/dist/core/unity/serialized-type-index.js +105 -0
  111. package/dist/core/unity/serialized-type-index.test.d.ts +1 -0
  112. package/dist/core/unity/serialized-type-index.test.js +34 -0
  113. package/dist/core/unity/u2-thresholds.test.d.ts +1 -0
  114. package/dist/core/unity/u2-thresholds.test.js +71 -0
  115. package/dist/core/unity/yaml-object-graph.d.ts +9 -0
  116. package/dist/core/unity/yaml-object-graph.js +92 -0
  117. package/dist/core/unity/yaml-object-graph.test.d.ts +1 -0
  118. package/dist/core/unity/yaml-object-graph.test.js +49 -0
  119. package/dist/mcp/local/cluster-aggregation.d.ts +20 -0
  120. package/dist/mcp/local/cluster-aggregation.js +48 -0
  121. package/dist/mcp/local/cluster-aggregation.test.d.ts +1 -0
  122. package/dist/mcp/local/cluster-aggregation.test.js +22 -0
  123. package/dist/mcp/local/local-backend.js +12 -1
  124. package/dist/mcp/local/unity-enrichment.d.ts +6 -0
  125. package/dist/mcp/local/unity-enrichment.js +91 -0
  126. package/dist/mcp/local/unity-enrichment.test.d.ts +1 -0
  127. package/dist/mcp/local/unity-enrichment.test.js +130 -0
  128. package/dist/mcp/tools.js +12 -0
  129. package/dist/types/pipeline.d.ts +7 -0
  130. package/dist/types/pipeline.js +2 -0
  131. package/hooks/check-release-path-hygiene.mjs +108 -0
  132. package/package.json +14 -7
@@ -0,0 +1,75 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
7
+ const FIXTURE_REPO = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../benchmarks/fixtures/unity-mini');
8
+ async function copyFixtureRepo() {
9
+ const tmpRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-custom-modules-'));
10
+ await fs.cp(FIXTURE_REPO, tmpRepo, { recursive: true });
11
+ await fs.mkdir(path.join(tmpRepo, '.gitnexus'), { recursive: true });
12
+ return tmpRepo;
13
+ }
14
+ test('pipeline mixed mode writes config module + auto fallback memberships', { timeout: 120_000 }, async () => {
15
+ const repoPath = await copyFixtureRepo();
16
+ const configPath = path.join(repoPath, '.gitnexus', 'modules.json');
17
+ await fs.writeFile(configPath, JSON.stringify({
18
+ version: 1,
19
+ mode: 'mixed',
20
+ modules: [
21
+ {
22
+ name: 'Factory',
23
+ defaultPriority: 100,
24
+ rules: [
25
+ {
26
+ id: 'factory-file-rule',
27
+ when: {
28
+ all: [{ field: 'file.path', op: 'contains', value: 'MinionFactory.cs' }],
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ {
34
+ name: 'Battle',
35
+ defaultPriority: 100,
36
+ rules: [],
37
+ },
38
+ ],
39
+ }), 'utf-8');
40
+ const result = await runPipelineFromRepo(repoPath, () => { }, { includeExtensions: ['.cs'] });
41
+ const communities = [...result.graph.iterNodes()].filter((n) => n.label === 'Community');
42
+ const memberships = [...result.graph.iterRelationships()].filter((r) => r.type === 'MEMBER_OF');
43
+ const commById = new Map(communities.map((c) => [c.id, c]));
44
+ const labels = communities.map((c) => String(c.properties.heuristicLabel || c.properties.name || c.id));
45
+ assert.ok(labels.includes('Factory'));
46
+ assert.ok(labels.includes('Battle'));
47
+ const createMembership = memberships.find((m) => m.sourceId.includes('MinionFactory.cs:Create'));
48
+ assert.ok(createMembership);
49
+ const targetCommunity = commById.get(createMembership.targetId);
50
+ assert.ok(targetCommunity);
51
+ assert.equal(String(targetCommunity.properties.heuristicLabel), 'Factory');
52
+ const uniquePerSymbol = new Set(memberships.map((m) => m.sourceId));
53
+ assert.equal(uniquePerSymbol.size, memberships.length);
54
+ const battleCommunity = communities.find((c) => String(c.properties.heuristicLabel) === 'Battle');
55
+ assert.ok(battleCommunity);
56
+ assert.ok(!memberships.some((m) => m.targetId === battleCommunity.id));
57
+ const membershipCommunities = new Set(memberships.map((m) => m.targetId));
58
+ const processCommunities = new Set((result.processResult?.processes || []).flatMap((p) => p.communities || []));
59
+ for (const processCommunity of processCommunities) {
60
+ assert.ok(membershipCommunities.has(processCommunity));
61
+ }
62
+ });
63
+ test('pipeline mixed + invalid modules.json fails fast', { timeout: 120_000 }, async () => {
64
+ const repoPath = await copyFixtureRepo();
65
+ const configPath = path.join(repoPath, '.gitnexus', 'modules.json');
66
+ await fs.writeFile(configPath, JSON.stringify({
67
+ version: 1,
68
+ mode: 'mixed',
69
+ modules: [
70
+ { name: 'Dup', defaultPriority: 100, rules: [] },
71
+ { name: 'Dup', defaultPriority: 100, rules: [] },
72
+ ],
73
+ }), 'utf-8');
74
+ await assert.rejects(runPipelineFromRepo(repoPath, () => { }, { includeExtensions: ['.cs'] }), /duplicate module name/i);
75
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { formatModuleDiagnostics } from './analyze.js';
7
+ test('prints one-time fallback warning for mixed + missing modules.json', () => {
8
+ const lines = formatModuleDiagnostics({
9
+ mode: 'mixed',
10
+ usedFallbackAuto: true,
11
+ warnings: ['modules.json missing in mixed mode, fallback to auto'],
12
+ emptyModules: [],
13
+ configuredModuleCount: 0,
14
+ finalModuleCount: 3,
15
+ });
16
+ assert.ok(lines.some((line) => /fallback to auto/i.test(line)));
17
+ });
18
+ test('prints warning for empty modules', () => {
19
+ const lines = formatModuleDiagnostics({
20
+ mode: 'mixed',
21
+ usedFallbackAuto: false,
22
+ warnings: [],
23
+ emptyModules: ['Battle'],
24
+ configuredModuleCount: 1,
25
+ finalModuleCount: 2,
26
+ });
27
+ assert.ok(lines.some((line) => /empty module/i.test(line)));
28
+ assert.ok(lines.some((line) => /Battle/.test(line)));
29
+ });
30
+ test('README mentions modules.json mode semantics and fallback behavior', async () => {
31
+ const here = path.dirname(fileURLToPath(import.meta.url));
32
+ const readmePath = path.resolve(here, '../../README.md');
33
+ const readme = await fs.readFile(readmePath, 'utf-8');
34
+ assert.match(readme, /\.gitnexus\/modules\.json/);
35
+ assert.match(readme, /mixed.*fallback.*auto/i);
36
+ });
@@ -20,3 +20,13 @@ test('runPipelineFromRepo deduplicates overlapping scope matches and reports dia
20
20
  assert.equal(result.scopeDiagnostics?.dedupedMatchCount, 3);
21
21
  assert.equal(result.scopeDiagnostics?.normalizedCollisions.length, 0);
22
22
  });
23
+ test('pipeline forwards extension-filtered scoped paths to unity enrich', { timeout: 60_000 }, async () => {
24
+ const here = path.dirname(fileURLToPath(import.meta.url));
25
+ const fixtureRepo = path.resolve(here, '../../src/core/unity/__fixtures__/mini-unity');
26
+ const result = await runPipelineFromRepo(fixtureRepo, () => { }, {
27
+ includeExtensions: ['.cs'],
28
+ scopeRules: ['Assets'],
29
+ });
30
+ assert.equal(result.unityResult?.bindingCount, 0);
31
+ assert.ok(result.unityResult?.diagnostics.some((message) => message.includes('scanContext:')));
32
+ });
@@ -0,0 +1,7 @@
1
+ export interface FallbackInsertStats {
2
+ attempted: number;
3
+ succeeded: number;
4
+ failed: number;
5
+ }
6
+ export declare function formatUnityDiagnosticsSummary(diagnostics: string[] | undefined, previewLimit?: number): string[];
7
+ export declare function formatFallbackSummary(warnings: string[] | undefined, stats: FallbackInsertStats | undefined, previewLimit?: number): string[];
@@ -0,0 +1,37 @@
1
+ export function formatUnityDiagnosticsSummary(diagnostics, previewLimit = 3) {
2
+ if (!diagnostics || diagnostics.length === 0) {
3
+ return [];
4
+ }
5
+ const limit = previewLimit > 0 ? previewLimit : diagnostics.length;
6
+ const preview = diagnostics.slice(0, limit);
7
+ const lines = [`Unity Diagnostics: ${diagnostics.length} message(s)`];
8
+ for (const message of preview) {
9
+ lines.push(`- ${message}`);
10
+ }
11
+ if (diagnostics.length > preview.length) {
12
+ lines.push(`... ${diagnostics.length - preview.length} more`);
13
+ }
14
+ return lines;
15
+ }
16
+ export function formatFallbackSummary(warnings, stats, previewLimit = 5) {
17
+ if (!warnings || warnings.length === 0) {
18
+ return [];
19
+ }
20
+ const safeStats = stats ?? {
21
+ attempted: 0,
22
+ succeeded: 0,
23
+ failed: 0,
24
+ };
25
+ const limit = previewLimit > 0 ? previewLimit : warnings.length;
26
+ const preview = warnings.slice(0, limit);
27
+ const lines = [
28
+ `Fallback edges: attempted=${safeStats.attempted}, succeeded=${safeStats.succeeded}, failed=${safeStats.failed}, pairTypes=${warnings.length}`,
29
+ ];
30
+ for (const warning of preview) {
31
+ lines.push(`- ${warning}`);
32
+ }
33
+ if (warnings.length > preview.length) {
34
+ lines.push(`... ${warnings.length - preview.length} more`);
35
+ }
36
+ return lines;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
4
+ test('formatUnityDiagnosticsSummary returns empty when diagnostics are missing', () => {
5
+ const lines = formatUnityDiagnosticsSummary([]);
6
+ assert.deepEqual(lines, []);
7
+ });
8
+ test('formatUnityDiagnosticsSummary renders diagnostics with count and bullets', () => {
9
+ const lines = formatUnityDiagnosticsSummary([
10
+ 'scanContext: scripts=4, guids=4, resources=0',
11
+ ]);
12
+ assert.deepEqual(lines, [
13
+ 'Unity Diagnostics: 1 message(s)',
14
+ '- scanContext: scripts=4, guids=4, resources=0',
15
+ ]);
16
+ });
17
+ test('formatUnityDiagnosticsSummary truncates output after max preview items', () => {
18
+ const lines = formatUnityDiagnosticsSummary([
19
+ 'diag-a',
20
+ 'diag-b',
21
+ 'diag-c',
22
+ 'diag-d',
23
+ ]);
24
+ assert.deepEqual(lines, [
25
+ 'Unity Diagnostics: 4 message(s)',
26
+ '- diag-a',
27
+ '- diag-b',
28
+ '- diag-c',
29
+ '... 1 more',
30
+ ]);
31
+ });
32
+ test('formatFallbackSummary returns empty when no warnings exist', () => {
33
+ const lines = formatFallbackSummary([], {
34
+ attempted: 0,
35
+ succeeded: 0,
36
+ failed: 0,
37
+ });
38
+ assert.deepEqual(lines, []);
39
+ });
40
+ test('formatFallbackSummary renders attempted/succeeded/failed with warning preview', () => {
41
+ const lines = formatFallbackSummary([
42
+ 'Method->Delegate (1233 edges): missing rel pair in schema',
43
+ 'Class->Property (200 edges): missing rel pair in schema',
44
+ 'Constructor->Property (97 edges): missing rel pair in schema',
45
+ 'Function->Property (17 edges): missing rel pair in schema',
46
+ ], {
47
+ attempted: 1547,
48
+ succeeded: 0,
49
+ failed: 1547,
50
+ }, 3);
51
+ assert.deepEqual(lines, [
52
+ 'Fallback edges: attempted=1547, succeeded=0, failed=1547, pairTypes=4',
53
+ '- Method->Delegate (1233 edges): missing rel pair in schema',
54
+ '- Class->Property (200 edges): missing rel pair in schema',
55
+ '- Constructor->Property (97 edges): missing rel pair in schema',
56
+ '... 1 more',
57
+ ]);
58
+ });
@@ -19,6 +19,7 @@ import { generateAIContextFiles } from './ai-context.js';
19
19
  import fs from 'fs/promises';
20
20
  import { registerClaudeHook } from './claude-hooks.js';
21
21
  import { normalizeRepoAlias, parseExtensionList, resolveAnalyzeScopeRules } from './analyze-options.js';
22
+ import { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
22
23
  const HEAP_MB = 8192;
23
24
  const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
24
25
  /** Re-exec the process with an 8GB heap if we're currently below that. */
@@ -350,6 +351,10 @@ export const analyzeCommand = async (inputPath, options) => {
350
351
  }
351
352
  }
352
353
  }
354
+ const unitySummaryLines = formatUnityDiagnosticsSummary(pipelineResult.unityResult?.diagnostics);
355
+ for (const line of unitySummaryLines) {
356
+ console.log(` ${line}`);
357
+ }
353
358
  console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
354
359
  console.log(` KuzuDB ${kuzuTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
355
360
  if (includeExtensions.length > 0) {
@@ -362,13 +367,13 @@ export const analyzeCommand = async (inputPath, options) => {
362
367
  if (hookResult.registered) {
363
368
  console.log(` Hooks: ${hookResult.message}`);
364
369
  }
365
- // Show a quiet summary if some edge types needed fallback insertion
370
+ // Show fallback summary with pair-level preview and explicit outcomes
366
371
  if (kuzuWarnings.length > 0) {
367
- const totalFallback = kuzuWarnings.reduce((sum, w) => {
368
- const m = w.match(/\((\d+) edges\)/);
369
- return sum + (m ? parseInt(m[1]) : 0);
370
- }, 0);
371
- console.log(` Note: ${totalFallback} edges across ${kuzuWarnings.length} types inserted via fallback (schema will be updated in next release)`);
372
+ const fallbackLines = formatFallbackSummary(kuzuWarnings, kuzuResult.fallbackStats);
373
+ for (const line of fallbackLines) {
374
+ console.log(` ${line}`);
375
+ }
376
+ console.log(' Note: schema pair coverage should be updated when fallback failures are non-zero');
372
377
  }
373
378
  try {
374
379
  await fs.access(getGlobalRegistryPath());
@@ -0,0 +1,9 @@
1
+ export interface U2E2EArgs {
2
+ configPath: string;
3
+ reportDir?: string;
4
+ }
5
+ export declare function resolveU2E2EArgs(argv: string[]): U2E2EArgs;
6
+ export declare function benchmarkU2E2ECommand(options: {
7
+ config?: string;
8
+ reportDir?: string;
9
+ }): Promise<import("../benchmark/u2-e2e/neonspark-full-e2e.js").E2ERunFailure | import("../benchmark/u2-e2e/neonspark-full-e2e.js").E2ERunSuccess>;
@@ -0,0 +1,35 @@
1
+ import path from 'node:path';
2
+ import { runNeonsparkU2E2E } from '../benchmark/u2-e2e/neonspark-full-e2e.js';
3
+ export function resolveU2E2EArgs(argv) {
4
+ const findValue = (name) => {
5
+ const index = argv.indexOf(name);
6
+ if (index === -1 || index + 1 >= argv.length)
7
+ return undefined;
8
+ return argv[index + 1];
9
+ };
10
+ const config = findValue('--config') || '../benchmarks/u2-e2e/neonspark-full-u2-e2e.config.json';
11
+ const reportDir = findValue('--report-dir');
12
+ return {
13
+ configPath: path.resolve(config),
14
+ ...(reportDir ? { reportDir: path.resolve(reportDir) } : {}),
15
+ };
16
+ }
17
+ export async function benchmarkU2E2ECommand(options) {
18
+ const result = await runNeonsparkU2E2E({
19
+ configPath: path.resolve(options.config || '../benchmarks/u2-e2e/neonspark-full-u2-e2e.config.json'),
20
+ reportDir: options.reportDir ? path.resolve(options.reportDir) : undefined,
21
+ });
22
+ if (result.status === 'failed') {
23
+ process.stderr.write(`FAIL\n`);
24
+ process.stderr.write(`Run ID: ${result.runId}\n`);
25
+ process.stderr.write(`Failed Gate: ${result.failedGate}\n`);
26
+ process.stderr.write(`Report: ${result.reportDir}\n`);
27
+ process.stderr.write(`Error: ${result.error}\n`);
28
+ process.exitCode = 1;
29
+ return result;
30
+ }
31
+ process.stderr.write('PASS\n');
32
+ process.stderr.write(`Run ID: ${result.runId}\n`);
33
+ process.stderr.write(`Report: ${result.reportDir}\n`);
34
+ return result;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { resolveU2E2EArgs } from './benchmark-u2-e2e.js';
4
+ test('benchmark-u2-e2e resolves config and report directory', () => {
5
+ const out = resolveU2E2EArgs(['--config', 'benchmarks/u2-e2e/neonspark-full-u2-e2e.config.json']);
6
+ assert.match(out.configPath, /neonspark-full-u2-e2e\.config\.json$/);
7
+ });
package/dist/cli/index.js CHANGED
@@ -36,6 +36,8 @@ import { queryCommand, contextCommand, impactCommand, cypherCommand } from './to
36
36
  import { evalServerCommand } from './eval-server.js';
37
37
  import { benchmarkUnityCommand } from './benchmark-unity.js';
38
38
  import { benchmarkAgentContextCommand } from './benchmark-agent-context.js';
39
+ import { unityBindingsCommand } from './unity-bindings.js';
40
+ import { benchmarkU2E2ECommand } from './benchmark-u2-e2e.js';
39
41
  function resolveCliVersion() {
40
42
  try {
41
43
  const currentFile = fileURLToPath(import.meta.url);
@@ -121,6 +123,7 @@ program
121
123
  .option('-g, --goal <text>', 'What you want to find')
122
124
  .option('-l, --limit <n>', 'Max processes to return (default: 5)')
123
125
  .option('--content', 'Include full symbol source code')
126
+ .option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
124
127
  .action(queryCommand);
125
128
  program
126
129
  .command('context [name]')
@@ -129,7 +132,16 @@ program
129
132
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
130
133
  .option('-f, --file <path>', 'File path to disambiguate common names')
131
134
  .option('--content', 'Include full symbol source code')
135
+ .option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
132
136
  .action(contextCommand);
137
+ program
138
+ .command('unity-bindings <symbol>')
139
+ .description('Experimental: inspect Unity resource bindings for a C# symbol')
140
+ .option('--target-path <path>', 'Unity project root (default: cwd)')
141
+ .option('--json', 'Output JSON')
142
+ .action(async (symbol, options) => {
143
+ await unityBindingsCommand(symbol, options);
144
+ });
133
145
  program
134
146
  .command('impact <target>')
135
147
  .description('Blast radius analysis: what breaks if you change a symbol')
@@ -181,4 +193,12 @@ program
181
193
  .action(async (dataset, options) => {
182
194
  await benchmarkAgentContextCommand(dataset, options);
183
195
  });
196
+ program
197
+ .command('benchmark-u2-e2e')
198
+ .description('Run fail-fast full neonspark U2 E2E benchmark and emit evidence reports')
199
+ .option('--config <path>', 'Path to E2E config JSON')
200
+ .option('--report-dir <path>', 'Output directory for reports')
201
+ .action(async (options) => {
202
+ await benchmarkU2E2ECommand(options);
203
+ });
184
204
  program.parse(process.argv);
package/dist/cli/setup.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * in either global or project scope.
7
7
  */
8
8
  import fs from 'fs/promises';
9
+ import { readFileSync } from 'node:fs';
9
10
  import path from 'path';
10
11
  import os from 'os';
11
12
  import { execFile } from 'node:child_process';
@@ -16,6 +17,7 @@ import { getGitRoot } from '../storage/git.js';
16
17
  const __filename = fileURLToPath(import.meta.url);
17
18
  const __dirname = path.dirname(__filename);
18
19
  const execFileAsync = promisify(execFile);
20
+ const FALLBACK_MCP_PACKAGE = 'gitnexus@latest';
19
21
  function resolveSetupScope(rawScope) {
20
22
  if (!rawScope || rawScope.trim() === '')
21
23
  return 'global';
@@ -32,6 +34,25 @@ function resolveSetupAgent(rawAgent) {
32
34
  }
33
35
  throw new Error(`Invalid --agent value "${rawAgent}". Use "claude", "opencode", or "codex".`);
34
36
  }
37
+ /**
38
+ * Resolve the package spec used by MCP commands.
39
+ * Defaults to gitnexus@latest when package metadata is unavailable.
40
+ */
41
+ function resolveMcpPackageSpec() {
42
+ try {
43
+ const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
44
+ const raw = readFileSync(packageJsonPath, 'utf-8');
45
+ const parsed = JSON.parse(raw);
46
+ if (typeof parsed.name === 'string' && parsed.name.trim().length > 0) {
47
+ return `${parsed.name}@latest`;
48
+ }
49
+ }
50
+ catch {
51
+ // Fallback keeps behavior for unusual runtimes.
52
+ }
53
+ return FALLBACK_MCP_PACKAGE;
54
+ }
55
+ const MCP_PACKAGE_SPEC = resolveMcpPackageSpec();
35
56
  /**
36
57
  * The MCP server entry for all editors.
37
58
  * On Windows, npx must be invoked via cmd /c since it's a .cmd script.
@@ -40,12 +61,12 @@ function getMcpEntry() {
40
61
  if (process.platform === 'win32') {
41
62
  return {
42
63
  command: 'cmd',
43
- args: ['/c', 'npx', '-y', 'gitnexus@latest', 'mcp'],
64
+ args: ['/c', 'npx', '-y', MCP_PACKAGE_SPEC, 'mcp'],
44
65
  };
45
66
  }
46
67
  return {
47
68
  command: 'npx',
48
- args: ['-y', 'gitnexus@latest', 'mcp'],
69
+ args: ['-y', MCP_PACKAGE_SPEC, 'mcp'],
49
70
  };
50
71
  }
51
72
  function getOpenCodeMcpEntry() {
@@ -190,7 +211,7 @@ async function setupClaudeCode(result) {
190
211
  console.log('');
191
212
  console.log(' Claude Code detected. Run this command to add GitNexus MCP:');
192
213
  console.log('');
193
- console.log(' claude mcp add gitnexus -- npx -y gitnexus mcp');
214
+ console.log(` claude mcp add gitnexus -- npx -y ${MCP_PACKAGE_SPEC} mcp`);
194
215
  console.log('');
195
216
  result.configured.push('Claude Code (MCP manual step printed)');
196
217
  }
@@ -10,6 +10,8 @@ const execFileAsync = promisify(execFile);
10
10
  const here = path.dirname(fileURLToPath(import.meta.url));
11
11
  const packageRoot = path.resolve(here, '..', '..');
12
12
  const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
13
+ const packageName = JSON.parse(await fs.readFile(path.join(packageRoot, 'package.json'), 'utf-8'));
14
+ const expectedMcpPackage = `${packageName.name || 'gitnexus'}@latest`;
13
15
  async function runSetup(args, env, cwd = packageRoot) {
14
16
  return execFileAsync(process.execPath, [cliPath, 'setup', ...args], { cwd, env });
15
17
  }
@@ -112,7 +114,7 @@ process.exit(0);
112
114
  const raw = await fs.readFile(outputPath, 'utf-8');
113
115
  const parsed = JSON.parse(raw);
114
116
  assert.deepEqual(parsed.args.slice(0, 4), ['mcp', 'add', 'gitnexus', '--']);
115
- assert.ok(parsed.args.includes('gitnexus@latest'));
117
+ assert.ok(parsed.args.includes(expectedMcpPackage));
116
118
  assert.ok(parsed.args.includes('mcp'));
117
119
  }
118
120
  finally {
@@ -134,7 +136,7 @@ test('setup configures OpenCode MCP in ~/.config/opencode/opencode.json', async
134
136
  const opencodeRaw = await fs.readFile(opencodeConfigPath, 'utf-8');
135
137
  const opencodeConfig = JSON.parse(opencodeRaw);
136
138
  assert.equal(opencodeConfig.mcp?.gitnexus?.type, 'local');
137
- assert.deepEqual(opencodeConfig.mcp?.gitnexus?.command, ['npx', '-y', 'gitnexus@latest', 'mcp']);
139
+ assert.deepEqual(opencodeConfig.mcp?.gitnexus?.command, ['npx', '-y', expectedMcpPackage, 'mcp']);
138
140
  }
139
141
  finally {
140
142
  await fs.rm(fakeHome, { recursive: true, force: true });
@@ -157,7 +159,7 @@ test('setup keeps using legacy ~/.config/opencode/config.json when it already ex
157
159
  const legacyConfig = JSON.parse(legacyRaw);
158
160
  assert.equal(legacyConfig.existing, true);
159
161
  assert.equal(legacyConfig.mcp?.gitnexus?.type, 'local');
160
- assert.deepEqual(legacyConfig.mcp?.gitnexus?.command, ['npx', '-y', 'gitnexus@latest', 'mcp']);
162
+ assert.deepEqual(legacyConfig.mcp?.gitnexus?.command, ['npx', '-y', expectedMcpPackage, 'mcp']);
161
163
  await assert.rejects(fs.access(preferredConfigPath));
162
164
  }
163
165
  finally {
@@ -254,7 +256,7 @@ test('setup --scope project --agent opencode writes only opencode.json', async (
254
256
  const configRaw = await fs.readFile(configPath, 'utf-8');
255
257
  const config = JSON.parse(configRaw);
256
258
  assert.equal(opencodeConfig.mcp?.gitnexus?.type, 'local');
257
- assert.deepEqual(opencodeConfig.mcp?.gitnexus?.command, ['npx', '-y', 'gitnexus@latest', 'mcp']);
259
+ assert.deepEqual(opencodeConfig.mcp?.gitnexus?.command, ['npx', '-y', expectedMcpPackage, 'mcp']);
258
260
  await assert.rejects(fs.access(projectMcpPath));
259
261
  await assert.rejects(fs.access(codexConfigPath));
260
262
  await fs.access(localSkillPath);
@@ -13,18 +13,21 @@
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
17
  export declare function queryCommand(queryText: string, options?: {
17
18
  repo?: string;
18
19
  context?: string;
19
20
  goal?: string;
20
21
  limit?: string;
21
22
  content?: boolean;
23
+ unityResources?: UnityResourcesMode;
22
24
  }): Promise<void>;
23
25
  export declare function contextCommand(name: string, options?: {
24
26
  repo?: string;
25
27
  file?: string;
26
28
  uid?: string;
27
29
  content?: boolean;
30
+ unityResources?: UnityResourcesMode;
28
31
  }): Promise<void>;
29
32
  export declare function impactCommand(target: string, options?: {
30
33
  direction?: string;
package/dist/cli/tool.js CHANGED
@@ -43,6 +43,7 @@ export async function queryCommand(queryText, options) {
43
43
  goal: options?.goal,
44
44
  limit: options?.limit ? parseInt(options.limit) : undefined,
45
45
  include_content: options?.content ?? false,
46
+ unity_resources: options?.unityResources,
46
47
  repo: options?.repo,
47
48
  });
48
49
  output(result);
@@ -58,6 +59,7 @@ export async function contextCommand(name, options) {
58
59
  uid: options?.uid,
59
60
  file_path: options?.file,
60
61
  include_content: options?.content ?? false,
62
+ unity_resources: options?.unityResources,
61
63
  repo: options?.repo,
62
64
  });
63
65
  output(result);
@@ -0,0 +1,8 @@
1
+ import { resolveUnityBindings } from '../core/unity/resolver.js';
2
+ export declare function unityBindingsCommand(symbol: string, options: {
3
+ targetPath?: string;
4
+ json?: boolean;
5
+ }, deps?: {
6
+ resolver?: typeof resolveUnityBindings;
7
+ writeLine?: (line: string) => void;
8
+ }): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import path from 'node:path';
2
+ import { resolveUnityBindings } from '../core/unity/resolver.js';
3
+ export async function unityBindingsCommand(symbol, options, deps) {
4
+ const resolver = deps?.resolver || resolveUnityBindings;
5
+ const writeLine = deps?.writeLine || ((line) => process.stderr.write(`${line}\n`));
6
+ const repoRoot = path.resolve(options.targetPath || process.cwd());
7
+ const result = await resolver({ repoRoot, symbol });
8
+ if (options.json) {
9
+ writeLine(JSON.stringify(result, null, 2));
10
+ return;
11
+ }
12
+ writeLine(`Unity bindings for ${result.symbol}`);
13
+ writeLine(`Script: ${result.scriptPath}`);
14
+ writeLine(`GUID: ${result.scriptGuid}`);
15
+ writeLine(`Resource bindings: ${result.resourceBindings.length}`);
16
+ for (const binding of result.resourceBindings) {
17
+ writeLine(`- ${binding.resourceType} ${binding.resourcePath} [${binding.bindingKind}] component=${binding.componentObjectId}`);
18
+ }
19
+ writeLine(`Scalar fields: ${result.serializedFields.scalarFields.length}`);
20
+ for (const field of result.serializedFields.scalarFields) {
21
+ writeLine(`- ${field.name} = ${field.value} (${field.sourceLayer})`);
22
+ }
23
+ writeLine(`Reference fields: ${result.serializedFields.referenceFields.length}`);
24
+ for (const field of result.serializedFields.referenceFields) {
25
+ writeLine(`- ${field.name} -> ${field.guid || field.fileId || 'unresolved'} (${field.sourceLayer})`);
26
+ }
27
+ if (result.unityDiagnostics.length > 0) {
28
+ writeLine(`Diagnostics: ${result.unityDiagnostics.length}`);
29
+ for (const diagnostic of result.unityDiagnostics) {
30
+ writeLine(`- ${diagnostic}`);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { unityBindingsCommand } from './unity-bindings.js';
6
+ const here = path.dirname(fileURLToPath(import.meta.url));
7
+ const fixtureRoot = path.resolve(here, '../../src/core/unity/__fixtures__/mini-unity');
8
+ test('prints human readable summary by default', async () => {
9
+ const lines = [];
10
+ await unityBindingsCommand('MainUIManager', { targetPath: fixtureRoot }, { writeLine: (line) => lines.push(line) });
11
+ const output = lines.join('\n');
12
+ assert.match(output, /resource bindings/i);
13
+ assert.match(output, /MainUIManager/);
14
+ assert.match(output, /needPause/);
15
+ });
16
+ test('prints JSON when --json is enabled', async () => {
17
+ const lines = [];
18
+ await unityBindingsCommand('MainUIManager', { targetPath: fixtureRoot, json: true }, { writeLine: (line) => lines.push(line) });
19
+ const payload = JSON.parse(lines.join('\n'));
20
+ assert.equal(payload.symbol, 'MainUIManager');
21
+ assert.ok(Array.isArray(payload.resourceBindings));
22
+ assert.ok(Array.isArray(payload.serializedFields.scalarFields));
23
+ assert.ok(Array.isArray(payload.serializedFields.referenceFields));
24
+ });
@@ -20,7 +20,7 @@ export type NodeProperties = {
20
20
  entryPointScore?: number;
21
21
  entryPointReason?: string;
22
22
  };
23
- export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS';
23
+ export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'OVERRIDES' | 'IMPORTS' | 'USES' | 'DEFINES' | 'DECORATES' | 'IMPLEMENTS' | 'EXTENDS' | 'MEMBER_OF' | 'STEP_IN_PROCESS' | 'UNITY_COMPONENT_IN' | 'UNITY_COMPONENT_INSTANCE' | 'UNITY_SERIALIZED_TYPE_IN';
24
24
  export interface GraphNode {
25
25
  id: string;
26
26
  label: NodeLabel;
@@ -0,0 +1,33 @@
1
+ import type { CommunityMembership, CommunityNode } from '../community-processor.js';
2
+ import type { ModuleConfig, ModuleMode } from './types.js';
3
+ import { type MatchableSymbol } from './rule-matcher.js';
4
+ export type AssignmentSource = 'config-rule' | 'auto-fallback';
5
+ export type AssignmentResolvedBy = 'priority' | 'specificity' | 'rule-order' | 'module-lexicographic' | 'fallback-auto';
6
+ export interface AssignmentInput {
7
+ mode: ModuleMode;
8
+ symbols: MatchableSymbol[];
9
+ autoCommunities: CommunityNode[];
10
+ autoMemberships: CommunityMembership[];
11
+ config: ModuleConfig | null;
12
+ }
13
+ export interface SymbolAssignment {
14
+ symbolId: string;
15
+ moduleName: string;
16
+ communityId: string;
17
+ assignmentSource: AssignmentSource;
18
+ matchedRuleId?: string;
19
+ resolvedBy: AssignmentResolvedBy;
20
+ }
21
+ export interface AssignmentDiagnostics {
22
+ mode: ModuleMode;
23
+ configuredModuleCount: number;
24
+ finalModuleCount: number;
25
+ emptyModules: string[];
26
+ }
27
+ export interface AssignmentOutput {
28
+ finalModules: CommunityNode[];
29
+ finalMemberships: CommunityMembership[];
30
+ membershipsBySymbol: Map<string, SymbolAssignment>;
31
+ diagnostics: AssignmentDiagnostics;
32
+ }
33
+ export declare function assignModules(input: AssignmentInput): AssignmentOutput;