@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.
- package/dist/benchmark/agent-context/runner.js +3 -0
- package/dist/benchmark/agent-context/runner.test.js +22 -0
- package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
- package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
- package/dist/benchmark/agent-safe-query-context/io.js +86 -0
- package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
- package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
- package/dist/benchmark/agent-safe-query-context/report.js +159 -0
- package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
- package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
- package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
- package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
- package/dist/benchmark/agent-safe-query-context/types.js +8 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
- package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
- package/dist/benchmark/runtime-poc/runner.js +163 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
- package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
- package/dist/cli/ai-context.js +2 -12
- package/dist/cli/ai-context.test.js +8 -0
- package/dist/cli/analyze-runtime-summary.js +1 -0
- package/dist/cli/analyze-runtime-summary.test.js +2 -0
- package/dist/cli/analyze-summary.d.ts +2 -0
- package/dist/cli/analyze-summary.js +24 -0
- package/dist/cli/analyze-summary.test.js +65 -1
- package/dist/cli/analyze.js +5 -1
- package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
- package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
- package/dist/cli/benchmark.d.ts +29 -0
- package/dist/cli/benchmark.js +55 -0
- package/dist/cli/index.js +23 -0
- package/dist/cli/rule-lab.d.ts +3 -7
- package/dist/cli/rule-lab.js +13 -22
- package/dist/cli/rule-lab.test.js +23 -3
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +2 -0
- package/dist/core/config/unity-config.d.ts +0 -1
- package/dist/core/config/unity-config.js +0 -1
- package/dist/core/ingestion/pipeline.js +35 -6
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
- package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
- package/dist/core/ingestion/unity-parity-seed.js +8 -0
- package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
- package/dist/core/ingestion/unity-resource-processor.js +102 -0
- package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +15 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +178 -30
- package/dist/core/lbug/csv-generator.test.js +2 -2
- package/dist/core/unity/doc-contract.test.d.ts +1 -0
- package/dist/core/unity/doc-contract.test.js +30 -0
- package/dist/core/unity/prefab-source-scan.d.ts +25 -0
- package/dist/core/unity/prefab-source-scan.js +152 -0
- package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
- package/dist/core/unity/prefab-source-scan.test.js +70 -0
- package/dist/core/unity/scan-context.d.ts +12 -0
- package/dist/core/unity/scan-context.js +50 -2
- package/dist/core/unity/scan-context.test.js +74 -0
- package/dist/mcp/local/agent-safe-response.d.ts +10 -0
- package/dist/mcp/local/agent-safe-response.js +639 -0
- package/dist/mcp/local/derived-process-reader.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +18 -1
- package/dist/mcp/local/local-backend.js +319 -125
- package/dist/mcp/local/process-confidence.d.ts +1 -2
- package/dist/mcp/local/process-confidence.js +0 -3
- package/dist/mcp/local/process-confidence.test.js +4 -2
- package/dist/mcp/local/process-evidence.d.ts +1 -8
- package/dist/mcp/local/process-evidence.js +1 -23
- package/dist/mcp/local/process-evidence.test.js +2 -16
- package/dist/mcp/local/process-ref.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
- package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-verify.js +149 -138
- package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
- package/dist/mcp/local/runtime-claim.d.ts +11 -0
- package/dist/mcp/local/runtime-claim.js +28 -0
- package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
- package/dist/mcp/local/unity-evidence-view.js +1 -1
- package/dist/mcp/local/unity-evidence-view.test.js +22 -0
- package/dist/mcp/tools.js +51 -21
- package/dist/rule-lab/analyze.d.ts +2 -1
- package/dist/rule-lab/analyze.js +94 -59
- package/dist/rule-lab/analyze.test.js +238 -20
- package/dist/rule-lab/curate.d.ts +2 -1
- package/dist/rule-lab/curate.js +24 -3
- package/dist/rule-lab/curate.test.js +65 -0
- package/dist/rule-lab/curation-input-builder.d.ts +45 -0
- package/dist/rule-lab/curation-input-builder.js +133 -0
- package/dist/rule-lab/promote.js +80 -7
- package/dist/rule-lab/promote.test.js +150 -0
- package/dist/rule-lab/review-pack.d.ts +3 -0
- package/dist/rule-lab/review-pack.js +41 -1
- package/dist/rule-lab/review-pack.test.js +67 -0
- package/dist/rule-lab/types.d.ts +29 -0
- package/dist/types/pipeline.d.ts +3 -0
- package/package.json +4 -3
- package/scripts/run-node-tests.mjs +61 -0
- package/skills/_shared/unity-rule-authoring-contract.md +64 -0
- package/skills/_shared/unity-runtime-process-contract.md +16 -0
- package/skills/gitnexus-cli.md +8 -0
- package/skills/gitnexus-debugging.md +9 -0
- package/skills/gitnexus-exploring.md +66 -18
- package/skills/gitnexus-guide.md +42 -3
- package/skills/gitnexus-impact-analysis.md +8 -0
- package/skills/gitnexus-pr-review.md +8 -0
- package/skills/gitnexus-refactoring.md +8 -0
- package/skills/gitnexus-unity-rule-gen.md +66 -312
|
@@ -11,8 +11,10 @@ describe('process confidence', () => {
|
|
|
11
11
|
it('deriveConfidence returns medium for method projected rows', () => {
|
|
12
12
|
expect(deriveConfidence({ evidenceMode: 'method_projected' })).toBe('medium');
|
|
13
13
|
});
|
|
14
|
-
it('
|
|
15
|
-
expect(deriveConfidence({ evidenceMode: '
|
|
14
|
+
it('rejects legacy heuristic evidence mode at type/runtime boundary', () => {
|
|
15
|
+
expect(deriveConfidence({ evidenceMode: 'method_projected' })).toBe('medium');
|
|
16
|
+
// @ts-expect-error legacy mode removed
|
|
17
|
+
deriveConfidence({ evidenceMode: 'resource_heuristic' });
|
|
16
18
|
});
|
|
17
19
|
it('buildVerificationHint includes parity retry guidance for low confidence rows', () => {
|
|
18
20
|
const hint = buildVerificationHint({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ProcessConfidence, type ProcessEvidenceMode
|
|
1
|
+
import { type ProcessConfidence, type ProcessEvidenceMode } from './process-confidence.js';
|
|
2
2
|
import { type RuntimeChainEvidenceLevel } from './runtime-chain-evidence.js';
|
|
3
3
|
export interface ProcessEvidenceRow {
|
|
4
4
|
pid: string;
|
|
@@ -10,20 +10,13 @@ export interface ProcessEvidenceRow {
|
|
|
10
10
|
export interface ProjectedProcessEvidenceRow extends ProcessEvidenceRow {
|
|
11
11
|
viaMethodId?: string;
|
|
12
12
|
}
|
|
13
|
-
export interface HeuristicProcessEvidenceRow extends ProcessEvidenceRow {
|
|
14
|
-
processSubtype?: string;
|
|
15
|
-
needsParityRetry?: boolean;
|
|
16
|
-
verificationTarget?: string;
|
|
17
|
-
}
|
|
18
13
|
export interface MergedProcessEvidenceRow extends ProcessEvidenceRow {
|
|
19
14
|
evidence_mode: ProcessEvidenceMode;
|
|
20
15
|
confidence: ProcessConfidence;
|
|
21
16
|
runtime_chain_evidence_level: RuntimeChainEvidenceLevel;
|
|
22
|
-
verification_hint?: VerificationHint;
|
|
23
17
|
}
|
|
24
18
|
export declare function deriveEvidenceFingerprint(...parts: unknown[]): string;
|
|
25
19
|
export declare function mergeProcessEvidence(input: {
|
|
26
20
|
directRows: ProcessEvidenceRow[];
|
|
27
21
|
projectedRows: ProjectedProcessEvidenceRow[];
|
|
28
|
-
heuristicRows?: HeuristicProcessEvidenceRow[];
|
|
29
22
|
}): MergedProcessEvidenceRow[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveConfidence, } from './process-confidence.js';
|
|
2
2
|
import { deriveRuntimeChainEvidenceLevel, } from './runtime-chain-evidence.js';
|
|
3
3
|
function asFingerprintToken(value) {
|
|
4
4
|
if (value === null || value === undefined)
|
|
@@ -29,28 +29,6 @@ const normalizeProcessConfidence = (raw, fallback) => {
|
|
|
29
29
|
};
|
|
30
30
|
export function mergeProcessEvidence(input) {
|
|
31
31
|
const byPid = new Map();
|
|
32
|
-
for (const row of input.heuristicRows || []) {
|
|
33
|
-
const confidence = deriveConfidence({
|
|
34
|
-
evidenceMode: 'resource_heuristic',
|
|
35
|
-
processSubtype: String(row.processSubtype || ''),
|
|
36
|
-
hasPartialUnityEvidence: true,
|
|
37
|
-
});
|
|
38
|
-
byPid.set(row.pid, {
|
|
39
|
-
...row,
|
|
40
|
-
pid: row.pid,
|
|
41
|
-
label: row.label,
|
|
42
|
-
step: row.step,
|
|
43
|
-
stepCount: row.stepCount,
|
|
44
|
-
evidence_mode: 'resource_heuristic',
|
|
45
|
-
confidence,
|
|
46
|
-
runtime_chain_evidence_level: deriveRuntimeChainEvidenceLevel({ mode: 'heuristic_clue' }),
|
|
47
|
-
verification_hint: buildVerificationHint({
|
|
48
|
-
confidence,
|
|
49
|
-
needsParityRetry: Boolean(row.needsParityRetry),
|
|
50
|
-
target: row.verificationTarget || row.label || row.pid,
|
|
51
|
-
}),
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
32
|
for (const row of input.projectedRows) {
|
|
55
33
|
byPid.set(row.pid, {
|
|
56
34
|
...row,
|
|
@@ -33,26 +33,12 @@ test('direct rows dominate projected rows for same process id', () => {
|
|
|
33
33
|
assert.equal(out[0].evidence_mode, 'direct_step');
|
|
34
34
|
assert.equal(out[0].confidence, 'high');
|
|
35
35
|
});
|
|
36
|
-
test('
|
|
36
|
+
test('mergeProcessEvidence never emits resource_heuristic rows', () => {
|
|
37
37
|
const out = mergeProcessEvidence({
|
|
38
38
|
directRows: [],
|
|
39
39
|
projectedRows: [],
|
|
40
|
-
heuristicRows: [
|
|
41
|
-
{
|
|
42
|
-
pid: 'proc:reload-clue',
|
|
43
|
-
label: 'Reload runtime clue',
|
|
44
|
-
step: 0,
|
|
45
|
-
stepCount: 0,
|
|
46
|
-
processSubtype: 'unity_lifecycle',
|
|
47
|
-
needsParityRetry: true,
|
|
48
|
-
verificationTarget: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
40
|
});
|
|
52
|
-
assert.equal(out
|
|
53
|
-
assert.equal(out[0].confidence, 'low');
|
|
54
|
-
assert.equal(out[0].verification_hint?.action, 'rerun_parity_hydration');
|
|
55
|
-
assert.match(out[0].verification_hint?.next_command || '', /parity/i);
|
|
41
|
+
assert.equal(out.some((row) => String(row.evidence_mode) === 'resource_heuristic'), false);
|
|
56
42
|
});
|
|
57
43
|
test('deriveEvidenceFingerprint is stable for same input ordering', () => {
|
|
58
44
|
const left = deriveEvidenceFingerprint({ resourcePath: 'Assets/A.prefab', bindingKind: 'component', line: 10 }, { pid: 'proc:123', step: 1 });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ProcessRefKind = 'persistent' | 'derived';
|
|
2
|
-
export type ProcessRefOrigin = 'step_in_process' | 'method_projected'
|
|
2
|
+
export type ProcessRefOrigin = 'step_in_process' | 'method_projected';
|
|
3
3
|
export interface ProcessRef {
|
|
4
4
|
id: string;
|
|
5
5
|
kind: ProcessRefKind;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RuntimeGraphCandidate } from './runtime-chain-graph-candidates.js';
|
|
2
|
+
type RuntimeChainStatus = 'pending' | 'verified_partial' | 'verified_full' | 'failed';
|
|
3
|
+
type RuntimeChainEvidenceLevel = 'none' | 'clue' | 'verified_segment' | 'verified_chain';
|
|
4
|
+
interface RuntimeChainGap {
|
|
5
|
+
segment: 'resource' | 'guid_map' | 'loader' | 'runtime';
|
|
6
|
+
reason: string;
|
|
7
|
+
next_command: string;
|
|
8
|
+
why_not_next?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RuntimeClosureEvaluation {
|
|
11
|
+
status: RuntimeChainStatus;
|
|
12
|
+
evidence_level: RuntimeChainEvidenceLevel;
|
|
13
|
+
gaps: RuntimeChainGap[];
|
|
14
|
+
segments: {
|
|
15
|
+
anchor: boolean;
|
|
16
|
+
bind: boolean;
|
|
17
|
+
bridge: boolean;
|
|
18
|
+
runtime: boolean;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface EvaluateRuntimeClosureInput {
|
|
22
|
+
queryText?: string;
|
|
23
|
+
symbolName?: string;
|
|
24
|
+
resourceSeedPath?: string;
|
|
25
|
+
mappedSeedTargets?: string[];
|
|
26
|
+
resourceBindings?: Array<{
|
|
27
|
+
resourcePath?: string;
|
|
28
|
+
}>;
|
|
29
|
+
candidates: RuntimeGraphCandidate[];
|
|
30
|
+
nextCommand: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function evaluateRuntimeClosure(input: EvaluateRuntimeClosureInput): RuntimeClosureEvaluation;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
function normalize(value) {
|
|
2
|
+
return String(value || '').trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
function tokenize(value) {
|
|
5
|
+
return normalize(value)
|
|
6
|
+
.split(/[^a-z0-9]+/)
|
|
7
|
+
.map((token) => token.trim())
|
|
8
|
+
.filter((token) => token.length > 0);
|
|
9
|
+
}
|
|
10
|
+
function normalizeLoose(value) {
|
|
11
|
+
return normalize(value).replace(/[^a-z0-9]+/g, '');
|
|
12
|
+
}
|
|
13
|
+
function basenameStem(value) {
|
|
14
|
+
const text = String(value || '').trim();
|
|
15
|
+
if (!text)
|
|
16
|
+
return '';
|
|
17
|
+
const parts = text.split(/[\\/]/);
|
|
18
|
+
const base = parts[parts.length - 1] || '';
|
|
19
|
+
const stem = base.replace(/\.[^.]+$/, '');
|
|
20
|
+
return normalize(stem);
|
|
21
|
+
}
|
|
22
|
+
function isBridgeCandidate(candidate) {
|
|
23
|
+
const reason = normalize(candidate.reason);
|
|
24
|
+
return reason.includes('bridge') || reason.startsWith('unity-rule-');
|
|
25
|
+
}
|
|
26
|
+
function isAnchoredEndpoint(input, endpoint) {
|
|
27
|
+
const symbolName = normalize(input.symbolName);
|
|
28
|
+
if (!symbolName)
|
|
29
|
+
return false;
|
|
30
|
+
const looseSymbol = normalizeLoose(symbolName);
|
|
31
|
+
if (normalize(endpoint.name) === symbolName)
|
|
32
|
+
return true;
|
|
33
|
+
const anchoredNeighborhood = [endpoint.id, endpoint.filePath];
|
|
34
|
+
return anchoredNeighborhood.some((value) => normalizeLoose(value).includes(looseSymbol));
|
|
35
|
+
}
|
|
36
|
+
function candidateNodeKey(endpoint) {
|
|
37
|
+
const id = normalize(endpoint.id);
|
|
38
|
+
if (id)
|
|
39
|
+
return `id:${id}`;
|
|
40
|
+
const name = normalize(endpoint.name);
|
|
41
|
+
if (!name)
|
|
42
|
+
return '';
|
|
43
|
+
const filePath = normalize(endpoint.filePath);
|
|
44
|
+
return filePath ? `nf:${name}@${filePath}` : `n:${name}`;
|
|
45
|
+
}
|
|
46
|
+
function collectAnchorNodeKeys(input) {
|
|
47
|
+
const anchorKeys = new Set();
|
|
48
|
+
for (const candidate of input.candidates) {
|
|
49
|
+
const sourceKey = candidateNodeKey({
|
|
50
|
+
id: candidate.sourceId,
|
|
51
|
+
name: candidate.sourceName,
|
|
52
|
+
filePath: candidate.sourceFilePath,
|
|
53
|
+
});
|
|
54
|
+
const targetKey = candidateNodeKey({
|
|
55
|
+
id: candidate.targetId,
|
|
56
|
+
name: candidate.targetName,
|
|
57
|
+
filePath: candidate.targetFilePath,
|
|
58
|
+
});
|
|
59
|
+
if (sourceKey
|
|
60
|
+
&& isAnchoredEndpoint(input, {
|
|
61
|
+
name: candidate.sourceName,
|
|
62
|
+
id: candidate.sourceId,
|
|
63
|
+
filePath: candidate.sourceFilePath,
|
|
64
|
+
})) {
|
|
65
|
+
anchorKeys.add(sourceKey);
|
|
66
|
+
}
|
|
67
|
+
if (targetKey
|
|
68
|
+
&& isAnchoredEndpoint(input, {
|
|
69
|
+
name: candidate.targetName,
|
|
70
|
+
id: candidate.targetId,
|
|
71
|
+
filePath: candidate.targetFilePath,
|
|
72
|
+
})) {
|
|
73
|
+
anchorKeys.add(targetKey);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return anchorKeys;
|
|
77
|
+
}
|
|
78
|
+
function evaluateAnchorSegment(input) {
|
|
79
|
+
return collectAnchorNodeKeys(input).size > 0;
|
|
80
|
+
}
|
|
81
|
+
function evaluateBindSegment(input) {
|
|
82
|
+
const seedPath = normalize(input.resourceSeedPath);
|
|
83
|
+
if (!seedPath)
|
|
84
|
+
return false;
|
|
85
|
+
const mapped = new Set((input.mappedSeedTargets || []).map((value) => normalize(value)).filter(Boolean));
|
|
86
|
+
const bindings = new Set((input.resourceBindings || []).map((binding) => normalize(binding.resourcePath)).filter(Boolean));
|
|
87
|
+
if (bindings.has(seedPath))
|
|
88
|
+
return true;
|
|
89
|
+
if (mapped.size === 0)
|
|
90
|
+
return false;
|
|
91
|
+
const mappedStems = new Set(Array.from(mapped).map((value) => basenameStem(value)).filter(Boolean));
|
|
92
|
+
for (const binding of bindings) {
|
|
93
|
+
if (mapped.has(binding))
|
|
94
|
+
return true;
|
|
95
|
+
const bindingStem = basenameStem(binding);
|
|
96
|
+
if (bindingStem && mappedStems.has(bindingStem))
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
function evaluateBridgeSegment(input, anchorSatisfied) {
|
|
102
|
+
if (!anchorSatisfied)
|
|
103
|
+
return false;
|
|
104
|
+
return input.candidates.some((candidate) => isBridgeCandidate(candidate));
|
|
105
|
+
}
|
|
106
|
+
function evaluateRuntimeSegment(input) {
|
|
107
|
+
const anchorKeys = collectAnchorNodeKeys(input);
|
|
108
|
+
if (anchorKeys.size === 0)
|
|
109
|
+
return false;
|
|
110
|
+
const adjacency = new Map();
|
|
111
|
+
const edgeByPair = new Map();
|
|
112
|
+
for (const candidate of input.candidates) {
|
|
113
|
+
const sourceKey = candidateNodeKey({
|
|
114
|
+
id: candidate.sourceId,
|
|
115
|
+
name: candidate.sourceName,
|
|
116
|
+
filePath: candidate.sourceFilePath,
|
|
117
|
+
});
|
|
118
|
+
const targetKey = candidateNodeKey({
|
|
119
|
+
id: candidate.targetId,
|
|
120
|
+
name: candidate.targetName,
|
|
121
|
+
filePath: candidate.targetFilePath,
|
|
122
|
+
});
|
|
123
|
+
if (!sourceKey || !targetKey)
|
|
124
|
+
continue;
|
|
125
|
+
const sourceNeighbors = adjacency.get(sourceKey) || new Set();
|
|
126
|
+
sourceNeighbors.add(targetKey);
|
|
127
|
+
adjacency.set(sourceKey, sourceNeighbors);
|
|
128
|
+
const targetNeighbors = adjacency.get(targetKey) || new Set();
|
|
129
|
+
targetNeighbors.add(sourceKey);
|
|
130
|
+
adjacency.set(targetKey, targetNeighbors);
|
|
131
|
+
const pair = [sourceKey, targetKey].sort().join('||');
|
|
132
|
+
const existing = edgeByPair.get(pair) || { bridge: false, runtime: false };
|
|
133
|
+
const bridge = isBridgeCandidate(candidate);
|
|
134
|
+
edgeByPair.set(pair, {
|
|
135
|
+
bridge: existing.bridge || bridge,
|
|
136
|
+
runtime: existing.runtime || !bridge,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const seenNodes = new Set();
|
|
140
|
+
for (const node of anchorKeys) {
|
|
141
|
+
if (seenNodes.has(node))
|
|
142
|
+
continue;
|
|
143
|
+
const queue = [node];
|
|
144
|
+
seenNodes.add(node);
|
|
145
|
+
const componentNodes = [];
|
|
146
|
+
while (queue.length > 0) {
|
|
147
|
+
const current = queue.shift();
|
|
148
|
+
if (!current)
|
|
149
|
+
break;
|
|
150
|
+
componentNodes.push(current);
|
|
151
|
+
const neighbors = adjacency.get(current) || new Set();
|
|
152
|
+
for (const neighbor of neighbors) {
|
|
153
|
+
if (seenNodes.has(neighbor))
|
|
154
|
+
continue;
|
|
155
|
+
seenNodes.add(neighbor);
|
|
156
|
+
queue.push(neighbor);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let hasBridge = false;
|
|
160
|
+
let hasRuntime = false;
|
|
161
|
+
for (const componentNode of componentNodes) {
|
|
162
|
+
const neighbors = adjacency.get(componentNode) || new Set();
|
|
163
|
+
for (const neighbor of neighbors) {
|
|
164
|
+
const pair = [componentNode, neighbor].sort().join('||');
|
|
165
|
+
const edge = edgeByPair.get(pair);
|
|
166
|
+
if (!edge)
|
|
167
|
+
continue;
|
|
168
|
+
if (edge.bridge)
|
|
169
|
+
hasBridge = true;
|
|
170
|
+
if (edge.runtime)
|
|
171
|
+
hasRuntime = true;
|
|
172
|
+
}
|
|
173
|
+
if (hasBridge && hasRuntime)
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
function hasAnchorIntersection(input) {
|
|
180
|
+
const symbolTokens = new Set(tokenize(input.symbolName));
|
|
181
|
+
if (symbolTokens.size === 0)
|
|
182
|
+
return false;
|
|
183
|
+
const resourceTokens = new Set([
|
|
184
|
+
...tokenize(input.resourceSeedPath),
|
|
185
|
+
...(input.mappedSeedTargets || []).flatMap((value) => tokenize(value)),
|
|
186
|
+
...(input.resourceBindings || []).flatMap((binding) => tokenize(binding.resourcePath)),
|
|
187
|
+
]);
|
|
188
|
+
if (resourceTokens.size === 0)
|
|
189
|
+
return false;
|
|
190
|
+
for (const token of symbolTokens) {
|
|
191
|
+
if (resourceTokens.has(token))
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function hasUbiquitousRuntimeSignal(input) {
|
|
197
|
+
const ubiquitous = new Set(['getcomponent', 'awake', 'start', 'update', 'lateupdate', 'fixedupdate', 'onenable']);
|
|
198
|
+
return input.candidates.some((candidate) => {
|
|
199
|
+
const source = normalize(candidate.sourceName);
|
|
200
|
+
const target = normalize(candidate.targetName);
|
|
201
|
+
return ubiquitous.has(source) || ubiquitous.has(target);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
export function evaluateRuntimeClosure(input) {
|
|
205
|
+
const anchor = evaluateAnchorSegment(input);
|
|
206
|
+
const bind = evaluateBindSegment(input);
|
|
207
|
+
const bridge = evaluateBridgeSegment(input, anchor);
|
|
208
|
+
const runtime = evaluateRuntimeSegment(input);
|
|
209
|
+
const anchorIntersection = hasAnchorIntersection(input);
|
|
210
|
+
const ubiquitousRuntimeSignal = hasUbiquitousRuntimeSignal(input);
|
|
211
|
+
const segments = { anchor, bind, bridge, runtime };
|
|
212
|
+
const allSatisfied = anchor && bind && bridge && runtime;
|
|
213
|
+
const precisionPenalty = allSatisfied && !anchorIntersection && ubiquitousRuntimeSignal;
|
|
214
|
+
const anySatisfied = anchor || bind || bridge || runtime;
|
|
215
|
+
const gaps = [];
|
|
216
|
+
if (!anchor) {
|
|
217
|
+
gaps.push({
|
|
218
|
+
segment: 'loader',
|
|
219
|
+
reason: 'anchor segment missing',
|
|
220
|
+
next_command: input.nextCommand,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (!bind) {
|
|
224
|
+
gaps.push({
|
|
225
|
+
segment: 'guid_map',
|
|
226
|
+
reason: 'bind segment missing',
|
|
227
|
+
next_command: input.nextCommand,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (!bridge) {
|
|
231
|
+
gaps.push({
|
|
232
|
+
segment: 'loader',
|
|
233
|
+
reason: 'bridge segment missing',
|
|
234
|
+
next_command: input.nextCommand,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (!runtime) {
|
|
238
|
+
gaps.push({
|
|
239
|
+
segment: 'runtime',
|
|
240
|
+
reason: 'runtime segment missing',
|
|
241
|
+
next_command: input.nextCommand,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (precisionPenalty) {
|
|
245
|
+
gaps.push({
|
|
246
|
+
segment: 'loader',
|
|
247
|
+
reason: 'anchor intersection absent; downgraded for precision-first policy',
|
|
248
|
+
next_command: input.nextCommand,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (allSatisfied && !precisionPenalty) {
|
|
252
|
+
return {
|
|
253
|
+
status: 'verified_full',
|
|
254
|
+
evidence_level: 'verified_chain',
|
|
255
|
+
gaps: [],
|
|
256
|
+
segments,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (anySatisfied) {
|
|
260
|
+
return {
|
|
261
|
+
status: 'verified_partial',
|
|
262
|
+
evidence_level: 'verified_segment',
|
|
263
|
+
gaps,
|
|
264
|
+
segments,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
status: 'failed',
|
|
269
|
+
evidence_level: 'none',
|
|
270
|
+
gaps,
|
|
271
|
+
segments,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface QueryExecutor {
|
|
2
|
+
(query: string, params?: Record<string, unknown>): Promise<any[]>;
|
|
3
|
+
}
|
|
4
|
+
interface GraphCandidateInput {
|
|
5
|
+
executeParameterized: QueryExecutor;
|
|
6
|
+
symbolName?: string;
|
|
7
|
+
symbolFilePath?: string;
|
|
8
|
+
maxSymbols?: number;
|
|
9
|
+
maxEdgesPerSymbol?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RuntimeGraphCandidate {
|
|
12
|
+
sourceId?: string;
|
|
13
|
+
sourceName: string;
|
|
14
|
+
sourceFilePath?: string;
|
|
15
|
+
sourceStartLine?: number;
|
|
16
|
+
targetId?: string;
|
|
17
|
+
targetName: string;
|
|
18
|
+
targetFilePath?: string;
|
|
19
|
+
targetStartLine?: number;
|
|
20
|
+
reason?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function extractRuntimeGraphCandidates(input: GraphCandidateInput): Promise<RuntimeGraphCandidate[]>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
function normalizeName(value) {
|
|
2
|
+
return String(value || '').trim();
|
|
3
|
+
}
|
|
4
|
+
function dedupeCandidates(candidates) {
|
|
5
|
+
const out = [];
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
for (const candidate of candidates) {
|
|
8
|
+
const source = normalizeName(candidate.sourceId) || `${normalizeName(candidate.sourceName)}@${normalizeName(candidate.sourceFilePath)}`;
|
|
9
|
+
const target = normalizeName(candidate.targetId) || `${normalizeName(candidate.targetName)}@${normalizeName(candidate.targetFilePath)}`;
|
|
10
|
+
const key = `${source}->${target}`;
|
|
11
|
+
if (!source || !target || seen.has(key))
|
|
12
|
+
continue;
|
|
13
|
+
seen.add(key);
|
|
14
|
+
out.push(candidate);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
async function resolveAnchorSymbols(input) {
|
|
19
|
+
const symbolName = normalizeName(input.symbolName);
|
|
20
|
+
if (!symbolName)
|
|
21
|
+
return [];
|
|
22
|
+
const maxSymbols = Math.max(1, Number(input.maxSymbols || 8));
|
|
23
|
+
const symbolFilePath = normalizeName(input.symbolFilePath);
|
|
24
|
+
if (symbolFilePath) {
|
|
25
|
+
const byFile = await input.executeParameterized(`
|
|
26
|
+
MATCH (n)
|
|
27
|
+
WHERE n.filePath = $filePath AND n.name = $symbolName
|
|
28
|
+
RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.startLine AS startLine
|
|
29
|
+
LIMIT ${maxSymbols}
|
|
30
|
+
`, { filePath: symbolFilePath, symbolName });
|
|
31
|
+
if (Array.isArray(byFile) && byFile.length > 0) {
|
|
32
|
+
return byFile.map((row) => ({
|
|
33
|
+
id: normalizeName(row.id),
|
|
34
|
+
name: normalizeName(row.name),
|
|
35
|
+
filePath: normalizeName(row.filePath) || undefined,
|
|
36
|
+
startLine: Number(row.startLine || 1),
|
|
37
|
+
})).filter((row) => row.id && row.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const byName = await input.executeParameterized(`
|
|
41
|
+
MATCH (n)
|
|
42
|
+
WHERE n.name IN $symbolNames
|
|
43
|
+
RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.startLine AS startLine
|
|
44
|
+
LIMIT ${maxSymbols}
|
|
45
|
+
`, { symbolNames: [symbolName] });
|
|
46
|
+
return (Array.isArray(byName) ? byName : [])
|
|
47
|
+
.map((row) => ({
|
|
48
|
+
id: normalizeName(row.id),
|
|
49
|
+
name: normalizeName(row.name),
|
|
50
|
+
filePath: normalizeName(row.filePath) || undefined,
|
|
51
|
+
startLine: Number(row.startLine || 1),
|
|
52
|
+
}))
|
|
53
|
+
.filter((row) => row.id && row.name);
|
|
54
|
+
}
|
|
55
|
+
function toCandidate(row) {
|
|
56
|
+
const sourceName = normalizeName(row.sourceName);
|
|
57
|
+
const targetName = normalizeName(row.targetName);
|
|
58
|
+
if (!sourceName || !targetName)
|
|
59
|
+
return undefined;
|
|
60
|
+
return {
|
|
61
|
+
sourceId: normalizeName(row.sourceId) || undefined,
|
|
62
|
+
sourceName,
|
|
63
|
+
sourceFilePath: normalizeName(row.sourceFilePath) || undefined,
|
|
64
|
+
sourceStartLine: Number(row.sourceStartLine || 1),
|
|
65
|
+
targetId: normalizeName(row.targetId) || undefined,
|
|
66
|
+
targetName,
|
|
67
|
+
targetFilePath: normalizeName(row.targetFilePath) || undefined,
|
|
68
|
+
targetStartLine: Number(row.targetStartLine || 1),
|
|
69
|
+
reason: normalizeName(row.reason) || undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export async function extractRuntimeGraphCandidates(input) {
|
|
73
|
+
const anchors = await resolveAnchorSymbols(input);
|
|
74
|
+
if (anchors.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
const maxEdgesPerSymbol = Math.max(1, Number(input.maxEdgesPerSymbol || 16));
|
|
77
|
+
const rawCandidates = [];
|
|
78
|
+
for (const symbol of anchors) {
|
|
79
|
+
const directRows = await input.executeParameterized(`
|
|
80
|
+
MATCH (s {id: $symbolId})-[r:CodeRelation {type: 'CALLS'}]->(t)
|
|
81
|
+
RETURN s.id AS sourceId, s.name AS sourceName, s.filePath AS sourceFilePath, s.startLine AS sourceStartLine,
|
|
82
|
+
t.id AS targetId, t.name AS targetName, t.filePath AS targetFilePath, t.startLine AS targetStartLine,
|
|
83
|
+
r.reason AS reason
|
|
84
|
+
LIMIT ${maxEdgesPerSymbol}
|
|
85
|
+
`, { symbolId: symbol.id });
|
|
86
|
+
for (const row of Array.isArray(directRows) ? directRows : []) {
|
|
87
|
+
const candidate = toCandidate(row);
|
|
88
|
+
if (candidate)
|
|
89
|
+
rawCandidates.push(candidate);
|
|
90
|
+
}
|
|
91
|
+
const methodRows = await input.executeParameterized(`
|
|
92
|
+
MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)
|
|
93
|
+
MATCH (m)-[r:CodeRelation {type: 'CALLS'}]->(t)
|
|
94
|
+
RETURN m.id AS sourceId, m.name AS sourceName, m.filePath AS sourceFilePath, m.startLine AS sourceStartLine,
|
|
95
|
+
t.id AS targetId, t.name AS targetName, t.filePath AS targetFilePath, t.startLine AS targetStartLine,
|
|
96
|
+
r.reason AS reason
|
|
97
|
+
LIMIT ${maxEdgesPerSymbol}
|
|
98
|
+
`, { symbolId: symbol.id });
|
|
99
|
+
for (const row of Array.isArray(methodRows) ? methodRows : []) {
|
|
100
|
+
const candidate = toCandidate(row);
|
|
101
|
+
if (candidate)
|
|
102
|
+
rawCandidates.push(candidate);
|
|
103
|
+
}
|
|
104
|
+
const incomingClassRows = await input.executeParameterized(`
|
|
105
|
+
MATCH (caller)-[r:CodeRelation {type: 'CALLS'}]->(s {id: $symbolId})
|
|
106
|
+
RETURN caller.id AS sourceId, caller.name AS sourceName, caller.filePath AS sourceFilePath, caller.startLine AS sourceStartLine,
|
|
107
|
+
s.id AS targetId, s.name AS targetName, s.filePath AS targetFilePath, s.startLine AS targetStartLine,
|
|
108
|
+
r.reason AS reason
|
|
109
|
+
LIMIT ${maxEdgesPerSymbol}
|
|
110
|
+
`, { symbolId: symbol.id });
|
|
111
|
+
for (const row of Array.isArray(incomingClassRows) ? incomingClassRows : []) {
|
|
112
|
+
const candidate = toCandidate(row);
|
|
113
|
+
if (candidate)
|
|
114
|
+
rawCandidates.push(candidate);
|
|
115
|
+
}
|
|
116
|
+
const incomingMethodRows = await input.executeParameterized(`
|
|
117
|
+
MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)
|
|
118
|
+
MATCH (caller)-[r:CodeRelation {type: 'CALLS'}]->(m)
|
|
119
|
+
RETURN caller.id AS sourceId, caller.name AS sourceName, caller.filePath AS sourceFilePath, caller.startLine AS sourceStartLine,
|
|
120
|
+
m.id AS targetId, m.name AS targetName, m.filePath AS targetFilePath, m.startLine AS targetStartLine,
|
|
121
|
+
r.reason AS reason
|
|
122
|
+
LIMIT ${maxEdgesPerSymbol}
|
|
123
|
+
`, { symbolId: symbol.id });
|
|
124
|
+
for (const row of Array.isArray(incomingMethodRows) ? incomingMethodRows : []) {
|
|
125
|
+
const candidate = toCandidate(row);
|
|
126
|
+
if (candidate)
|
|
127
|
+
rawCandidates.push(candidate);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return dedupeCandidates(rawCandidates);
|
|
131
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RuntimeChainEvidenceLevel } from './runtime-chain-evidence.js';
|
|
2
2
|
import { type RuntimeClaim } from './runtime-claim.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { RuntimeClaimRule } from './runtime-claim-rule-registry.js';
|
|
4
4
|
export type RuntimeChainVerifyMode = 'off' | 'on-demand';
|
|
5
5
|
export type RuntimeChainStatus = 'pending' | 'verified_partial' | 'verified_full' | 'failed';
|
|
6
6
|
export type RuntimeChainHopType = 'resource' | 'guid_map' | 'code_loader' | 'code_runtime';
|