@veewo/gitnexus 1.4.10-rc → 1.4.11-rc

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 (84) hide show
  1. package/dist/benchmark/u2-e2e/live-evidence-validator.d.ts +19 -0
  2. package/dist/benchmark/u2-e2e/live-evidence-validator.js +87 -0
  3. package/dist/benchmark/u2-e2e/live-evidence-validator.test.d.ts +1 -0
  4. package/dist/benchmark/u2-e2e/live-evidence-validator.test.js +33 -0
  5. package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +23 -4
  6. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.d.ts +38 -0
  7. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +206 -0
  8. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.d.ts +1 -0
  9. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.js +72 -0
  10. package/dist/benchmark/u2-e2e/report.d.ts +1 -0
  11. package/dist/benchmark/u2-e2e/report.js +2 -0
  12. package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +34 -0
  13. package/dist/benchmark/u2-e2e/retrieval-runner.js +95 -5
  14. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +161 -2
  15. package/dist/cli/ai-context.js +31 -1
  16. package/dist/cli/ai-context.test.js +10 -0
  17. package/dist/cli/analyze-summary.d.ts +1 -0
  18. package/dist/cli/analyze-summary.js +21 -0
  19. package/dist/cli/analyze-summary.test.js +7 -1
  20. package/dist/cli/analyze.js +3 -10
  21. package/dist/cli/eval-server.js +1 -1
  22. package/dist/cli/index.js +2 -0
  23. package/dist/cli/setup.js +9 -0
  24. package/dist/cli/setup.test.js +2 -0
  25. package/dist/cli/tool.d.ts +2 -0
  26. package/dist/cli/tool.js +2 -0
  27. package/dist/core/ingestion/pipeline.js +24 -3
  28. package/dist/core/ingestion/process-processor.d.ts +6 -0
  29. package/dist/core/ingestion/process-processor.js +188 -7
  30. package/dist/core/ingestion/unity-lifecycle-config.d.ts +5 -0
  31. package/dist/core/ingestion/unity-lifecycle-config.js +25 -0
  32. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +26 -0
  33. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +384 -0
  34. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.d.ts +1 -0
  35. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +541 -0
  36. package/dist/core/ingestion/unity-resource-processor.test.js +81 -0
  37. package/dist/core/lbug/csv-generator.js +11 -1
  38. package/dist/core/lbug/fallback-relationship-replay.d.ts +21 -0
  39. package/dist/core/lbug/fallback-relationship-replay.js +39 -0
  40. package/dist/core/lbug/fallback-relationship-replay.test.d.ts +1 -0
  41. package/dist/core/lbug/fallback-relationship-replay.test.js +25 -0
  42. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  43. package/dist/core/lbug/lbug-adapter.js +22 -23
  44. package/dist/core/lbug/schema.d.ts +2 -2
  45. package/dist/core/lbug/schema.js +9 -0
  46. package/dist/core/lbug/schema.test.js +1 -0
  47. package/dist/mcp/local/local-backend.d.ts +1 -1
  48. package/dist/mcp/local/local-backend.js +339 -50
  49. package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
  50. package/dist/mcp/local/process-confidence.d.ts +19 -0
  51. package/dist/mcp/local/process-confidence.js +29 -0
  52. package/dist/mcp/local/process-confidence.test.d.ts +1 -0
  53. package/dist/mcp/local/process-confidence.test.js +36 -0
  54. package/dist/mcp/local/process-evidence.d.ts +28 -0
  55. package/dist/mcp/local/process-evidence.js +65 -0
  56. package/dist/mcp/local/process-evidence.test.d.ts +1 -0
  57. package/dist/mcp/local/process-evidence.test.js +56 -0
  58. package/dist/mcp/local/runtime-chain-evidence.d.ts +7 -0
  59. package/dist/mcp/local/runtime-chain-evidence.js +13 -0
  60. package/dist/mcp/local/runtime-chain-evidence.test.d.ts +1 -0
  61. package/dist/mcp/local/runtime-chain-evidence.test.js +24 -0
  62. package/dist/mcp/local/runtime-chain-verify.d.ts +37 -0
  63. package/dist/mcp/local/runtime-chain-verify.js +221 -0
  64. package/dist/mcp/local/runtime-chain-verify.test.d.ts +1 -0
  65. package/dist/mcp/local/runtime-chain-verify.test.js +56 -0
  66. package/dist/mcp/local/unity-process-confidence-config.d.ts +1 -0
  67. package/dist/mcp/local/unity-process-confidence-config.js +4 -0
  68. package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +1 -0
  69. package/dist/mcp/local/unity-runtime-chain-verify-config.js +10 -0
  70. package/dist/mcp/local/unity-runtime-hydration.d.ts +50 -0
  71. package/dist/mcp/local/unity-runtime-hydration.js +323 -0
  72. package/dist/mcp/local/unity-runtime-hydration.test.d.ts +1 -0
  73. package/dist/mcp/local/unity-runtime-hydration.test.js +108 -0
  74. package/dist/mcp/resources.js +12 -2
  75. package/dist/mcp/tools.js +32 -0
  76. package/package.json +1 -1
  77. package/skills/_shared/unity-runtime-process-contract.md +38 -0
  78. package/skills/gitnexus-cli.md +16 -0
  79. package/skills/gitnexus-debugging.md +6 -0
  80. package/skills/gitnexus-exploring.md +6 -0
  81. package/skills/gitnexus-guide.md +4 -0
  82. package/skills/gitnexus-impact-analysis.md +6 -0
  83. package/skills/gitnexus-pr-review.md +5 -0
  84. package/skills/gitnexus-refactoring.md +4 -0
@@ -8,7 +8,14 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady } from '../core/lbug-adapter.js';
11
+ import { parseUnityHydrationMode, parseUnityResourcesMode } from '../../core/unity/options.js';
11
12
  import { runUnityUiTrace } from '../../core/unity/ui-trace.js';
13
+ import { loadUnityContext } from './unity-enrichment.js';
14
+ import { hydrateUnityForSymbol } from './unity-runtime-hydration.js';
15
+ import { mergeProcessEvidence } from './process-evidence.js';
16
+ import { resolveUnityProcessConfidenceFieldsEnabled } from './unity-process-confidence-config.js';
17
+ import { resolveUnityRuntimeChainVerifyEnabled } from './unity-runtime-chain-verify-config.js';
18
+ import { verifyRuntimeChainOnDemand, } from './runtime-chain-verify.js';
12
19
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
13
20
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
14
21
  // git utilities available if needed
@@ -74,6 +81,46 @@ function resolveQueryScopePreset(scopePreset) {
74
81
  }
75
82
  return undefined;
76
83
  }
84
+ function aggregateProcessConfidence(rows) {
85
+ if (rows.some((row) => row.process_confidence === 'high'))
86
+ return 'high';
87
+ if (rows.some((row) => row.process_confidence === 'medium'))
88
+ return 'medium';
89
+ return 'low';
90
+ }
91
+ function aggregateProcessEvidenceMode(rows) {
92
+ if (rows.some((row) => row.process_evidence_mode === 'direct_step'))
93
+ return 'direct_step';
94
+ if (rows.some((row) => row.process_evidence_mode === 'method_projected'))
95
+ return 'method_projected';
96
+ return 'resource_heuristic';
97
+ }
98
+ function selectVerificationHint(rows) {
99
+ return rows.find((row) => row.verification_hint)?.verification_hint;
100
+ }
101
+ function aggregateRuntimeChainEvidenceLevel(rows) {
102
+ if (rows.some((row) => row.runtime_chain_evidence_level === 'verified_chain'))
103
+ return 'verified_chain';
104
+ if (rows.some((row) => row.runtime_chain_evidence_level === 'verified_segment'))
105
+ return 'verified_segment';
106
+ if (rows.some((row) => row.runtime_chain_evidence_level === 'clue'))
107
+ return 'clue';
108
+ return 'none';
109
+ }
110
+ function confidenceRank(confidence) {
111
+ if (confidence === 'high')
112
+ return 3;
113
+ if (confidence === 'medium')
114
+ return 2;
115
+ return 1;
116
+ }
117
+ function evidenceModeRank(mode) {
118
+ if (mode === 'direct_step')
119
+ return 3;
120
+ if (mode === 'method_projected')
121
+ return 2;
122
+ return 1;
123
+ }
77
124
  export function filterBm25ResultsByScopePreset(rows, scopePreset) {
78
125
  const preset = resolveQueryScopePreset(scopePreset);
79
126
  if (!preset || preset === 'unity-all')
@@ -504,7 +551,19 @@ export class LocalBackend {
504
551
  const processLimit = params.limit || 5;
505
552
  const maxSymbolsPerProcess = params.max_symbols || 10;
506
553
  const includeContent = params.include_content ?? false;
554
+ const confidenceFieldsEnabled = resolveUnityProcessConfidenceFieldsEnabled(process.env);
555
+ let unityResourcesMode = 'off';
556
+ let unityHydrationMode = 'compact';
557
+ try {
558
+ unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
559
+ unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
560
+ }
561
+ catch (error) {
562
+ return { error: error instanceof Error ? error.message : String(error) };
563
+ }
507
564
  const searchQuery = params.query.trim();
565
+ const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
566
+ const runtimeChainVerifyEnabled = resolveUnityRuntimeChainVerifyEnabled(process.env);
508
567
  // Step 1: Run hybrid search to get matching symbols
509
568
  const searchLimit = processLimit * maxSymbolsPerProcess; // fetch enough raw results
510
569
  const [bm25Results, semanticResults] = await Promise.all([
@@ -554,17 +613,42 @@ export class LocalBackend {
554
613
  });
555
614
  continue;
556
615
  }
557
- // Find processes this symbol participates in
558
- let processRows = [];
616
+ // Find processes this symbol participates in (direct + method projection for class-like symbols).
617
+ let directProcessRows = [];
618
+ let projectedProcessRows = [];
559
619
  try {
560
- processRows = await executeParameterized(repo.id, `
620
+ directProcessRows = await executeParameterized(repo.id, `
561
621
  MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
562
- RETURN p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
622
+ RETURN p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, p.stepCount AS stepCount, r.step AS step
563
623
  `, { nodeId: sym.nodeId });
564
624
  }
565
625
  catch (e) {
566
626
  logQueryError('query:process-lookup', e);
567
627
  }
628
+ const symIdLower = String(sym.nodeId).toLowerCase();
629
+ const isClassLike = ['Class', 'Interface', 'Struct', 'Trait', 'Impl', 'Record'].includes(String(sym.type || ''))
630
+ || symIdLower.startsWith('class:')
631
+ || symIdLower.startsWith('interface:')
632
+ || symIdLower.startsWith('struct:')
633
+ || symIdLower.startsWith('trait:')
634
+ || symIdLower.startsWith('impl:')
635
+ || symIdLower.startsWith('record:');
636
+ if (isClassLike) {
637
+ try {
638
+ projectedProcessRows = await executeParameterized(repo.id, `
639
+ MATCH (n {id: $nodeId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)
640
+ MATCH (m)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
641
+ RETURN p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, p.stepCount AS stepCount, MIN(r.step) AS step
642
+ `, { nodeId: sym.nodeId });
643
+ }
644
+ catch (e) {
645
+ logQueryError('query:method-process-projection', e);
646
+ }
647
+ }
648
+ let processRows = mergeProcessEvidence({
649
+ directRows: directProcessRows,
650
+ projectedRows: isClassLike ? projectedProcessRows : [],
651
+ });
568
652
  // Get cluster membership + cohesion (cohesion used as internal ranking signal)
569
653
  let cohesion = 0;
570
654
  let module;
@@ -607,7 +691,57 @@ export class LocalBackend {
607
691
  endLine: sym.endLine,
608
692
  ...(module ? { module } : {}),
609
693
  ...(includeContent && content ? { content } : {}),
694
+ ...((unityResourcesMode !== 'off'
695
+ && sym.nodeId
696
+ && (sym.type === 'Class' || String(sym.nodeId).toLowerCase().startsWith('class:')))
697
+ ? await hydrateUnityForSymbol({
698
+ mode: unityHydrationMode,
699
+ basePayload: await loadUnityContext(repo.id, sym.nodeId, (query) => executeQuery(repo.id, query)),
700
+ deps: {
701
+ executeQuery: (query, queryParams) => {
702
+ if (queryParams && Object.keys(queryParams).length > 0) {
703
+ return executeParameterized(repo.id, query, queryParams);
704
+ }
705
+ return executeQuery(repo.id, query);
706
+ },
707
+ repoPath: repo.repoPath,
708
+ storagePath: repo.storagePath,
709
+ indexedCommit: repo.lastCommit,
710
+ },
711
+ symbol: {
712
+ uid: sym.nodeId,
713
+ name: sym.name || '',
714
+ filePath: sym.filePath || '',
715
+ },
716
+ })
717
+ : {}),
610
718
  };
719
+ if (processRows.length === 0 && unityResourcesMode !== 'off') {
720
+ const resourceBindings = Array.isArray(symbolEntry.resourceBindings)
721
+ ? symbolEntry.resourceBindings
722
+ : [];
723
+ const needsParityRetry = Boolean(symbolEntry.hydrationMeta?.needsParityRetry);
724
+ const hasPartialUnityEvidence = resourceBindings.length > 0 || needsParityRetry;
725
+ if (hasPartialUnityEvidence) {
726
+ const firstBindingPath = String(resourceBindings[0]?.resourcePath || '').trim();
727
+ const verificationTarget = firstBindingPath || sym.filePath || sym.name || sym.nodeId;
728
+ processRows = mergeProcessEvidence({
729
+ directRows: [],
730
+ projectedRows: [],
731
+ heuristicRows: [{
732
+ pid: `proc:heuristic:${String(sym.nodeId || '').replace(/\s+/g, '_')}`,
733
+ label: `${String(sym.name || 'Symbol')} runtime heuristic clue`,
734
+ processType: 'unity_resource_heuristic',
735
+ processSubtype: 'unity_lifecycle',
736
+ runtimeChainConfidence: 'low',
737
+ step: 1,
738
+ stepCount: 1,
739
+ needsParityRetry,
740
+ verificationTarget,
741
+ }],
742
+ });
743
+ }
744
+ }
611
745
  if (processRows.length === 0) {
612
746
  // Symbol not in any process — goes to definitions
613
747
  definitions.push(symbolEntry);
@@ -615,12 +749,12 @@ export class LocalBackend {
615
749
  else {
616
750
  // Add to each process it belongs to
617
751
  for (const row of processRows) {
618
- const pid = row.pid ?? row[0];
619
- const label = row.label ?? row[1];
620
- const hLabel = row.heuristicLabel ?? row[2];
621
- const pType = row.processType ?? row[3];
622
- const stepCount = row.stepCount ?? row[4];
623
- const step = row.step ?? row[5];
752
+ const pid = String(row.pid || '');
753
+ const label = String(row.label || '');
754
+ const hLabel = String(row.heuristicLabel || label);
755
+ const pType = String(row.processType || '');
756
+ const stepCount = Number(row.stepCount || 0);
757
+ const step = Number(row.step || 0);
624
758
  if (!processMap.has(pid)) {
625
759
  processMap.set(pid, {
626
760
  id: pid,
@@ -628,6 +762,8 @@ export class LocalBackend {
628
762
  heuristicLabel: hLabel,
629
763
  processType: pType,
630
764
  stepCount,
765
+ processSubtype: String(row.processSubtype || ''),
766
+ runtimeChainConfidence: String(row.runtimeChainConfidence || ''),
631
767
  totalScore: 0,
632
768
  cohesionBoost: 0,
633
769
  symbols: [],
@@ -640,6 +776,14 @@ export class LocalBackend {
640
776
  ...symbolEntry,
641
777
  process_id: pid,
642
778
  step_index: step,
779
+ process_subtype: String(row.processSubtype || ''),
780
+ process_evidence_mode: row.evidence_mode,
781
+ process_confidence: row.confidence,
782
+ ...(confidenceFieldsEnabled ? {
783
+ runtime_chain_confidence: row.confidence,
784
+ runtime_chain_evidence_level: row.runtime_chain_evidence_level,
785
+ verification_hint: row.verification_hint,
786
+ } : {}),
643
787
  });
644
788
  }
645
789
  }
@@ -659,25 +803,54 @@ export class LocalBackend {
659
803
  priority: Math.round(p.priority * 1000) / 1000,
660
804
  symbol_count: p.symbols.length,
661
805
  process_type: p.processType,
806
+ process_subtype: p.processSubtype || undefined,
662
807
  step_count: p.stepCount,
808
+ evidence_mode: aggregateProcessEvidenceMode(p.symbols),
809
+ confidence: aggregateProcessConfidence(p.symbols),
810
+ ...(confidenceFieldsEnabled ? {
811
+ runtime_chain_confidence: aggregateProcessConfidence(p.symbols),
812
+ runtime_chain_evidence_level: aggregateRuntimeChainEvidenceLevel(p.symbols),
813
+ verification_hint: selectVerificationHint(p.symbols),
814
+ } : {}),
663
815
  }));
664
816
  const processSymbols = rankedProcesses.flatMap(p => p.symbols.slice(0, maxSymbolsPerProcess).map(s => ({
665
817
  ...s,
666
818
  // remove internal fields
667
819
  })));
668
- // Deduplicate process_symbols by id
669
- const seen = new Set();
670
- const dedupedSymbols = processSymbols.filter(s => {
671
- if (seen.has(s.id))
672
- return false;
673
- seen.add(s.id);
674
- return true;
675
- });
676
- return {
820
+ // Deduplicate process_symbols by id, keeping the highest-confidence/evidence variant.
821
+ const dedupedById = new Map();
822
+ for (const symbol of processSymbols) {
823
+ const existing = dedupedById.get(symbol.id);
824
+ if (!existing) {
825
+ dedupedById.set(symbol.id, symbol);
826
+ continue;
827
+ }
828
+ const existingScore = (confidenceRank(existing.process_confidence) * 10)
829
+ + evidenceModeRank(existing.process_evidence_mode);
830
+ const nextScore = (confidenceRank(symbol.process_confidence) * 10)
831
+ + evidenceModeRank(symbol.process_evidence_mode);
832
+ if (nextScore > existingScore) {
833
+ dedupedById.set(symbol.id, symbol);
834
+ }
835
+ }
836
+ const dedupedSymbols = [...dedupedById.values()];
837
+ const result = {
677
838
  processes,
678
839
  process_symbols: dedupedSymbols,
679
840
  definitions: definitions.slice(0, 20), // cap standalone definitions
680
841
  };
842
+ if (runtimeChainVerifyMode === 'on-demand' && runtimeChainVerifyEnabled) {
843
+ const resourceBindings = dedupedSymbols
844
+ .flatMap((symbol) => (Array.isArray(symbol.resourceBindings) ? symbol.resourceBindings : []))
845
+ .concat(definitions.flatMap((symbol) => (Array.isArray(symbol.resourceBindings) ? symbol.resourceBindings : [])));
846
+ result.runtime_chain = await verifyRuntimeChainOnDemand({
847
+ repoPath: repo.repoPath,
848
+ executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
849
+ queryText: searchQuery,
850
+ resourceBindings,
851
+ });
852
+ }
853
+ return result;
681
854
  }
682
855
  /**
683
856
  * BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
@@ -957,6 +1130,18 @@ export class LocalBackend {
957
1130
  async context(repo, params) {
958
1131
  await this.ensureInitialized(repo.id);
959
1132
  const { name, uid, file_path, include_content } = params;
1133
+ const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
1134
+ const runtimeChainVerifyEnabled = resolveUnityRuntimeChainVerifyEnabled(process.env);
1135
+ const confidenceFieldsEnabled = resolveUnityProcessConfidenceFieldsEnabled(process.env);
1136
+ let unityResourcesMode = 'off';
1137
+ let unityHydrationMode = 'compact';
1138
+ try {
1139
+ unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
1140
+ unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
1141
+ }
1142
+ catch (error) {
1143
+ return { error: error instanceof Error ? error.message : String(error) };
1144
+ }
960
1145
  if (!name && !uid) {
961
1146
  return { error: 'Either "name" or "uid" parameter is required.' };
962
1147
  }
@@ -1011,6 +1196,9 @@ export class LocalBackend {
1011
1196
  // Step 3: Build full context
1012
1197
  const sym = symbols[0];
1013
1198
  const symId = sym.id || sym[0];
1199
+ const symNodeId = String(symId || '');
1200
+ const symName = String(sym.name || sym[1] || '');
1201
+ const symFilePath = String(sym.filePath || sym[3] || '');
1014
1202
  // Direct incoming refs for the selected symbol.
1015
1203
  const directIncomingRows = await executeParameterized(repo.id, `
1016
1204
  MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
@@ -1068,17 +1256,34 @@ export class LocalBackend {
1068
1256
  incomingRows = dedupe([...directIncomingRows, ...methodIncomingRows]);
1069
1257
  outgoingRows = dedupe([...directOutgoingRows, ...methodOutgoingRows]);
1070
1258
  }
1071
- // Process participation
1072
- let processRows = [];
1259
+ // Process participation with class-level method projection.
1260
+ let directProcessRows = [];
1261
+ let projectedProcessRows = [];
1073
1262
  try {
1074
- processRows = await executeParameterized(repo.id, `
1263
+ directProcessRows = await executeParameterized(repo.id, `
1075
1264
  MATCH (n {id: $symId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
1076
- RETURN p.id AS pid, p.heuristicLabel AS label, r.step AS step, p.stepCount AS stepCount
1265
+ RETURN p.id AS pid, p.heuristicLabel AS label, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, r.step AS step, p.stepCount AS stepCount
1077
1266
  `, { symId });
1078
1267
  }
1079
1268
  catch (e) {
1080
1269
  logQueryError('context:process-participation', e);
1081
1270
  }
1271
+ if (isMethodContainer) {
1272
+ try {
1273
+ projectedProcessRows = await executeParameterized(repo.id, `
1274
+ MATCH (n {id: $symId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)
1275
+ MATCH (m)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
1276
+ RETURN p.id AS pid, p.heuristicLabel AS label, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, MIN(r.step) AS step, p.stepCount AS stepCount
1277
+ `, { symId });
1278
+ }
1279
+ catch (e) {
1280
+ logQueryError('context:method-process-projection', e);
1281
+ }
1282
+ }
1283
+ let processRows = mergeProcessEvidence({
1284
+ directRows: directProcessRows,
1285
+ projectedRows: projectedProcessRows,
1286
+ });
1082
1287
  // Helper to categorize refs
1083
1288
  const categorize = (rows) => {
1084
1289
  const cats = {};
@@ -1096,13 +1301,13 @@ export class LocalBackend {
1096
1301
  }
1097
1302
  return cats;
1098
1303
  };
1099
- return {
1304
+ const result = {
1100
1305
  status: 'found',
1101
1306
  symbol: {
1102
1307
  uid: sym.id || sym[0],
1103
- name: sym.name || sym[1],
1308
+ name: symName,
1104
1309
  kind,
1105
- filePath: sym.filePath || sym[3],
1310
+ filePath: symFilePath,
1106
1311
  startLine: sym.startLine || sym[4],
1107
1312
  endLine: sym.endLine || sym[5],
1108
1313
  ...(include_content && (sym.content || sym[6]) ? { content: sym.content || sym[6] } : {}),
@@ -1111,13 +1316,81 @@ export class LocalBackend {
1111
1316
  outgoing: categorize(outgoingRows),
1112
1317
  directIncoming: categorize(directIncomingRows),
1113
1318
  directOutgoing: categorize(directOutgoingRows),
1114
- processes: processRows.map((r) => ({
1115
- id: r.pid || r[0],
1116
- name: r.label || r[1],
1117
- step_index: r.step || r[2],
1118
- step_count: r.stepCount || r[3],
1119
- })),
1319
+ processes: [],
1120
1320
  };
1321
+ if (unityResourcesMode !== 'off' && symNodeId && (kind === 'Class' || symNodeId.toLowerCase().startsWith('class:'))) {
1322
+ const unityContext = await loadUnityContext(repo.id, symNodeId, (query) => executeQuery(repo.id, query));
1323
+ const hydratedUnityContext = await hydrateUnityForSymbol({
1324
+ mode: unityHydrationMode,
1325
+ basePayload: unityContext,
1326
+ deps: {
1327
+ executeQuery: (query, queryParams) => {
1328
+ if (queryParams && Object.keys(queryParams).length > 0) {
1329
+ return executeParameterized(repo.id, query, queryParams);
1330
+ }
1331
+ return executeQuery(repo.id, query);
1332
+ },
1333
+ repoPath: repo.repoPath,
1334
+ storagePath: repo.storagePath,
1335
+ indexedCommit: repo.lastCommit,
1336
+ },
1337
+ symbol: {
1338
+ uid: symNodeId,
1339
+ name: symName,
1340
+ filePath: symFilePath,
1341
+ },
1342
+ });
1343
+ Object.assign(result, hydratedUnityContext);
1344
+ if (processRows.length === 0) {
1345
+ const resourceBindings = Array.isArray(result.resourceBindings)
1346
+ ? result.resourceBindings
1347
+ : [];
1348
+ const needsParityRetry = Boolean(result.hydrationMeta?.needsParityRetry);
1349
+ if (resourceBindings.length > 0 || needsParityRetry) {
1350
+ const firstBindingPath = String(resourceBindings[0]?.resourcePath || '').trim();
1351
+ const verificationTarget = firstBindingPath || symFilePath || symName || symNodeId;
1352
+ processRows = mergeProcessEvidence({
1353
+ directRows: [],
1354
+ projectedRows: [],
1355
+ heuristicRows: [{
1356
+ pid: `proc:heuristic:${String(symNodeId || '').replace(/\s+/g, '_')}`,
1357
+ label: `${String(symName || 'Symbol')} runtime heuristic clue`,
1358
+ processType: 'unity_resource_heuristic',
1359
+ processSubtype: 'unity_lifecycle',
1360
+ runtimeChainConfidence: 'low',
1361
+ step: 1,
1362
+ stepCount: 1,
1363
+ needsParityRetry,
1364
+ verificationTarget,
1365
+ }],
1366
+ });
1367
+ }
1368
+ }
1369
+ }
1370
+ result.processes = processRows.map((r) => ({
1371
+ id: r.pid || r[0],
1372
+ name: r.label || r[1],
1373
+ process_subtype: r.processSubtype || r[2],
1374
+ step_index: r.step || r[4],
1375
+ step_count: r.stepCount || r[5],
1376
+ evidence_mode: r.evidence_mode,
1377
+ confidence: r.confidence,
1378
+ ...(confidenceFieldsEnabled ? {
1379
+ runtime_chain_confidence: r.confidence,
1380
+ runtime_chain_evidence_level: r.runtime_chain_evidence_level,
1381
+ verification_hint: r.verification_hint,
1382
+ } : {}),
1383
+ }));
1384
+ if (runtimeChainVerifyMode === 'on-demand' && runtimeChainVerifyEnabled) {
1385
+ result.runtime_chain = await verifyRuntimeChainOnDemand({
1386
+ repoPath: repo.repoPath,
1387
+ executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
1388
+ symbolName: symName,
1389
+ symbolFilePath: symFilePath,
1390
+ resourceBindings: Array.isArray(result.resourceBindings) ? result.resourceBindings : [],
1391
+ });
1392
+ }
1393
+ return result;
1121
1394
  }
1122
1395
  /**
1123
1396
  * Legacy explore — kept for backwards compatibility with resources.ts.
@@ -1171,7 +1444,7 @@ export class LocalBackend {
1171
1444
  const processes = await executeParameterized(repo.id, `
1172
1445
  MATCH (p:Process)
1173
1446
  WHERE p.label = $processName OR p.heuristicLabel = $processName
1174
- RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
1447
+ RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, p.stepCount AS stepCount
1175
1448
  LIMIT 1
1176
1449
  `, { processName: name });
1177
1450
  if (processes.length === 0)
@@ -1180,16 +1453,24 @@ export class LocalBackend {
1180
1453
  const procId = proc.id || proc[0];
1181
1454
  const steps = await executeParameterized(repo.id, `
1182
1455
  MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
1183
- RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
1456
+ RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step, r.reason AS reason, r.confidence AS confidence
1184
1457
  ORDER BY r.step
1185
1458
  `, { procId });
1186
1459
  return {
1187
1460
  process: {
1188
1461
  id: procId, label: proc.label || proc[1], heuristicLabel: proc.heuristicLabel || proc[2],
1189
- processType: proc.processType || proc[3], stepCount: proc.stepCount || proc[4],
1462
+ processType: proc.processType || proc[3],
1463
+ processSubtype: proc.processSubtype || proc[4],
1464
+ runtimeChainConfidence: proc.runtimeChainConfidence || proc[5],
1465
+ stepCount: proc.stepCount || proc[6],
1190
1466
  },
1191
1467
  steps: steps.map((s) => ({
1192
- step: s.step || s[3], name: s.name || s[0], type: s.type || s[1], filePath: s.filePath || s[2],
1468
+ step: s.step || s[3],
1469
+ reason: s.reason || s[4],
1470
+ confidence: s.confidence || s[5],
1471
+ name: s.name || s[0],
1472
+ type: s.type || s[1],
1473
+ filePath: s.filePath || s[2],
1193
1474
  })),
1194
1475
  };
1195
1476
  }
@@ -1527,13 +1808,12 @@ export class LocalBackend {
1527
1808
  // Bridge class-like symbols through HAS_METHOD so class-level impact includes method-level dependencies.
1528
1809
  if (shouldBridgeClassMethods) {
1529
1810
  if (frontier.length > 0) {
1530
- const bridgeIdList = frontier.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
1531
1811
  try {
1532
- const bridgeRows = await executeQuery(repo.id, `
1812
+ const bridgeRows = await executeParameterized(repo.id, `
1533
1813
  MATCH (container)-[r:CodeRelation {type: 'HAS_METHOD'}]->(method)
1534
- WHERE container.id IN [${bridgeIdList}]${confidenceFilter}
1814
+ WHERE container.id IN $frontierIds${confidenceFilter}
1535
1815
  RETURN container.id AS containerId, method.id AS methodId, method.name AS methodName, labels(method)[0] AS methodType, method.filePath AS methodFilePath
1536
- `);
1816
+ `, { frontierIds: frontier });
1537
1817
  for (const bridge of bridgeRows) {
1538
1818
  const methodId = bridge.methodId || bridge[1];
1539
1819
  const methodType = bridge.methodType || bridge[3];
@@ -1575,12 +1855,11 @@ export class LocalBackend {
1575
1855
  // de-dupe traversal frontier (class + bridged methods)
1576
1856
  traversalFrontier = [...new Set(traversalFrontier)];
1577
1857
  // Batch frontier nodes into a single Cypher query per depth level
1578
- const idList = traversalFrontier.map(id => `'${id.replace(/'/g, "''")}'`).join(', ');
1579
1858
  const query = direction === 'upstream'
1580
- ? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
1581
- : `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
1859
+ ? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN $frontierIds AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
1860
+ : `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN $frontierIds AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
1582
1861
  try {
1583
- const related = await executeQuery(repo.id, query);
1862
+ const related = await executeParameterized(repo.id, query, { frontierIds: traversalFrontier });
1584
1863
  for (const rel of related) {
1585
1864
  const relId = rel.id || rel[1];
1586
1865
  const filePath = rel.filePath || rel[4] || '';
@@ -1738,7 +2017,7 @@ export class LocalBackend {
1738
2017
  try {
1739
2018
  const processes = await executeQuery(repo.id, `
1740
2019
  MATCH (p:Process)
1741
- RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
2020
+ RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, p.stepCount AS stepCount
1742
2021
  ORDER BY p.stepCount DESC
1743
2022
  LIMIT ${limit}
1744
2023
  `);
@@ -1748,7 +2027,9 @@ export class LocalBackend {
1748
2027
  label: p.label || p[1],
1749
2028
  heuristicLabel: p.heuristicLabel || p[2],
1750
2029
  processType: p.processType || p[3],
1751
- stepCount: p.stepCount || p[4],
2030
+ processSubtype: p.processSubtype || p[4],
2031
+ runtimeChainConfidence: p.runtimeChainConfidence || p[5],
2032
+ stepCount: p.stepCount || p[6],
1752
2033
  })),
1753
2034
  };
1754
2035
  }
@@ -1810,7 +2091,7 @@ export class LocalBackend {
1810
2091
  const processes = await executeParameterized(repo.id, `
1811
2092
  MATCH (p:Process)
1812
2093
  WHERE p.label = $processName OR p.heuristicLabel = $processName
1813
- RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
2094
+ RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.processSubtype AS processSubtype, p.runtimeChainConfidence AS runtimeChainConfidence, p.stepCount AS stepCount
1814
2095
  LIMIT 1
1815
2096
  `, { processName: name });
1816
2097
  if (processes.length === 0)
@@ -1819,16 +2100,24 @@ export class LocalBackend {
1819
2100
  const procId = proc.id || proc[0];
1820
2101
  const steps = await executeParameterized(repo.id, `
1821
2102
  MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
1822
- RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
2103
+ RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step, r.reason AS reason, r.confidence AS confidence
1823
2104
  ORDER BY r.step
1824
2105
  `, { procId });
1825
2106
  return {
1826
2107
  process: {
1827
2108
  id: procId, label: proc.label || proc[1], heuristicLabel: proc.heuristicLabel || proc[2],
1828
- processType: proc.processType || proc[3], stepCount: proc.stepCount || proc[4],
2109
+ processType: proc.processType || proc[3],
2110
+ processSubtype: proc.processSubtype || proc[4],
2111
+ runtimeChainConfidence: proc.runtimeChainConfidence || proc[5],
2112
+ stepCount: proc.stepCount || proc[6],
1829
2113
  },
1830
2114
  steps: steps.map((s) => ({
1831
- step: s.step || s[3], name: s.name || s[0], type: s.type || s[1], filePath: s.filePath || s[2],
2115
+ step: s.step || s[3],
2116
+ reason: s.reason || s[4],
2117
+ confidence: s.confidence || s[5],
2118
+ name: s.name || s[0],
2119
+ type: s.type || s[1],
2120
+ filePath: s.filePath || s[2],
1832
2121
  })),
1833
2122
  };
1834
2123
  }
@@ -2,7 +2,7 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { projectUnityBindings } from './unity-enrichment.js';
4
4
  import { hydrateLazyBindings } from './unity-lazy-hydrator.js';
5
- import { attachUnityHydrationMeta, mergeParityUnityBindings, mergeUnityBindings } from './local-backend.js';
5
+ import { attachUnityHydrationMeta, mergeParityUnityBindings, mergeUnityBindings } from './unity-runtime-hydration.js';
6
6
  test('summary-only rows hydrate and merge into full bindings with preserved field coverage', async () => {
7
7
  const projected = projectUnityBindings([
8
8
  {
@@ -0,0 +1,19 @@
1
+ export type ProcessEvidenceMode = 'direct_step' | 'method_projected' | 'resource_heuristic';
2
+ export type ProcessConfidence = 'high' | 'medium' | 'low';
3
+ export interface VerificationHint {
4
+ action: 'rerun_parity_hydration' | 'manual_asset_meta_verification';
5
+ target: string;
6
+ next_command: string;
7
+ }
8
+ export interface DeriveConfidenceInput {
9
+ evidenceMode: ProcessEvidenceMode;
10
+ processSubtype?: string;
11
+ hasPartialUnityEvidence?: boolean;
12
+ }
13
+ export interface BuildVerificationHintInput {
14
+ confidence: ProcessConfidence;
15
+ needsParityRetry?: boolean;
16
+ target?: string;
17
+ }
18
+ export declare function deriveConfidence(input: DeriveConfidenceInput): ProcessConfidence;
19
+ export declare function buildVerificationHint(input: BuildVerificationHintInput): VerificationHint | undefined;
@@ -0,0 +1,29 @@
1
+ export function deriveConfidence(input) {
2
+ if (input.evidenceMode === 'resource_heuristic') {
3
+ return 'low';
4
+ }
5
+ if (input.evidenceMode === 'method_projected') {
6
+ return 'medium';
7
+ }
8
+ if (String(input.processSubtype || '').toLowerCase() === 'unity_lifecycle') {
9
+ return 'medium';
10
+ }
11
+ return 'high';
12
+ }
13
+ export function buildVerificationHint(input) {
14
+ if (input.confidence !== 'low')
15
+ return undefined;
16
+ const target = String(input.target || '').trim() || 'unity-runtime-chain';
17
+ if (input.needsParityRetry) {
18
+ return {
19
+ action: 'rerun_parity_hydration',
20
+ target,
21
+ next_command: 'GITNEXUS_UNITY_PROCESS_CONFIDENCE_FIELDS=on gitnexus query --unity-resources on --unity-hydration parity "<symbol-or-query>"',
22
+ };
23
+ }
24
+ return {
25
+ action: 'manual_asset_meta_verification',
26
+ target,
27
+ next_command: 'Inspect asset + .meta linkage for this target, then rerun query/context with --unity-resources on --unity-hydration parity',
28
+ };
29
+ }
@@ -0,0 +1 @@
1
+ export {};