@veewo/gitnexus 1.4.11-rc.2 → 1.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +14 -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
|
@@ -39,7 +39,7 @@ test('summary-only rows hydrate and merge into full bindings with preserved fiel
|
|
|
39
39
|
.map((binding) => binding.resourcePath))];
|
|
40
40
|
const hydration = await hydrateLazyBindings({
|
|
41
41
|
pendingPaths,
|
|
42
|
-
config: {
|
|
42
|
+
config: { lazyMaxPaths: 10, lazyBatchSize: 10, lazyMaxMs: 5000 },
|
|
43
43
|
resolveBatch: async () => new Map([
|
|
44
44
|
['Assets/Doors/Door.prefab', [
|
|
45
45
|
{
|
|
@@ -18,7 +18,7 @@ export function buildVerificationHint(input) {
|
|
|
18
18
|
return {
|
|
19
19
|
action: 'rerun_parity_hydration',
|
|
20
20
|
target,
|
|
21
|
-
next_command: '
|
|
21
|
+
next_command: 'gitnexus query --unity-resources on --unity-hydration parity "<symbol-or-query>"',
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
return {
|
|
@@ -21,6 +21,7 @@ export interface MergedProcessEvidenceRow extends ProcessEvidenceRow {
|
|
|
21
21
|
runtime_chain_evidence_level: RuntimeChainEvidenceLevel;
|
|
22
22
|
verification_hint?: VerificationHint;
|
|
23
23
|
}
|
|
24
|
+
export declare function deriveEvidenceFingerprint(...parts: unknown[]): string;
|
|
24
25
|
export declare function mergeProcessEvidence(input: {
|
|
25
26
|
directRows: ProcessEvidenceRow[];
|
|
26
27
|
projectedRows: ProjectedProcessEvidenceRow[];
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { buildVerificationHint, deriveConfidence, } from './process-confidence.js';
|
|
2
2
|
import { deriveRuntimeChainEvidenceLevel, } from './runtime-chain-evidence.js';
|
|
3
|
+
function asFingerprintToken(value) {
|
|
4
|
+
if (value === null || value === undefined)
|
|
5
|
+
return '';
|
|
6
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
7
|
+
return String(value);
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return value.map((item) => asFingerprintToken(item)).join(',');
|
|
11
|
+
}
|
|
12
|
+
const obj = value;
|
|
13
|
+
const ordered = {};
|
|
14
|
+
for (const key of Object.keys(obj).sort()) {
|
|
15
|
+
ordered[key] = obj[key];
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(ordered);
|
|
18
|
+
}
|
|
19
|
+
export function deriveEvidenceFingerprint(...parts) {
|
|
20
|
+
return parts
|
|
21
|
+
.map((part) => asFingerprintToken(part))
|
|
22
|
+
.filter((token) => token.length > 0)
|
|
23
|
+
.join('::');
|
|
24
|
+
}
|
|
3
25
|
const normalizeProcessConfidence = (raw, fallback) => {
|
|
4
26
|
if (raw === 'high' || raw === 'medium' || raw === 'low')
|
|
5
27
|
return raw;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mergeProcessEvidence } from './process-evidence.js';
|
|
3
|
+
import { deriveEvidenceFingerprint, mergeProcessEvidence } from './process-evidence.js';
|
|
4
4
|
test('projected-only rows are method_projected + medium', () => {
|
|
5
5
|
const out = mergeProcessEvidence({
|
|
6
6
|
directRows: [],
|
|
@@ -54,3 +54,13 @@ test('heuristic-only rows emit low confidence with verification hint', () => {
|
|
|
54
54
|
assert.equal(out[0].verification_hint?.action, 'rerun_parity_hydration');
|
|
55
55
|
assert.match(out[0].verification_hint?.next_command || '', /parity/i);
|
|
56
56
|
});
|
|
57
|
+
test('deriveEvidenceFingerprint is stable for same input ordering', () => {
|
|
58
|
+
const left = deriveEvidenceFingerprint({ resourcePath: 'Assets/A.prefab', bindingKind: 'component', line: 10 }, { pid: 'proc:123', step: 1 });
|
|
59
|
+
const right = deriveEvidenceFingerprint({ line: 10, bindingKind: 'component', resourcePath: 'Assets/A.prefab' }, { step: 1, pid: 'proc:123' });
|
|
60
|
+
assert.equal(left, right);
|
|
61
|
+
});
|
|
62
|
+
test('deriveEvidenceFingerprint changes when signal changes', () => {
|
|
63
|
+
const left = deriveEvidenceFingerprint({ resourcePath: 'Assets/A.prefab', line: 10 });
|
|
64
|
+
const right = deriveEvidenceFingerprint({ resourcePath: 'Assets/A.prefab', line: 11 });
|
|
65
|
+
assert.notEqual(left, right);
|
|
66
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ProcessRefKind = 'persistent' | 'derived';
|
|
2
|
+
export type ProcessRefOrigin = 'step_in_process' | 'method_projected' | 'resource_heuristic';
|
|
3
|
+
export interface ProcessRef {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: ProcessRefKind;
|
|
6
|
+
readable: boolean;
|
|
7
|
+
reader_uri: string;
|
|
8
|
+
origin: ProcessRefOrigin;
|
|
9
|
+
}
|
|
10
|
+
export interface BuildDerivedProcessIdInput {
|
|
11
|
+
indexedCommit: string;
|
|
12
|
+
symbolUid: string;
|
|
13
|
+
evidenceFingerprint: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildDerivedProcessId(input: BuildDerivedProcessIdInput): string;
|
|
16
|
+
export interface BuildProcessRefInput {
|
|
17
|
+
repoName: string;
|
|
18
|
+
processId?: string;
|
|
19
|
+
origin: ProcessRefOrigin;
|
|
20
|
+
indexedCommit: string;
|
|
21
|
+
symbolUid: string;
|
|
22
|
+
evidenceFingerprint: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildProcessRef(input: BuildProcessRefInput): ProcessRef;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function buildDerivedProcessId(input) {
|
|
3
|
+
const key = `${input.indexedCommit}::${input.symbolUid}::${input.evidenceFingerprint}`;
|
|
4
|
+
const hash = createHash('sha1').update(key).digest('hex').slice(0, 16);
|
|
5
|
+
return `derived:${hash}`;
|
|
6
|
+
}
|
|
7
|
+
function isPersistentProcessId(processId) {
|
|
8
|
+
return processId.length > 0 && !processId.startsWith('proc:heuristic:');
|
|
9
|
+
}
|
|
10
|
+
export function buildProcessRef(input) {
|
|
11
|
+
const processId = String(input.processId || '').trim();
|
|
12
|
+
if (isPersistentProcessId(processId)) {
|
|
13
|
+
return {
|
|
14
|
+
id: processId,
|
|
15
|
+
kind: 'persistent',
|
|
16
|
+
readable: true,
|
|
17
|
+
reader_uri: `gitnexus://repo/${encodeURIComponent(input.repoName)}/process/${encodeURIComponent(processId)}`,
|
|
18
|
+
origin: input.origin,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const id = buildDerivedProcessId({
|
|
22
|
+
indexedCommit: input.indexedCommit,
|
|
23
|
+
symbolUid: input.symbolUid,
|
|
24
|
+
evidenceFingerprint: input.evidenceFingerprint,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
id,
|
|
28
|
+
kind: 'derived',
|
|
29
|
+
readable: true,
|
|
30
|
+
reader_uri: `gitnexus://repo/${encodeURIComponent(input.repoName)}/derived-process/${encodeURIComponent(id)}`,
|
|
31
|
+
origin: input.origin,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildDerivedProcessId } from './process-ref.js';
|
|
4
|
+
test('buildDerivedProcessId is stable for identical fingerprint input', () => {
|
|
5
|
+
const left = buildDerivedProcessId({
|
|
6
|
+
indexedCommit: 'abc',
|
|
7
|
+
symbolUid: 'Class:Assets/A.cs:A',
|
|
8
|
+
evidenceFingerprint: 'resource=Assets/A.prefab;line=10',
|
|
9
|
+
});
|
|
10
|
+
const right = buildDerivedProcessId({
|
|
11
|
+
indexedCommit: 'abc',
|
|
12
|
+
symbolUid: 'Class:Assets/A.cs:A',
|
|
13
|
+
evidenceFingerprint: 'resource=Assets/A.prefab;line=10',
|
|
14
|
+
});
|
|
15
|
+
assert.equal(left, right);
|
|
16
|
+
});
|
|
17
|
+
test('buildDerivedProcessId does not leak heuristic process id prefix', () => {
|
|
18
|
+
const id = buildDerivedProcessId({
|
|
19
|
+
indexedCommit: 'abc',
|
|
20
|
+
symbolUid: 'Class:Assets/A.cs:A',
|
|
21
|
+
evidenceFingerprint: 'resource=Assets/A.prefab;line=11',
|
|
22
|
+
});
|
|
23
|
+
assert.doesNotMatch(id, /^proc:heuristic:/);
|
|
24
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RuntimeChainEvidenceLevel } from './runtime-chain-evidence.js';
|
|
2
|
+
import { type RuntimeClaim } from './runtime-claim.js';
|
|
3
|
+
import { type RuntimeClaimRule } from './runtime-claim-rule-registry.js';
|
|
2
4
|
export type RuntimeChainVerifyMode = 'off' | 'on-demand';
|
|
3
5
|
export type RuntimeChainStatus = 'pending' | 'verified_partial' | 'verified_full' | 'failed';
|
|
4
6
|
export type RuntimeChainHopType = 'resource' | 'guid_map' | 'code_loader' | 'code_runtime';
|
|
@@ -13,12 +15,15 @@ export interface RuntimeChainGap {
|
|
|
13
15
|
segment: 'resource' | 'guid_map' | 'loader' | 'runtime';
|
|
14
16
|
reason: string;
|
|
15
17
|
next_command: string;
|
|
18
|
+
why_not_next?: string;
|
|
16
19
|
}
|
|
17
20
|
export interface RuntimeChainResult {
|
|
18
21
|
status: RuntimeChainStatus;
|
|
19
22
|
evidence_level: RuntimeChainEvidenceLevel;
|
|
23
|
+
evidence_source?: 'analyze_time' | 'query_time';
|
|
20
24
|
hops: RuntimeChainHop[];
|
|
21
25
|
gaps: RuntimeChainGap[];
|
|
26
|
+
why_not_next?: string[];
|
|
22
27
|
}
|
|
23
28
|
interface QueryExecutor {
|
|
24
29
|
(query: string, params?: Record<string, unknown>): Promise<any[]>;
|
|
@@ -27,11 +32,20 @@ interface VerifyRuntimeChainInput {
|
|
|
27
32
|
repoPath: string;
|
|
28
33
|
executeParameterized: QueryExecutor;
|
|
29
34
|
queryText?: string;
|
|
35
|
+
resourceSeedPath?: string;
|
|
36
|
+
mappedSeedTargets?: string[];
|
|
30
37
|
symbolName?: string;
|
|
31
38
|
symbolFilePath?: string;
|
|
32
39
|
resourceBindings?: Array<{
|
|
33
40
|
resourcePath?: string;
|
|
34
41
|
}>;
|
|
42
|
+
requiredHops?: string[];
|
|
43
|
+
rule?: RuntimeClaimRule;
|
|
44
|
+
}
|
|
45
|
+
interface VerifyRuntimeClaimInput extends VerifyRuntimeChainInput {
|
|
46
|
+
rulesRoot?: string;
|
|
47
|
+
minimumEvidenceSatisfied?: boolean;
|
|
35
48
|
}
|
|
36
49
|
export declare function verifyRuntimeChainOnDemand(input: VerifyRuntimeChainInput): Promise<RuntimeChainResult | undefined>;
|
|
50
|
+
export declare function verifyRuntimeClaimOnDemand(input: VerifyRuntimeClaimInput): Promise<RuntimeClaim>;
|
|
37
51
|
export {};
|
|
@@ -1,221 +1,225 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'curgungraph',
|
|
10
|
-
'registerevents',
|
|
11
|
-
'startroutinewithevents',
|
|
12
|
-
];
|
|
13
|
-
const RESOURCE_ASSET_PATH = 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset';
|
|
14
|
-
const GRAPH_ASSET_PATH = 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset';
|
|
15
|
-
const RELOAD_META_PATH = 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs.meta';
|
|
16
|
-
const RELOAD_GUID = 'bd387039cacb475381a86f156b54bac2';
|
|
17
|
-
const GRAPH_GUID = '69199acacbf8a7e489ad4aa872efcabd';
|
|
18
|
-
const VERIFY_NEXT_COMMAND = 'node gitnexus/dist/cli/index.js query --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand "Reload NEON.Game.Graph.Nodes.Reloads"';
|
|
19
|
-
function normalizeText(value) {
|
|
20
|
-
return String(value || '').trim();
|
|
1
|
+
import { buildRuntimeClaimFromRule } from './runtime-claim.js';
|
|
2
|
+
import { RuleRegistryLoadError, loadRuleRegistry } from './runtime-claim-rule-registry.js';
|
|
3
|
+
function buildDefaultVerifyNextCommand(queryText) {
|
|
4
|
+
const normalizedQuery = String(queryText || '').trim() || 'Reload NEON.Game.Graph.Nodes.Reloads';
|
|
5
|
+
const escapedQuery = normalizedQuery
|
|
6
|
+
.replace(/\\/g, '\\\\')
|
|
7
|
+
.replace(/"/g, '\\"');
|
|
8
|
+
return `node gitnexus/dist/cli/index.js query --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand "${escapedQuery}"`;
|
|
21
9
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
10
|
+
function buildRuntimeMatchHaystack(input) {
|
|
11
|
+
return [
|
|
24
12
|
input.queryText,
|
|
13
|
+
input.resourceSeedPath,
|
|
25
14
|
input.symbolName,
|
|
26
15
|
input.symbolFilePath,
|
|
16
|
+
...(input.mappedSeedTargets || []),
|
|
27
17
|
...(input.resourceBindings || []).map((binding) => binding.resourcePath),
|
|
28
18
|
]
|
|
29
19
|
.filter(Boolean)
|
|
30
20
|
.join(' ')
|
|
31
21
|
.toLowerCase();
|
|
32
|
-
return RELOAD_QUERY_TOKENS.some((token) => haystack.includes(token));
|
|
33
22
|
}
|
|
34
|
-
function
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
function parseTriggerTokens(triggerFamily) {
|
|
24
|
+
return String(triggerFamily || '')
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.split(/[\s,|/]+/)
|
|
27
|
+
.map((token) => token.trim())
|
|
28
|
+
.filter((token) => token.length > 0);
|
|
40
29
|
}
|
|
41
|
-
async function
|
|
30
|
+
async function verifyRuleDrivenRuntimeChain(input) {
|
|
31
|
+
const ruleId = input.rule?.id;
|
|
32
|
+
if (!ruleId) {
|
|
33
|
+
return { status: 'failed', evidence_level: 'none', evidence_source: 'analyze_time', hops: [], gaps: [] };
|
|
34
|
+
}
|
|
42
35
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
const rows = await input.executeParameterized(`
|
|
37
|
+
MATCH (s)-[r:CodeRelation {type: 'CALLS'}]->(t)
|
|
38
|
+
WHERE r.reason CONTAINS $ruleId
|
|
39
|
+
AND r.reason STARTS WITH 'unity-rule-'
|
|
40
|
+
RETURN s.name AS sourceName, s.filePath AS sourceFilePath, s.startLine AS sourceStartLine,
|
|
41
|
+
t.name AS targetName, t.filePath AS targetFilePath, t.startLine AS targetStartLine,
|
|
42
|
+
r.reason AS reason
|
|
43
|
+
LIMIT 20
|
|
44
|
+
`, { ruleId });
|
|
45
|
+
if (rows.length > 0) {
|
|
46
|
+
const hops = rows.map((row) => ({
|
|
47
|
+
hop_type: 'code_runtime',
|
|
48
|
+
anchor: `${row.sourceFilePath}:${row.sourceStartLine || 1}->${row.targetFilePath}:${row.targetStartLine || 1}`,
|
|
49
|
+
confidence: 'high',
|
|
50
|
+
note: `Synthetic edge injected at analyze time (${row.reason}).`,
|
|
51
|
+
snippet: `${row.sourceName} -> ${row.targetName}`,
|
|
52
|
+
}));
|
|
53
|
+
return {
|
|
54
|
+
status: 'verified_full',
|
|
55
|
+
evidence_level: 'verified_chain',
|
|
56
|
+
evidence_source: 'analyze_time',
|
|
57
|
+
hops,
|
|
58
|
+
gaps: [],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
62
|
catch {
|
|
48
|
-
|
|
63
|
+
// Graph query failed; fall through to no match.
|
|
49
64
|
}
|
|
50
|
-
}
|
|
51
|
-
async function findLineAnchor(repoPath, relativePath, pattern) {
|
|
52
|
-
const lines = await readFileLines(repoPath, relativePath);
|
|
53
|
-
if (!lines)
|
|
54
|
-
return null;
|
|
55
|
-
const index = lines.findIndex((line) => pattern.test(line));
|
|
56
|
-
if (index < 0)
|
|
57
|
-
return null;
|
|
58
65
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
status: 'failed',
|
|
67
|
+
evidence_level: 'none',
|
|
68
|
+
evidence_source: 'analyze_time',
|
|
69
|
+
hops: [],
|
|
70
|
+
gaps: [],
|
|
61
71
|
};
|
|
62
72
|
}
|
|
63
|
-
async function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const rows = await executeParameterized(`
|
|
68
|
-
MATCH (n:Method)
|
|
69
|
-
WHERE n.name = $name AND n.filePath CONTAINS $filePathPattern
|
|
70
|
-
RETURN n.filePath AS filePath, n.startLine AS startLine
|
|
71
|
-
ORDER BY n.startLine ASC
|
|
72
|
-
LIMIT 1
|
|
73
|
-
`, { name, filePathPattern });
|
|
74
|
-
if (!rows[0])
|
|
75
|
-
return null;
|
|
76
|
-
return {
|
|
77
|
-
filePath: String(rows[0].filePath || rows[0][0] || ''),
|
|
78
|
-
line: Number(rows[0].startLine || rows[0][1] || 1),
|
|
79
|
-
};
|
|
73
|
+
export async function verifyRuntimeChainOnDemand(input) {
|
|
74
|
+
if (!input.rule)
|
|
75
|
+
return undefined;
|
|
76
|
+
return await verifyRuleDrivenRuntimeChain(input);
|
|
80
77
|
}
|
|
81
|
-
|
|
82
|
-
const method = await findMethodAnchor(executeParameterized, filePathPattern, name);
|
|
83
|
-
if (!method?.filePath)
|
|
84
|
-
return null;
|
|
85
|
-
const lines = await readFileLines(repoPath, method.filePath);
|
|
86
|
-
const snippet = lines?.[Math.max(0, method.line - 1)]?.trim() || name;
|
|
78
|
+
function buildFailureRuntimeClaim(input) {
|
|
87
79
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
rule_id: input.rule?.id || 'none',
|
|
81
|
+
rule_version: input.rule?.version || '0.0.0',
|
|
82
|
+
scope: {
|
|
83
|
+
resource_types: input.rule?.resource_types || [],
|
|
84
|
+
host_base_type: input.rule?.host_base_type || [],
|
|
85
|
+
trigger_family: input.rule?.trigger_family || 'none',
|
|
86
|
+
},
|
|
87
|
+
status: 'failed',
|
|
88
|
+
evidence_level: 'none',
|
|
89
|
+
guarantees: [],
|
|
90
|
+
non_guarantees: input.rule?.non_guarantees?.length
|
|
91
|
+
? [...input.rule.non_guarantees]
|
|
92
|
+
: ['runtime_chain_verification_not_executed'],
|
|
93
|
+
hops: [],
|
|
94
|
+
gaps: [],
|
|
95
|
+
reason: input.reason,
|
|
96
|
+
next_action: input.next_action,
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
confidence: hasResourceAnchor ? 'high' : 'medium',
|
|
113
|
-
note: `PowerUp asset references WeaponPowerUp and gungraph guid ${GRAPH_GUID}.`,
|
|
114
|
-
snippet: resourceAnchor.snippet,
|
|
115
|
-
});
|
|
116
|
-
if (hasResourceAnchor) {
|
|
117
|
-
foundSegments.add('resource');
|
|
99
|
+
function scoreRuntimeClaimRule(rule, input) {
|
|
100
|
+
const haystack = buildRuntimeMatchHaystack(input);
|
|
101
|
+
const tokens = Array.isArray(rule.match?.trigger_tokens) && rule.match.trigger_tokens.length > 0
|
|
102
|
+
? rule.match.trigger_tokens
|
|
103
|
+
: parseTriggerTokens(rule.trigger_family);
|
|
104
|
+
if (tokens.length === 0)
|
|
105
|
+
return Number.NEGATIVE_INFINITY;
|
|
106
|
+
let score = 0;
|
|
107
|
+
let matchedTrigger = false;
|
|
108
|
+
for (const token of tokens) {
|
|
109
|
+
const normalized = String(token || '').trim().toLowerCase();
|
|
110
|
+
if (!normalized)
|
|
111
|
+
continue;
|
|
112
|
+
if (haystack.includes(normalized)) {
|
|
113
|
+
matchedTrigger = true;
|
|
114
|
+
score += 10 + normalized.length;
|
|
115
|
+
}
|
|
118
116
|
}
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
if (!matchedTrigger)
|
|
118
|
+
return Number.NEGATIVE_INFINITY;
|
|
119
|
+
const boostLists = [
|
|
120
|
+
...(Array.isArray(rule.match?.host_base_type) ? [rule.match.host_base_type] : []),
|
|
121
|
+
...(Array.isArray(rule.host_base_type) ? [rule.host_base_type] : []),
|
|
122
|
+
];
|
|
123
|
+
for (const list of boostLists) {
|
|
124
|
+
for (const token of list) {
|
|
125
|
+
const normalized = String(token || '').trim().toLowerCase();
|
|
126
|
+
if (normalized && haystack.includes(normalized))
|
|
127
|
+
score += 20 + normalized.length;
|
|
128
|
+
}
|
|
121
129
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
foundSegments.add('guid_map');
|
|
130
|
+
const resourceLists = [
|
|
131
|
+
...(Array.isArray(rule.match?.resource_types) ? [rule.match.resource_types] : []),
|
|
132
|
+
...(Array.isArray(rule.resource_types) ? [rule.resource_types] : []),
|
|
133
|
+
];
|
|
134
|
+
for (const list of resourceLists) {
|
|
135
|
+
for (const token of list) {
|
|
136
|
+
const normalized = String(token || '').trim().toLowerCase();
|
|
137
|
+
if (normalized && haystack.includes(normalized))
|
|
138
|
+
score += 4 + normalized.length;
|
|
139
|
+
}
|
|
133
140
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
confidence: 'medium',
|
|
139
|
-
note: `Graph asset guid ${GRAPH_GUID} maps to Reload.cs.meta guid ${RELOAD_GUID}; wiring includes ResultRPM -> GunOutput.RPM.`,
|
|
140
|
-
snippet: 'guid_map anchor unavailable in test fixture',
|
|
141
|
-
});
|
|
142
|
-
gaps.push(buildGap('guid_map', 'missing Reload guid_map or graph wiring anchor'));
|
|
141
|
+
for (const token of rule.match?.module_scope || []) {
|
|
142
|
+
const normalized = String(token || '').trim().toLowerCase();
|
|
143
|
+
if (normalized && haystack.includes(normalized))
|
|
144
|
+
score += 8 + normalized.length;
|
|
143
145
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
confidence: 'high',
|
|
151
|
-
note: 'PickItUp -> EquipWithEvent -> Equip; CurGunGraph assignment happens in WeaponPowerUp.Equip.',
|
|
152
|
-
snippet: loaderAssignmentAnchor.snippet,
|
|
153
|
-
});
|
|
154
|
-
foundSegments.add('code_loader');
|
|
146
|
+
return score;
|
|
147
|
+
}
|
|
148
|
+
export async function verifyRuntimeClaimOnDemand(input) {
|
|
149
|
+
let registry;
|
|
150
|
+
try {
|
|
151
|
+
registry = await loadRuleRegistry(input.repoPath, input.rulesRoot);
|
|
155
152
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof RuleRegistryLoadError) {
|
|
155
|
+
if (error.code === 'rule_catalog_missing' || error.code === 'rule_file_missing') {
|
|
156
|
+
return buildFailureRuntimeClaim({
|
|
157
|
+
reason: 'rule_not_matched',
|
|
158
|
+
next_action: buildDefaultVerifyNextCommand(input.queryText),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
snippet: 'loader anchor unavailable in test fixture',
|
|
164
|
+
const activeRules = registry.activeRules || [];
|
|
165
|
+
const fallbackNextAction = buildDefaultVerifyNextCommand(input.queryText);
|
|
166
|
+
if (activeRules.length === 0) {
|
|
167
|
+
return buildFailureRuntimeClaim({
|
|
168
|
+
reason: 'rule_not_matched',
|
|
169
|
+
next_action: fallbackNextAction,
|
|
171
170
|
});
|
|
172
|
-
gaps.push(buildGap('loader', 'missing PickItUp/EquipWithEvent/Equip anchor and CurGunGraph assignment anchor'));
|
|
173
|
-
}
|
|
174
|
-
const runtimeGraphHop = await buildMethodHop(input.repoPath, input.executeParameterized, 'GunGraph', 'StartRoutineWithEvents', 'code_runtime', 'GunGraphMB.RegisterGraphEvents -> GunGraph.RegisterEvents -> StartRoutineWithEvents.') || await buildMethodHop(input.repoPath, input.executeParameterized, 'GunGraphMB.cs', 'RegisterGraphEvents', 'code_runtime', 'GunGraphMB.RegisterGraphEvents -> GunGraph.RegisterEvents -> StartRoutineWithEvents.');
|
|
175
|
-
let hasRuntimeGraphAnchor = false;
|
|
176
|
-
if (runtimeGraphHop) {
|
|
177
|
-
hops.push(runtimeGraphHop);
|
|
178
|
-
hasRuntimeGraphAnchor = true;
|
|
179
|
-
}
|
|
180
|
-
const reloadRuntimeHop = await buildMethodHop(input.repoPath, input.executeParameterized, 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs', 'GetValue', 'code_runtime', 'ReloadBase.GetValue -> CheckReload -> ReloadRoutine closes the runtime reload chain.');
|
|
181
|
-
let hasReloadRuntimeAnchor = false;
|
|
182
|
-
if (reloadRuntimeHop) {
|
|
183
|
-
hops.push(reloadRuntimeHop);
|
|
184
|
-
hasReloadRuntimeAnchor = true;
|
|
185
171
|
}
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
const matchedRule = [...activeRules]
|
|
173
|
+
.map((rule) => ({ rule, score: scoreRuntimeClaimRule(rule, input) }))
|
|
174
|
+
.filter((entry) => Number.isFinite(entry.score))
|
|
175
|
+
.sort((a, b) => (b.score - a.score) || a.rule.id.localeCompare(b.rule.id))[0]?.rule;
|
|
176
|
+
if (!matchedRule) {
|
|
177
|
+
return buildFailureRuntimeClaim({
|
|
178
|
+
reason: 'rule_not_matched',
|
|
179
|
+
next_action: fallbackNextAction,
|
|
180
|
+
});
|
|
188
181
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
note: 'RegisterEvents -> StartRoutineWithEvents -> ReloadBase.GetValue -> CheckReload -> ReloadRoutine.',
|
|
201
|
-
snippet: 'runtime anchor unavailable in test fixture',
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
gaps.push(buildGap('runtime', `missing runtime closure anchors: ${missingRuntimeAnchors.join(' + ')}`));
|
|
182
|
+
const runtimeChain = await verifyRuntimeChainOnDemand({
|
|
183
|
+
...input,
|
|
184
|
+
requiredHops: matchedRule.required_hops,
|
|
185
|
+
rule: matchedRule,
|
|
186
|
+
});
|
|
187
|
+
if (!runtimeChain) {
|
|
188
|
+
return buildFailureRuntimeClaim({
|
|
189
|
+
reason: 'rule_matched_but_evidence_missing',
|
|
190
|
+
next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
|
|
191
|
+
rule: matchedRule,
|
|
192
|
+
});
|
|
205
193
|
}
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
194
|
+
const normalizedStatus = (runtimeChain.status === 'pending' ? 'failed' : runtimeChain.status);
|
|
195
|
+
const verificationFailed = normalizedStatus === 'failed'
|
|
196
|
+
|| (runtimeChain.evidence_level === 'none' && normalizedStatus !== 'verified_full');
|
|
197
|
+
const resolved = buildRuntimeClaimFromRule({
|
|
198
|
+
rule: matchedRule,
|
|
199
|
+
status: verificationFailed ? 'failed' : normalizedStatus,
|
|
200
|
+
evidence_level: runtimeChain.evidence_level,
|
|
201
|
+
hops: runtimeChain.hops,
|
|
202
|
+
gaps: runtimeChain.gaps,
|
|
203
|
+
...(verificationFailed
|
|
204
|
+
? {
|
|
205
|
+
reason: 'rule_matched_but_verification_failed',
|
|
206
|
+
next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
|
|
207
|
+
}
|
|
208
|
+
: {}),
|
|
211
209
|
});
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
const chainClosed = resolved.status === 'verified_full'
|
|
211
|
+
&& resolved.evidence_level === 'verified_chain'
|
|
212
|
+
&& resolved.gaps.length === 0;
|
|
213
|
+
if (input.minimumEvidenceSatisfied === false && !chainClosed) {
|
|
214
|
+
return {
|
|
215
|
+
...resolved,
|
|
216
|
+
status: 'failed',
|
|
217
|
+
evidence_level: 'clue',
|
|
218
|
+
guarantees: [],
|
|
219
|
+
non_guarantees: [...resolved.non_guarantees, 'minimum_evidence_contract_not_satisfied'],
|
|
220
|
+
reason: 'rule_matched_but_evidence_missing',
|
|
221
|
+
next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return resolved;
|
|
221
225
|
}
|