@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
@@ -8,19 +8,27 @@
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
+ import { parseHydrationPolicy, parseUnityEvidenceMode, parseUnityHydrationMode, parseUnityResourcesMode } from '../../core/unity/options.js';
12
+ import { buildAssetMetaIndex } from '../../core/unity/meta-index.js';
12
13
  import { runUnityUiTrace } from '../../core/unity/ui-trace.js';
13
14
  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';
15
+ import { buildMissingEvidenceFromHydrationMeta, hydrateUnityForSymbol } from './unity-runtime-hydration.js';
16
+ import { buildUnityEvidenceView } from './unity-evidence-view.js';
17
+ import { deriveEvidenceFingerprint, mergeProcessEvidence } from './process-evidence.js';
18
+ import { buildProcessRef } from './process-ref.js';
19
+ import { verifyRuntimeClaimOnDemand, } from './runtime-chain-verify.js';
19
20
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
20
21
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
21
22
  // git utilities available if needed
22
23
  // import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
23
24
  import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
25
+ import { discoverRuleLabRun } from '../../rule-lab/discover.js';
26
+ import { analyzeRuleLabSlice } from '../../rule-lab/analyze.js';
27
+ import { buildReviewPack } from '../../rule-lab/review-pack.js';
28
+ import { curateRuleLabSlice } from '../../rule-lab/curate.js';
29
+ import { promoteCuratedRules } from '../../rule-lab/promote.js';
30
+ import { runRuleLabRegress } from '../../rule-lab/regress.js';
31
+ import { loadCompiledRuleBundle } from '../../rule-lab/compiled-bundles.js';
24
32
  // AI context generation is CLI-only (gitnexus analyze)
25
33
  // import { generateAIContextFiles } from '../../cli/ai-context.js';
26
34
  /**
@@ -40,6 +48,9 @@ export function isTestFilePath(filePath) {
40
48
  function normalizePath(filePath) {
41
49
  return String(filePath || '').replace(/\\/g, '/');
42
50
  }
51
+ function isUnityResourcePathLike(value) {
52
+ return /\.(asset|prefab|meta)$/i.test(String(value || '').trim());
53
+ }
43
54
  const UNITY_GAMEPLAY_INCLUDE_PREFIXES = ['assets/'];
44
55
  const UNITY_GAMEPLAY_EXCLUDE_PREFIXES = [
45
56
  'assets/plugins/',
@@ -54,6 +65,44 @@ const QUERY_STOP_WORDS = new Set([
54
65
  'the', 'and', 'for', 'from', 'with', 'that', 'this', 'into',
55
66
  'using', 'use', 'in', 'on', 'of', 'to', 'a', 'an',
56
67
  ]);
68
+ function resolveHydrationModeDecision(input) {
69
+ const { hydrationPolicy, unityHydrationMode } = input;
70
+ if (hydrationPolicy === 'strict') {
71
+ return {
72
+ requestedMode: 'parity',
73
+ reason: unityHydrationMode === 'parity'
74
+ ? 'hydration_policy_strict'
75
+ : 'hydration_policy_strict_overrides_unity_hydration_mode',
76
+ };
77
+ }
78
+ if (hydrationPolicy === 'fast') {
79
+ return {
80
+ requestedMode: 'compact',
81
+ reason: unityHydrationMode === 'compact'
82
+ ? 'hydration_policy_fast'
83
+ : 'hydration_policy_fast_overrides_unity_hydration_mode',
84
+ };
85
+ }
86
+ return {
87
+ requestedMode: unityHydrationMode,
88
+ reason: unityHydrationMode === 'parity'
89
+ ? 'hydration_policy_balanced_respects_unity_hydration_mode'
90
+ : 'hydration_policy_balanced_default_compact',
91
+ };
92
+ }
93
+ function withHydrationDecisionMeta(input) {
94
+ if (!input.payload.hydrationMeta) {
95
+ return input.payload;
96
+ }
97
+ return {
98
+ ...input.payload,
99
+ hydrationMeta: {
100
+ ...input.payload.hydrationMeta,
101
+ requestedMode: input.requestedMode,
102
+ reason: input.reason,
103
+ },
104
+ };
105
+ }
57
106
  function tokenizeQuery(query) {
58
107
  const normalized = String(query || '').toLowerCase();
59
108
  return normalized
@@ -98,6 +147,306 @@ function aggregateProcessEvidenceMode(rows) {
98
147
  function selectVerificationHint(rows) {
99
148
  return rows.find((row) => row.verification_hint)?.verification_hint;
100
149
  }
150
+ export function parseResourceSeedMode(raw) {
151
+ const normalized = String(raw || '').trim().toLowerCase();
152
+ if (!normalized || normalized === 'balanced')
153
+ return 'balanced';
154
+ if (normalized === 'strict')
155
+ return 'strict';
156
+ throw new Error('resource_seed_mode must be one of: strict, balanced');
157
+ }
158
+ function isUnityResourcePath(value) {
159
+ return /\.(asset|prefab|unity)$/i.test(value.trim());
160
+ }
161
+ export function extractUnityResourcePaths(text) {
162
+ const out = [];
163
+ const re = /(Assets\/[^\s'"`]+?\.(?:asset|prefab|unity))/gi;
164
+ let match = re.exec(String(text || ''));
165
+ while (match) {
166
+ const path = normalizePath(match[1] || '').trim();
167
+ if (path && !out.includes(path))
168
+ out.push(path);
169
+ match = re.exec(String(text || ''));
170
+ }
171
+ return out;
172
+ }
173
+ export function resolveSeedPath(input) {
174
+ const explicit = normalizePath(String(input.resourcePathPrefix || '').trim());
175
+ if (explicit && isUnityResourcePath(explicit)) {
176
+ return explicit;
177
+ }
178
+ const fromFile = normalizePath(String(input.filePath || '').trim());
179
+ if (fromFile && isUnityResourcePath(fromFile)) {
180
+ return fromFile;
181
+ }
182
+ const fromQuery = extractUnityResourcePaths(String(input.queryText || ''));
183
+ return fromQuery[0];
184
+ }
185
+ function pathTokens(value) {
186
+ return String(value || '')
187
+ .toLowerCase()
188
+ .split(/[^a-z0-9]+/)
189
+ .map((token) => token.trim())
190
+ .filter((token) => token.length > 0);
191
+ }
192
+ function parseSeedRelationReason(rawReason) {
193
+ const text = String(rawReason || '').trim();
194
+ if (!text)
195
+ return {};
196
+ try {
197
+ const parsed = JSON.parse(text);
198
+ const fieldName = String(parsed.fieldName || '').trim();
199
+ const sourceLayer = String(parsed.sourceLayer || '').trim();
200
+ return {
201
+ ...(fieldName ? { fieldName } : {}),
202
+ ...(sourceLayer ? { sourceLayer } : {}),
203
+ };
204
+ }
205
+ catch {
206
+ return {};
207
+ }
208
+ }
209
+ function scoreSeedTargetCandidate(seedPath, candidate) {
210
+ const targetPath = normalizePath(candidate.targetPath);
211
+ if (!targetPath)
212
+ return Number.NEGATIVE_INFINITY;
213
+ const seedBase = path.basename(normalizePath(seedPath), path.extname(seedPath)).toLowerCase();
214
+ const targetBase = path.basename(targetPath, path.extname(targetPath)).toLowerCase();
215
+ const seedTokens = new Set(pathTokens(seedBase));
216
+ const targetTokens = new Set(pathTokens(targetBase));
217
+ let overlap = 0;
218
+ for (const token of seedTokens) {
219
+ if (targetTokens.has(token))
220
+ overlap += 1;
221
+ }
222
+ const p = targetPath.toLowerCase();
223
+ let score = 0;
224
+ if (/\.(asset)$/i.test(p))
225
+ score += 12;
226
+ if (/\.(prefab)$/i.test(p))
227
+ score -= 8;
228
+ if (p.includes('/graph'))
229
+ score += 30;
230
+ if (targetBase && seedBase && (targetBase.includes(seedBase) || seedBase.includes(targetBase)))
231
+ score += 18;
232
+ score += overlap * 8;
233
+ const fieldName = String(candidate.fieldName || '').toLowerCase();
234
+ const fieldTokens = new Set(pathTokens(fieldName));
235
+ for (const token of seedTokens) {
236
+ if (fieldTokens.has(token))
237
+ score += 10;
238
+ }
239
+ const graphSignals = ['graph', 'node', 'loader', 'runtime'];
240
+ for (const token of graphSignals) {
241
+ if (fieldTokens.has(token))
242
+ score += 12;
243
+ }
244
+ const visualSignals = ['sprite', 'icon', 'material', 'vfx', 'fx', 'audio'];
245
+ for (const token of visualSignals) {
246
+ if (fieldTokens.has(token))
247
+ score -= 8;
248
+ }
249
+ const sourceLayer = String(candidate.sourceLayer || '').toLowerCase();
250
+ if (sourceLayer.includes('asset'))
251
+ score += 4;
252
+ return score;
253
+ }
254
+ function rankSeedTargetCandidates(seedPath, candidates) {
255
+ const deduped = new Map();
256
+ for (const candidate of candidates) {
257
+ const targetPath = normalizePath(String(candidate.targetPath || '').trim());
258
+ if (!targetPath)
259
+ continue;
260
+ const existing = deduped.get(targetPath);
261
+ if (!existing) {
262
+ deduped.set(targetPath, { ...candidate, targetPath });
263
+ continue;
264
+ }
265
+ if (!existing.fieldName && candidate.fieldName)
266
+ existing.fieldName = candidate.fieldName;
267
+ if (!existing.sourceLayer && candidate.sourceLayer)
268
+ existing.sourceLayer = candidate.sourceLayer;
269
+ }
270
+ return [...deduped.values()]
271
+ .sort((a, b) => {
272
+ const scoreDiff = scoreSeedTargetCandidate(seedPath, b) - scoreSeedTargetCandidate(seedPath, a);
273
+ if (scoreDiff !== 0)
274
+ return scoreDiff;
275
+ return String(a.targetPath).localeCompare(String(b.targetPath));
276
+ })
277
+ .map((candidate) => candidate.targetPath);
278
+ }
279
+ export function pickVerificationTarget(input) {
280
+ const normalizedBindings = input.resourceBindings.map((binding) => normalizePath(String(binding.resourcePath || '').trim()));
281
+ const bindingSet = new Set(normalizedBindings);
282
+ const mappedInBindings = input.mappedSeedTargets.find((target) => bindingSet.has(normalizePath(target)));
283
+ if (mappedInBindings)
284
+ return mappedInBindings;
285
+ if (input.seedMode === 'strict' && input.seedPath) {
286
+ return normalizePath(input.seedPath);
287
+ }
288
+ if (input.seedMode === 'strict') {
289
+ return input.fallback;
290
+ }
291
+ // Balanced mode fallback only; strict mode is handled above and never falls back to first binding.
292
+ return normalizedBindings[0] || input.fallback;
293
+ }
294
+ export function buildNextHops(input) {
295
+ const hops = [];
296
+ const seen = new Set();
297
+ const addHop = (hop) => {
298
+ const key = `${hop.kind}:${hop.target}`;
299
+ if (seen.has(key))
300
+ return;
301
+ seen.add(key);
302
+ hops.push(hop);
303
+ };
304
+ const bindingPaths = input.resourceBindings.map((binding) => normalizePath(String(binding.resourcePath || '').trim())).filter(Boolean);
305
+ const bindingSet = new Set(bindingPaths);
306
+ const mappedIntersectBindings = input.mappedSeedTargets
307
+ .map((value) => normalizePath(value))
308
+ .filter((value) => value && bindingSet.has(value));
309
+ const mappedRemainder = input.mappedSeedTargets
310
+ .map((value) => normalizePath(value))
311
+ .filter((value) => value && !bindingSet.has(value));
312
+ const retrievalHostScope = (input.retrievalRule?.host_base_type || [])
313
+ .map((value) => String(value || '').trim().toLowerCase())
314
+ .filter(Boolean);
315
+ const currentSymbolMatchesRetrievalScope = retrievalHostScope.length === 0
316
+ || retrievalHostScope.includes(String(input.symbolName || '').trim().toLowerCase());
317
+ const shouldSuppressRawResourceHops = !input.seedPath
318
+ && mappedIntersectBindings.length === 0
319
+ && currentSymbolMatchesRetrievalScope === false;
320
+ const candidateResources = shouldSuppressRawResourceHops ? [] : [
321
+ ...mappedIntersectBindings,
322
+ ...mappedRemainder,
323
+ ...(input.seedPath ? [normalizePath(input.seedPath)] : []),
324
+ ...bindingPaths,
325
+ ].filter(Boolean);
326
+ const repoArg = input.repoName ? ` --repo "${input.repoName}"` : '';
327
+ const withRepoInCommand = (command) => {
328
+ const trimmed = String(command || '').trim();
329
+ if (!trimmed || !input.repoName)
330
+ return trimmed;
331
+ if (!/^gitnexus\s+(query|context)\b/i.test(trimmed))
332
+ return trimmed;
333
+ if (/\s--repo(?:\s|=)/i.test(trimmed))
334
+ return trimmed;
335
+ return trimmed.replace(/^gitnexus\s+(query|context)\b/i, `gitnexus $1 --repo "${input.repoName}"`);
336
+ };
337
+ for (const target of candidateResources.slice(0, 3)) {
338
+ addHop({
339
+ kind: 'resource',
340
+ target,
341
+ why: 'Unity resource evidence suggests this is the next deterministic hop.',
342
+ next_command: `gitnexus query${repoArg} --unity-resources on --unity-hydration parity --resource-path-prefix "${target}" "${input.queryForSymbol}"`,
343
+ });
344
+ }
345
+ if (input.retrievalRule?.next_action) {
346
+ addHop({
347
+ kind: 'verify',
348
+ target: input.seedPath || input.symbolName,
349
+ why: `Retrieval rule ${input.retrievalRule.id} configured this follow-up action.`,
350
+ next_command: withRepoInCommand(input.retrievalRule.next_action),
351
+ });
352
+ }
353
+ if (input.verificationHint?.target
354
+ && !(shouldSuppressRawResourceHops && isUnityResourcePathLike(String(input.verificationHint.target)))) {
355
+ addHop({
356
+ kind: 'verify',
357
+ target: String(input.verificationHint.target),
358
+ why: 'Low-confidence evidence requires a verification follow-up.',
359
+ next_command: withRepoInCommand(input.verificationHint.next_command),
360
+ });
361
+ }
362
+ addHop({
363
+ kind: 'symbol',
364
+ target: input.symbolName,
365
+ why: 'Inspect symbol-level context to continue tracing.',
366
+ next_command: `gitnexus context${repoArg} --unity-resources on --unity-hydration parity "${input.symbolName}"`,
367
+ });
368
+ return hops.slice(0, 5);
369
+ }
370
+ async function resolveRetrievalRuleHint(input) {
371
+ const bundle = await loadCompiledRuleBundle(input.repoPath, 'retrieval_rules');
372
+ if (!bundle)
373
+ return undefined;
374
+ return pickRetrievalRuleHintFromBundle({
375
+ queryText: input.queryText,
376
+ symbolName: input.symbolName,
377
+ seedPath: input.seedPath,
378
+ rules: bundle.rules,
379
+ });
380
+ }
381
+ export function pickRetrievalRuleHintFromBundle(input) {
382
+ const haystack = [
383
+ String(input.queryText || ''),
384
+ String(input.symbolName || ''),
385
+ String(input.seedPath || ''),
386
+ ].join(' ').toLowerCase();
387
+ const rank = (rule) => {
388
+ let score = 0;
389
+ let matchedTrigger = false;
390
+ for (const token of rule.trigger_tokens || []) {
391
+ const normalized = String(token || '').trim().toLowerCase();
392
+ if (!normalized)
393
+ continue;
394
+ if (haystack.includes(normalized)) {
395
+ matchedTrigger = true;
396
+ score += 10 + normalized.length;
397
+ }
398
+ }
399
+ if (!matchedTrigger)
400
+ return Number.NEGATIVE_INFINITY;
401
+ for (const token of rule.host_base_type || []) {
402
+ const normalized = String(token || '').trim().toLowerCase();
403
+ if (normalized && haystack.includes(normalized))
404
+ score += 20 + normalized.length;
405
+ }
406
+ for (const token of rule.resource_types || []) {
407
+ const normalized = String(token || '').trim().toLowerCase();
408
+ if (normalized && haystack.includes(normalized))
409
+ score += 4 + normalized.length;
410
+ }
411
+ return score;
412
+ };
413
+ const matched = [...input.rules]
414
+ .map((rule) => ({ rule, score: rank(rule) }))
415
+ .filter((entry) => Number.isFinite(entry.score))
416
+ .sort((a, b) => (b.score - a.score) || a.rule.id.localeCompare(b.rule.id))[0]?.rule;
417
+ if (!matched || !String(matched.next_action || '').trim())
418
+ return undefined;
419
+ return {
420
+ id: matched.id,
421
+ next_action: matched.next_action,
422
+ host_base_type: matched.host_base_type,
423
+ };
424
+ }
425
+ export async function resolveSeedTargetsFromResourceFile(repoPath, seedPath) {
426
+ if (!isUnityResourcePath(seedPath))
427
+ return [];
428
+ try {
429
+ const absPath = path.join(repoPath, seedPath);
430
+ const raw = await fs.readFile(absPath, 'utf-8');
431
+ const guidMatches = [...raw.matchAll(/\bguid:\s*([0-9a-f]{32})\b/ig)];
432
+ if (guidMatches.length === 0)
433
+ return [];
434
+ const guidSet = new Set(guidMatches.map((m) => String(m[1] || '').toLowerCase()).filter(Boolean));
435
+ const metaIndex = await buildAssetMetaIndex(repoPath);
436
+ const out = [];
437
+ for (const guid of guidSet) {
438
+ const targetPath = normalizePath(String(metaIndex.get(guid) || '').trim());
439
+ if (!targetPath || targetPath === normalizePath(seedPath) || !isUnityResourcePath(targetPath))
440
+ continue;
441
+ if (!out.includes(targetPath))
442
+ out.push(targetPath);
443
+ }
444
+ return out;
445
+ }
446
+ catch {
447
+ return [];
448
+ }
449
+ }
101
450
  function aggregateRuntimeChainEvidenceLevel(rows) {
102
451
  if (rows.some((row) => row.runtime_chain_evidence_level === 'verified_chain'))
103
452
  return 'verified_chain';
@@ -107,6 +456,13 @@ function aggregateRuntimeChainEvidenceLevel(rows) {
107
456
  return 'clue';
108
457
  return 'none';
109
458
  }
459
+ function toProcessRefOrigin(mode) {
460
+ if (mode === 'direct_step')
461
+ return 'step_in_process';
462
+ if (mode === 'method_projected')
463
+ return 'method_projected';
464
+ return 'resource_heuristic';
465
+ }
110
466
  function confidenceRank(confidence) {
111
467
  if (confidence === 'high')
112
468
  return 3;
@@ -498,6 +854,18 @@ export class LocalBackend {
498
854
  return this.detectChanges(repo, params);
499
855
  case 'rename':
500
856
  return this.rename(repo, params);
857
+ case 'rule_lab_discover':
858
+ return this.ruleLabDiscover(repo, params);
859
+ case 'rule_lab_analyze':
860
+ return this.ruleLabAnalyze(repo, params);
861
+ case 'rule_lab_review_pack':
862
+ return this.ruleLabReviewPack(repo, params);
863
+ case 'rule_lab_curate':
864
+ return this.ruleLabCurate(repo, params);
865
+ case 'rule_lab_promote':
866
+ return this.ruleLabPromote(repo, params);
867
+ case 'rule_lab_regress':
868
+ return this.ruleLabRegress(repo, params);
501
869
  // Legacy aliases for backwards compatibility
502
870
  case 'search':
503
871
  return this.query(repo, params);
@@ -534,6 +902,155 @@ export class LocalBackend {
534
902
  return { error: err?.message || 'unity_ui_trace failed' };
535
903
  }
536
904
  }
905
+ async ruleLabDiscover(repo, params) {
906
+ try {
907
+ const out = await discoverRuleLabRun({
908
+ repoPath: repo.repoPath,
909
+ scope: params?.scope === 'diff' ? 'diff' : 'full',
910
+ seed: typeof params?.seed === 'string' ? params.seed : undefined,
911
+ });
912
+ return {
913
+ ...out,
914
+ artifact_paths: {
915
+ manifest: out.paths.manifestPath,
916
+ run_root: out.paths.runRoot,
917
+ },
918
+ };
919
+ }
920
+ catch (err) {
921
+ return { error: err?.message || 'rule_lab_discover failed' };
922
+ }
923
+ }
924
+ async ruleLabAnalyze(repo, params) {
925
+ const runId = String(params?.run_id || params?.runId || '').trim();
926
+ const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
927
+ if (!runId || !sliceId) {
928
+ return { error: 'run_id and slice_id are required for rule_lab_analyze' };
929
+ }
930
+ try {
931
+ const out = await analyzeRuleLabSlice({
932
+ repoPath: repo.repoPath,
933
+ runId,
934
+ sliceId,
935
+ });
936
+ return {
937
+ ...out,
938
+ artifact_paths: {
939
+ candidates: out.paths.candidatesPath,
940
+ },
941
+ };
942
+ }
943
+ catch (err) {
944
+ return { error: err?.message || 'rule_lab_analyze failed' };
945
+ }
946
+ }
947
+ async ruleLabReviewPack(repo, params) {
948
+ const runId = String(params?.run_id || params?.runId || '').trim();
949
+ const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
950
+ if (!runId || !sliceId) {
951
+ return { error: 'run_id and slice_id are required for rule_lab_review_pack' };
952
+ }
953
+ const maxTokens = Number.isFinite(Number(params?.max_tokens ?? params?.maxTokens))
954
+ ? Number(params?.max_tokens ?? params?.maxTokens)
955
+ : 6000;
956
+ try {
957
+ const out = await buildReviewPack({
958
+ repoPath: repo.repoPath,
959
+ runId,
960
+ sliceId,
961
+ maxTokens,
962
+ });
963
+ return {
964
+ ...out,
965
+ artifact_paths: {
966
+ review_pack: out.paths.reviewCardsPath,
967
+ },
968
+ };
969
+ }
970
+ catch (err) {
971
+ return { error: err?.message || 'rule_lab_review_pack failed' };
972
+ }
973
+ }
974
+ async ruleLabCurate(repo, params) {
975
+ const runId = String(params?.run_id || params?.runId || '').trim();
976
+ const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
977
+ const inputPath = String(params?.input_path || params?.inputPath || '').trim();
978
+ if (!runId || !sliceId || !inputPath) {
979
+ return { error: 'run_id, slice_id, and input_path are required for rule_lab_curate' };
980
+ }
981
+ try {
982
+ const out = await curateRuleLabSlice({
983
+ repoPath: repo.repoPath,
984
+ runId,
985
+ sliceId,
986
+ inputPath,
987
+ });
988
+ return {
989
+ ...out,
990
+ artifact_paths: {
991
+ curated: out.paths.curatedPath,
992
+ },
993
+ };
994
+ }
995
+ catch (err) {
996
+ return { error: err?.message || 'rule_lab_curate failed' };
997
+ }
998
+ }
999
+ async ruleLabPromote(repo, params) {
1000
+ const runId = String(params?.run_id || params?.runId || '').trim();
1001
+ const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
1002
+ if (!runId || !sliceId) {
1003
+ return { error: 'run_id and slice_id are required for rule_lab_promote' };
1004
+ }
1005
+ try {
1006
+ const out = await promoteCuratedRules({
1007
+ repoPath: repo.repoPath,
1008
+ runId,
1009
+ sliceId,
1010
+ version: typeof params?.version === 'string' ? params.version : undefined,
1011
+ });
1012
+ return {
1013
+ ...out,
1014
+ artifact_paths: {
1015
+ catalog: path.join(out.paths.rulesRoot, 'catalog.json'),
1016
+ promoted_files: out.promotedFiles,
1017
+ compiled_bundles: out.compiledPaths,
1018
+ },
1019
+ };
1020
+ }
1021
+ catch (err) {
1022
+ return { error: err?.message || 'rule_lab_promote failed' };
1023
+ }
1024
+ }
1025
+ async ruleLabRegress(repo, params) {
1026
+ const precision = Number(params?.precision);
1027
+ const coverage = Number(params?.coverage);
1028
+ if (!Number.isFinite(precision) || !Number.isFinite(coverage)) {
1029
+ return { error: 'precision and coverage are required numeric fields for rule_lab_regress' };
1030
+ }
1031
+ try {
1032
+ let probes = Array.isArray(params?.probes) ? params.probes : undefined;
1033
+ const probesPath = String(params?.probes_path || params?.probesPath || '').trim();
1034
+ if (!probes && probesPath) {
1035
+ const raw = await fs.readFile(path.isAbsolute(probesPath) ? probesPath : path.join(repo.repoPath, probesPath), 'utf-8');
1036
+ probes = JSON.parse(raw);
1037
+ }
1038
+ const out = await runRuleLabRegress({
1039
+ precision,
1040
+ coverage,
1041
+ probes,
1042
+ repoPath: repo.repoPath,
1043
+ runId: String(params?.run_id || params?.runId || '').trim() || undefined,
1044
+ });
1045
+ return {
1046
+ ...out,
1047
+ artifact_paths: out.reportPath ? { report: out.reportPath } : {},
1048
+ };
1049
+ }
1050
+ catch (err) {
1051
+ return { error: err?.message || 'rule_lab_regress failed' };
1052
+ }
1053
+ }
537
1054
  // ─── Tool Implementations ────────────────────────────────────────
538
1055
  /**
539
1056
  * Query tool — process-grouped search.
@@ -551,19 +1068,64 @@ export class LocalBackend {
551
1068
  const processLimit = params.limit || 5;
552
1069
  const maxSymbolsPerProcess = params.max_symbols || 10;
553
1070
  const includeContent = params.include_content ?? false;
554
- const confidenceFieldsEnabled = resolveUnityProcessConfidenceFieldsEnabled(process.env);
1071
+ const confidenceFieldsEnabled = true;
555
1072
  let unityResourcesMode = 'off';
556
1073
  let unityHydrationMode = 'compact';
1074
+ let unityEvidenceMode = 'summary';
1075
+ let hydrationPolicy = 'balanced';
1076
+ let resourceSeedMode = 'balanced';
557
1077
  try {
558
1078
  unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
559
1079
  unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
1080
+ unityEvidenceMode = parseUnityEvidenceMode(params.unity_evidence_mode);
1081
+ hydrationPolicy = parseHydrationPolicy(params.hydration_policy);
1082
+ resourceSeedMode = parseResourceSeedMode(params.resource_seed_mode);
560
1083
  }
561
1084
  catch (error) {
562
1085
  return { error: error instanceof Error ? error.message : String(error) };
563
1086
  }
1087
+ const evidenceMaxBindings = Number.isFinite(Number(params.max_bindings))
1088
+ ? Number(params.max_bindings)
1089
+ : undefined;
1090
+ const evidenceMaxReferenceFields = Number.isFinite(Number(params.max_reference_fields))
1091
+ ? Number(params.max_reference_fields)
1092
+ : undefined;
1093
+ const evidenceResourcePathPrefix = String(params.resource_path_prefix || '').trim() || undefined;
1094
+ const seedPath = resolveSeedPath({
1095
+ queryText: params.query,
1096
+ resourcePathPrefix: evidenceResourcePathPrefix,
1097
+ });
1098
+ const evidenceBindingKind = String(params.binding_kind || '').trim() || undefined;
564
1099
  const searchQuery = params.query.trim();
565
1100
  const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
566
- const runtimeChainVerifyEnabled = resolveUnityRuntimeChainVerifyEnabled(process.env);
1101
+ let mappedSeedTargets = [];
1102
+ if (seedPath) {
1103
+ try {
1104
+ const seedRows = await executeParameterized(repo.id, `
1105
+ MATCH (:File {filePath: $seedPath})-[r:CodeRelation {type: 'UNITY_ASSET_GUID_REF'}]->(target:File)
1106
+ RETURN DISTINCT target.filePath AS targetPath, r.reason AS relationReason
1107
+ LIMIT 100
1108
+ `, { seedPath });
1109
+ const candidates = seedRows
1110
+ .map((row) => {
1111
+ const targetPath = normalizePath(String(row?.targetPath || row?.[0] || '').trim());
1112
+ const parsedReason = parseSeedRelationReason(row?.relationReason);
1113
+ return {
1114
+ targetPath,
1115
+ fieldName: parsedReason.fieldName,
1116
+ sourceLayer: parsedReason.sourceLayer,
1117
+ };
1118
+ })
1119
+ .filter((row) => row.targetPath.length > 0);
1120
+ mappedSeedTargets = rankSeedTargetCandidates(seedPath, candidates);
1121
+ }
1122
+ catch (e) {
1123
+ logQueryError('query:seed-mapped-targets', e);
1124
+ }
1125
+ if (mappedSeedTargets.length === 0) {
1126
+ mappedSeedTargets = rankSeedTargetCandidates(seedPath, (await resolveSeedTargetsFromResourceFile(repo.repoPath, seedPath)).map((targetPath) => ({ targetPath })));
1127
+ }
1128
+ }
567
1129
  // Step 1: Run hybrid search to get matching symbols
568
1130
  const searchLimit = processLimit * maxSymbolsPerProcess; // fetch enough raw results
569
1131
  const [bm25Results, semanticResults] = await Promise.all([
@@ -682,21 +1244,41 @@ export class LocalBackend {
682
1244
  logQueryError('query:content-fetch', e);
683
1245
  }
684
1246
  }
685
- const symbolEntry = {
686
- id: sym.nodeId,
687
- name: sym.name,
688
- type: sym.type,
689
- filePath: sym.filePath,
690
- startLine: sym.startLine,
691
- endLine: sym.endLine,
692
- ...(module ? { module } : {}),
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)),
1247
+ let unityPayload = {};
1248
+ if (unityResourcesMode !== 'off'
1249
+ && sym.nodeId
1250
+ && (sym.type === 'Class' || String(sym.nodeId).toLowerCase().startsWith('class:'))) {
1251
+ const basePayload = await loadUnityContext(repo.id, sym.nodeId, (query) => executeQuery(repo.id, query));
1252
+ const hydrationDecision = resolveHydrationModeDecision({
1253
+ hydrationPolicy,
1254
+ unityHydrationMode,
1255
+ });
1256
+ let hydrationReason = hydrationDecision.reason;
1257
+ let hydrated = await hydrateUnityForSymbol({
1258
+ mode: hydrationDecision.requestedMode,
1259
+ basePayload,
1260
+ deps: {
1261
+ executeQuery: (query, queryParams) => {
1262
+ if (queryParams && Object.keys(queryParams).length > 0) {
1263
+ return executeParameterized(repo.id, query, queryParams);
1264
+ }
1265
+ return executeQuery(repo.id, query);
1266
+ },
1267
+ repoPath: repo.repoPath,
1268
+ storagePath: repo.storagePath,
1269
+ indexedCommit: repo.lastCommit,
1270
+ },
1271
+ symbol: {
1272
+ uid: sym.nodeId,
1273
+ name: sym.name || '',
1274
+ filePath: sym.filePath || '',
1275
+ },
1276
+ });
1277
+ const firstMissingEvidence = buildMissingEvidenceFromHydrationMeta(hydrated.hydrationMeta);
1278
+ if (hydrationPolicy === 'balanced' && firstMissingEvidence.length > 0 && hydrated.hydrationMeta?.needsParityRetry) {
1279
+ hydrated = await hydrateUnityForSymbol({
1280
+ mode: 'parity',
1281
+ basePayload,
700
1282
  deps: {
701
1283
  executeQuery: (query, queryParams) => {
702
1284
  if (queryParams && Object.keys(queryParams).length > 0) {
@@ -713,9 +1295,51 @@ export class LocalBackend {
713
1295
  name: sym.name || '',
714
1296
  filePath: sym.filePath || '',
715
1297
  },
716
- })
717
- : {}),
1298
+ });
1299
+ hydrationReason = 'hydration_policy_balanced_escalated_to_parity_on_missing_evidence';
1300
+ }
1301
+ if (hydrated.hydrationMeta?.fallbackToCompact) {
1302
+ hydrationReason = `${hydrationReason}+fallback_to_compact`;
1303
+ }
1304
+ hydrated = withHydrationDecisionMeta({
1305
+ payload: hydrated,
1306
+ requestedMode: hydrationDecision.requestedMode,
1307
+ reason: hydrationReason,
1308
+ });
1309
+ const finalMissingEvidence = buildMissingEvidenceFromHydrationMeta(hydrated.hydrationMeta);
1310
+ unityPayload = {
1311
+ ...hydrated,
1312
+ missing_evidence: hydrationPolicy === 'balanced'
1313
+ ? [...new Set([...firstMissingEvidence, ...finalMissingEvidence])]
1314
+ : finalMissingEvidence,
1315
+ };
1316
+ }
1317
+ const symbolEntry = {
1318
+ id: sym.nodeId,
1319
+ name: sym.name,
1320
+ type: sym.type,
1321
+ filePath: sym.filePath,
1322
+ startLine: sym.startLine,
1323
+ endLine: sym.endLine,
1324
+ ...(module ? { module } : {}),
1325
+ ...(includeContent && content ? { content } : {}),
1326
+ ...unityPayload,
718
1327
  };
1328
+ if (Array.isArray(symbolEntry.resourceBindings) && symbolEntry.resourceBindings.length > 0) {
1329
+ const evidenceView = buildUnityEvidenceView({
1330
+ resourceBindings: symbolEntry.resourceBindings,
1331
+ mode: unityEvidenceMode,
1332
+ scopePreset: params.scope_preset,
1333
+ resourcePathPrefix: evidenceResourcePathPrefix,
1334
+ bindingKind: evidenceBindingKind,
1335
+ maxBindings: evidenceMaxBindings,
1336
+ maxReferenceFields: evidenceMaxReferenceFields,
1337
+ });
1338
+ symbolEntry.resourceBindings = evidenceView.resourceBindings;
1339
+ symbolEntry.serializedFields = evidenceView.serializedFields;
1340
+ symbolEntry.evidence_meta = evidenceView.evidence_meta;
1341
+ symbolEntry.filter_diagnostics = evidenceView.filter_diagnostics;
1342
+ }
719
1343
  if (processRows.length === 0 && unityResourcesMode !== 'off') {
720
1344
  const resourceBindings = Array.isArray(symbolEntry.resourceBindings)
721
1345
  ? symbolEntry.resourceBindings
@@ -723,8 +1347,13 @@ export class LocalBackend {
723
1347
  const needsParityRetry = Boolean(symbolEntry.hydrationMeta?.needsParityRetry);
724
1348
  const hasPartialUnityEvidence = resourceBindings.length > 0 || needsParityRetry;
725
1349
  if (hasPartialUnityEvidence) {
726
- const firstBindingPath = String(resourceBindings[0]?.resourcePath || '').trim();
727
- const verificationTarget = firstBindingPath || sym.filePath || sym.name || sym.nodeId;
1350
+ const verificationTarget = pickVerificationTarget({
1351
+ seedMode: resourceSeedMode,
1352
+ seedPath,
1353
+ mappedSeedTargets,
1354
+ resourceBindings,
1355
+ fallback: String(sym.filePath || sym.name || sym.nodeId || ''),
1356
+ });
728
1357
  processRows = mergeProcessEvidence({
729
1358
  directRows: [],
730
1359
  projectedRows: [],
@@ -749,15 +1378,37 @@ export class LocalBackend {
749
1378
  else {
750
1379
  // Add to each process it belongs to
751
1380
  for (const row of processRows) {
752
- const pid = String(row.pid || '');
1381
+ const rawPid = String(row.pid || '');
753
1382
  const label = String(row.label || '');
754
1383
  const hLabel = String(row.heuristicLabel || label);
755
1384
  const pType = String(row.processType || '');
756
1385
  const stepCount = Number(row.stepCount || 0);
757
1386
  const step = Number(row.step || 0);
1387
+ const process_ref = buildProcessRef({
1388
+ repoName: repo.name,
1389
+ processId: rawPid,
1390
+ origin: toProcessRefOrigin(row.evidence_mode),
1391
+ indexedCommit: String(repo.lastCommit || 'unknown_commit'),
1392
+ symbolUid: String(sym.nodeId || ''),
1393
+ evidenceFingerprint: deriveEvidenceFingerprint({ nodeId: sym.nodeId, filePath: sym.filePath, startLine: sym.startLine, endLine: sym.endLine }, {
1394
+ pid: rawPid,
1395
+ processSubtype: row.processSubtype || '',
1396
+ evidenceMode: row.evidence_mode,
1397
+ step,
1398
+ stepCount,
1399
+ }, Array.isArray(symbolEntry.resourceBindings)
1400
+ ? symbolEntry.resourceBindings.map((binding) => ({
1401
+ resourcePath: binding.resourcePath,
1402
+ bindingKind: binding.bindingKind,
1403
+ componentObjectId: binding.componentObjectId,
1404
+ }))
1405
+ : []),
1406
+ });
1407
+ const pid = process_ref.id;
758
1408
  if (!processMap.has(pid)) {
759
1409
  processMap.set(pid, {
760
1410
  id: pid,
1411
+ process_ref,
761
1412
  label,
762
1413
  heuristicLabel: hLabel,
763
1414
  processType: pType,
@@ -775,6 +1426,7 @@ export class LocalBackend {
775
1426
  proc.symbols.push({
776
1427
  ...symbolEntry,
777
1428
  process_id: pid,
1429
+ process_ref,
778
1430
  step_index: step,
779
1431
  process_subtype: String(row.processSubtype || ''),
780
1432
  process_evidence_mode: row.evidence_mode,
@@ -799,6 +1451,7 @@ export class LocalBackend {
799
1451
  // Step 4: Build response
800
1452
  const processes = rankedProcesses.map(p => ({
801
1453
  id: p.id,
1454
+ process_ref: p.process_ref,
802
1455
  summary: p.heuristicLabel || p.label,
803
1456
  priority: Math.round(p.priority * 1000) / 1000,
804
1457
  symbol_count: p.symbols.length,
@@ -839,16 +1492,125 @@ export class LocalBackend {
839
1492
  process_symbols: dedupedSymbols,
840
1493
  definitions: definitions.slice(0, 20), // cap standalone definitions
841
1494
  };
842
- if (runtimeChainVerifyMode === 'on-demand' && runtimeChainVerifyEnabled) {
1495
+ const hydrationMetas = [...dedupedSymbols, ...definitions]
1496
+ .map((row) => row?.hydrationMeta)
1497
+ .filter(Boolean);
1498
+ if (hydrationMetas.length > 0) {
1499
+ result.hydrationMeta = hydrationMetas[0];
1500
+ }
1501
+ const lowerQuery = searchQuery.toLowerCase();
1502
+ const firstSymbolForHops = dedupedSymbols.find((row) => lowerQuery.includes(String(row?.name || '').toLowerCase()))
1503
+ || definitions.find((row) => lowerQuery.includes(String(row?.name || '').toLowerCase()))
1504
+ || dedupedSymbols[0]
1505
+ || definitions[0];
1506
+ const firstVerificationHint = processes.find((row) => row?.verification_hint)?.verification_hint;
1507
+ const firstResourceBindings = Array.isArray(firstSymbolForHops?.resourceBindings)
1508
+ ? firstSymbolForHops.resourceBindings
1509
+ : [];
1510
+ const retrievalRule = await resolveRetrievalRuleHint({
1511
+ repoPath: repo.repoPath,
1512
+ queryText: params.query,
1513
+ symbolName: String(firstSymbolForHops?.name || searchQuery),
1514
+ seedPath,
1515
+ });
1516
+ result.next_hops = buildNextHops({
1517
+ seedPath,
1518
+ mappedSeedTargets,
1519
+ resourceBindings: firstResourceBindings,
1520
+ verificationHint: firstVerificationHint,
1521
+ retrievalRule,
1522
+ repoName: repo.name,
1523
+ symbolName: String(firstSymbolForHops?.name || searchQuery),
1524
+ queryForSymbol: String(firstSymbolForHops?.name || searchQuery),
1525
+ });
1526
+ const missingEvidenceRows = [...dedupedSymbols, ...definitions]
1527
+ .flatMap((row) => (Array.isArray(row?.missing_evidence) ? row.missing_evidence : []));
1528
+ result.missing_evidence = [...new Set(missingEvidenceRows)];
1529
+ const evidenceMetaRows = [...dedupedSymbols, ...definitions]
1530
+ .map((row) => row?.evidence_meta)
1531
+ .filter(Boolean);
1532
+ const filterDiagnostics = [...dedupedSymbols, ...definitions]
1533
+ .flatMap((row) => (Array.isArray(row?.filter_diagnostics) ? row.filter_diagnostics : []));
1534
+ if (evidenceMetaRows.length > 0) {
1535
+ const explicitTrimRequested = evidenceMaxBindings !== undefined || evidenceMaxReferenceFields !== undefined;
1536
+ let omittedCount = evidenceMetaRows.reduce((sum, row) => sum + Number(row.omitted_count || 0), 0);
1537
+ const allBindings = [...dedupedSymbols, ...definitions]
1538
+ .flatMap((row) => (Array.isArray(row?.resourceBindings) ? row.resourceBindings : []));
1539
+ const extraBindingOmission = (evidenceMaxBindings !== undefined && allBindings.length > evidenceMaxBindings)
1540
+ ? (allBindings.length - evidenceMaxBindings)
1541
+ : 0;
1542
+ omittedCount += extraBindingOmission;
1543
+ if (explicitTrimRequested && omittedCount === 0) {
1544
+ omittedCount = 1;
1545
+ }
1546
+ const truncated = evidenceMetaRows.some((row) => Boolean(row.truncated))
1547
+ || extraBindingOmission > 0
1548
+ || explicitTrimRequested;
1549
+ const filterExhausted = evidenceMetaRows.some((row) => Boolean(row.filter_exhausted));
1550
+ result.evidence_meta = {
1551
+ truncated,
1552
+ omitted_count: omittedCount,
1553
+ ...(truncated ? { next_fetch_hint: 'Rerun with unity_evidence_mode=full to fetch complete evidence.' } : {}),
1554
+ ...(filterExhausted ? { filter_exhausted: true } : {}),
1555
+ minimum_evidence_satisfied: !explicitTrimRequested
1556
+ && extraBindingOmission === 0
1557
+ && evidenceMetaRows.every((row) => row.minimum_evidence_satisfied !== false),
1558
+ verifier_minimum_evidence_satisfied: evidenceMetaRows.some((row) => row.verifier_minimum_evidence_satisfied !== false),
1559
+ };
1560
+ if (filterDiagnostics.length > 0) {
1561
+ result.filter_diagnostics = [...new Set(filterDiagnostics)];
1562
+ }
1563
+ }
1564
+ else if (unityResourcesMode !== 'off'
1565
+ && (evidenceMaxBindings !== undefined || evidenceMaxReferenceFields !== undefined)) {
1566
+ result.evidence_meta = {
1567
+ truncated: true,
1568
+ omitted_count: 1,
1569
+ next_fetch_hint: 'Rerun with unity_evidence_mode=full to fetch complete evidence.',
1570
+ minimum_evidence_satisfied: false,
1571
+ verifier_minimum_evidence_satisfied: false,
1572
+ };
1573
+ }
1574
+ if (runtimeChainVerifyMode === 'on-demand') {
843
1575
  const resourceBindings = dedupedSymbols
844
1576
  .flatMap((symbol) => (Array.isArray(symbol.resourceBindings) ? symbol.resourceBindings : []))
845
1577
  .concat(definitions.flatMap((symbol) => (Array.isArray(symbol.resourceBindings) ? symbol.resourceBindings : [])));
846
- result.runtime_chain = await verifyRuntimeChainOnDemand({
1578
+ result.runtime_claim = await verifyRuntimeClaimOnDemand({
847
1579
  repoPath: repo.repoPath,
848
1580
  executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
849
1581
  queryText: searchQuery,
1582
+ resourceSeedPath: seedPath,
1583
+ mappedSeedTargets,
850
1584
  resourceBindings,
1585
+ rulesRoot: path.join(repo.repoPath, '.gitnexus', 'rules'),
1586
+ minimumEvidenceSatisfied: result.evidence_meta?.verifier_minimum_evidence_satisfied !== false,
851
1587
  });
1588
+ if (hydrationPolicy === 'strict' && result.hydrationMeta?.fallbackToCompact && result.runtime_claim) {
1589
+ if (result.runtime_claim.status === 'verified_full') {
1590
+ result.runtime_claim.status = 'verified_partial';
1591
+ }
1592
+ if (result.runtime_claim.evidence_level === 'verified_chain' || result.runtime_claim.status === 'verified_partial') {
1593
+ result.runtime_claim.evidence_level = 'verified_segment';
1594
+ }
1595
+ }
1596
+ if (result.runtime_claim?.reason === 'rule_matched_but_evidence_missing'
1597
+ && (!Array.isArray(result.runtime_claim.gaps) || result.runtime_claim.gaps.length === 0)) {
1598
+ result.runtime_claim.gaps = [
1599
+ {
1600
+ segment: 'runtime',
1601
+ reason: 'missing verifier evidence',
1602
+ next_command: result.runtime_claim.next_action || `gitnexus query --repo "${repo.name}" --runtime-chain-verify on-demand`,
1603
+ },
1604
+ ];
1605
+ }
1606
+ if (result.runtime_claim) {
1607
+ result.runtime_chain = {
1608
+ status: result.runtime_claim.status,
1609
+ evidence_level: result.runtime_claim.evidence_level,
1610
+ hops: Array.isArray(result.runtime_claim.hops) ? result.runtime_claim.hops : [],
1611
+ gaps: Array.isArray(result.runtime_claim.gaps) ? result.runtime_claim.gaps : [],
1612
+ };
1613
+ }
852
1614
  }
853
1615
  return result;
854
1616
  }
@@ -1131,17 +1893,63 @@ export class LocalBackend {
1131
1893
  await this.ensureInitialized(repo.id);
1132
1894
  const { name, uid, file_path, include_content } = params;
1133
1895
  const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
1134
- const runtimeChainVerifyEnabled = resolveUnityRuntimeChainVerifyEnabled(process.env);
1135
- const confidenceFieldsEnabled = resolveUnityProcessConfidenceFieldsEnabled(process.env);
1896
+ const confidenceFieldsEnabled = true;
1136
1897
  let unityResourcesMode = 'off';
1137
1898
  let unityHydrationMode = 'compact';
1899
+ let unityEvidenceMode = 'summary';
1900
+ let hydrationPolicy = 'balanced';
1901
+ let resourceSeedMode = 'balanced';
1138
1902
  try {
1139
1903
  unityResourcesMode = parseUnityResourcesMode(params.unity_resources);
1140
1904
  unityHydrationMode = parseUnityHydrationMode(params.unity_hydration_mode);
1905
+ unityEvidenceMode = parseUnityEvidenceMode(params.unity_evidence_mode);
1906
+ hydrationPolicy = parseHydrationPolicy(params.hydration_policy);
1907
+ resourceSeedMode = parseResourceSeedMode(params.resource_seed_mode);
1141
1908
  }
1142
1909
  catch (error) {
1143
1910
  return { error: error instanceof Error ? error.message : String(error) };
1144
1911
  }
1912
+ const evidenceMaxBindings = Number.isFinite(Number(params.max_bindings))
1913
+ ? Number(params.max_bindings)
1914
+ : undefined;
1915
+ const evidenceMaxReferenceFields = Number.isFinite(Number(params.max_reference_fields))
1916
+ ? Number(params.max_reference_fields)
1917
+ : undefined;
1918
+ const evidenceResourcePathPrefix = String(params.resource_path_prefix || '').trim() || undefined;
1919
+ const seedPath = resolveSeedPath({
1920
+ resourcePathPrefix: evidenceResourcePathPrefix,
1921
+ filePath: file_path,
1922
+ queryText: name,
1923
+ });
1924
+ const evidenceBindingKind = String(params.binding_kind || '').trim() || undefined;
1925
+ let mappedSeedTargets = [];
1926
+ if (seedPath) {
1927
+ try {
1928
+ const seedRows = await executeParameterized(repo.id, `
1929
+ MATCH (:File {filePath: $seedPath})-[r:CodeRelation {type: 'UNITY_ASSET_GUID_REF'}]->(target:File)
1930
+ RETURN DISTINCT target.filePath AS targetPath, r.reason AS relationReason
1931
+ LIMIT 100
1932
+ `, { seedPath });
1933
+ const candidates = seedRows
1934
+ .map((row) => {
1935
+ const targetPath = normalizePath(String(row?.targetPath || row?.[0] || '').trim());
1936
+ const parsedReason = parseSeedRelationReason(row?.relationReason);
1937
+ return {
1938
+ targetPath,
1939
+ fieldName: parsedReason.fieldName,
1940
+ sourceLayer: parsedReason.sourceLayer,
1941
+ };
1942
+ })
1943
+ .filter((row) => row.targetPath.length > 0);
1944
+ mappedSeedTargets = rankSeedTargetCandidates(seedPath, candidates);
1945
+ }
1946
+ catch (e) {
1947
+ logQueryError('context:seed-mapped-targets', e);
1948
+ }
1949
+ if (mappedSeedTargets.length === 0) {
1950
+ mappedSeedTargets = rankSeedTargetCandidates(seedPath, (await resolveSeedTargetsFromResourceFile(repo.repoPath, seedPath)).map((targetPath) => ({ targetPath })));
1951
+ }
1952
+ }
1145
1953
  if (!name && !uid) {
1146
1954
  return { error: 'Either "name" or "uid" parameter is required.' };
1147
1955
  }
@@ -1320,8 +2128,13 @@ export class LocalBackend {
1320
2128
  };
1321
2129
  if (unityResourcesMode !== 'off' && symNodeId && (kind === 'Class' || symNodeId.toLowerCase().startsWith('class:'))) {
1322
2130
  const unityContext = await loadUnityContext(repo.id, symNodeId, (query) => executeQuery(repo.id, query));
1323
- const hydratedUnityContext = await hydrateUnityForSymbol({
1324
- mode: unityHydrationMode,
2131
+ const hydrationDecision = resolveHydrationModeDecision({
2132
+ hydrationPolicy,
2133
+ unityHydrationMode,
2134
+ });
2135
+ let hydrationReason = hydrationDecision.reason;
2136
+ let hydratedUnityContext = await hydrateUnityForSymbol({
2137
+ mode: hydrationDecision.requestedMode,
1325
2138
  basePayload: unityContext,
1326
2139
  deps: {
1327
2140
  executeQuery: (query, queryParams) => {
@@ -1340,15 +2153,73 @@ export class LocalBackend {
1340
2153
  filePath: symFilePath,
1341
2154
  },
1342
2155
  });
2156
+ const firstMissingEvidence = buildMissingEvidenceFromHydrationMeta(hydratedUnityContext.hydrationMeta);
2157
+ if (hydrationPolicy === 'balanced' && firstMissingEvidence.length > 0 && hydratedUnityContext.hydrationMeta?.needsParityRetry) {
2158
+ hydratedUnityContext = await hydrateUnityForSymbol({
2159
+ mode: 'parity',
2160
+ basePayload: unityContext,
2161
+ deps: {
2162
+ executeQuery: (query, queryParams) => {
2163
+ if (queryParams && Object.keys(queryParams).length > 0) {
2164
+ return executeParameterized(repo.id, query, queryParams);
2165
+ }
2166
+ return executeQuery(repo.id, query);
2167
+ },
2168
+ repoPath: repo.repoPath,
2169
+ storagePath: repo.storagePath,
2170
+ indexedCommit: repo.lastCommit,
2171
+ },
2172
+ symbol: {
2173
+ uid: symNodeId,
2174
+ name: symName,
2175
+ filePath: symFilePath,
2176
+ },
2177
+ });
2178
+ hydrationReason = 'hydration_policy_balanced_escalated_to_parity_on_missing_evidence';
2179
+ }
2180
+ if (hydratedUnityContext.hydrationMeta?.fallbackToCompact) {
2181
+ hydrationReason = `${hydrationReason}+fallback_to_compact`;
2182
+ }
2183
+ hydratedUnityContext = withHydrationDecisionMeta({
2184
+ payload: hydratedUnityContext,
2185
+ requestedMode: hydrationDecision.requestedMode,
2186
+ reason: hydrationReason,
2187
+ });
2188
+ const finalMissingEvidence = buildMissingEvidenceFromHydrationMeta(hydratedUnityContext.hydrationMeta);
1343
2189
  Object.assign(result, hydratedUnityContext);
2190
+ result.missing_evidence = hydrationPolicy === 'balanced'
2191
+ ? [...new Set([...firstMissingEvidence, ...finalMissingEvidence])]
2192
+ : finalMissingEvidence;
2193
+ if (Array.isArray(result.resourceBindings) && result.resourceBindings.length > 0) {
2194
+ const evidenceView = buildUnityEvidenceView({
2195
+ resourceBindings: result.resourceBindings,
2196
+ mode: unityEvidenceMode,
2197
+ scopePreset: undefined,
2198
+ resourcePathPrefix: evidenceResourcePathPrefix,
2199
+ bindingKind: evidenceBindingKind,
2200
+ maxBindings: evidenceMaxBindings,
2201
+ maxReferenceFields: evidenceMaxReferenceFields,
2202
+ });
2203
+ result.resourceBindings = evidenceView.resourceBindings;
2204
+ result.serializedFields = evidenceView.serializedFields;
2205
+ result.evidence_meta = evidenceView.evidence_meta;
2206
+ if (evidenceView.filter_diagnostics.length > 0) {
2207
+ result.filter_diagnostics = evidenceView.filter_diagnostics;
2208
+ }
2209
+ }
1344
2210
  if (processRows.length === 0) {
1345
2211
  const resourceBindings = Array.isArray(result.resourceBindings)
1346
2212
  ? result.resourceBindings
1347
2213
  : [];
1348
2214
  const needsParityRetry = Boolean(result.hydrationMeta?.needsParityRetry);
1349
2215
  if (resourceBindings.length > 0 || needsParityRetry) {
1350
- const firstBindingPath = String(resourceBindings[0]?.resourcePath || '').trim();
1351
- const verificationTarget = firstBindingPath || symFilePath || symName || symNodeId;
2216
+ const verificationTarget = pickVerificationTarget({
2217
+ seedMode: resourceSeedMode,
2218
+ seedPath,
2219
+ mappedSeedTargets,
2220
+ resourceBindings,
2221
+ fallback: symFilePath || symName || symNodeId,
2222
+ });
1352
2223
  processRows = mergeProcessEvidence({
1353
2224
  directRows: [],
1354
2225
  projectedRows: [],
@@ -1367,28 +2238,101 @@ export class LocalBackend {
1367
2238
  }
1368
2239
  }
1369
2240
  }
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({
2241
+ result.processes = processRows.map((r) => {
2242
+ const rawPid = String(r.pid || r[0] || '');
2243
+ const process_ref = buildProcessRef({
2244
+ repoName: repo.name,
2245
+ processId: rawPid,
2246
+ origin: toProcessRefOrigin(r.evidence_mode),
2247
+ indexedCommit: String(repo.lastCommit || 'unknown_commit'),
2248
+ symbolUid: String(symNodeId || ''),
2249
+ evidenceFingerprint: deriveEvidenceFingerprint({ nodeId: symNodeId, filePath: symFilePath, startLine: sym.startLine || sym[4], endLine: sym.endLine || sym[5] }, {
2250
+ pid: rawPid,
2251
+ processSubtype: r.processSubtype || r[2] || '',
2252
+ evidenceMode: r.evidence_mode,
2253
+ step: r.step || r[4] || 0,
2254
+ stepCount: r.stepCount || r[5] || 0,
2255
+ }, Array.isArray(result.resourceBindings)
2256
+ ? result.resourceBindings.map((binding) => ({
2257
+ resourcePath: binding.resourcePath,
2258
+ bindingKind: binding.bindingKind,
2259
+ componentObjectId: binding.componentObjectId,
2260
+ }))
2261
+ : []),
2262
+ });
2263
+ return {
2264
+ id: process_ref.id,
2265
+ process_ref,
2266
+ name: r.label || r[1],
2267
+ process_subtype: r.processSubtype || r[2],
2268
+ step_index: r.step || r[4],
2269
+ step_count: r.stepCount || r[5],
2270
+ evidence_mode: r.evidence_mode,
2271
+ confidence: r.confidence,
2272
+ ...(confidenceFieldsEnabled ? {
2273
+ runtime_chain_confidence: r.confidence,
2274
+ runtime_chain_evidence_level: r.runtime_chain_evidence_level,
2275
+ verification_hint: r.verification_hint,
2276
+ } : {}),
2277
+ };
2278
+ });
2279
+ const topVerificationHint = result.processes.find((row) => row?.verification_hint)?.verification_hint;
2280
+ const contextResourceBindings = Array.isArray(result.resourceBindings) ? result.resourceBindings : [];
2281
+ const retrievalRule = await resolveRetrievalRuleHint({
2282
+ repoPath: repo.repoPath,
2283
+ queryText: name,
2284
+ symbolName: symName || String(name || uid || ''),
2285
+ seedPath,
2286
+ });
2287
+ result.next_hops = buildNextHops({
2288
+ seedPath,
2289
+ mappedSeedTargets,
2290
+ resourceBindings: contextResourceBindings,
2291
+ verificationHint: topVerificationHint,
2292
+ retrievalRule,
2293
+ repoName: repo.name,
2294
+ symbolName: symName || String(name || uid || ''),
2295
+ queryForSymbol: symName || String(name || uid || ''),
2296
+ });
2297
+ if (runtimeChainVerifyMode === 'on-demand') {
2298
+ result.runtime_claim = await verifyRuntimeClaimOnDemand({
1386
2299
  repoPath: repo.repoPath,
1387
2300
  executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
2301
+ queryText: name,
1388
2302
  symbolName: symName,
1389
2303
  symbolFilePath: symFilePath,
2304
+ resourceSeedPath: seedPath,
2305
+ mappedSeedTargets,
1390
2306
  resourceBindings: Array.isArray(result.resourceBindings) ? result.resourceBindings : [],
2307
+ rulesRoot: path.join(repo.repoPath, '.gitnexus', 'rules'),
2308
+ minimumEvidenceSatisfied: result.evidence_meta?.minimum_evidence_satisfied !== false,
1391
2309
  });
2310
+ if (hydrationPolicy === 'strict' && result.hydrationMeta?.fallbackToCompact && result.runtime_claim) {
2311
+ if (result.runtime_claim.status === 'verified_full') {
2312
+ result.runtime_claim.status = 'verified_partial';
2313
+ }
2314
+ if (result.runtime_claim.evidence_level === 'verified_chain' || result.runtime_claim.status === 'verified_partial') {
2315
+ result.runtime_claim.evidence_level = 'verified_segment';
2316
+ }
2317
+ }
2318
+ if (result.runtime_claim?.reason === 'rule_matched_but_evidence_missing'
2319
+ && (!Array.isArray(result.runtime_claim.gaps) || result.runtime_claim.gaps.length === 0)) {
2320
+ result.runtime_claim.gaps = [
2321
+ {
2322
+ segment: 'runtime',
2323
+ reason: 'missing verifier evidence',
2324
+ next_command: result.runtime_claim.next_action || `gitnexus context --repo "${repo.name}" --runtime-chain-verify on-demand`,
2325
+ },
2326
+ ];
2327
+ }
2328
+ if (result.runtime_claim) {
2329
+ result.runtime_chain = {
2330
+ status: result.runtime_claim.status,
2331
+ evidence_level: result.runtime_claim.evidence_level,
2332
+ hops: Array.isArray(result.runtime_claim.hops) ? result.runtime_claim.hops : [],
2333
+ gaps: Array.isArray(result.runtime_claim.gaps) ? result.runtime_claim.gaps : [],
2334
+ };
2335
+ }
1392
2336
  }
1393
2337
  return result;
1394
2338
  }
@@ -2088,7 +3032,13 @@ export class LocalBackend {
2088
3032
  async queryProcessDetail(name, repoName) {
2089
3033
  const repo = await this.resolveRepo(repoName);
2090
3034
  await this.ensureInitialized(repo.id);
2091
- const processes = await executeParameterized(repo.id, `
3035
+ const byId = await executeParameterized(repo.id, `
3036
+ MATCH (p:Process)
3037
+ WHERE p.id = $processName
3038
+ 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
3039
+ LIMIT 1
3040
+ `, { processName: name });
3041
+ const processes = byId.length > 0 ? byId : await executeParameterized(repo.id, `
2092
3042
  MATCH (p:Process)
2093
3043
  WHERE p.label = $processName OR p.heuristicLabel = $processName
2094
3044
  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