@veewo/gitnexus 1.4.8 → 1.4.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 (49) hide show
  1. package/dist/cli/ai-context.js +1 -2
  2. package/dist/cli/analyze.js +2 -1
  3. package/dist/cli/index.js +8 -0
  4. package/dist/cli/repo-manager-alias.test.js +2 -0
  5. package/dist/cli/tool.d.ts +11 -0
  6. package/dist/cli/tool.js +59 -4
  7. package/dist/cli/unity-ui-trace.test.d.ts +1 -0
  8. package/dist/cli/unity-ui-trace.test.js +24 -0
  9. package/dist/core/ingestion/call-processor.js +9 -1
  10. package/dist/core/ingestion/type-env.d.ts +2 -0
  11. package/dist/core/ingestion/type-extractors/csharp.js +17 -2
  12. package/dist/core/ingestion/type-extractors/types.d.ts +4 -1
  13. package/dist/core/unity/csharp-selector-binding.d.ts +7 -0
  14. package/dist/core/unity/csharp-selector-binding.js +32 -0
  15. package/dist/core/unity/csharp-selector-binding.test.d.ts +1 -0
  16. package/dist/core/unity/csharp-selector-binding.test.js +14 -0
  17. package/dist/core/unity/scan-context.d.ts +2 -0
  18. package/dist/core/unity/scan-context.js +17 -0
  19. package/dist/core/unity/ui-asset-ref-scanner.d.ts +14 -0
  20. package/dist/core/unity/ui-asset-ref-scanner.js +213 -0
  21. package/dist/core/unity/ui-asset-ref-scanner.test.d.ts +1 -0
  22. package/dist/core/unity/ui-asset-ref-scanner.test.js +44 -0
  23. package/dist/core/unity/ui-meta-index.d.ts +8 -0
  24. package/dist/core/unity/ui-meta-index.js +60 -0
  25. package/dist/core/unity/ui-meta-index.test.d.ts +1 -0
  26. package/dist/core/unity/ui-meta-index.test.js +18 -0
  27. package/dist/core/unity/ui-trace-storage-guard.test.d.ts +1 -0
  28. package/dist/core/unity/ui-trace-storage-guard.test.js +10 -0
  29. package/dist/core/unity/ui-trace.acceptance.test.d.ts +1 -0
  30. package/dist/core/unity/ui-trace.acceptance.test.js +38 -0
  31. package/dist/core/unity/ui-trace.d.ts +31 -0
  32. package/dist/core/unity/ui-trace.js +363 -0
  33. package/dist/core/unity/ui-trace.test.d.ts +1 -0
  34. package/dist/core/unity/ui-trace.test.js +183 -0
  35. package/dist/core/unity/uss-selector-parser.d.ts +6 -0
  36. package/dist/core/unity/uss-selector-parser.js +21 -0
  37. package/dist/core/unity/uss-selector-parser.test.d.ts +1 -0
  38. package/dist/core/unity/uss-selector-parser.test.js +13 -0
  39. package/dist/core/unity/uxml-ref-parser.d.ts +10 -0
  40. package/dist/core/unity/uxml-ref-parser.js +22 -0
  41. package/dist/core/unity/uxml-ref-parser.test.d.ts +1 -0
  42. package/dist/core/unity/uxml-ref-parser.test.js +31 -0
  43. package/dist/mcp/local/local-backend.d.ts +13 -0
  44. package/dist/mcp/local/local-backend.js +298 -25
  45. package/dist/mcp/tools.js +41 -0
  46. package/dist/storage/repo-manager.d.ts +1 -0
  47. package/package.json +2 -1
  48. package/skills/gitnexus-cli.md +29 -0
  49. package/skills/gitnexus-guide.md +23 -0
@@ -36,13 +36,12 @@ function generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec,
36
36
  return `${GITNEXUS_START_MARKER}
37
37
  # GitNexus MCP
38
38
 
39
- This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows).
40
-
41
39
  ## Always Start Here
42
40
 
43
41
  1. **Read \`gitnexus://repo/{name}/context\`** — codebase overview + check index freshness
44
42
  2. **Match your task to a skill below** and **read that skill file**
45
43
  3. **Follow the skill's workflow and checklist**
44
+ 4. **Follow config/state file rules:** \`docs/gitnexus-config-files.md\`
46
45
 
47
46
  > If step 1 warns the index is stale, ask user whether to rebuild index via \`gitnexus analyze\` when local CLI exists; otherwise resolve the pinned npx package spec from \`~/.gitnexus/config.json\` (\`cliPackageSpec\` first, then \`cliVersion\`) and run \`${reindexCmd}\` with that exact package spec (it reuses previous analyze scope/options by default; add \`--no-reuse-options\` to reset). If user declines, explicitly warn that retrieval may not reflect current codebase. For build/analyze/test commands, use a 10-30 minute timeout; on failure/timeout, report exact tool output and do not auto-retry or silently fall back to glob/grep.
48
47
 
@@ -360,9 +360,10 @@ export const analyzeCommand = async (inputPath, options) => {
360
360
  embeddings: embeddingCount,
361
361
  },
362
362
  };
363
+ const registeredRepo = await registerRepo(repoPath, meta, { repoAlias });
364
+ meta.repoId = registeredRepo.name;
363
365
  await saveMeta(storagePath, meta);
364
366
  await persistUnityParitySeed(storagePath, pipelineRuntime.unityResult?.paritySeed);
365
- const registeredRepo = await registerRepo(repoPath, meta, { repoAlias });
366
367
  await addToGitignore(repoPath);
367
368
  const projectName = path.basename(repoPath);
368
369
  let aggregatedClusterCount = 0;
package/dist/cli/index.js CHANGED
@@ -82,6 +82,7 @@ program
82
82
  .option('-g, --goal <text>', 'What you want to find')
83
83
  .option('-l, --limit <n>', 'Max processes to return (default: 5)')
84
84
  .option('--content', 'Include full symbol source code')
85
+ .option('--scope-preset <preset>', 'Scope preset for retrieval: unity-gameplay|unity-all')
85
86
  .option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
86
87
  .option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
87
88
  .action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
@@ -101,6 +102,13 @@ program
101
102
  .option('--target-path <path>', 'Unity project root (default: cwd)')
102
103
  .option('--json', 'Output JSON')
103
104
  .action(createLazyAction(() => import('./unity-bindings.js'), 'unityBindingsCommand'));
105
+ program
106
+ .command('unity-ui-trace <target>')
107
+ .description('Query-time Unity UI evidence tracing (asset_refs|template_refs|selector_bindings)')
108
+ .option('-r, --repo <name>', 'Target repository (omit if only one indexed)')
109
+ .option('--goal <goal>', 'Trace goal: asset_refs|template_refs|selector_bindings', 'asset_refs')
110
+ .option('--selector-mode <mode>', 'Selector matching mode for selector_bindings: strict|balanced', 'balanced')
111
+ .action(createLazyAction(() => import('./tool.js'), 'unityUiTraceCommand'));
104
112
  program
105
113
  .command('impact <target>')
106
114
  .description('Blast radius analysis: what breaks if you change a symbol')
@@ -43,6 +43,7 @@ test('saveMeta/loadMeta persists analyzeOptions for future re-index reuse', asyn
43
43
  const storagePath = path.join(tmpDir, '.gitnexus');
44
44
  await saveMeta(storagePath, {
45
45
  repoPath: tmpDir,
46
+ repoId: 'neonspark-v1-subset',
46
47
  lastCommit: 'abc1234',
47
48
  indexedAt: '2026-03-12T00:00:00.000Z',
48
49
  analyzeOptions: {
@@ -60,4 +61,5 @@ test('saveMeta/loadMeta persists analyzeOptions for future re-index reuse', asyn
60
61
  repoAlias: 'neonspark-v1-subset',
61
62
  embeddings: true,
62
63
  });
64
+ assert.equal(meta?.repoId, 'neonspark-v1-subset');
63
65
  });
@@ -21,6 +21,7 @@ export declare function queryCommand(queryText: string, options?: {
21
21
  goal?: string;
22
22
  limit?: string;
23
23
  content?: boolean;
24
+ scopePreset?: 'unity-gameplay' | 'unity-all';
24
25
  unityResources?: UnityResourcesMode;
25
26
  unityHydration?: UnityHydrationMode;
26
27
  }): Promise<void>;
@@ -44,3 +45,13 @@ export declare function impactCommand(target: string, options?: {
44
45
  export declare function cypherCommand(query: string, options?: {
45
46
  repo?: string;
46
47
  }): Promise<void>;
48
+ export declare function unityUiTraceCommand(target: string, options?: {
49
+ repo?: string;
50
+ goal?: string;
51
+ selectorMode?: string;
52
+ }, deps?: {
53
+ backend?: {
54
+ callTool: (method: string, params: any) => Promise<any>;
55
+ };
56
+ output?: (data: any) => void;
57
+ }): Promise<void>;
package/dist/cli/tool.js CHANGED
@@ -15,7 +15,10 @@
15
15
  * See the output() function for details (#324).
16
16
  */
17
17
  import { writeSync } from 'node:fs';
18
+ import path from 'node:path';
18
19
  import { LocalBackend } from '../mcp/local/local-backend.js';
20
+ import { getGitRoot } from '../storage/git.js';
21
+ import { getStoragePaths, listRegisteredRepos, loadMeta } from '../storage/repo-manager.js';
19
22
  let _backend = null;
20
23
  async function getBackend() {
21
24
  if (_backend)
@@ -53,21 +56,45 @@ function output(data) {
53
56
  process.stderr.write(text + '\n');
54
57
  }
55
58
  }
59
+ function isUnityUiTraceGoal(value) {
60
+ return value === 'asset_refs' || value === 'template_refs' || value === 'selector_bindings';
61
+ }
62
+ function isUnityUiSelectorMode(value) {
63
+ return value === 'strict' || value === 'balanced';
64
+ }
65
+ async function resolveRepoOption(explicitRepo) {
66
+ if (explicitRepo?.trim())
67
+ return explicitRepo.trim();
68
+ const gitRoot = getGitRoot(process.cwd());
69
+ if (!gitRoot)
70
+ return undefined;
71
+ const { storagePath } = getStoragePaths(gitRoot);
72
+ const meta = await loadMeta(storagePath);
73
+ const repoId = typeof meta?.repoId === 'string' ? meta.repoId.trim() : '';
74
+ if (repoId)
75
+ return repoId;
76
+ // Backward compatibility for indexes created before repoId persisted in meta.json.
77
+ const entries = await listRegisteredRepos({ validate: false });
78
+ const matched = entries.find((entry) => path.resolve(entry.path) === gitRoot);
79
+ return matched?.name || undefined;
80
+ }
56
81
  export async function queryCommand(queryText, options) {
57
82
  if (!queryText?.trim()) {
58
83
  console.error('Usage: gitnexus query <search_query>');
59
84
  process.exit(1);
60
85
  }
61
86
  const backend = await getBackend();
87
+ const repo = await resolveRepoOption(options?.repo);
62
88
  const result = await backend.callTool('query', {
63
89
  query: queryText,
64
90
  task_context: options?.context,
65
91
  goal: options?.goal,
66
92
  limit: options?.limit ? parseInt(options.limit) : undefined,
67
93
  include_content: options?.content ?? false,
94
+ scope_preset: options?.scopePreset,
68
95
  unity_resources: options?.unityResources,
69
96
  unity_hydration_mode: options?.unityHydration,
70
- repo: options?.repo,
97
+ repo,
71
98
  });
72
99
  output(result);
73
100
  }
@@ -77,6 +104,7 @@ export async function contextCommand(name, options) {
77
104
  process.exit(1);
78
105
  }
79
106
  const backend = await getBackend();
107
+ const repo = await resolveRepoOption(options?.repo);
80
108
  const result = await backend.callTool('context', {
81
109
  name: name || undefined,
82
110
  uid: options?.uid,
@@ -84,7 +112,7 @@ export async function contextCommand(name, options) {
84
112
  include_content: options?.content ?? false,
85
113
  unity_resources: options?.unityResources,
86
114
  unity_hydration_mode: options?.unityHydration,
87
- repo: options?.repo,
115
+ repo,
88
116
  });
89
117
  output(result);
90
118
  }
@@ -95,6 +123,7 @@ export async function impactCommand(target, options) {
95
123
  }
96
124
  try {
97
125
  const backend = await getBackend();
126
+ const repo = await resolveRepoOption(options?.repo);
98
127
  const result = await backend.callTool('impact', {
99
128
  target,
100
129
  target_uid: options?.uid,
@@ -103,7 +132,7 @@ export async function impactCommand(target, options) {
103
132
  maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
104
133
  minConfidence: options?.minConfidence ? parseFloat(options.minConfidence) : undefined,
105
134
  includeTests: options?.includeTests ?? false,
106
- repo: options?.repo,
135
+ repo,
107
136
  });
108
137
  output(result);
109
138
  }
@@ -124,9 +153,35 @@ export async function cypherCommand(query, options) {
124
153
  process.exit(1);
125
154
  }
126
155
  const backend = await getBackend();
156
+ const repo = await resolveRepoOption(options?.repo);
127
157
  const result = await backend.callTool('cypher', {
128
158
  query,
129
- repo: options?.repo,
159
+ repo,
130
160
  });
131
161
  output(result);
132
162
  }
163
+ export async function unityUiTraceCommand(target, options, deps) {
164
+ if (!target?.trim()) {
165
+ console.error('Usage: gitnexus unity-ui-trace <target> --goal asset_refs|template_refs|selector_bindings');
166
+ process.exit(1);
167
+ }
168
+ const goal = String(options?.goal || 'asset_refs').trim();
169
+ if (!isUnityUiTraceGoal(goal)) {
170
+ console.error('Invalid --goal. Use one of: asset_refs, template_refs, selector_bindings');
171
+ process.exit(1);
172
+ }
173
+ const selectorMode = String(options?.selectorMode || 'balanced').trim();
174
+ if (!isUnityUiSelectorMode(selectorMode)) {
175
+ console.error('Invalid --selector-mode. Use one of: strict, balanced');
176
+ process.exit(1);
177
+ }
178
+ const backend = deps?.backend || await getBackend();
179
+ const repo = await resolveRepoOption(options?.repo);
180
+ const result = await backend.callTool('unity_ui_trace', {
181
+ target,
182
+ goal,
183
+ selector_mode: selectorMode,
184
+ repo,
185
+ });
186
+ (deps?.output || output)(result);
187
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { unityUiTraceCommand } from './tool.js';
4
+ test('unity-ui-trace command forwards params and prints result', async () => {
5
+ const calls = [];
6
+ let printed = null;
7
+ await unityUiTraceCommand('EliteBossScreenController', { goal: 'selector_bindings', selectorMode: 'strict', repo: 'mini-unity-ui' }, {
8
+ backend: {
9
+ async callTool(method, params) {
10
+ calls.push({ method, params });
11
+ return { goal: 'selector_bindings', results: [{ evidence_chain: [{ path: 'a', line: 1 }] }], diagnostics: [] };
12
+ },
13
+ },
14
+ output: (value) => {
15
+ printed = value;
16
+ },
17
+ });
18
+ assert.equal(calls.length, 1);
19
+ assert.equal(calls[0].method, 'unity_ui_trace');
20
+ assert.equal(calls[0].params.target, 'EliteBossScreenController');
21
+ assert.equal(calls[0].params.goal, 'selector_bindings');
22
+ assert.equal(calls[0].params.selector_mode, 'strict');
23
+ assert.equal(printed.goal, 'selector_bindings');
24
+ });
@@ -35,7 +35,15 @@ const findEnclosingFunction = (node, filePath, ctx) => {
35
35
  */
36
36
  const verifyConstructorBindings = (bindings, filePath, ctx, graph) => {
37
37
  const verified = new Map();
38
- for (const { scope, varName, calleeName, receiverClassName } of bindings) {
38
+ for (const { scope, varName, calleeName, receiverClassName, inferredTypeName } of bindings) {
39
+ if (inferredTypeName) {
40
+ const inferred = ctx.resolve(inferredTypeName, filePath);
41
+ const isReceivableType = inferred?.candidates.some(def => def.type === 'Class' || def.type === 'Interface' || def.type === 'Struct' || def.type === 'Enum') ?? false;
42
+ if (isReceivableType) {
43
+ verified.set(receiverKey(scope, varName), inferredTypeName);
44
+ continue;
45
+ }
46
+ }
39
47
  const tiered = ctx.resolve(calleeName, filePath);
40
48
  const isClass = tiered?.candidates.some(def => def.type === 'Class') ?? false;
41
49
  if (isClass) {
@@ -46,4 +46,6 @@ export interface ConstructorBinding {
46
46
  calleeName: string;
47
47
  /** Enclosing class name when callee is a method on a known receiver (e.g. $this) */
48
48
  receiverClassName?: string;
49
+ /** Optional direct type hint extracted from safe generic invocation patterns. */
50
+ inferredTypeName?: string;
49
51
  }
@@ -1,4 +1,4 @@
1
- import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
1
+ import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
2
2
  /** Known container property accessors that operate on the container itself (e.g., dict.Keys, dict.Values) */
3
3
  const KNOWN_CONTAINER_PROPS = new Set(['Keys', 'Values']);
4
4
  const DECLARATION_NODE_TYPES = new Set([
@@ -145,7 +145,22 @@ const scanConstructorBinding = (node) => {
145
145
  const calleeName = extractSimpleTypeName(func);
146
146
  if (!calleeName)
147
147
  return undefined;
148
- return { varName: nameNode.text, calleeName };
148
+ // Safe generic-invocation receiver hint:
149
+ // var user = GetComponentInParent<User>()
150
+ // var user = svc.GetComponentInParent<User>()
151
+ // infer user -> User directly from the single type argument.
152
+ let inferredTypeName;
153
+ const genericCallee = func.type === 'generic_name'
154
+ ? func
155
+ : func.type === 'member_access_expression'
156
+ ? func.childForFieldName('name')
157
+ : null;
158
+ if (genericCallee?.type === 'generic_name') {
159
+ const typeArgs = extractGenericTypeArgs(genericCallee);
160
+ if (typeArgs.length === 1)
161
+ inferredTypeName = typeArgs[0];
162
+ }
163
+ return { varName: nameNode.text, calleeName, inferredTypeName };
149
164
  };
150
165
  const FOR_LOOP_NODE_TYPES = new Set([
151
166
  'foreach_statement',
@@ -13,11 +13,14 @@ export type InitializerExtractor = (node: SyntaxNode, env: Map<string, string>,
13
13
  /** Scans an AST node for untyped `var = callee()` patterns for return-type inference.
14
14
  * Returns { varName, calleeName } if the node matches, undefined otherwise.
15
15
  * `receiverClassName` — optional hint for method calls on known receivers
16
- * (e.g. $this->getUser() in PHP provides the enclosing class name). */
16
+ * (e.g. $this->getUser() in PHP provides the enclosing class name).
17
+ * `inferredTypeName` — optional direct type hint from safe generic invocations
18
+ * (e.g. `var x = GetComponentInParent<User>()` gives `User`). */
17
19
  export type ConstructorBindingScanner = (node: SyntaxNode) => {
18
20
  varName: string;
19
21
  calleeName: string;
20
22
  receiverClassName?: string;
23
+ inferredTypeName?: string;
21
24
  } | undefined;
22
25
  /** Extracts a return type string from a method/function definition node.
23
26
  * Used for languages where return types are expressed in comments (e.g. YARD @return [Type])
@@ -0,0 +1,7 @@
1
+ export interface CsharpSelectorBinding {
2
+ className: string;
3
+ line: number;
4
+ snippet: string;
5
+ isDynamic: boolean;
6
+ }
7
+ export declare function extractCsharpSelectorBindings(content: string): CsharpSelectorBinding[];
@@ -0,0 +1,32 @@
1
+ const ADD_TO_CLASS_LIST_LITERAL = /\bAddToClassList\s*\(\s*"([^"]+)"\s*\)/g;
2
+ const Q_CLASS_NAME_LITERAL = /\bQ\s*<[^>]*>\s*\([^)]*\bclassName\s*:\s*"([^"]+)"[^)]*\)/g;
3
+ export function extractCsharpSelectorBindings(content) {
4
+ const out = [];
5
+ const lines = content.split(/\r?\n/);
6
+ for (let i = 0; i < lines.length; i += 1) {
7
+ const line = lines[i];
8
+ ADD_TO_CLASS_LIST_LITERAL.lastIndex = 0;
9
+ let addMatch = ADD_TO_CLASS_LIST_LITERAL.exec(line);
10
+ while (addMatch) {
11
+ out.push({
12
+ className: addMatch[1],
13
+ line: i + 1,
14
+ snippet: line.trim(),
15
+ isDynamic: false,
16
+ });
17
+ addMatch = ADD_TO_CLASS_LIST_LITERAL.exec(line);
18
+ }
19
+ Q_CLASS_NAME_LITERAL.lastIndex = 0;
20
+ let qMatch = Q_CLASS_NAME_LITERAL.exec(line);
21
+ while (qMatch) {
22
+ out.push({
23
+ className: qMatch[1],
24
+ line: i + 1,
25
+ snippet: line.trim(),
26
+ isDynamic: false,
27
+ });
28
+ qMatch = Q_CLASS_NAME_LITERAL.exec(line);
29
+ }
30
+ }
31
+ return out;
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
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 { extractCsharpSelectorBindings } from './csharp-selector-binding.js';
7
+ const here = path.dirname(fileURLToPath(import.meta.url));
8
+ const fixtureRoot = path.resolve(here, '../../../src/core/unity/__fixtures__/mini-unity-ui');
9
+ test('extracts static-only selector bindings from csharp', async () => {
10
+ const source = await fs.readFile(path.join(fixtureRoot, 'Assets/Scripts/EliteBossScreenController.cs'), 'utf-8');
11
+ const bindings = extractCsharpSelectorBindings(source);
12
+ assert.equal(bindings.some((entry) => entry.className === 'tooltip-box'), true);
13
+ assert.equal(bindings.some((entry) => entry.isDynamic), false);
14
+ });
@@ -19,6 +19,8 @@ export interface UnityScanContext {
19
19
  serializableSymbols: Set<string>;
20
20
  hostFieldTypeHints: Map<string, Map<string, string>>;
21
21
  assetGuidToPath?: Map<string, string>;
22
+ uxmlGuidToPath?: Map<string, string>;
23
+ ussGuidToPath?: Map<string, string>;
22
24
  resourceDocCache: Map<string, UnityObjectBlock[]>;
23
25
  }
24
26
  export declare function buildUnityScanContext(input: BuildScanContextInput): Promise<UnityScanContext>;
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { createInterface } from 'node:readline';
5
5
  import { glob } from 'glob';
6
6
  import { buildAssetMetaIndex, buildMetaIndex } from './meta-index.js';
7
+ import { buildUnityUiMetaIndex } from './ui-meta-index.js';
7
8
  import { buildSerializableTypeIndexFromFiles } from './serialized-type-index.js';
8
9
  const DECLARATION_PATTERN = /\b(?:class|struct|interface)\s+([A-Za-z_][A-Za-z0-9_]*)\b/g;
9
10
  const SCRIPT_GUID_IN_LINE_PATTERN = /\bm_Script\s*:\s*\{[^}]*\bguid\s*:\s*([0-9a-f]{32})\b/gi;
@@ -32,6 +33,7 @@ export async function buildUnityScanContext(input) {
32
33
  const guidToResourceHits = await buildGuidHitIndex(input.repoRoot, scriptPathToGuid, resourceFiles);
33
34
  const assetMetaFiles = resolveAssetMetaFiles(input.repoRoot, input.scopedPaths, scriptFiles, resourceFiles);
34
35
  const assetGuidToPath = await buildAssetMetaIndex(input.repoRoot, { metaFiles: assetMetaFiles });
36
+ const uiMetaIndex = await buildUnityUiMetaIndex(input.repoRoot, { scopedPaths: input.scopedPaths });
35
37
  const symbolToCanonicalScriptPath = buildCanonicalScriptPathIndex(symbolToScriptPaths, scriptPathToGuid, guidToResourceHits);
36
38
  const symbolToScriptPath = new Map(symbolToCanonicalScriptPath);
37
39
  return {
@@ -43,6 +45,8 @@ export async function buildUnityScanContext(input) {
43
45
  serializableSymbols: serializableTypeIndex.serializableSymbols,
44
46
  hostFieldTypeHints: serializableTypeIndex.hostFieldTypeHints,
45
47
  assetGuidToPath,
48
+ uxmlGuidToPath: uiMetaIndex.uxmlGuidToPath,
49
+ ussGuidToPath: uiMetaIndex.ussGuidToPath,
46
50
  resourceDocCache: new Map(),
47
51
  };
48
52
  }
@@ -114,11 +118,22 @@ export function buildUnityScanContextFromSeed(input) {
114
118
  }
115
119
  }
116
120
  const assetGuidToPath = new Map();
121
+ const uxmlGuidToPath = new Map();
122
+ const ussGuidToPath = new Map();
117
123
  for (const [guid, assetPath] of Object.entries(seed.assetGuidToPath || {})) {
118
124
  const normalizedPath = normalizeSlashes(String(assetPath || '').trim());
119
125
  if (!guid || !normalizedPath)
120
126
  continue;
121
127
  assetGuidToPath.set(guid, normalizedPath);
128
+ assetGuidToPath.set(guid.toLowerCase(), normalizedPath);
129
+ if (normalizedPath.endsWith('.uxml')) {
130
+ uxmlGuidToPath.set(guid, normalizedPath);
131
+ uxmlGuidToPath.set(guid.toLowerCase(), normalizedPath);
132
+ }
133
+ if (normalizedPath.endsWith('.uss')) {
134
+ ussGuidToPath.set(guid, normalizedPath);
135
+ ussGuidToPath.set(guid.toLowerCase(), normalizedPath);
136
+ }
122
137
  }
123
138
  return {
124
139
  symbolToScriptPaths,
@@ -129,6 +144,8 @@ export function buildUnityScanContextFromSeed(input) {
129
144
  serializableSymbols: new Set(),
130
145
  hostFieldTypeHints: new Map(),
131
146
  assetGuidToPath,
147
+ uxmlGuidToPath,
148
+ ussGuidToPath,
132
149
  resourceDocCache: new Map(),
133
150
  };
134
151
  }
@@ -0,0 +1,14 @@
1
+ export interface UiAssetRefEvidence {
2
+ sourceType: 'prefab' | 'asset';
3
+ sourcePath: string;
4
+ line: number;
5
+ fieldName: string;
6
+ guid: string;
7
+ snippet: string;
8
+ }
9
+ export interface ScanUiAssetRefInput {
10
+ repoRoot: string;
11
+ scopedPaths?: string[];
12
+ targetGuids?: string[];
13
+ }
14
+ export declare function scanUiAssetRefs(input: ScanUiAssetRefInput): Promise<UiAssetRefEvidence[]>;