@veewo/gitnexus 1.5.0-rc.4 → 1.5.1

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 (175) hide show
  1. package/dist/benchmark/agent-context/runner.js +3 -0
  2. package/dist/benchmark/agent-context/runner.test.js +22 -0
  3. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
  4. package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
  5. package/dist/benchmark/agent-safe-query-context/io.js +86 -0
  6. package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
  7. package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
  8. package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
  9. package/dist/benchmark/agent-safe-query-context/report.js +159 -0
  10. package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
  11. package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
  12. package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
  13. package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
  14. package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
  15. package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
  16. package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
  17. package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
  18. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
  19. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
  20. package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
  21. package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
  22. package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
  23. package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
  24. package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
  25. package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
  26. package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
  27. package/dist/benchmark/agent-safe-query-context/types.js +8 -0
  28. package/dist/benchmark/analyze-runner.d.ts +1 -1
  29. package/dist/benchmark/analyze-runner.js +4 -3
  30. package/dist/benchmark/analyze-runner.test.js +7 -0
  31. package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
  32. package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
  33. package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
  34. package/dist/benchmark/runtime-poc/runner.js +163 -0
  35. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
  36. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
  37. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
  38. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
  39. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
  40. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
  41. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
  42. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
  43. package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
  44. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
  45. package/dist/cli/ai-context.d.ts +0 -1
  46. package/dist/cli/ai-context.js +5 -6
  47. package/dist/cli/ai-context.test.js +8 -0
  48. package/dist/cli/analyze-options.js +58 -34
  49. package/dist/cli/analyze-options.test.js +57 -0
  50. package/dist/cli/analyze-runtime-summary.js +2 -0
  51. package/dist/cli/analyze-runtime-summary.test.js +12 -0
  52. package/dist/cli/analyze-summary.d.ts +4 -0
  53. package/dist/cli/analyze-summary.js +43 -0
  54. package/dist/cli/analyze-summary.test.js +65 -1
  55. package/dist/cli/analyze.d.ts +11 -0
  56. package/dist/cli/analyze.js +34 -5
  57. package/dist/cli/analyze.test.d.ts +1 -0
  58. package/dist/cli/analyze.test.js +25 -0
  59. package/dist/cli/benchmark-agent-context.js +1 -1
  60. package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
  61. package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
  62. package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
  63. package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
  64. package/dist/cli/benchmark-unity.js +1 -1
  65. package/dist/cli/benchmark-unity.test.js +5 -1
  66. package/dist/cli/benchmark.d.ts +29 -0
  67. package/dist/cli/benchmark.js +55 -0
  68. package/dist/cli/index.js +27 -2
  69. package/dist/cli/rule-lab.d.ts +3 -7
  70. package/dist/cli/rule-lab.js +13 -22
  71. package/dist/cli/rule-lab.test.js +23 -3
  72. package/dist/cli/scope-manifest-config.d.ts +9 -0
  73. package/dist/cli/scope-manifest-config.js +37 -0
  74. package/dist/cli/setup.js +40 -41
  75. package/dist/cli/setup.test.js +14 -14
  76. package/dist/cli/sync-manifest.d.ts +27 -0
  77. package/dist/cli/sync-manifest.js +200 -0
  78. package/dist/cli/sync-manifest.test.d.ts +1 -0
  79. package/dist/cli/sync-manifest.test.js +88 -0
  80. package/dist/cli/tool.d.ts +2 -0
  81. package/dist/cli/tool.js +2 -0
  82. package/dist/core/config/unity-config.d.ts +1 -1
  83. package/dist/core/config/unity-config.js +1 -1
  84. package/dist/core/ingestion/call-processor.d.ts +2 -1
  85. package/dist/core/ingestion/call-processor.js +28 -6
  86. package/dist/core/ingestion/heritage-processor.d.ts +2 -1
  87. package/dist/core/ingestion/heritage-processor.js +30 -7
  88. package/dist/core/ingestion/import-processor.d.ts +2 -1
  89. package/dist/core/ingestion/import-processor.js +28 -6
  90. package/dist/core/ingestion/parsing-processor.d.ts +5 -3
  91. package/dist/core/ingestion/parsing-processor.js +46 -13
  92. package/dist/core/ingestion/pipeline.js +100 -19
  93. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
  94. package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
  95. package/dist/core/ingestion/unity-parity-seed.js +8 -0
  96. package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
  97. package/dist/core/ingestion/unity-resource-processor.js +102 -0
  98. package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
  99. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +16 -1
  100. package/dist/core/ingestion/unity-runtime-binding-rules.js +193 -42
  101. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
  102. package/dist/core/ingestion/workers/parse-worker.js +50 -6
  103. package/dist/core/lbug/csv-generator.test.js +2 -2
  104. package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
  105. package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
  106. package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
  107. package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
  108. package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
  109. package/dist/core/tree-sitter/parser-loader.js +19 -0
  110. package/dist/core/unity/doc-contract.test.d.ts +1 -0
  111. package/dist/core/unity/doc-contract.test.js +30 -0
  112. package/dist/core/unity/prefab-source-scan.d.ts +25 -0
  113. package/dist/core/unity/prefab-source-scan.js +152 -0
  114. package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
  115. package/dist/core/unity/prefab-source-scan.test.js +70 -0
  116. package/dist/core/unity/scan-context.d.ts +12 -0
  117. package/dist/core/unity/scan-context.js +50 -2
  118. package/dist/core/unity/scan-context.test.js +74 -0
  119. package/dist/mcp/local/agent-safe-response.d.ts +10 -0
  120. package/dist/mcp/local/agent-safe-response.js +639 -0
  121. package/dist/mcp/local/derived-process-reader.js +1 -1
  122. package/dist/mcp/local/local-backend.d.ts +18 -1
  123. package/dist/mcp/local/local-backend.js +319 -125
  124. package/dist/mcp/local/process-confidence.d.ts +1 -2
  125. package/dist/mcp/local/process-confidence.js +0 -3
  126. package/dist/mcp/local/process-confidence.test.js +4 -2
  127. package/dist/mcp/local/process-evidence.d.ts +1 -8
  128. package/dist/mcp/local/process-evidence.js +1 -23
  129. package/dist/mcp/local/process-evidence.test.js +2 -16
  130. package/dist/mcp/local/process-ref.d.ts +1 -1
  131. package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
  132. package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
  133. package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
  134. package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
  135. package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
  136. package/dist/mcp/local/runtime-chain-verify.js +149 -138
  137. package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
  138. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
  139. package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
  140. package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
  141. package/dist/mcp/local/runtime-claim.d.ts +11 -0
  142. package/dist/mcp/local/runtime-claim.js +28 -0
  143. package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
  144. package/dist/mcp/local/unity-evidence-view.js +1 -1
  145. package/dist/mcp/local/unity-evidence-view.test.js +22 -0
  146. package/dist/mcp/tools.js +51 -21
  147. package/dist/rule-lab/analyze.d.ts +2 -1
  148. package/dist/rule-lab/analyze.js +94 -59
  149. package/dist/rule-lab/analyze.test.js +238 -20
  150. package/dist/rule-lab/curate.d.ts +2 -1
  151. package/dist/rule-lab/curate.js +24 -3
  152. package/dist/rule-lab/curate.test.js +65 -0
  153. package/dist/rule-lab/curation-input-builder.d.ts +45 -0
  154. package/dist/rule-lab/curation-input-builder.js +133 -0
  155. package/dist/rule-lab/promote.js +80 -7
  156. package/dist/rule-lab/promote.test.js +150 -0
  157. package/dist/rule-lab/review-pack.d.ts +3 -0
  158. package/dist/rule-lab/review-pack.js +41 -1
  159. package/dist/rule-lab/review-pack.test.js +67 -0
  160. package/dist/rule-lab/types.d.ts +29 -0
  161. package/dist/types/pipeline.d.ts +16 -0
  162. package/package.json +14 -13
  163. package/scripts/check-sync-manifest-traceability.mjs +203 -0
  164. package/scripts/run-node-tests.mjs +61 -0
  165. package/scripts/tree-sitter-audit-classify.mjs +172 -0
  166. package/skills/_shared/unity-rule-authoring-contract.md +64 -0
  167. package/skills/_shared/unity-runtime-process-contract.md +16 -0
  168. package/skills/gitnexus-cli.md +44 -4
  169. package/skills/gitnexus-debugging.md +9 -0
  170. package/skills/gitnexus-exploring.md +66 -18
  171. package/skills/gitnexus-guide.md +42 -3
  172. package/skills/gitnexus-impact-analysis.md +8 -0
  173. package/skills/gitnexus-pr-review.md +8 -0
  174. package/skills/gitnexus-refactoring.md +8 -0
  175. package/skills/gitnexus-unity-rule-gen.md +66 -312
@@ -7,6 +7,8 @@ import { fileURLToPath } from 'node:url';
7
7
  import { generateId } from '../../lib/utils.js';
8
8
  import { createKnowledgeGraph } from '../graph/graph.js';
9
9
  import { closeLbug, executeQuery, initLbug, loadGraphToLbug } from '../lbug/lbug-adapter.js';
10
+ import { buildUnityScanContext } from '../unity/scan-context.js';
11
+ import { streamPrefabSourceRefs } from '../unity/prefab-source-scan.js';
10
12
  import { processUnityResources } from './unity-resource-processor.js';
11
13
  const here = path.dirname(fileURLToPath(import.meta.url));
12
14
  const fixtureRoot = path.resolve(here, '../../../src/core/unity/__fixtures__/mini-unity');
@@ -82,6 +84,49 @@ test('processUnityResources emits schema-compatible component-instance edges and
82
84
  assert.ok(result.timingsMs.resolve >= 0);
83
85
  assert.ok(result.timingsMs.graphWrite > 0);
84
86
  });
87
+ test('fixture run emits scene->prefab UNITY_ASSET_GUID_REF and keeps script ref edges', async () => {
88
+ const graph = createKnowledgeGraph();
89
+ for (const symbol of symbols) {
90
+ const filePath = `Assets/Scripts/${symbol}.cs`;
91
+ const fileId = generateId('File', filePath);
92
+ const classId = generateId('Class', `${filePath}:${symbol}`);
93
+ graph.addNode({
94
+ id: fileId,
95
+ label: 'File',
96
+ properties: {
97
+ name: `${symbol}.cs`,
98
+ filePath,
99
+ },
100
+ });
101
+ graph.addNode({
102
+ id: classId,
103
+ label: 'Class',
104
+ properties: {
105
+ name: symbol,
106
+ filePath,
107
+ },
108
+ });
109
+ graph.addRelationship({
110
+ id: generateId('DEFINES', `${fileId}->${classId}`),
111
+ type: 'DEFINES',
112
+ sourceId: fileId,
113
+ targetId: classId,
114
+ confidence: 1.0,
115
+ reason: '',
116
+ });
117
+ }
118
+ await processUnityResources(graph, { repoPath: fixtureRoot });
119
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
120
+ const scenePrefabRef = guidRefs.find((rel) => {
121
+ const reason = JSON.parse(String(rel.reason || '{}'));
122
+ return (reason.resourcePath === 'Assets/Scene/MainUIManager.unity'
123
+ && reason.targetResourcePath === 'Assets/Prefabs/BattleMode.prefab'
124
+ && reason.fieldName === 'm_SourcePrefab');
125
+ });
126
+ assert.ok(scenePrefabRef);
127
+ const scriptRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_GRAPH_NODE_SCRIPT_REF');
128
+ assert.ok(scriptRefs.length > 0);
129
+ });
85
130
  test('processUnityResources persists UNITY_RESOURCE_SUMMARY in LadybugDB', async () => {
86
131
  const graph = createKnowledgeGraph();
87
132
  for (const symbol of symbols) {
@@ -518,6 +563,410 @@ test('processUnityResources writes asset-guid and graph-node reference edges fro
518
563
  assert.equal(guidReason.targetResourcePath, 'Assets/Graphs/Weapon.asset');
519
564
  assert.equal(guidReason.fieldName, 'gungraph');
520
565
  });
566
+ test('processUnityResources emits prefab-source edges from scanContext.prefabSourceRefs only', async () => {
567
+ const graph = createKnowledgeGraph();
568
+ const fakeScanContext = {
569
+ symbolToScriptPath: new Map(),
570
+ scriptPathToGuid: new Map(),
571
+ guidToResourceHits: new Map(),
572
+ prefabSourceRefs: [
573
+ {
574
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
575
+ targetGuid: '99999999999999999999999999999999',
576
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
577
+ fileId: '100100000',
578
+ fieldName: 'm_SourcePrefab',
579
+ sourceLayer: 'scene',
580
+ },
581
+ ],
582
+ resourceDocCache: new Map(),
583
+ };
584
+ const result = await processUnityResources(graph, { repoPath: fixtureRoot }, {
585
+ buildScanContext: async () => fakeScanContext,
586
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
587
+ });
588
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
589
+ assert.equal(guidRefs.length, 1);
590
+ const reason = JSON.parse(String(guidRefs[0]?.reason || '{}'));
591
+ assert.equal(reason.fieldName, 'm_SourcePrefab');
592
+ assert.equal(reason.sourceLayer, 'scene');
593
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab-source: emitted=1')));
594
+ });
595
+ test('processUnityResources consumes prefab-source stream incrementally and emits edges', async () => {
596
+ const graph = createKnowledgeGraph();
597
+ const fakeScanContext = {
598
+ symbolToScriptPath: new Map(),
599
+ scriptPathToGuid: new Map(),
600
+ guidToResourceHits: new Map(),
601
+ assetGuidToPath: new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]),
602
+ streamPrefabSourceRefs: async function* () {
603
+ yield {
604
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
605
+ targetGuid: '99999999999999999999999999999999',
606
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
607
+ fileId: '100100000',
608
+ fieldName: 'm_SourcePrefab',
609
+ sourceLayer: 'scene',
610
+ };
611
+ await new Promise((resolve) => setTimeout(resolve, 1));
612
+ yield {
613
+ sourceResourcePath: 'Assets/Scene/Global.unity',
614
+ targetGuid: '99999999999999999999999999999999',
615
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
616
+ fileId: '100100001',
617
+ fieldName: 'm_SourcePrefab',
618
+ sourceLayer: 'scene',
619
+ };
620
+ },
621
+ prefabSourceRefs: [],
622
+ resourceDocCache: new Map(),
623
+ };
624
+ const result = await processUnityResources(graph, { repoPath: fixtureRoot }, {
625
+ buildScanContext: async () => fakeScanContext,
626
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
627
+ });
628
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab-source: emitted=2')));
629
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.rows_emitted=2')));
630
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.rows_parsed=')));
631
+ });
632
+ test('prefab-source accounting invariant closes', async () => {
633
+ const graph = createKnowledgeGraph();
634
+ const fakeScanContext = {
635
+ symbolToScriptPath: new Map(),
636
+ scriptPathToGuid: new Map(),
637
+ guidToResourceHits: new Map(),
638
+ assetGuidToPath: new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]),
639
+ streamPrefabSourceRefs: async function* () {
640
+ yield {
641
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
642
+ targetGuid: '99999999999999999999999999999999',
643
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
644
+ fileId: '100100000',
645
+ fieldName: 'm_SourcePrefab',
646
+ sourceLayer: 'scene',
647
+ };
648
+ yield {
649
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
650
+ targetGuid: '00000000000000000000000000000000',
651
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
652
+ fileId: '100100001',
653
+ fieldName: 'm_SourcePrefab',
654
+ sourceLayer: 'scene',
655
+ };
656
+ yield {
657
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
658
+ targetGuid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
659
+ targetResourcePath: '__PLACEHOLDER__',
660
+ fileId: '100100002',
661
+ fieldName: 'm_SourcePrefab',
662
+ sourceLayer: 'scene',
663
+ };
664
+ yield {
665
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
666
+ targetGuid: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
667
+ targetResourcePath: '',
668
+ fileId: '100100003',
669
+ fieldName: 'm_SourcePrefab',
670
+ sourceLayer: 'scene',
671
+ };
672
+ yield {
673
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
674
+ targetGuid: '99999999999999999999999999999999',
675
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
676
+ fileId: '100100000',
677
+ fieldName: 'm_SourcePrefab',
678
+ sourceLayer: 'scene',
679
+ };
680
+ },
681
+ prefabSourceRefs: [],
682
+ resourceDocCache: new Map(),
683
+ };
684
+ const result = await processUnityResources(graph, { repoPath: fixtureRoot }, {
685
+ buildScanContext: async () => fakeScanContext,
686
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
687
+ });
688
+ assert.equal(result.prefabSourceStats.rowsParsed, result.prefabSourceStats.rowsFilteredZeroGuid
689
+ + result.prefabSourceStats.rowsFilteredPlaceholder
690
+ + result.prefabSourceStats.rowsFilteredUnresolved
691
+ + result.prefabSourceStats.rowsDeduped
692
+ + result.prefabSourceStats.rowsEmitted);
693
+ });
694
+ test('no cross-signal dedupe collision when same source has script-guid and prefab-source refs', async () => {
695
+ const graph = createKnowledgeGraph();
696
+ const filePath = 'Assets/Scripts/MainUIManager.cs';
697
+ const classId = generateId('Class', `${filePath}:MainUIManager`);
698
+ graph.addNode({
699
+ id: classId,
700
+ label: 'Class',
701
+ properties: { name: 'MainUIManager', filePath },
702
+ });
703
+ const fakeScanContext = {
704
+ symbolToScriptPath: new Map([['MainUIManager', filePath]]),
705
+ scriptPathToGuid: new Map([[filePath, 'dddddddddddddddddddddddddddddddd']]),
706
+ guidToResourceHits: new Map([
707
+ ['dddddddddddddddddddddddddddddddd', [{ resourcePath: 'Assets/Scene/MainUIManager.unity', resourceType: 'scene', line: 1, lineText: 'guid' }]],
708
+ ]),
709
+ assetGuidToPath: new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]),
710
+ streamPrefabSourceRefs: async function* () {
711
+ yield {
712
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
713
+ targetGuid: '99999999999999999999999999999999',
714
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
715
+ fileId: '100100000',
716
+ fieldName: 'm_SourcePrefab',
717
+ sourceLayer: 'scene',
718
+ };
719
+ },
720
+ prefabSourceRefs: [],
721
+ resourceDocCache: new Map(),
722
+ };
723
+ await processUnityResources(graph, { repoPath: fixtureRoot }, {
724
+ buildScanContext: async () => fakeScanContext,
725
+ resolveBindings: async () => ({
726
+ symbol: 'MainUIManager',
727
+ scriptPath: filePath,
728
+ scriptGuid: 'dddddddddddddddddddddddddddddddd',
729
+ resourceBindings: [
730
+ {
731
+ resourcePath: 'Assets/Scene/MainUIManager.unity',
732
+ resourceType: 'scene',
733
+ bindingKind: 'direct',
734
+ componentObjectId: '11400000',
735
+ evidence: { line: 1, lineText: 'guid' },
736
+ serializedFields: { scalarFields: [], referenceFields: [] },
737
+ resolvedReferences: [
738
+ {
739
+ fieldName: 'gungraph',
740
+ sourceLayer: 'scene',
741
+ fileId: '11400000',
742
+ guid: '99999999999999999999999999999999',
743
+ fromList: false,
744
+ resolution: 'external-asset',
745
+ target: { assetPath: 'Assets/Prefabs/BattleMode.prefab' },
746
+ },
747
+ ],
748
+ },
749
+ ],
750
+ serializedFields: { scalarFields: [], referenceFields: [] },
751
+ unityDiagnostics: [],
752
+ }),
753
+ });
754
+ const refs = [...graph.iterRelationships()]
755
+ .filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF')
756
+ .map((rel) => JSON.parse(String(rel.reason || '{}')));
757
+ assert.ok(refs.some((reason) => reason.fieldName === 'm_SourcePrefab'));
758
+ assert.ok(refs.some((reason) => reason.fieldName === 'gungraph'));
759
+ });
760
+ test('scan-context does not write graph edges directly; processor remains sole writer', async () => {
761
+ const graph = createKnowledgeGraph();
762
+ const context = await buildUnityScanContext({ repoRoot: fixtureRoot });
763
+ assert.equal([...graph.iterRelationships()].length, 0);
764
+ await processUnityResources(graph, { repoPath: fixtureRoot }, {
765
+ buildScanContext: async () => context,
766
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
767
+ });
768
+ assert.ok([...graph.iterRelationships()].some((rel) => rel.type === 'UNITY_ASSET_GUID_REF'));
769
+ });
770
+ test('prefab source pass can be disabled via env toggle', async () => {
771
+ const graph = createKnowledgeGraph();
772
+ const fakeScanContext = {
773
+ symbolToScriptPath: new Map(),
774
+ scriptPathToGuid: new Map(),
775
+ guidToResourceHits: new Map(),
776
+ prefabSourceRefs: [
777
+ {
778
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
779
+ targetGuid: '99999999999999999999999999999999',
780
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
781
+ fileId: '100100000',
782
+ fieldName: 'm_SourcePrefab',
783
+ sourceLayer: 'scene',
784
+ },
785
+ ],
786
+ resourceDocCache: new Map(),
787
+ };
788
+ const originalValue = process.env.GITNEXUS_DISABLE_PREFAB_SOURCE_PASS;
789
+ process.env.GITNEXUS_DISABLE_PREFAB_SOURCE_PASS = '1';
790
+ try {
791
+ const result = await processUnityResources(graph, { repoPath: fixtureRoot }, {
792
+ buildScanContext: async () => fakeScanContext,
793
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
794
+ });
795
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
796
+ assert.equal(guidRefs.length, 0);
797
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab-source: skipped')));
798
+ }
799
+ finally {
800
+ if (typeof originalValue === 'undefined') {
801
+ delete process.env.GITNEXUS_DISABLE_PREFAB_SOURCE_PASS;
802
+ }
803
+ else {
804
+ process.env.GITNEXUS_DISABLE_PREFAB_SOURCE_PASS = originalValue;
805
+ }
806
+ }
807
+ });
808
+ test('prefab nested source dedupes duplicate PrefabInstance rows', async () => {
809
+ const graph = createKnowledgeGraph();
810
+ const fakeScanContext = {
811
+ symbolToScriptPath: new Map(),
812
+ scriptPathToGuid: new Map(),
813
+ guidToResourceHits: new Map(),
814
+ prefabSourceRefs: [
815
+ {
816
+ sourceResourcePath: 'Assets/Prefabs/BattleMode.prefab',
817
+ targetGuid: '99999999999999999999999999999999',
818
+ targetResourcePath: 'Assets/Prefabs/Nested.prefab',
819
+ fileId: '100100000',
820
+ fieldName: 'm_SourcePrefab',
821
+ sourceLayer: 'prefab',
822
+ },
823
+ {
824
+ sourceResourcePath: 'Assets/Prefabs/BattleMode.prefab',
825
+ targetGuid: '99999999999999999999999999999999',
826
+ targetResourcePath: 'Assets/Prefabs/Nested.prefab',
827
+ fileId: '100100000',
828
+ fieldName: 'm_SourcePrefab',
829
+ sourceLayer: 'prefab',
830
+ },
831
+ ],
832
+ resourceDocCache: new Map(),
833
+ };
834
+ await processUnityResources(graph, { repoPath: fixtureRoot }, {
835
+ buildScanContext: async () => fakeScanContext,
836
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
837
+ });
838
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
839
+ assert.equal(guidRefs.length, 1);
840
+ const reason = JSON.parse(String(guidRefs[0]?.reason || '{}'));
841
+ assert.equal(reason.guid, '99999999999999999999999999999999');
842
+ });
843
+ test('drops placeholder unresolved and zero-guid prefab-source rows and reports filtered counters', async () => {
844
+ const graph = createKnowledgeGraph();
845
+ const fakeScanContext = {
846
+ symbolToScriptPath: new Map(),
847
+ scriptPathToGuid: new Map(),
848
+ guidToResourceHits: new Map(),
849
+ prefabSourceRefs: [
850
+ {
851
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
852
+ targetGuid: '00000000000000000000000000000000',
853
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
854
+ fileId: '1',
855
+ fieldName: 'm_SourcePrefab',
856
+ sourceLayer: 'scene',
857
+ },
858
+ {
859
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
860
+ targetGuid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
861
+ targetResourcePath: '__PLACEHOLDER__',
862
+ fileId: '2',
863
+ fieldName: 'm_SourcePrefab',
864
+ sourceLayer: 'scene',
865
+ },
866
+ {
867
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
868
+ targetGuid: 'cccccccccccccccccccccccccccccccc',
869
+ targetResourcePath: '',
870
+ fileId: '3',
871
+ fieldName: 'm_SourcePrefab',
872
+ sourceLayer: 'scene',
873
+ },
874
+ {
875
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
876
+ targetGuid: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
877
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
878
+ fileId: '4',
879
+ fieldName: 'm_SourcePrefab',
880
+ sourceLayer: 'scene',
881
+ },
882
+ ],
883
+ resourceDocCache: new Map(),
884
+ };
885
+ const result = await processUnityResources(graph, { repoPath: fixtureRoot }, {
886
+ buildScanContext: async () => fakeScanContext,
887
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
888
+ });
889
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
890
+ assert.equal(guidRefs.length, 1);
891
+ const reason = JSON.parse(String(guidRefs[0]?.reason || '{}'));
892
+ assert.equal(reason.targetResourcePath, 'Assets/Prefabs/BattleMode.prefab');
893
+ assert.notEqual(reason.guid, '00000000000000000000000000000000');
894
+ assert.ok(result.prefabSourceStats.rowsFilteredZeroGuid > 0);
895
+ assert.ok(result.prefabSourceStats.rowsFilteredPlaceholder > 0);
896
+ assert.ok(result.prefabSourceStats.rowsFilteredUnresolved > 0);
897
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.rows_filtered_zero_guid=')));
898
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.rows_filtered_placeholder=')));
899
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.rows_filtered_unresolved=')));
900
+ });
901
+ test('per-file scan failure is isolated and does not abort subsequent file emission', async () => {
902
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-prefab-source-file-error-'));
903
+ const graph = createKnowledgeGraph();
904
+ try {
905
+ const sceneDir = path.join(tempRoot, 'Assets/Scene');
906
+ const prefabDir = path.join(tempRoot, 'Assets/Prefabs');
907
+ await fs.mkdir(path.join(sceneDir, 'Broken.unity'), { recursive: true });
908
+ await fs.mkdir(prefabDir, { recursive: true });
909
+ await fs.writeFile(path.join(sceneDir, 'Good.unity'), [
910
+ '--- !u!1001 &100100000',
911
+ 'PrefabInstance:',
912
+ ' m_SourcePrefab: {fileID: 100100000, guid: 99999999999999999999999999999999, type: 3}',
913
+ ].join('\n'), 'utf-8');
914
+ await fs.writeFile(path.join(prefabDir, 'BattleMode.prefab'), '--- !u!1 &1\nGameObject:\n', 'utf-8');
915
+ const fakeScanContext = {
916
+ symbolToScriptPath: new Map(),
917
+ scriptPathToGuid: new Map(),
918
+ guidToResourceHits: new Map(),
919
+ assetGuidToPath: new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]),
920
+ streamPrefabSourceRefs: (options) => streamPrefabSourceRefs({
921
+ repoRoot: tempRoot,
922
+ resourceFiles: ['Assets/Scene/Broken.unity', 'Assets/Scene/Good.unity'],
923
+ assetGuidToPath: new Map([['99999999999999999999999999999999', 'Assets/Prefabs/BattleMode.prefab']]),
924
+ hooks: options?.hooks,
925
+ }),
926
+ prefabSourceRefs: [],
927
+ resourceDocCache: new Map(),
928
+ };
929
+ const result = await processUnityResources(graph, { repoPath: tempRoot }, {
930
+ buildScanContext: async () => fakeScanContext,
931
+ resolveBindings: async () => ({ resourceBindings: [], unityDiagnostics: [] }),
932
+ });
933
+ assert.ok(result.prefabSourceStats.rowsEmitted > 0);
934
+ assert.ok(result.diagnostics.some((line) => line.includes('prefab_source.file_errors=1')));
935
+ }
936
+ finally {
937
+ await fs.rm(tempRoot, { recursive: true, force: true });
938
+ }
939
+ });
940
+ test('extracts prefab source refs without class binding resolve', async () => {
941
+ const graph = createKnowledgeGraph();
942
+ let resolveBindingsCallCount = 0;
943
+ const fakeScanContext = {
944
+ symbolToScriptPath: new Map(),
945
+ scriptPathToGuid: new Map(),
946
+ guidToResourceHits: new Map(),
947
+ prefabSourceRefs: [
948
+ {
949
+ sourceResourcePath: 'Assets/Scene/MainUIManager.unity',
950
+ targetGuid: '99999999999999999999999999999999',
951
+ targetResourcePath: 'Assets/Prefabs/BattleMode.prefab',
952
+ fileId: '100100000',
953
+ fieldName: 'm_SourcePrefab',
954
+ sourceLayer: 'scene',
955
+ },
956
+ ],
957
+ resourceDocCache: new Map(),
958
+ };
959
+ await processUnityResources(graph, { repoPath: fixtureRoot }, {
960
+ buildScanContext: async () => fakeScanContext,
961
+ resolveBindings: async () => {
962
+ resolveBindingsCallCount += 1;
963
+ return ({ resourceBindings: [], unityDiagnostics: [] });
964
+ },
965
+ });
966
+ const guidRefs = [...graph.iterRelationships()].filter((rel) => rel.type === 'UNITY_ASSET_GUID_REF');
967
+ assert.equal(resolveBindingsCallCount, 0);
968
+ assert.equal(guidRefs.length, 1);
969
+ });
521
970
  test('processUnityResources writes compact UNITY_RESOURCE_SUMMARY reason by default', async () => {
522
971
  const graph = createKnowledgeGraph();
523
972
  const classId = generateId('Class', 'Assets/Scripts/Compact.cs:CompactSymbol');
@@ -7,5 +7,20 @@ export interface UnityRuntimeBindingResult {
7
7
  ruleId: string;
8
8
  edgesInjected: number;
9
9
  }>;
10
+ diagnostics: UnityRuntimeBindingDiagnostics;
10
11
  }
11
- export declare function applyUnityRuntimeBindingRules(graph: KnowledgeGraph, rules: RuntimeClaimRule[], _config: UnityConfig): UnityRuntimeBindingResult;
12
+ export interface UnityRuntimeBindingDiagnostics {
13
+ rulesEvaluated: number;
14
+ bindingsEvaluated: number;
15
+ bindingsByKind: Record<string, number>;
16
+ methodLookupCalls: number;
17
+ methodLookupCacheHits: number;
18
+ sceneRuntimeTraversalCalls: number;
19
+ sceneRuntimeTraversalCacheHits: number;
20
+ sceneRuntimeResourcesVisited: number;
21
+ anomalies: string[];
22
+ shouldAgentReport: boolean;
23
+ agentReportReason: string;
24
+ summary: string[];
25
+ }
26
+ export declare function applyUnityRuntimeBindingRules(graph: KnowledgeGraph, rules: RuntimeClaimRule[], config: UnityConfig): UnityRuntimeBindingResult;