@veewo/gitnexus 1.5.0 → 1.5.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 (137) hide show
  1. package/dist/benchmark/agent-context/runner.js +3 -0
  2. package/dist/benchmark/agent-context/runner.test.js +22 -0
  3. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
  4. package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
  5. package/dist/benchmark/agent-safe-query-context/io.js +86 -0
  6. package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
  7. package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
  8. package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
  9. package/dist/benchmark/agent-safe-query-context/report.js +159 -0
  10. package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
  11. package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
  12. package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
  13. package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
  14. package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
  15. package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
  16. package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
  17. package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
  18. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
  19. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
  20. package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
  21. package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
  22. package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
  23. package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
  24. package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
  25. package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
  26. package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
  27. package/dist/benchmark/agent-safe-query-context/types.js +8 -0
  28. package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
  29. package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
  30. package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
  31. package/dist/benchmark/runtime-poc/runner.js +163 -0
  32. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
  33. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
  34. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
  35. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
  36. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
  37. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
  38. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
  39. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
  40. package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
  41. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
  42. package/dist/cli/ai-context.js +2 -12
  43. package/dist/cli/ai-context.test.js +8 -0
  44. package/dist/cli/analyze-runtime-summary.js +1 -0
  45. package/dist/cli/analyze-runtime-summary.test.js +2 -0
  46. package/dist/cli/analyze-summary.d.ts +2 -0
  47. package/dist/cli/analyze-summary.js +24 -0
  48. package/dist/cli/analyze-summary.test.js +65 -1
  49. package/dist/cli/analyze.js +5 -1
  50. package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
  51. package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
  52. package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
  53. package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
  54. package/dist/cli/benchmark.d.ts +29 -0
  55. package/dist/cli/benchmark.js +55 -0
  56. package/dist/cli/index.js +23 -0
  57. package/dist/cli/rule-lab.d.ts +3 -7
  58. package/dist/cli/rule-lab.js +13 -22
  59. package/dist/cli/rule-lab.test.js +23 -3
  60. package/dist/cli/tool.d.ts +2 -0
  61. package/dist/cli/tool.js +2 -0
  62. package/dist/core/config/unity-config.d.ts +0 -1
  63. package/dist/core/config/unity-config.js +0 -1
  64. package/dist/core/ingestion/pipeline.js +35 -6
  65. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
  66. package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
  67. package/dist/core/ingestion/unity-parity-seed.js +8 -0
  68. package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
  69. package/dist/core/ingestion/unity-resource-processor.js +102 -0
  70. package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
  71. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +15 -0
  72. package/dist/core/ingestion/unity-runtime-binding-rules.js +178 -30
  73. package/dist/core/lbug/csv-generator.test.js +2 -2
  74. package/dist/core/unity/doc-contract.test.d.ts +1 -0
  75. package/dist/core/unity/doc-contract.test.js +30 -0
  76. package/dist/core/unity/prefab-source-scan.d.ts +25 -0
  77. package/dist/core/unity/prefab-source-scan.js +152 -0
  78. package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
  79. package/dist/core/unity/prefab-source-scan.test.js +70 -0
  80. package/dist/core/unity/scan-context.d.ts +12 -0
  81. package/dist/core/unity/scan-context.js +50 -2
  82. package/dist/core/unity/scan-context.test.js +74 -0
  83. package/dist/mcp/local/agent-safe-response.d.ts +10 -0
  84. package/dist/mcp/local/agent-safe-response.js +639 -0
  85. package/dist/mcp/local/derived-process-reader.js +1 -1
  86. package/dist/mcp/local/local-backend.d.ts +18 -1
  87. package/dist/mcp/local/local-backend.js +319 -125
  88. package/dist/mcp/local/process-confidence.d.ts +1 -2
  89. package/dist/mcp/local/process-confidence.js +0 -3
  90. package/dist/mcp/local/process-confidence.test.js +4 -2
  91. package/dist/mcp/local/process-evidence.d.ts +1 -8
  92. package/dist/mcp/local/process-evidence.js +1 -23
  93. package/dist/mcp/local/process-evidence.test.js +2 -16
  94. package/dist/mcp/local/process-ref.d.ts +1 -1
  95. package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
  96. package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
  97. package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
  98. package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
  99. package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
  100. package/dist/mcp/local/runtime-chain-verify.js +149 -138
  101. package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
  102. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
  103. package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
  104. package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
  105. package/dist/mcp/local/runtime-claim.d.ts +11 -0
  106. package/dist/mcp/local/runtime-claim.js +28 -0
  107. package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
  108. package/dist/mcp/local/unity-evidence-view.js +1 -1
  109. package/dist/mcp/local/unity-evidence-view.test.js +22 -0
  110. package/dist/mcp/tools.js +51 -21
  111. package/dist/rule-lab/analyze.d.ts +2 -1
  112. package/dist/rule-lab/analyze.js +94 -59
  113. package/dist/rule-lab/analyze.test.js +238 -20
  114. package/dist/rule-lab/curate.d.ts +2 -1
  115. package/dist/rule-lab/curate.js +24 -3
  116. package/dist/rule-lab/curate.test.js +65 -0
  117. package/dist/rule-lab/curation-input-builder.d.ts +45 -0
  118. package/dist/rule-lab/curation-input-builder.js +133 -0
  119. package/dist/rule-lab/promote.js +80 -7
  120. package/dist/rule-lab/promote.test.js +150 -0
  121. package/dist/rule-lab/review-pack.d.ts +3 -0
  122. package/dist/rule-lab/review-pack.js +41 -1
  123. package/dist/rule-lab/review-pack.test.js +67 -0
  124. package/dist/rule-lab/types.d.ts +29 -0
  125. package/dist/types/pipeline.d.ts +3 -0
  126. package/package.json +4 -3
  127. package/scripts/run-node-tests.mjs +61 -0
  128. package/skills/_shared/unity-rule-authoring-contract.md +64 -0
  129. package/skills/_shared/unity-runtime-process-contract.md +16 -0
  130. package/skills/gitnexus-cli.md +8 -0
  131. package/skills/gitnexus-debugging.md +9 -0
  132. package/skills/gitnexus-exploring.md +66 -18
  133. package/skills/gitnexus-guide.md +42 -3
  134. package/skills/gitnexus-impact-analysis.md +8 -0
  135. package/skills/gitnexus-pr-review.md +8 -0
  136. package/skills/gitnexus-refactoring.md +8 -0
  137. package/skills/gitnexus-unity-rule-gen.md +66 -312
@@ -14,15 +14,16 @@ import { runUnityUiTrace } from '../../core/unity/ui-trace.js';
14
14
  import { loadUnityContext } from './unity-enrichment.js';
15
15
  import { buildMissingEvidenceFromHydrationMeta, hydrateUnityForSymbol } from './unity-runtime-hydration.js';
16
16
  import { buildUnityEvidenceView } from './unity-evidence-view.js';
17
+ import { buildSlimContextResult, buildSlimQueryResult, resolveResponseProfile, } from './agent-safe-response.js';
17
18
  import { deriveEvidenceFingerprint, mergeProcessEvidence } from './process-evidence.js';
18
19
  import { buildProcessRef } from './process-ref.js';
19
20
  import { verifyRuntimeClaimOnDemand, } from './runtime-chain-verify.js';
21
+ import { adjustRuntimeClaimForPolicy } from './runtime-claim.js';
20
22
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
21
23
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
22
24
  // git utilities available if needed
23
25
  // import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
24
26
  import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
25
- import { discoverRuleLabRun } from '../../rule-lab/discover.js';
26
27
  import { analyzeRuleLabSlice } from '../../rule-lab/analyze.js';
27
28
  import { buildReviewPack } from '../../rule-lab/review-pack.js';
28
29
  import { curateRuleLabSlice } from '../../rule-lab/curate.js';
@@ -140,9 +141,7 @@ function aggregateProcessConfidence(rows) {
140
141
  function aggregateProcessEvidenceMode(rows) {
141
142
  if (rows.some((row) => row.process_evidence_mode === 'direct_step'))
142
143
  return 'direct_step';
143
- if (rows.some((row) => row.process_evidence_mode === 'method_projected'))
144
- return 'method_projected';
145
- return 'resource_heuristic';
144
+ return 'method_projected';
146
145
  }
147
146
  function selectVerificationHint(rows) {
148
147
  return rows.find((row) => row.verification_hint)?.verification_hint;
@@ -170,6 +169,16 @@ export function extractUnityResourcePaths(text) {
170
169
  }
171
170
  return out;
172
171
  }
172
+ export function computeVerifierMinimumEvidenceSatisfied(input) {
173
+ if (input.truncated || input.filterExhausted)
174
+ return false;
175
+ if (!Array.isArray(input.evidenceMetaRows) || input.evidenceMetaRows.length === 0)
176
+ return false;
177
+ return input.evidenceMetaRows.every((row) => (row?.truncated !== true
178
+ && row?.filter_exhausted !== true
179
+ && row?.minimum_evidence_satisfied !== false
180
+ && row?.verifier_minimum_evidence_satisfied !== false));
181
+ }
173
182
  export function resolveSeedPath(input) {
174
183
  const explicit = normalizePath(String(input.resourcePathPrefix || '').trim());
175
184
  if (explicit && isUnityResourcePath(explicit)) {
@@ -206,6 +215,113 @@ function parseSeedRelationReason(rawReason) {
206
215
  return {};
207
216
  }
208
217
  }
218
+ async function loadSeedUnityResourceChains(input) {
219
+ const seedPath = normalizePath(String(input.seedPath || '').trim());
220
+ if (!seedPath)
221
+ return [];
222
+ const targetSymbols = (input.targetSymbols || [])
223
+ .map((symbol) => ({
224
+ id: String(symbol?.id || '').trim(),
225
+ name: String(symbol?.name || '').trim(),
226
+ filePath: normalizePath(String(symbol?.filePath || '').trim()),
227
+ requireExact: Boolean(symbol?.requireExact),
228
+ }))
229
+ .filter((symbol) => symbol.id || symbol.name || symbol.filePath);
230
+ let rows = [];
231
+ try {
232
+ rows = await executeParameterized(input.repoId, `
233
+ MATCH (source:File {filePath: $seedPath})-[r1:CodeRelation {type: 'UNITY_ASSET_GUID_REF'}]->(intermediate:File)-[r2:CodeRelation {type: 'UNITY_GRAPH_NODE_SCRIPT_REF'}]->(target)
234
+ RETURN source.filePath AS sourceResourcePath,
235
+ r1.type AS relationType,
236
+ r1.reason AS relationReason,
237
+ intermediate.filePath AS intermediateResourcePath,
238
+ r2.type AS nextRelationType,
239
+ r2.reason AS nextRelationReason,
240
+ target.id AS targetUid,
241
+ target.name AS targetName,
242
+ labels(target)[0] AS targetKind,
243
+ target.filePath AS targetFilePath
244
+ LIMIT 200
245
+ `, { seedPath });
246
+ }
247
+ catch (e) {
248
+ logQueryError('unity-resource-chains:seed-second-hop', e);
249
+ return [];
250
+ }
251
+ const seen = new Set();
252
+ const chains = rows
253
+ .map((row, index) => {
254
+ const targetUid = String(row?.targetUid || row?.[6] || '').trim();
255
+ const targetName = String(row?.targetName || row?.[7] || '').trim();
256
+ const targetFilePath = normalizePath(String(row?.targetFilePath || row?.[9] || '').trim());
257
+ const chain = {
258
+ sourceResourcePath: normalizePath(String(row?.sourceResourcePath || row?.[0] || '').trim()),
259
+ relationType: 'UNITY_ASSET_GUID_REF',
260
+ relationReason: String(row?.relationReason || row?.[2] || '').trim() || undefined,
261
+ intermediateResourcePath: normalizePath(String(row?.intermediateResourcePath || row?.[3] || '').trim()),
262
+ nextRelationType: 'UNITY_GRAPH_NODE_SCRIPT_REF',
263
+ nextRelationReason: String(row?.nextRelationReason || row?.[5] || '').trim() || undefined,
264
+ targetSymbol: {
265
+ ...(targetUid ? { uid: targetUid } : {}),
266
+ ...(targetName ? { name: targetName } : {}),
267
+ kind: String(row?.targetKind || row?.[8] || '').trim() || undefined,
268
+ ...(targetFilePath ? { filePath: targetFilePath } : {}),
269
+ },
270
+ };
271
+ return {
272
+ chain,
273
+ index,
274
+ score: scoreUnityResourceChainTarget(chain, targetSymbols),
275
+ };
276
+ })
277
+ .filter(({ chain, score }) => {
278
+ if (!chain.sourceResourcePath || !chain.intermediateResourcePath || !chain.targetSymbol?.name)
279
+ return false;
280
+ if (targetSymbols.length > 0 && score <= 0)
281
+ return false;
282
+ const key = `${chain.sourceResourcePath}->${chain.intermediateResourcePath}->${chain.targetSymbol.uid || chain.targetSymbol.name}`;
283
+ if (seen.has(key))
284
+ return false;
285
+ seen.add(key);
286
+ return true;
287
+ })
288
+ .sort((a, b) => (b.score - a.score) || (a.index - b.index))
289
+ .slice(0, 20)
290
+ .map((entry) => entry.chain);
291
+ return chains;
292
+ }
293
+ function scoreUnityResourceChainTarget(chain, targetSymbols) {
294
+ if (targetSymbols.length === 0)
295
+ return 1;
296
+ const activeTargetSymbols = targetSymbols.some((symbol) => symbol.requireExact)
297
+ ? targetSymbols.filter((symbol) => symbol.requireExact)
298
+ : targetSymbols;
299
+ const targetUid = String(chain.targetSymbol?.uid || '').trim();
300
+ const targetName = String(chain.targetSymbol?.name || '').trim();
301
+ const targetFilePath = normalizePath(String(chain.targetSymbol?.filePath || '').trim());
302
+ let best = 0;
303
+ for (const symbol of activeTargetSymbols) {
304
+ let score = 0;
305
+ const exactMatched = Boolean((targetUid && symbol.id && targetUid === symbol.id)
306
+ || (targetName && symbol.name && targetName === symbol.name));
307
+ if (targetUid && symbol.id && targetUid === symbol.id)
308
+ score += 100;
309
+ if (targetFilePath && symbol.filePath && targetFilePath === symbol.filePath)
310
+ score += 60;
311
+ if (targetName && symbol.name && targetName === symbol.name)
312
+ score += 30;
313
+ if (symbol.requireExact && !exactMatched)
314
+ score = 0;
315
+ best = Math.max(best, score);
316
+ }
317
+ return best;
318
+ }
319
+ function exactResourceChainQuerySymbol(queryText) {
320
+ const trimmed = String(queryText || '').trim();
321
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed))
322
+ return undefined;
323
+ return { name: trimmed, requireExact: true };
324
+ }
209
325
  function scoreSeedTargetCandidate(seedPath, candidate) {
210
326
  const targetPath = normalizePath(candidate.targetPath);
211
327
  if (!targetPath)
@@ -291,6 +407,54 @@ export function pickVerificationTarget(input) {
291
407
  // Balanced mode fallback only; strict mode is handled above and never falls back to first binding.
292
408
  return normalizedBindings[0] || input.fallback;
293
409
  }
410
+ export function pickVerifierSymbolAnchor(input) {
411
+ const rows = [
412
+ ...input.processSymbols,
413
+ ...input.definitions,
414
+ ];
415
+ const evidenceModeRank = (row) => {
416
+ const mode = String(row?.process_evidence_mode || row?.evidence_mode || '').trim().toLowerCase();
417
+ if (mode === 'direct_step')
418
+ return 3;
419
+ if (mode === 'method_projected')
420
+ return 2;
421
+ if (mode === 'resource_heuristic')
422
+ return 1;
423
+ return 0;
424
+ };
425
+ const confidenceRank = (row) => {
426
+ const confidence = String(row?.process_confidence || row?.confidence || '').trim().toLowerCase();
427
+ if (confidence === 'high')
428
+ return 3;
429
+ if (confidence === 'medium')
430
+ return 2;
431
+ if (confidence === 'low')
432
+ return 1;
433
+ return 0;
434
+ };
435
+ const preferredStructuredSymbol = rows
436
+ .map((row, index) => ({ row, index }))
437
+ .filter(({ row }) => evidenceModeRank(row) >= 2 && confidenceRank(row) >= 2)
438
+ .sort((a, b) => {
439
+ const modeDiff = evidenceModeRank(b.row) - evidenceModeRank(a.row);
440
+ if (modeDiff !== 0)
441
+ return modeDiff;
442
+ const confidenceDiff = confidenceRank(b.row) - confidenceRank(a.row);
443
+ if (confidenceDiff !== 0)
444
+ return confidenceDiff;
445
+ return a.index - b.index;
446
+ })[0]?.row;
447
+ const resourceAnchoredSymbol = rows.find((row) => Array.isArray(row?.resourceBindings) && row.resourceBindings.length > 0);
448
+ const lowerQuery = String(input.queryText || '').toLowerCase();
449
+ const firstMatchInQuery = rows.find((row) => lowerQuery.includes(String(row?.name || '').toLowerCase()));
450
+ const anchor = preferredStructuredSymbol || resourceAnchoredSymbol || input.processSymbols[0] || firstMatchInQuery || input.definitions[0];
451
+ const symbolName = String(anchor?.name || '').trim();
452
+ const symbolFilePath = normalizePath(String(anchor?.filePath || '').trim());
453
+ return {
454
+ ...(symbolName ? { symbolName } : {}),
455
+ ...(symbolFilePath ? { symbolFilePath } : {}),
456
+ };
457
+ }
294
458
  export function buildNextHops(input) {
295
459
  const hops = [];
296
460
  const seen = new Set();
@@ -317,12 +481,12 @@ export function buildNextHops(input) {
317
481
  const shouldSuppressRawResourceHops = !input.seedPath
318
482
  && mappedIntersectBindings.length === 0
319
483
  && currentSymbolMatchesRetrievalScope === false;
320
- const candidateResources = shouldSuppressRawResourceHops ? [] : [
321
- ...mappedIntersectBindings,
322
- ...mappedRemainder,
323
- ...(input.seedPath ? [normalizePath(input.seedPath)] : []),
324
- ...bindingPaths,
325
- ].filter(Boolean);
484
+ const candidateResources = shouldSuppressRawResourceHops ? [] : rankCandidateResources([
485
+ ...(input.seedPath ? [{ target: normalizePath(input.seedPath), bucket: 0 }] : []),
486
+ ...mappedIntersectBindings.map((target) => ({ target, bucket: 1 })),
487
+ ...mappedRemainder.map((target) => ({ target, bucket: 2 })),
488
+ ...bindingPaths.map((target) => ({ target, bucket: 3 })),
489
+ ]);
326
490
  const repoArg = input.repoName ? ` --repo "${input.repoName}"` : '';
327
491
  const withRepoInCommand = (command) => {
328
492
  const trimmed = String(command || '').trim();
@@ -367,6 +531,54 @@ export function buildNextHops(input) {
367
531
  });
368
532
  return hops.slice(0, 5);
369
533
  }
534
+ function rankCandidateResources(candidates) {
535
+ const deduped = new Map();
536
+ for (const candidate of candidates) {
537
+ const target = normalizePath(String(candidate.target || '').trim());
538
+ if (!target)
539
+ continue;
540
+ const next = {
541
+ target,
542
+ bucket: candidate.bucket,
543
+ noisePenalty: scoreResourcePathNoise(target),
544
+ };
545
+ const existing = deduped.get(target);
546
+ if (!existing) {
547
+ deduped.set(target, next);
548
+ continue;
549
+ }
550
+ if (next.bucket < existing.bucket)
551
+ existing.bucket = next.bucket;
552
+ if (next.noisePenalty < existing.noisePenalty)
553
+ existing.noisePenalty = next.noisePenalty;
554
+ }
555
+ return [...deduped.values()]
556
+ .sort((a, b) => {
557
+ const bucketDiff = a.bucket - b.bucket;
558
+ if (bucketDiff !== 0)
559
+ return bucketDiff;
560
+ const noiseDiff = a.noisePenalty - b.noisePenalty;
561
+ if (noiseDiff !== 0)
562
+ return noiseDiff;
563
+ return a.target.localeCompare(b.target);
564
+ })
565
+ .map((entry) => entry.target);
566
+ }
567
+ function scoreResourcePathNoise(resourcePath) {
568
+ const haystack = normalizePath(String(resourcePath || '').trim()).toLowerCase();
569
+ let penalty = 0;
570
+ if (!haystack)
571
+ return penalty;
572
+ if (haystack.includes('/test') || haystack.includes('testgraphs'))
573
+ penalty += 5;
574
+ if (haystack.includes('debug'))
575
+ penalty += 4;
576
+ if (haystack.includes('测试'))
577
+ penalty += 6;
578
+ if (haystack.includes('标记'))
579
+ penalty += 6;
580
+ return penalty;
581
+ }
370
582
  async function resolveRetrievalRuleHint(input) {
371
583
  const bundle = await loadCompiledRuleBundle(input.repoPath, 'retrieval_rules');
372
584
  if (!bundle)
@@ -387,27 +599,35 @@ export function pickRetrievalRuleHintFromBundle(input) {
387
599
  const rank = (rule) => {
388
600
  let score = 0;
389
601
  let matchedTrigger = false;
602
+ let matchedEvidence = false;
390
603
  for (const token of rule.trigger_tokens || []) {
391
604
  const normalized = String(token || '').trim().toLowerCase();
392
605
  if (!normalized)
393
606
  continue;
394
607
  if (haystack.includes(normalized)) {
395
608
  matchedTrigger = true;
609
+ matchedEvidence = true;
396
610
  score += 10 + normalized.length;
397
611
  }
398
612
  }
399
- if (!matchedTrigger)
400
- return Number.NEGATIVE_INFINITY;
401
613
  for (const token of rule.host_base_type || []) {
402
614
  const normalized = String(token || '').trim().toLowerCase();
403
- if (normalized && haystack.includes(normalized))
615
+ if (normalized && haystack.includes(normalized)) {
616
+ matchedEvidence = true;
404
617
  score += 20 + normalized.length;
618
+ }
405
619
  }
406
620
  for (const token of rule.resource_types || []) {
407
621
  const normalized = String(token || '').trim().toLowerCase();
408
- if (normalized && haystack.includes(normalized))
622
+ if (normalized && haystack.includes(normalized)) {
623
+ matchedEvidence = true;
409
624
  score += 4 + normalized.length;
625
+ }
410
626
  }
627
+ if (!matchedEvidence)
628
+ return Number.NEGATIVE_INFINITY;
629
+ if (!matchedTrigger)
630
+ score -= 3;
411
631
  return score;
412
632
  };
413
633
  const matched = [...input.rules]
@@ -459,9 +679,7 @@ function aggregateRuntimeChainEvidenceLevel(rows) {
459
679
  function toProcessRefOrigin(mode) {
460
680
  if (mode === 'direct_step')
461
681
  return 'step_in_process';
462
- if (mode === 'method_projected')
463
- return 'method_projected';
464
- return 'resource_heuristic';
682
+ return 'method_projected';
465
683
  }
466
684
  function confidenceRank(confidence) {
467
685
  if (confidence === 'high')
@@ -854,8 +1072,6 @@ export class LocalBackend {
854
1072
  return this.detectChanges(repo, params);
855
1073
  case 'rename':
856
1074
  return this.rename(repo, params);
857
- case 'rule_lab_discover':
858
- return this.ruleLabDiscover(repo, params);
859
1075
  case 'rule_lab_analyze':
860
1076
  return this.ruleLabAnalyze(repo, params);
861
1077
  case 'rule_lab_review_pack':
@@ -902,25 +1118,6 @@ export class LocalBackend {
902
1118
  return { error: err?.message || 'unity_ui_trace failed' };
903
1119
  }
904
1120
  }
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
1121
  async ruleLabAnalyze(repo, params) {
925
1122
  const runId = String(params?.run_id || params?.runId || '').trim();
926
1123
  const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
@@ -1098,6 +1295,7 @@ export class LocalBackend {
1098
1295
  const evidenceBindingKind = String(params.binding_kind || '').trim() || undefined;
1099
1296
  const searchQuery = params.query.trim();
1100
1297
  const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
1298
+ const responseProfile = resolveResponseProfile(params.response_profile);
1101
1299
  let mappedSeedTargets = [];
1102
1300
  if (seedPath) {
1103
1301
  try {
@@ -1340,37 +1538,6 @@ export class LocalBackend {
1340
1538
  symbolEntry.evidence_meta = evidenceView.evidence_meta;
1341
1539
  symbolEntry.filter_diagnostics = evidenceView.filter_diagnostics;
1342
1540
  }
1343
- if (processRows.length === 0 && unityResourcesMode !== 'off') {
1344
- const resourceBindings = Array.isArray(symbolEntry.resourceBindings)
1345
- ? symbolEntry.resourceBindings
1346
- : [];
1347
- const needsParityRetry = Boolean(symbolEntry.hydrationMeta?.needsParityRetry);
1348
- const hasPartialUnityEvidence = resourceBindings.length > 0 || needsParityRetry;
1349
- if (hasPartialUnityEvidence) {
1350
- const verificationTarget = pickVerificationTarget({
1351
- seedMode: resourceSeedMode,
1352
- seedPath,
1353
- mappedSeedTargets,
1354
- resourceBindings,
1355
- fallback: String(sym.filePath || sym.name || sym.nodeId || ''),
1356
- });
1357
- processRows = mergeProcessEvidence({
1358
- directRows: [],
1359
- projectedRows: [],
1360
- heuristicRows: [{
1361
- pid: `proc:heuristic:${String(sym.nodeId || '').replace(/\s+/g, '_')}`,
1362
- label: `${String(sym.name || 'Symbol')} runtime heuristic clue`,
1363
- processType: 'unity_resource_heuristic',
1364
- processSubtype: 'unity_lifecycle',
1365
- runtimeChainConfidence: 'low',
1366
- step: 1,
1367
- stepCount: 1,
1368
- needsParityRetry,
1369
- verificationTarget,
1370
- }],
1371
- });
1372
- }
1373
- }
1374
1541
  if (processRows.length === 0) {
1375
1542
  // Symbol not in any process — goes to definitions
1376
1543
  definitions.push(symbolEntry);
@@ -1492,15 +1659,33 @@ export class LocalBackend {
1492
1659
  process_symbols: dedupedSymbols,
1493
1660
  definitions: definitions.slice(0, 20), // cap standalone definitions
1494
1661
  };
1662
+ if (unityResourcesMode !== 'off' && seedPath) {
1663
+ result.resource_chains = await loadSeedUnityResourceChains({
1664
+ repoId: repo.id,
1665
+ seedPath,
1666
+ targetSymbols: [
1667
+ exactResourceChainQuerySymbol(searchQuery),
1668
+ ...[...dedupedSymbols, ...definitions].map((row) => ({
1669
+ id: row?.id,
1670
+ name: row?.name,
1671
+ filePath: row?.filePath,
1672
+ })),
1673
+ ].filter(Boolean),
1674
+ });
1675
+ }
1495
1676
  const hydrationMetas = [...dedupedSymbols, ...definitions]
1496
1677
  .map((row) => row?.hydrationMeta)
1497
1678
  .filter(Boolean);
1498
1679
  if (hydrationMetas.length > 0) {
1499
1680
  result.hydrationMeta = hydrationMetas[0];
1500
1681
  }
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()))
1682
+ const verifierAnchor = pickVerifierSymbolAnchor({
1683
+ queryText: searchQuery,
1684
+ processSymbols: dedupedSymbols,
1685
+ definitions,
1686
+ });
1687
+ const firstSymbolForHops = dedupedSymbols.find((row) => String(row?.name || '') === verifierAnchor.symbolName)
1688
+ || definitions.find((row) => String(row?.name || '') === verifierAnchor.symbolName)
1504
1689
  || dedupedSymbols[0]
1505
1690
  || definitions[0];
1506
1691
  const firstVerificationHint = processes.find((row) => row?.verification_hint)?.verification_hint;
@@ -1523,6 +1708,12 @@ export class LocalBackend {
1523
1708
  symbolName: String(firstSymbolForHops?.name || searchQuery),
1524
1709
  queryForSymbol: String(firstSymbolForHops?.name || searchQuery),
1525
1710
  });
1711
+ const queryStrictAnchorMode = Boolean(evidenceResourcePathPrefix);
1712
+ result.decision_context = {
1713
+ strict_anchor_mode: queryStrictAnchorMode,
1714
+ anchor_symbol_name: String(firstSymbolForHops?.name || verifierAnchor.symbolName || searchQuery),
1715
+ anchor_resource_path: evidenceResourcePathPrefix || seedPath || mappedSeedTargets[0] || null,
1716
+ };
1526
1717
  const missingEvidenceRows = [...dedupedSymbols, ...definitions]
1527
1718
  .flatMap((row) => (Array.isArray(row?.missing_evidence) ? row.missing_evidence : []));
1528
1719
  result.missing_evidence = [...new Set(missingEvidenceRows)];
@@ -1555,7 +1746,11 @@ export class LocalBackend {
1555
1746
  minimum_evidence_satisfied: !explicitTrimRequested
1556
1747
  && extraBindingOmission === 0
1557
1748
  && evidenceMetaRows.every((row) => row.minimum_evidence_satisfied !== false),
1558
- verifier_minimum_evidence_satisfied: evidenceMetaRows.some((row) => row.verifier_minimum_evidence_satisfied !== false),
1749
+ verifier_minimum_evidence_satisfied: computeVerifierMinimumEvidenceSatisfied({
1750
+ evidenceMetaRows,
1751
+ truncated,
1752
+ filterExhausted,
1753
+ }),
1559
1754
  };
1560
1755
  if (filterDiagnostics.length > 0) {
1561
1756
  result.filter_diagnostics = [...new Set(filterDiagnostics)];
@@ -1578,20 +1773,20 @@ export class LocalBackend {
1578
1773
  result.runtime_claim = await verifyRuntimeClaimOnDemand({
1579
1774
  repoPath: repo.repoPath,
1580
1775
  executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
1581
- queryText: searchQuery,
1776
+ queryText: searchQuery || verifierAnchor.symbolName || seedPath,
1777
+ symbolName: verifierAnchor.symbolName,
1778
+ symbolFilePath: verifierAnchor.symbolFilePath,
1582
1779
  resourceSeedPath: seedPath,
1583
1780
  mappedSeedTargets,
1584
1781
  resourceBindings,
1585
- rulesRoot: path.join(repo.repoPath, '.gitnexus', 'rules'),
1586
- minimumEvidenceSatisfied: result.evidence_meta?.verifier_minimum_evidence_satisfied !== false,
1782
+ minimumEvidenceSatisfied: result.evidence_meta?.verifier_minimum_evidence_satisfied === true,
1587
1783
  });
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
- }
1784
+ if (result.runtime_claim) {
1785
+ result.runtime_claim = adjustRuntimeClaimForPolicy({
1786
+ claim: result.runtime_claim,
1787
+ hydrationPolicy,
1788
+ fallbackToCompact: Boolean(result.hydrationMeta?.fallbackToCompact),
1789
+ });
1595
1790
  }
1596
1791
  if (result.runtime_claim?.reason === 'rule_matched_but_evidence_missing'
1597
1792
  && (!Array.isArray(result.runtime_claim.gaps) || result.runtime_claim.gaps.length === 0)) {
@@ -1612,7 +1807,13 @@ export class LocalBackend {
1612
1807
  };
1613
1808
  }
1614
1809
  }
1615
- return result;
1810
+ if (responseProfile === 'full') {
1811
+ return result;
1812
+ }
1813
+ return buildSlimQueryResult(result, {
1814
+ repoName: repo.name,
1815
+ queryText: searchQuery,
1816
+ });
1616
1817
  }
1617
1818
  /**
1618
1819
  * BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
@@ -1893,6 +2094,7 @@ export class LocalBackend {
1893
2094
  await this.ensureInitialized(repo.id);
1894
2095
  const { name, uid, file_path, include_content } = params;
1895
2096
  const runtimeChainVerifyMode = String(params.runtime_chain_verify || 'off').trim().toLowerCase();
2097
+ const responseProfile = resolveResponseProfile(params.response_profile);
1896
2098
  const confidenceFieldsEnabled = true;
1897
2099
  let unityResourcesMode = 'off';
1898
2100
  let unityHydrationMode = 'compact';
@@ -2207,36 +2409,6 @@ export class LocalBackend {
2207
2409
  result.filter_diagnostics = evidenceView.filter_diagnostics;
2208
2410
  }
2209
2411
  }
2210
- if (processRows.length === 0) {
2211
- const resourceBindings = Array.isArray(result.resourceBindings)
2212
- ? result.resourceBindings
2213
- : [];
2214
- const needsParityRetry = Boolean(result.hydrationMeta?.needsParityRetry);
2215
- if (resourceBindings.length > 0 || needsParityRetry) {
2216
- const verificationTarget = pickVerificationTarget({
2217
- seedMode: resourceSeedMode,
2218
- seedPath,
2219
- mappedSeedTargets,
2220
- resourceBindings,
2221
- fallback: symFilePath || symName || symNodeId,
2222
- });
2223
- processRows = mergeProcessEvidence({
2224
- directRows: [],
2225
- projectedRows: [],
2226
- heuristicRows: [{
2227
- pid: `proc:heuristic:${String(symNodeId || '').replace(/\s+/g, '_')}`,
2228
- label: `${String(symName || 'Symbol')} runtime heuristic clue`,
2229
- processType: 'unity_resource_heuristic',
2230
- processSubtype: 'unity_lifecycle',
2231
- runtimeChainConfidence: 'low',
2232
- step: 1,
2233
- stepCount: 1,
2234
- needsParityRetry,
2235
- verificationTarget,
2236
- }],
2237
- });
2238
- }
2239
- }
2240
2412
  }
2241
2413
  result.processes = processRows.map((r) => {
2242
2414
  const rawPid = String(r.pid || r[0] || '');
@@ -2278,6 +2450,18 @@ export class LocalBackend {
2278
2450
  });
2279
2451
  const topVerificationHint = result.processes.find((row) => row?.verification_hint)?.verification_hint;
2280
2452
  const contextResourceBindings = Array.isArray(result.resourceBindings) ? result.resourceBindings : [];
2453
+ if (unityResourcesMode !== 'off' && seedPath) {
2454
+ result.resource_chains = await loadSeedUnityResourceChains({
2455
+ repoId: repo.id,
2456
+ seedPath,
2457
+ targetSymbols: [{
2458
+ id: symNodeId,
2459
+ name: symName,
2460
+ filePath: symFilePath,
2461
+ requireExact: true,
2462
+ }],
2463
+ });
2464
+ }
2281
2465
  const retrievalRule = await resolveRetrievalRuleHint({
2282
2466
  repoPath: repo.repoPath,
2283
2467
  queryText: name,
@@ -2294,26 +2478,30 @@ export class LocalBackend {
2294
2478
  symbolName: symName || String(name || uid || ''),
2295
2479
  queryForSymbol: symName || String(name || uid || ''),
2296
2480
  });
2481
+ const contextStrictAnchorMode = Boolean(uid || file_path || evidenceResourcePathPrefix);
2482
+ result.decision_context = {
2483
+ strict_anchor_mode: contextStrictAnchorMode,
2484
+ anchor_symbol_name: symName || String(name || uid || ''),
2485
+ anchor_resource_path: evidenceResourcePathPrefix || seedPath || mappedSeedTargets[0] || null,
2486
+ };
2297
2487
  if (runtimeChainVerifyMode === 'on-demand') {
2298
2488
  result.runtime_claim = await verifyRuntimeClaimOnDemand({
2299
2489
  repoPath: repo.repoPath,
2300
2490
  executeParameterized: (query, queryParams) => executeParameterized(repo.id, query, queryParams || {}),
2301
- queryText: name,
2491
+ queryText: name || symName || uid || seedPath,
2302
2492
  symbolName: symName,
2303
2493
  symbolFilePath: symFilePath,
2304
2494
  resourceSeedPath: seedPath,
2305
2495
  mappedSeedTargets,
2306
2496
  resourceBindings: Array.isArray(result.resourceBindings) ? result.resourceBindings : [],
2307
- rulesRoot: path.join(repo.repoPath, '.gitnexus', 'rules'),
2308
- minimumEvidenceSatisfied: result.evidence_meta?.minimum_evidence_satisfied !== false,
2497
+ minimumEvidenceSatisfied: result.evidence_meta?.verifier_minimum_evidence_satisfied === true,
2309
2498
  });
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
- }
2499
+ if (result.runtime_claim) {
2500
+ result.runtime_claim = adjustRuntimeClaimForPolicy({
2501
+ claim: result.runtime_claim,
2502
+ hydrationPolicy,
2503
+ fallbackToCompact: Boolean(result.hydrationMeta?.fallbackToCompact),
2504
+ });
2317
2505
  }
2318
2506
  if (result.runtime_claim?.reason === 'rule_matched_but_evidence_missing'
2319
2507
  && (!Array.isArray(result.runtime_claim.gaps) || result.runtime_claim.gaps.length === 0)) {
@@ -2334,7 +2522,13 @@ export class LocalBackend {
2334
2522
  };
2335
2523
  }
2336
2524
  }
2337
- return result;
2525
+ if (responseProfile === 'full') {
2526
+ return result;
2527
+ }
2528
+ return buildSlimContextResult(result, {
2529
+ repoName: repo.name,
2530
+ symbolName: symName || String(name || uid || ''),
2531
+ });
2338
2532
  }
2339
2533
  /**
2340
2534
  * Legacy explore — kept for backwards compatibility with resources.ts.
@@ -1,4 +1,4 @@
1
- export type ProcessEvidenceMode = 'direct_step' | 'method_projected' | 'resource_heuristic';
1
+ export type ProcessEvidenceMode = 'direct_step' | 'method_projected';
2
2
  export type ProcessConfidence = 'high' | 'medium' | 'low';
3
3
  export interface VerificationHint {
4
4
  action: 'rerun_parity_hydration' | 'manual_asset_meta_verification';
@@ -8,7 +8,6 @@ export interface VerificationHint {
8
8
  export interface DeriveConfidenceInput {
9
9
  evidenceMode: ProcessEvidenceMode;
10
10
  processSubtype?: string;
11
- hasPartialUnityEvidence?: boolean;
12
11
  }
13
12
  export interface BuildVerificationHintInput {
14
13
  confidence: ProcessConfidence;
@@ -1,7 +1,4 @@
1
1
  export function deriveConfidence(input) {
2
- if (input.evidenceMode === 'resource_heuristic') {
3
- return 'low';
4
- }
5
2
  if (input.evidenceMode === 'method_projected') {
6
3
  return 'medium';
7
4
  }