@veewo/gitnexus 1.4.11-rc.2 → 1.5.0-rc.2

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 (139) hide show
  1. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +55 -0
  2. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +190 -0
  3. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.test.js +13 -0
  4. package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.d.ts +22 -0
  5. package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.js +100 -0
  6. package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.d.ts +1 -0
  7. package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.js +13 -0
  8. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +27 -0
  9. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +118 -0
  10. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.d.ts +1 -0
  11. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +16 -0
  12. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +60 -0
  13. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +331 -0
  14. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +1 -0
  15. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +42 -0
  16. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +4 -4
  17. package/dist/benchmark/unity-lazy-context-sampler.d.ts +6 -0
  18. package/dist/benchmark/unity-lazy-context-sampler.js +49 -13
  19. package/dist/benchmark/unity-lazy-context-sampler.test.js +4 -0
  20. package/dist/cli/ai-context.js +6 -1
  21. package/dist/cli/eval-server.js +0 -3
  22. package/dist/cli/index.js +8 -0
  23. package/dist/cli/mcp.js +0 -3
  24. package/dist/cli/rule-lab.d.ts +42 -0
  25. package/dist/cli/rule-lab.js +157 -0
  26. package/dist/cli/rule-lab.test.d.ts +1 -0
  27. package/dist/cli/rule-lab.test.js +11 -0
  28. package/dist/cli/tool.d.ts +7 -1
  29. package/dist/cli/tool.js +6 -0
  30. package/dist/core/config/unity-config.d.ts +20 -0
  31. package/dist/core/config/unity-config.js +46 -0
  32. package/dist/core/graph/types.d.ts +1 -1
  33. package/dist/core/ingestion/pipeline.js +38 -13
  34. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +0 -2
  35. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +26 -213
  36. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +1 -1
  37. package/dist/core/ingestion/unity-resource-processor.js +87 -22
  38. package/dist/core/ingestion/unity-resource-processor.test.js +67 -2
  39. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +11 -0
  40. package/dist/core/ingestion/unity-runtime-binding-rules.js +179 -0
  41. package/dist/core/unity/options.d.ts +4 -0
  42. package/dist/core/unity/options.js +18 -0
  43. package/dist/core/unity/options.test.js +11 -1
  44. package/dist/core/unity/resolver.js +11 -1
  45. package/dist/core/unity/resolver.test.js +62 -0
  46. package/dist/core/unity/yaml-object-graph.js +1 -1
  47. package/dist/core/unity/yaml-object-graph.test.js +16 -0
  48. package/dist/mcp/local/derived-process-reader.d.ts +2 -0
  49. package/dist/mcp/local/derived-process-reader.js +15 -0
  50. package/dist/mcp/local/local-backend.d.ts +56 -0
  51. package/dist/mcp/local/local-backend.js +1003 -53
  52. package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
  53. package/dist/mcp/local/process-confidence.js +1 -1
  54. package/dist/mcp/local/process-evidence.d.ts +1 -0
  55. package/dist/mcp/local/process-evidence.js +22 -0
  56. package/dist/mcp/local/process-evidence.test.js +11 -1
  57. package/dist/mcp/local/process-ref.d.ts +24 -0
  58. package/dist/mcp/local/process-ref.js +33 -0
  59. package/dist/mcp/local/process-ref.test.d.ts +1 -0
  60. package/dist/mcp/local/process-ref.test.js +24 -0
  61. package/dist/mcp/local/runtime-chain-verify.d.ts +15 -1
  62. package/dist/mcp/local/runtime-chain-verify.js +191 -187
  63. package/dist/mcp/local/runtime-chain-verify.test.js +546 -19
  64. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +63 -0
  65. package/dist/mcp/local/runtime-claim-rule-registry.js +308 -0
  66. package/dist/mcp/local/runtime-claim-rule-registry.test.d.ts +1 -0
  67. package/dist/mcp/local/runtime-claim-rule-registry.test.js +215 -0
  68. package/dist/mcp/local/runtime-claim.d.ts +38 -0
  69. package/dist/mcp/local/runtime-claim.js +54 -0
  70. package/dist/mcp/local/runtime-claim.test.d.ts +1 -0
  71. package/dist/mcp/local/runtime-claim.test.js +27 -0
  72. package/dist/mcp/local/unity-enrichment.d.ts +1 -0
  73. package/dist/mcp/local/unity-enrichment.js +1 -1
  74. package/dist/mcp/local/unity-evidence-view.d.ts +26 -0
  75. package/dist/mcp/local/unity-evidence-view.js +96 -0
  76. package/dist/mcp/local/unity-evidence-view.test.d.ts +1 -0
  77. package/dist/mcp/local/unity-evidence-view.test.js +39 -0
  78. package/dist/mcp/local/unity-lazy-hydrator.d.ts +2 -2
  79. package/dist/mcp/local/unity-lazy-hydrator.js +3 -3
  80. package/dist/mcp/local/unity-lazy-hydrator.test.js +4 -4
  81. package/dist/mcp/local/unity-parity-cache.js +2 -6
  82. package/dist/mcp/local/unity-parity-seed-loader.d.ts +1 -0
  83. package/dist/mcp/local/unity-parity-seed-loader.js +10 -16
  84. package/dist/mcp/local/unity-parity-seed-loader.test.js +3 -12
  85. package/dist/mcp/local/unity-runtime-hydration.d.ts +3 -2
  86. package/dist/mcp/local/unity-runtime-hydration.js +13 -16
  87. package/dist/mcp/local/unity-runtime-hydration.test.js +15 -1
  88. package/dist/mcp/resources.js +13 -0
  89. package/dist/mcp/tools.js +166 -13
  90. package/dist/rule-lab/analyze.d.ts +12 -0
  91. package/dist/rule-lab/analyze.js +90 -0
  92. package/dist/rule-lab/analyze.test.d.ts +1 -0
  93. package/dist/rule-lab/analyze.test.js +28 -0
  94. package/dist/rule-lab/compile.d.ts +5 -0
  95. package/dist/rule-lab/compile.js +51 -0
  96. package/dist/rule-lab/compiled-bundles.d.ts +30 -0
  97. package/dist/rule-lab/compiled-bundles.js +36 -0
  98. package/dist/rule-lab/curate.d.ts +32 -0
  99. package/dist/rule-lab/curate.js +134 -0
  100. package/dist/rule-lab/curate.test.d.ts +1 -0
  101. package/dist/rule-lab/curate.test.js +72 -0
  102. package/dist/rule-lab/discover.d.ts +13 -0
  103. package/dist/rule-lab/discover.js +74 -0
  104. package/dist/rule-lab/discover.test.d.ts +1 -0
  105. package/dist/rule-lab/discover.test.js +42 -0
  106. package/dist/rule-lab/paths.d.ts +21 -0
  107. package/dist/rule-lab/paths.js +37 -0
  108. package/dist/rule-lab/paths.test.d.ts +1 -0
  109. package/dist/rule-lab/paths.test.js +46 -0
  110. package/dist/rule-lab/promote.d.ts +26 -0
  111. package/dist/rule-lab/promote.js +314 -0
  112. package/dist/rule-lab/promote.test.d.ts +1 -0
  113. package/dist/rule-lab/promote.test.js +164 -0
  114. package/dist/rule-lab/regress.d.ts +60 -0
  115. package/dist/rule-lab/regress.js +122 -0
  116. package/dist/rule-lab/regress.test.d.ts +1 -0
  117. package/dist/rule-lab/regress.test.js +68 -0
  118. package/dist/rule-lab/review-pack.d.ts +31 -0
  119. package/dist/rule-lab/review-pack.js +125 -0
  120. package/dist/rule-lab/review-pack.test.d.ts +1 -0
  121. package/dist/rule-lab/review-pack.test.js +49 -0
  122. package/dist/rule-lab/types.d.ts +99 -0
  123. package/dist/rule-lab/types.js +1 -0
  124. package/package.json +1 -1
  125. package/skills/_shared/unity-hydration-contract.md +11 -0
  126. package/skills/_shared/unity-ui-trace-contract.md +33 -0
  127. package/skills/gitnexus-cli.md +14 -25
  128. package/skills/gitnexus-guide.md +2 -0
  129. package/skills/gitnexus-unity-rule-gen.md +318 -0
  130. package/dist/core/ingestion/unity-lifecycle-config.d.ts +0 -5
  131. package/dist/core/ingestion/unity-lifecycle-config.js +0 -25
  132. package/dist/mcp/local/unity-lazy-config.d.ts +0 -6
  133. package/dist/mcp/local/unity-lazy-config.js +0 -7
  134. package/dist/mcp/local/unity-lazy-config.test.js +0 -9
  135. package/dist/mcp/local/unity-process-confidence-config.d.ts +0 -1
  136. package/dist/mcp/local/unity-process-confidence-config.js +0 -4
  137. package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +0 -1
  138. package/dist/mcp/local/unity-runtime-chain-verify-config.js +0 -10
  139. /package/dist/{mcp/local/unity-lazy-config.test.d.ts → benchmark/u2-e2e/hydration-policy-repeatability-runner.test.d.ts} +0 -0
@@ -12,33 +12,13 @@ const LIFECYCLE_CALLBACKS = new Set([
12
12
  'OnDisable',
13
13
  'OnDestroy',
14
14
  ]);
15
- const RUNTIME_LOADER_ANCHORS = new Set([
16
- 'Equip',
17
- 'EquipWithEvent',
18
- 'RegisterGraphEvents',
19
- 'RegisterEvents',
20
- 'StartRoutineWithEvents',
21
- 'GetValue',
22
- 'CheckReload',
23
- 'ReloadRoutine',
24
- ]);
25
- const DETERMINISTIC_LOADER_BRIDGES = [
26
- ['Equip', 'EquipWithEvent'],
27
- ['EquipWithEvent', 'RegisterEvents'],
28
- ['RegisterGraphEvents', 'RegisterEvents'],
29
- ['RegisterEvents', 'StartRoutineWithEvents'],
30
- ['StartRoutineWithEvents', 'GetValue'],
31
- ['GetValue', 'CheckReload'],
32
- ['CheckReload', 'ReloadRoutine'],
33
- ];
34
15
  const SYNTHETIC_RUNTIME_ROOT_NAME = 'unity-runtime-root';
35
16
  const SYNTHETIC_RUNTIME_ROOT_ID = generateId('Method', SYNTHETIC_RUNTIME_ROOT_NAME);
36
17
  export const DEFAULT_UNITY_LIFECYCLE_SYNTHETIC_CONFIG = {
37
- enabled: false,
18
+ enabled: true,
38
19
  maxSyntheticEdgesPerClass: 12,
39
20
  maxSyntheticEdgesTotal: 256,
40
21
  lifecycleEdgeConfidence: 0.72,
41
- loaderEdgeConfidence: 0.68,
42
22
  };
43
23
  export const detectUnityLifecycleHosts = (graph) => {
44
24
  const methodsByClass = new Map();
@@ -79,12 +59,10 @@ export const detectUnityLifecycleHosts = (graph) => {
79
59
  continue;
80
60
  const methods = methodsByClass.get(node.id) ?? [];
81
61
  const lifecycleCallbacks = methods.filter((method) => LIFECYCLE_CALLBACKS.has(method.properties.name));
82
- const loaderAnchors = methods.filter((method) => RUNTIME_LOADER_ANCHORS.has(method.properties.name));
83
62
  hosts.push({
84
63
  classNode: node,
85
64
  baseType,
86
65
  lifecycleCallbacks: sortMethodsByName(lifecycleCallbacks),
87
- loaderAnchors: sortMethodsByName(loaderAnchors),
88
66
  methods: sortMethodsByName(methods),
89
67
  });
90
68
  }
@@ -95,15 +73,6 @@ export const applyUnityLifecycleSyntheticCalls = (graph, config = {}) => {
95
73
  ...DEFAULT_UNITY_LIFECYCLE_SYNTHETIC_CONFIG,
96
74
  ...config,
97
75
  };
98
- if (!cfg.enabled) {
99
- return {
100
- syntheticEdgeCount: 0,
101
- lifecycleEdgeCount: 0,
102
- loaderEdgeCount: 0,
103
- hostCount: 0,
104
- rejectedHostCount: 0,
105
- };
106
- }
107
76
  const hosts = detectUnityLifecycleHosts(graph);
108
77
  const acceptedHosts = [];
109
78
  let rejectedHostCount = 0;
@@ -114,27 +83,11 @@ export const applyUnityLifecycleSyntheticCalls = (graph, config = {}) => {
114
83
  }
115
84
  acceptedHosts.push(host);
116
85
  }
117
- acceptedHosts.sort((left, right) => scoreUnityHost(right) - scoreUnityHost(left));
86
+ acceptedHosts.sort((left, right) => right.lifecycleCallbacks.length - left.lifecycleCallbacks.length);
118
87
  if (acceptedHosts.length === 0) {
119
- return {
120
- syntheticEdgeCount: 0,
121
- lifecycleEdgeCount: 0,
122
- loaderEdgeCount: 0,
123
- hostCount: 0,
124
- rejectedHostCount,
125
- };
88
+ return { syntheticEdgeCount: 0, lifecycleEdgeCount: 0, loaderEdgeCount: 0, hostCount: 0, rejectedHostCount };
126
89
  }
127
90
  ensureRuntimeRootNode(graph);
128
- const hostMethodToClassId = new Map();
129
- const methodsByName = new Map();
130
- for (const host of acceptedHosts) {
131
- for (const method of host.methods) {
132
- hostMethodToClassId.set(method.id, host.classNode.id);
133
- const list = methodsByName.get(method.properties.name) ?? [];
134
- list.push(method);
135
- methodsByName.set(method.properties.name, list);
136
- }
137
- }
138
91
  const existingPairs = new Set();
139
92
  for (const rel of graph.iterRelationships()) {
140
93
  if (rel.type === 'CALLS')
@@ -143,89 +96,35 @@ export const applyUnityLifecycleSyntheticCalls = (graph, config = {}) => {
143
96
  const syntheticEdgesPerClass = new Map();
144
97
  let syntheticEdgeCount = 0;
145
98
  let lifecycleEdgeCount = 0;
146
- let loaderEdgeCount = 0;
147
- const reservedBridgeBudget = Math.max(1, Math.floor(cfg.maxSyntheticEdgesTotal * 0.5));
148
- const preBridgeBudget = Math.max(0, cfg.maxSyntheticEdgesTotal - reservedBridgeBudget);
149
- const canAllocate = (classId, phase = 'pre_bridge') => {
150
- if (syntheticEdgeCount >= cfg.maxSyntheticEdgesTotal)
151
- return false;
152
- if (phase === 'pre_bridge' && syntheticEdgeCount >= preBridgeBudget)
153
- return false;
154
- const classCount = syntheticEdgesPerClass.get(classId) ?? 0;
155
- return classCount < cfg.maxSyntheticEdgesPerClass;
156
- };
157
- const addSyntheticEdge = (sourceId, targetId, reason, confidence, classId, phase = 'pre_bridge') => {
158
- if (sourceId === targetId)
159
- return false;
160
- if (!canAllocate(classId, phase))
161
- return false;
162
- if (PLACEHOLDER_RE.test(sourceId) || PLACEHOLDER_RE.test(targetId))
163
- return false;
164
- const pairKey = `${sourceId}->${targetId}`;
165
- if (existingPairs.has(pairKey))
166
- return false;
167
- graph.addRelationship({
168
- id: generateId('CALLS', `${sourceId}->${targetId}:${reason}`),
169
- sourceId,
170
- targetId,
171
- type: 'CALLS',
172
- confidence,
173
- reason,
174
- });
175
- existingPairs.add(pairKey);
176
- syntheticEdgeCount += 1;
177
- syntheticEdgesPerClass.set(classId, (syntheticEdgesPerClass.get(classId) ?? 0) + 1);
178
- if (reason === 'unity-lifecycle-synthetic')
179
- lifecycleEdgeCount += 1;
180
- if (reason === 'unity-runtime-loader-synthetic')
181
- loaderEdgeCount += 1;
182
- return true;
183
- };
184
99
  for (const host of acceptedHosts) {
185
100
  const classId = host.classNode.id;
186
101
  for (const callback of host.lifecycleCallbacks) {
187
- if (!canAllocate(classId))
102
+ const classCount = syntheticEdgesPerClass.get(classId) ?? 0;
103
+ if (syntheticEdgeCount >= cfg.maxSyntheticEdgesTotal)
188
104
  break;
189
- addSyntheticEdge(SYNTHETIC_RUNTIME_ROOT_ID, callback.id, 'unity-lifecycle-synthetic', cfg.lifecycleEdgeConfidence, classId, 'pre_bridge');
190
- }
191
- if (!canAllocate(classId))
192
- continue;
193
- for (const callback of host.lifecycleCallbacks) {
194
- if (!canAllocate(classId))
105
+ if (classCount >= cfg.maxSyntheticEdgesPerClass)
195
106
  break;
196
- for (const loader of host.loaderAnchors) {
197
- if (!canAllocate(classId))
198
- break;
199
- addSyntheticEdge(callback.id, loader.id, 'unity-runtime-loader-synthetic', cfg.loaderEdgeConfidence, classId, 'pre_bridge');
200
- }
201
- }
202
- }
203
- for (const [sourceName, targetName] of DETERMINISTIC_LOADER_BRIDGES) {
204
- const sourceMethods = [...(methodsByName.get(sourceName) ?? [])].sort((left, right) => scoreRuntimeLoaderMethod(right) - scoreRuntimeLoaderMethod(left));
205
- const targetMethods = [...(methodsByName.get(targetName) ?? [])].sort((left, right) => scoreRuntimeLoaderMethod(right) - scoreRuntimeLoaderMethod(left));
206
- if (sourceMethods.length === 0 || targetMethods.length === 0)
207
- continue;
208
- const topSource = sourceMethods[0];
209
- const topTarget = targetMethods[0];
210
- const topSourceClassId = topSource ? hostMethodToClassId.get(topSource.id) : undefined;
211
- if (topSource && topTarget && topSourceClassId) {
212
- addSyntheticEdge(topSource.id, topTarget.id, 'unity-runtime-loader-synthetic', cfg.loaderEdgeConfidence, topSourceClassId, 'bridge');
213
- }
214
- for (const sourceMethod of sourceMethods) {
215
- const classId = hostMethodToClassId.get(sourceMethod.id);
216
- if (!classId || !canAllocate(classId, 'bridge'))
107
+ const pairKey = `${SYNTHETIC_RUNTIME_ROOT_ID}->${callback.id}`;
108
+ if (existingPairs.has(pairKey))
217
109
  continue;
218
- for (const targetMethod of targetMethods) {
219
- if (!canAllocate(classId, 'bridge'))
220
- break;
221
- addSyntheticEdge(sourceMethod.id, targetMethod.id, 'unity-runtime-loader-synthetic', cfg.loaderEdgeConfidence, classId, 'bridge');
222
- }
110
+ graph.addRelationship({
111
+ id: generateId('CALLS', `${SYNTHETIC_RUNTIME_ROOT_ID}->${callback.id}:unity-lifecycle-synthetic`),
112
+ sourceId: SYNTHETIC_RUNTIME_ROOT_ID,
113
+ targetId: callback.id,
114
+ type: 'CALLS',
115
+ confidence: cfg.lifecycleEdgeConfidence,
116
+ reason: 'unity-lifecycle-synthetic',
117
+ });
118
+ existingPairs.add(pairKey);
119
+ syntheticEdgeCount += 1;
120
+ lifecycleEdgeCount += 1;
121
+ syntheticEdgesPerClass.set(classId, classCount + 1);
223
122
  }
224
123
  }
225
124
  return {
226
125
  syntheticEdgeCount,
227
126
  lifecycleEdgeCount,
228
- loaderEdgeCount,
127
+ loaderEdgeCount: 0,
229
128
  hostCount: acceptedHosts.length,
230
129
  rejectedHostCount,
231
130
  runtimeRootNodeId: syntheticEdgeCount > 0 ? SYNTHETIC_RUNTIME_ROOT_ID : undefined,
@@ -237,11 +136,7 @@ const resolveUnityBaseType = (graph, extendsByClass, classIdsByName, classId, vi
237
136
  visited.add(classId);
238
137
  for (const edge of extendsByClass.get(classId) ?? []) {
239
138
  const targetNode = graph.getNode(edge.targetId);
240
- const candidates = [
241
- targetNode?.properties?.name ?? '',
242
- edge.targetId,
243
- edge.reason,
244
- ];
139
+ const candidates = [targetNode?.properties?.name ?? '', edge.targetId, edge.reason];
245
140
  for (const candidate of candidates) {
246
141
  const normalized = normalizeBaseType(candidate);
247
142
  if (normalized)
@@ -264,12 +159,10 @@ const normalizeBaseType = (value) => {
264
159
  const text = String(value || '').trim();
265
160
  if (!text)
266
161
  return undefined;
267
- if (text.endsWith(UNITY_MONOBEHAVIOUR) || text.includes(`.${UNITY_MONOBEHAVIOUR}`)) {
162
+ if (text.endsWith(UNITY_MONOBEHAVIOUR) || text.includes(`.${UNITY_MONOBEHAVIOUR}`))
268
163
  return UNITY_MONOBEHAVIOUR;
269
- }
270
- if (text.endsWith(UNITY_SCRIPTABLE_OBJECT) || text.includes(`.${UNITY_SCRIPTABLE_OBJECT}`)) {
164
+ if (text.endsWith(UNITY_SCRIPTABLE_OBJECT) || text.includes(`.${UNITY_SCRIPTABLE_OBJECT}`))
271
165
  return UNITY_SCRIPTABLE_OBJECT;
272
- }
273
166
  return undefined;
274
167
  };
275
168
  const ensureRuntimeRootNode = (graph) => {
@@ -278,10 +171,7 @@ const ensureRuntimeRootNode = (graph) => {
278
171
  graph.addNode({
279
172
  id: SYNTHETIC_RUNTIME_ROOT_ID,
280
173
  label: 'Method',
281
- properties: {
282
- name: SYNTHETIC_RUNTIME_ROOT_NAME,
283
- filePath: '',
284
- },
174
+ properties: { name: SYNTHETIC_RUNTIME_ROOT_NAME, filePath: '' },
285
175
  });
286
176
  };
287
177
  const isPlaceholderHost = (host) => {
@@ -299,86 +189,9 @@ const sortMethodsByName = (methods) => [...methods].sort((left, right) => {
299
189
  return byName;
300
190
  return left.id.localeCompare(right.id);
301
191
  });
302
- const scoreUnityHost = (host) => {
303
- const filePath = String(host.classNode.properties.filePath || '');
304
- let score = 0;
305
- score += host.loaderAnchors.length * 40;
306
- score += host.lifecycleCallbacks.length * 8;
307
- if (filePath.includes('Assets/NEON/Code/Game/'))
308
- score += 200;
309
- else if (filePath.includes('Assets/NEON/Code/'))
310
- score += 120;
311
- else if (filePath.includes('Assets/'))
312
- score += 40;
313
- if (filePath.includes('/Graph/'))
314
- score += 60;
315
- if (filePath.includes('/PowerUps/'))
316
- score += 40;
317
- if (filePath.includes('/Core/'))
318
- score += 30;
319
- if (filePath.includes('/Reload'))
320
- score += 30;
321
- if (filePath.includes('/Packages/'))
322
- score -= 120;
323
- if (filePath.includes('/Legacy/'))
324
- score -= 80;
325
- if (filePath.startsWith('Assets/Scripts/'))
326
- score -= 60;
327
- for (const method of host.loaderAnchors) {
328
- const methodName = String(method.properties.name || '');
329
- if (methodName === 'RegisterGraphEvents' || methodName === 'RegisterEvents')
330
- score += 25;
331
- if (methodName === 'StartRoutineWithEvents')
332
- score += 25;
333
- if (methodName === 'GetValue' || methodName === 'CheckReload' || methodName === 'ReloadRoutine')
334
- score += 20;
335
- if (methodName === 'Equip' || methodName === 'EquipWithEvent')
336
- score += 15;
337
- }
338
- return score;
339
- };
340
- const scoreRuntimeLoaderMethod = (method) => {
341
- const filePath = String(method.properties.filePath || '');
342
- const methodName = String(method.properties.name || '');
343
- let score = 0;
344
- if (filePath.includes('GunGraphMB'))
345
- score += 200;
346
- if (filePath.includes('/Graph/Graphs/GunGraph.cs'))
347
- score += 220;
348
- if (filePath.includes('/Graph/Nodes/Reloads/'))
349
- score += 240;
350
- if (filePath.includes('/PowerUps/WeaponPowerUp.cs'))
351
- score += 180;
352
- if (filePath.includes('/Game/Core/'))
353
- score += 80;
354
- if (filePath.includes('/Game/Graph/'))
355
- score += 120;
356
- if (filePath.includes('/Packages/'))
357
- score -= 100;
358
- if (filePath.includes('/Legacy/'))
359
- score -= 80;
360
- if (filePath.includes('/MonoScript/Minion'))
361
- score -= 60;
362
- if (methodName === 'RegisterGraphEvents')
363
- score += 50;
364
- if (methodName === 'RegisterEvents')
365
- score += 60;
366
- if (methodName === 'StartRoutineWithEvents')
367
- score += 70;
368
- if (methodName === 'GetValue')
369
- score += 90;
370
- if (methodName === 'CheckReload')
371
- score += 90;
372
- if (methodName === 'ReloadRoutine')
373
- score += 90;
374
- if (methodName === 'Equip' || methodName === 'EquipWithEvent')
375
- score += 50;
376
- return score;
377
- };
378
192
  const resolveNamedClassTargets = (classIdsByName, rawTargetId) => {
379
193
  const text = String(rawTargetId || '').trim();
380
194
  if (!text.startsWith('Class:'))
381
195
  return [];
382
- const className = text.slice('Class:'.length);
383
- return classIdsByName.get(className) ?? [];
196
+ return classIdsByName.get(text.slice('Class:'.length)) ?? [];
384
197
  };
@@ -243,7 +243,7 @@ test('detects Unity hosts through transitive inheritance chains', () => {
243
243
  const gunGraphHost = hosts.find((host) => host.classNode.id === gunGraphId);
244
244
  assert.ok(gunGraphHost);
245
245
  assert.equal(gunGraphHost.baseType, 'ScriptableObject');
246
- assert.deepEqual(gunGraphHost.loaderAnchors.map((method) => method.properties.name), ['RegisterEvents', 'StartRoutineWithEvents']);
246
+ assert.ok(gunGraphHost.methods.length > 0);
247
247
  });
248
248
  test('prioritizes gameplay lifecycle hosts when synthetic edge budget is tight', () => {
249
249
  const graph = createKnowledgeGraph();
@@ -3,6 +3,7 @@ import { generateId } from '../../lib/utils.js';
3
3
  import { buildUnityScanContext } from '../unity/scan-context.js';
4
4
  import { resolveUnityBindings } from '../unity/resolver.js';
5
5
  import { buildUnityParitySeed } from './unity-parity-seed.js';
6
+ import { resolveUnityConfig } from '../config/unity-config.js';
6
7
  const UNITY_DIAGNOSTIC_SAMPLE_LIMIT = 3;
7
8
  export async function processUnityResources(graph, options, deps) {
8
9
  const tStart = performance.now();
@@ -116,6 +117,53 @@ export async function processUnityResources(graph, options, deps) {
116
117
  };
117
118
  for (const binding of resolved.resourceBindings) {
118
119
  bindingCount += 1;
120
+ componentCount += 1;
121
+ const resourceFileId = ensureResourceFileNode(graph, binding.resourcePath);
122
+ const componentPayload = buildUnityPayload(binding, payloadMode);
123
+ graph.addRelationship({
124
+ id: generateId('UNITY_COMPONENT_INSTANCE', `${classNode.id}->${resourceFileId}:${binding.componentObjectId}`),
125
+ type: 'UNITY_COMPONENT_INSTANCE',
126
+ sourceId: classNode.id,
127
+ targetId: resourceFileId,
128
+ confidence: 1.0,
129
+ reason: JSON.stringify(componentPayload),
130
+ });
131
+ graph.addRelationship({
132
+ id: generateId('UNITY_GRAPH_NODE_SCRIPT_REF', `${resourceFileId}->${classNode.id}`),
133
+ type: 'UNITY_GRAPH_NODE_SCRIPT_REF',
134
+ sourceId: resourceFileId,
135
+ targetId: classNode.id,
136
+ confidence: 1.0,
137
+ reason: JSON.stringify({
138
+ resourcePath: normalizePath(binding.resourcePath),
139
+ resourceType: binding.resourceType,
140
+ bindingKind: binding.bindingKind,
141
+ componentObjectId: binding.componentObjectId,
142
+ }),
143
+ });
144
+ for (const ref of binding.resolvedReferences || []) {
145
+ const targetAssetPath = normalizePath(String(ref.target?.assetPath || '').trim());
146
+ const referenceGuid = String(ref.guid || '').trim();
147
+ if (!targetAssetPath || !referenceGuid)
148
+ continue;
149
+ const sourceFileId = ensureResourceFileNode(graph, binding.resourcePath);
150
+ const targetFileId = ensureResourceFileNode(graph, targetAssetPath);
151
+ graph.addRelationship({
152
+ id: generateId('UNITY_ASSET_GUID_REF', `${sourceFileId}->${targetFileId}:${ref.fieldName}:${referenceGuid}:${String(ref.fileId || '')}`),
153
+ type: 'UNITY_ASSET_GUID_REF',
154
+ sourceId: sourceFileId,
155
+ targetId: targetFileId,
156
+ confidence: 1.0,
157
+ reason: JSON.stringify({
158
+ resourcePath: normalizePath(binding.resourcePath),
159
+ targetResourcePath: targetAssetPath,
160
+ guid: referenceGuid.toLowerCase(),
161
+ fileId: String(ref.fileId || ''),
162
+ fieldName: ref.fieldName,
163
+ sourceLayer: ref.sourceLayer || 'unknown',
164
+ }),
165
+ });
166
+ }
119
167
  appendSummary(classNode.id, binding);
120
168
  const serializableTypeLinking = collectSerializableTypeTargetsForBinding(symbol, binding, scanContext, canonicalClassNodeBySymbol);
121
169
  serializedTypeEdgeCount += serializableTypeLinking.edgeCount;
@@ -123,8 +171,22 @@ export async function processUnityResources(graph, options, deps) {
123
171
  for (const hitSymbol of serializableTypeLinking.symbols) {
124
172
  serializedTypeSymbols.add(hitSymbol);
125
173
  }
126
- for (const targetClassId of serializableTypeLinking.targetClassIds) {
127
- appendSummary(targetClassId, binding);
174
+ for (const link of serializableTypeLinking.links) {
175
+ appendSummary(link.targetClassId, binding);
176
+ graph.addRelationship({
177
+ id: generateId('UNITY_SERIALIZED_TYPE_IN', `${classNode.id}->${link.targetClassId}:${normalizePath(binding.resourcePath)}:${link.fieldName}`),
178
+ type: 'UNITY_SERIALIZED_TYPE_IN',
179
+ sourceId: classNode.id,
180
+ targetId: link.targetClassId,
181
+ confidence: 1.0,
182
+ reason: JSON.stringify({
183
+ hostSymbol: symbol,
184
+ declaredType: link.declaredType,
185
+ fieldName: link.fieldName,
186
+ sourceLayer: link.sourceLayer,
187
+ resourcePath: normalizePath(binding.resourcePath),
188
+ }),
189
+ });
128
190
  }
129
191
  }
130
192
  for (const [sourceNodeId, perPath] of summaryBySource.entries()) {
@@ -205,24 +267,7 @@ function normalizePath(filePath) {
205
267
  function resolveUnityPayloadMode(explicit) {
206
268
  if (explicit)
207
269
  return explicit;
208
- const envMode = String(process.env.GITNEXUS_UNITY_PAYLOAD_MODE || '').trim().toLowerCase();
209
- if (envMode === 'full')
210
- return 'full';
211
- return 'compact';
212
- }
213
- function createComponentNode(symbol, binding, payloadMode) {
214
- const payload = buildUnityPayload(binding, payloadMode);
215
- return {
216
- id: generateId('CodeElement', `${binding.resourcePath}:${binding.componentObjectId}`),
217
- label: 'CodeElement',
218
- properties: {
219
- name: `${symbol}@${binding.componentObjectId}`,
220
- filePath: binding.resourcePath,
221
- startLine: binding.evidence.line,
222
- endLine: binding.evidence.line,
223
- description: JSON.stringify(payload),
224
- },
225
- };
270
+ return resolveUnityConfig().config.payloadMode ?? 'compact';
226
271
  }
227
272
  function buildUnityPayload(binding, mode) {
228
273
  const payload = {
@@ -285,7 +330,7 @@ function collectSerializableTypeTargetsForBinding(hostSymbol, binding, scanConte
285
330
  edgeCount: 0,
286
331
  missCount: 0,
287
332
  symbols: new Set(),
288
- targetClassIds: new Set(),
333
+ links: [],
289
334
  };
290
335
  if (!scanContext)
291
336
  return stats;
@@ -310,7 +355,12 @@ function collectSerializableTypeTargetsForBinding(hostSymbol, binding, scanConte
310
355
  stats.missCount += 1;
311
356
  continue;
312
357
  }
313
- stats.targetClassIds.add(serializableNode.id);
358
+ stats.links.push({
359
+ targetClassId: serializableNode.id,
360
+ fieldName,
361
+ declaredType,
362
+ sourceLayer,
363
+ });
314
364
  stats.edgeCount += 1;
315
365
  stats.symbols.add(declaredType);
316
366
  }
@@ -330,6 +380,21 @@ function collectBindingFieldSources(binding) {
330
380
  }
331
381
  return fieldSources;
332
382
  }
383
+ function ensureResourceFileNode(graph, resourcePath) {
384
+ const normalizedPath = normalizePath(resourcePath);
385
+ const fileId = generateId('File', normalizedPath);
386
+ if (!graph.getNode(fileId)) {
387
+ graph.addNode({
388
+ id: fileId,
389
+ label: 'File',
390
+ properties: {
391
+ name: normalizedPath.split('/').pop() || normalizedPath,
392
+ filePath: normalizedPath,
393
+ },
394
+ });
395
+ }
396
+ return fileId;
397
+ }
333
398
  function collectResourceSummaryRows(bindings) {
334
399
  const summaryByPath = new Map();
335
400
  for (const binding of bindings) {
@@ -32,7 +32,7 @@ const listRelativeFixtureFiles = async (root) => {
32
32
  }
33
33
  return out;
34
34
  };
35
- test('processUnityResources does not emit UNITY_COMPONENT_IN or synthetic resource File nodes', async () => {
35
+ test('processUnityResources emits schema-compatible component-instance edges and materialized resource file nodes', async () => {
36
36
  const graph = createKnowledgeGraph();
37
37
  for (const symbol of symbols) {
38
38
  const filePath = `Assets/Scripts/${symbol}.cs`;
@@ -65,12 +65,14 @@ test('processUnityResources does not emit UNITY_COMPONENT_IN or synthetic resour
65
65
  }
66
66
  const result = await processUnityResources(graph, { repoPath: fixtureRoot });
67
67
  const unityFileRelations = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_COMPONENT_IN');
68
+ const unityComponentInstanceRelations = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_COMPONENT_INSTANCE');
68
69
  const unitySummaryRelations = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_RESOURCE_SUMMARY');
69
70
  const syntheticResourceFiles = [...graph.iterNodes()].filter((node) => node.label === 'File' && /\.(prefab|unity|asset)$/.test(String(node.properties.filePath)));
70
71
  const componentNodes = [...graph.iterNodes()].filter((node) => node.label === 'CodeElement');
71
72
  assert.equal(result.bindingCount > 0, true);
72
73
  assert.equal(unityFileRelations.length, 0);
73
- assert.equal(syntheticResourceFiles.length, 0);
74
+ assert.ok(unityComponentInstanceRelations.length > 0);
75
+ assert.ok(syntheticResourceFiles.length > 0);
74
76
  assert.ok(unitySummaryRelations.length > 0);
75
77
  assert.equal(componentNodes.length, 0);
76
78
  assert.ok(result.bindingCount >= symbols.length);
@@ -452,6 +454,69 @@ test('processUnityResources writes UNITY_RESOURCE_SUMMARY for serializable class
452
454
  const summaryRelations = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_RESOURCE_SUMMARY');
453
455
  const serializableSummary = summaryRelations.filter((rel) => rel.sourceId === serializableClassId);
454
456
  assert.equal(serializableSummary.length, 1);
457
+ const serializedTypeRelations = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_SERIALIZED_TYPE_IN');
458
+ assert.equal(serializedTypeRelations.length, 1);
459
+ const reason = JSON.parse(String(serializedTypeRelations[0]?.reason || '{}'));
460
+ assert.equal(reason.fieldName, 'assetRef');
461
+ assert.equal(reason.declaredType, 'AssetRef');
462
+ assert.equal(reason.hostSymbol, 'HostClass');
463
+ });
464
+ test('processUnityResources writes asset-guid and graph-node reference edges from resolved references', async () => {
465
+ const graph = createKnowledgeGraph();
466
+ const hostPath = 'Assets/Scripts/WeaponConfig.cs';
467
+ const classId = generateId('Class', `${hostPath}:WeaponConfig`);
468
+ graph.addNode({
469
+ id: classId,
470
+ label: 'Class',
471
+ properties: { name: 'WeaponConfig', filePath: hostPath },
472
+ });
473
+ const fakeScanContext = {
474
+ symbolToScriptPath: new Map([['WeaponConfig', hostPath]]),
475
+ scriptPathToGuid: new Map([[hostPath, '11111111111111111111111111111111']]),
476
+ guidToResourceHits: new Map([
477
+ ['11111111111111111111111111111111', [{ resourcePath: 'Assets/Data/WeaponConfig.asset', resourceType: 'asset', line: 3, lineText: 'guid: 1111' }]],
478
+ ]),
479
+ resourceDocCache: new Map(),
480
+ };
481
+ await processUnityResources(graph, { repoPath: fixtureRoot }, {
482
+ buildScanContext: async () => fakeScanContext,
483
+ resolveBindings: async () => ({
484
+ symbol: 'WeaponConfig',
485
+ scriptPath: hostPath,
486
+ scriptGuid: '11111111111111111111111111111111',
487
+ resourceBindings: [
488
+ {
489
+ resourcePath: 'Assets/Data/WeaponConfig.asset',
490
+ resourceType: 'asset',
491
+ bindingKind: 'direct',
492
+ componentObjectId: '11400000',
493
+ evidence: { line: 3, lineText: 'guid: 1111' },
494
+ serializedFields: { scalarFields: [], referenceFields: [] },
495
+ resolvedReferences: [
496
+ {
497
+ fieldName: 'gungraph',
498
+ sourceLayer: 'asset',
499
+ fileId: '11400000',
500
+ guid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
501
+ fromList: false,
502
+ resolution: 'external-asset',
503
+ target: { assetPath: 'Assets/Graphs/Weapon.asset' },
504
+ },
505
+ ],
506
+ },
507
+ ],
508
+ serializedFields: { scalarFields: [], referenceFields: [] },
509
+ unityDiagnostics: [],
510
+ }),
511
+ });
512
+ const graphNodeRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_GRAPH_NODE_SCRIPT_REF');
513
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
514
+ assert.equal(graphNodeRefs.length, 1);
515
+ assert.equal(guidRefs.length, 1);
516
+ const guidReason = JSON.parse(String(guidRefs[0]?.reason || '{}'));
517
+ assert.equal(guidReason.guid, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
518
+ assert.equal(guidReason.targetResourcePath, 'Assets/Graphs/Weapon.asset');
519
+ assert.equal(guidReason.fieldName, 'gungraph');
455
520
  });
456
521
  test('processUnityResources writes compact UNITY_RESOURCE_SUMMARY reason by default', async () => {
457
522
  const graph = createKnowledgeGraph();
@@ -0,0 +1,11 @@
1
+ import type { KnowledgeGraph } from '../graph/types.js';
2
+ import type { RuntimeClaimRule } from '../../mcp/local/runtime-claim-rule-registry.js';
3
+ import type { UnityConfig } from '../config/unity-config.js';
4
+ export interface UnityRuntimeBindingResult {
5
+ edgesInjected: number;
6
+ ruleResults: Array<{
7
+ ruleId: string;
8
+ edgesInjected: number;
9
+ }>;
10
+ }
11
+ export declare function applyUnityRuntimeBindingRules(graph: KnowledgeGraph, rules: RuntimeClaimRule[], _config: UnityConfig): UnityRuntimeBindingResult;