@veewo/gitnexus 1.4.11-rc.2 → 1.5.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +55 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +190 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.d.ts +22 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.js +100 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +27 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +118 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +16 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +60 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +331 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +42 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +4 -4
- package/dist/benchmark/unity-lazy-context-sampler.d.ts +6 -0
- package/dist/benchmark/unity-lazy-context-sampler.js +49 -13
- package/dist/benchmark/unity-lazy-context-sampler.test.js +4 -0
- package/dist/cli/ai-context.js +6 -1
- package/dist/cli/eval-server.js +0 -3
- package/dist/cli/index.js +8 -0
- package/dist/cli/mcp.js +0 -3
- package/dist/cli/rule-lab.d.ts +42 -0
- package/dist/cli/rule-lab.js +157 -0
- package/dist/cli/rule-lab.test.d.ts +1 -0
- package/dist/cli/rule-lab.test.js +11 -0
- package/dist/cli/tool.d.ts +7 -1
- package/dist/cli/tool.js +6 -0
- package/dist/core/config/unity-config.d.ts +20 -0
- package/dist/core/config/unity-config.js +46 -0
- package/dist/core/graph/types.d.ts +1 -1
- package/dist/core/ingestion/pipeline.js +38 -13
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +0 -2
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +26 -213
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +1 -1
- package/dist/core/ingestion/unity-resource-processor.js +87 -22
- package/dist/core/ingestion/unity-resource-processor.test.js +67 -2
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +11 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +179 -0
- package/dist/core/unity/options.d.ts +4 -0
- package/dist/core/unity/options.js +18 -0
- package/dist/core/unity/options.test.js +11 -1
- package/dist/core/unity/resolver.js +11 -1
- package/dist/core/unity/resolver.test.js +62 -0
- package/dist/core/unity/yaml-object-graph.js +1 -1
- package/dist/core/unity/yaml-object-graph.test.js +16 -0
- package/dist/mcp/local/derived-process-reader.d.ts +2 -0
- package/dist/mcp/local/derived-process-reader.js +15 -0
- package/dist/mcp/local/local-backend.d.ts +56 -0
- package/dist/mcp/local/local-backend.js +1003 -53
- package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
- package/dist/mcp/local/process-confidence.js +1 -1
- package/dist/mcp/local/process-evidence.d.ts +1 -0
- package/dist/mcp/local/process-evidence.js +22 -0
- package/dist/mcp/local/process-evidence.test.js +11 -1
- package/dist/mcp/local/process-ref.d.ts +24 -0
- package/dist/mcp/local/process-ref.js +33 -0
- package/dist/mcp/local/process-ref.test.d.ts +1 -0
- package/dist/mcp/local/process-ref.test.js +24 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +15 -1
- package/dist/mcp/local/runtime-chain-verify.js +191 -187
- package/dist/mcp/local/runtime-chain-verify.test.js +546 -19
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +63 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +308 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +215 -0
- package/dist/mcp/local/runtime-claim.d.ts +38 -0
- package/dist/mcp/local/runtime-claim.js +54 -0
- package/dist/mcp/local/runtime-claim.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim.test.js +27 -0
- package/dist/mcp/local/unity-enrichment.d.ts +1 -0
- package/dist/mcp/local/unity-enrichment.js +1 -1
- package/dist/mcp/local/unity-evidence-view.d.ts +26 -0
- package/dist/mcp/local/unity-evidence-view.js +96 -0
- package/dist/mcp/local/unity-evidence-view.test.d.ts +1 -0
- package/dist/mcp/local/unity-evidence-view.test.js +39 -0
- package/dist/mcp/local/unity-lazy-hydrator.d.ts +2 -2
- package/dist/mcp/local/unity-lazy-hydrator.js +3 -3
- package/dist/mcp/local/unity-lazy-hydrator.test.js +4 -4
- package/dist/mcp/local/unity-parity-cache.js +2 -6
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +1 -0
- package/dist/mcp/local/unity-parity-seed-loader.js +10 -16
- package/dist/mcp/local/unity-parity-seed-loader.test.js +3 -12
- package/dist/mcp/local/unity-runtime-hydration.d.ts +3 -2
- package/dist/mcp/local/unity-runtime-hydration.js +13 -16
- package/dist/mcp/local/unity-runtime-hydration.test.js +15 -1
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/tools.js +166 -13
- package/dist/rule-lab/analyze.d.ts +12 -0
- package/dist/rule-lab/analyze.js +90 -0
- package/dist/rule-lab/analyze.test.d.ts +1 -0
- package/dist/rule-lab/analyze.test.js +28 -0
- package/dist/rule-lab/compile.d.ts +5 -0
- package/dist/rule-lab/compile.js +51 -0
- package/dist/rule-lab/compiled-bundles.d.ts +30 -0
- package/dist/rule-lab/compiled-bundles.js +36 -0
- package/dist/rule-lab/curate.d.ts +32 -0
- package/dist/rule-lab/curate.js +134 -0
- package/dist/rule-lab/curate.test.d.ts +1 -0
- package/dist/rule-lab/curate.test.js +72 -0
- package/dist/rule-lab/discover.d.ts +13 -0
- package/dist/rule-lab/discover.js +74 -0
- package/dist/rule-lab/discover.test.d.ts +1 -0
- package/dist/rule-lab/discover.test.js +42 -0
- package/dist/rule-lab/paths.d.ts +21 -0
- package/dist/rule-lab/paths.js +37 -0
- package/dist/rule-lab/paths.test.d.ts +1 -0
- package/dist/rule-lab/paths.test.js +46 -0
- package/dist/rule-lab/promote.d.ts +26 -0
- package/dist/rule-lab/promote.js +314 -0
- package/dist/rule-lab/promote.test.d.ts +1 -0
- package/dist/rule-lab/promote.test.js +164 -0
- package/dist/rule-lab/regress.d.ts +60 -0
- package/dist/rule-lab/regress.js +122 -0
- package/dist/rule-lab/regress.test.d.ts +1 -0
- package/dist/rule-lab/regress.test.js +68 -0
- package/dist/rule-lab/review-pack.d.ts +31 -0
- package/dist/rule-lab/review-pack.js +125 -0
- package/dist/rule-lab/review-pack.test.d.ts +1 -0
- package/dist/rule-lab/review-pack.test.js +49 -0
- package/dist/rule-lab/types.d.ts +99 -0
- package/dist/rule-lab/types.js +1 -0
- package/package.json +1 -1
- package/skills/_shared/unity-hydration-contract.md +11 -0
- package/skills/_shared/unity-ui-trace-contract.md +33 -0
- package/skills/gitnexus-cli.md +11 -25
- package/skills/gitnexus-guide.md +2 -0
- package/skills/gitnexus-unity-rule-gen.md +318 -0
- package/dist/core/ingestion/unity-lifecycle-config.d.ts +0 -5
- package/dist/core/ingestion/unity-lifecycle-config.js +0 -25
- package/dist/mcp/local/unity-lazy-config.d.ts +0 -6
- package/dist/mcp/local/unity-lazy-config.js +0 -7
- package/dist/mcp/local/unity-lazy-config.test.js +0 -9
- package/dist/mcp/local/unity-process-confidence-config.d.ts +0 -1
- package/dist/mcp/local/unity-process-confidence-config.js +0 -4
- package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +0 -1
- package/dist/mcp/local/unity-runtime-chain-verify-config.js +0 -10
- /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 {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
type
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
727
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
1324
|
-
|
|
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
|
|
1351
|
-
|
|
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
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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
|
|
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
|