@veewo/gitnexus 1.4.10-rc → 1.4.11-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/live-evidence-validator.d.ts +19 -0
- package/dist/benchmark/u2-e2e/live-evidence-validator.js +87 -0
- package/dist/benchmark/u2-e2e/live-evidence-validator.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/live-evidence-validator.test.js +33 -0
- package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +23 -4
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.d.ts +38 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +206 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.js +72 -0
- package/dist/benchmark/u2-e2e/report.d.ts +1 -0
- package/dist/benchmark/u2-e2e/report.js +2 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +34 -0
- package/dist/benchmark/u2-e2e/retrieval-runner.js +95 -5
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +161 -2
- package/dist/cli/ai-context.js +25 -1
- package/dist/cli/ai-context.test.js +8 -0
- package/dist/cli/analyze-summary.d.ts +1 -0
- package/dist/cli/analyze-summary.js +21 -0
- package/dist/cli/analyze-summary.test.js +7 -1
- package/dist/cli/analyze.js +3 -10
- package/dist/cli/eval-server.js +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/setup.js +9 -0
- package/dist/cli/setup.test.js +2 -0
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +2 -0
- package/dist/core/ingestion/pipeline.js +24 -3
- package/dist/core/ingestion/process-processor.d.ts +6 -0
- package/dist/core/ingestion/process-processor.js +188 -7
- package/dist/core/ingestion/unity-lifecycle-config.d.ts +5 -0
- package/dist/core/ingestion/unity-lifecycle-config.js +25 -0
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +26 -0
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +384 -0
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.d.ts +1 -0
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +541 -0
- package/dist/core/ingestion/unity-resource-processor.test.js +81 -0
- package/dist/core/lbug/csv-generator.js +11 -1
- package/dist/core/lbug/fallback-relationship-replay.d.ts +21 -0
- package/dist/core/lbug/fallback-relationship-replay.js +39 -0
- package/dist/core/lbug/fallback-relationship-replay.test.d.ts +1 -0
- package/dist/core/lbug/fallback-relationship-replay.test.js +25 -0
- package/dist/core/lbug/lbug-adapter.d.ts +5 -0
- package/dist/core/lbug/lbug-adapter.js +22 -23
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +9 -0
- package/dist/core/lbug/schema.test.js +1 -0
- package/dist/mcp/local/local-backend.d.ts +1 -1
- package/dist/mcp/local/local-backend.js +339 -50
- package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
- package/dist/mcp/local/process-confidence.d.ts +19 -0
- package/dist/mcp/local/process-confidence.js +29 -0
- package/dist/mcp/local/process-confidence.test.d.ts +1 -0
- package/dist/mcp/local/process-confidence.test.js +36 -0
- package/dist/mcp/local/process-evidence.d.ts +28 -0
- package/dist/mcp/local/process-evidence.js +65 -0
- package/dist/mcp/local/process-evidence.test.d.ts +1 -0
- package/dist/mcp/local/process-evidence.test.js +56 -0
- package/dist/mcp/local/runtime-chain-evidence.d.ts +7 -0
- package/dist/mcp/local/runtime-chain-evidence.js +13 -0
- package/dist/mcp/local/runtime-chain-evidence.test.d.ts +1 -0
- package/dist/mcp/local/runtime-chain-evidence.test.js +24 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +37 -0
- package/dist/mcp/local/runtime-chain-verify.js +221 -0
- package/dist/mcp/local/runtime-chain-verify.test.d.ts +1 -0
- package/dist/mcp/local/runtime-chain-verify.test.js +56 -0
- package/dist/mcp/local/unity-process-confidence-config.d.ts +1 -0
- package/dist/mcp/local/unity-process-confidence-config.js +4 -0
- package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +1 -0
- package/dist/mcp/local/unity-runtime-chain-verify-config.js +10 -0
- package/dist/mcp/local/unity-runtime-hydration.d.ts +50 -0
- package/dist/mcp/local/unity-runtime-hydration.js +323 -0
- package/dist/mcp/local/unity-runtime-hydration.test.d.ts +1 -0
- package/dist/mcp/local/unity-runtime-hydration.test.js +108 -0
- package/dist/mcp/resources.js +12 -2
- package/dist/mcp/tools.js +32 -0
- package/package.json +1 -1
- package/skills/_shared/unity-runtime-process-contract.md +38 -0
- package/skills/gitnexus-cli.md +16 -0
- package/skills/gitnexus-debugging.md +6 -0
- package/skills/gitnexus-exploring.md +6 -0
- package/skills/gitnexus-guide.md +4 -0
- package/skills/gitnexus-impact-analysis.md +6 -0
- package/skills/gitnexus-pr-review.md +5 -0
- package/skills/gitnexus-refactoring.md +4 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface LiveEvidenceRow {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
command: string;
|
|
4
|
+
flags: Record<string, unknown>;
|
|
5
|
+
request_excerpt: string;
|
|
6
|
+
response_excerpt: string;
|
|
7
|
+
segment: string;
|
|
8
|
+
hop_anchor: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LiveEvidenceValidationResult {
|
|
11
|
+
valid: boolean;
|
|
12
|
+
errors: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function validateLiveEvidenceRows(rows: unknown[]): LiveEvidenceValidationResult;
|
|
15
|
+
export declare function parseLiveEvidenceJsonl(raw: string): {
|
|
16
|
+
rows: unknown[];
|
|
17
|
+
errors: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function validateLiveEvidenceJsonlFile(inputPath: string): Promise<LiveEvidenceValidationResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
function isNonEmptyString(value) {
|
|
5
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
6
|
+
}
|
|
7
|
+
function validateRow(row, index) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
if (!isNonEmptyString(row?.timestamp))
|
|
10
|
+
errors.push(`row ${index + 1}: missing timestamp`);
|
|
11
|
+
if (!isNonEmptyString(row?.command))
|
|
12
|
+
errors.push(`row ${index + 1}: missing command`);
|
|
13
|
+
if (!row || typeof row !== 'object' || !row.flags || typeof row.flags !== 'object') {
|
|
14
|
+
errors.push(`row ${index + 1}: missing flags object`);
|
|
15
|
+
}
|
|
16
|
+
if (!isNonEmptyString(row?.request_excerpt))
|
|
17
|
+
errors.push(`row ${index + 1}: missing request_excerpt`);
|
|
18
|
+
if (!isNonEmptyString(row?.response_excerpt))
|
|
19
|
+
errors.push(`row ${index + 1}: missing response_excerpt`);
|
|
20
|
+
if (!isNonEmptyString(row?.segment))
|
|
21
|
+
errors.push(`row ${index + 1}: missing segment`);
|
|
22
|
+
if (!isNonEmptyString(row?.hop_anchor))
|
|
23
|
+
errors.push(`row ${index + 1}: missing hop_anchor`);
|
|
24
|
+
return errors;
|
|
25
|
+
}
|
|
26
|
+
export function validateLiveEvidenceRows(rows) {
|
|
27
|
+
const errors = [];
|
|
28
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
29
|
+
errors.push(...validateRow(rows[i], i));
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
valid: errors.length === 0,
|
|
33
|
+
errors,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function parseLiveEvidenceJsonl(raw) {
|
|
37
|
+
const errors = [];
|
|
38
|
+
const rows = [];
|
|
39
|
+
const lines = String(raw || '')
|
|
40
|
+
.split(/\r?\n/)
|
|
41
|
+
.map((line) => line.trim())
|
|
42
|
+
.filter((line) => line.length > 0);
|
|
43
|
+
lines.forEach((line, index) => {
|
|
44
|
+
try {
|
|
45
|
+
rows.push(JSON.parse(line));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
errors.push(`line ${index + 1}: invalid JSON (${error instanceof Error ? error.message : String(error)})`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return { rows, errors };
|
|
52
|
+
}
|
|
53
|
+
export async function validateLiveEvidenceJsonlFile(inputPath) {
|
|
54
|
+
const raw = await fs.readFile(inputPath, 'utf-8');
|
|
55
|
+
const parsed = parseLiveEvidenceJsonl(raw);
|
|
56
|
+
if (parsed.errors.length > 0) {
|
|
57
|
+
return { valid: false, errors: parsed.errors };
|
|
58
|
+
}
|
|
59
|
+
return validateLiveEvidenceRows(parsed.rows);
|
|
60
|
+
}
|
|
61
|
+
function parseCliInputArg(argv) {
|
|
62
|
+
const index = argv.indexOf('--input');
|
|
63
|
+
if (index < 0)
|
|
64
|
+
return '';
|
|
65
|
+
return String(argv[index + 1] || '').trim();
|
|
66
|
+
}
|
|
67
|
+
async function runCli() {
|
|
68
|
+
const input = parseCliInputArg(process.argv);
|
|
69
|
+
if (!input) {
|
|
70
|
+
throw new Error('Usage: node dist/benchmark/u2-e2e/live-evidence-validator.js --input <jsonl-path>');
|
|
71
|
+
}
|
|
72
|
+
const resolved = path.resolve(input);
|
|
73
|
+
const result = await validateLiveEvidenceJsonlFile(resolved);
|
|
74
|
+
if (!result.valid) {
|
|
75
|
+
throw new Error(`live evidence validation failed:\n${result.errors.map((row) => `- ${row}`).join('\n')}`);
|
|
76
|
+
}
|
|
77
|
+
process.stdout.write(`live evidence validation passed: ${resolved}\n`);
|
|
78
|
+
}
|
|
79
|
+
const entryPath = process.argv[1] ? path.resolve(process.argv[1]) : '';
|
|
80
|
+
const thisPath = fileURLToPath(import.meta.url);
|
|
81
|
+
if (entryPath && entryPath === thisPath) {
|
|
82
|
+
runCli().catch((error) => {
|
|
83
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
84
|
+
process.stderr.write(`${msg}\n`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { validateLiveEvidenceRows } from './live-evidence-validator.js';
|
|
4
|
+
test('validateLiveEvidenceRows fails when required authenticity fields are missing', () => {
|
|
5
|
+
const result = validateLiveEvidenceRows([
|
|
6
|
+
{
|
|
7
|
+
timestamp: '2026-04-01T10:00:00.000Z',
|
|
8
|
+
command: 'node gitnexus/dist/cli/index.js query -r neonspark-core "Reload"',
|
|
9
|
+
flags: { confidenceFields: 'on' },
|
|
10
|
+
request_excerpt: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
11
|
+
response_excerpt: 'processes: []',
|
|
12
|
+
segment: 'resource',
|
|
13
|
+
// hop_anchor intentionally missing
|
|
14
|
+
},
|
|
15
|
+
]);
|
|
16
|
+
assert.equal(result.valid, false);
|
|
17
|
+
assert.ok(result.errors.some((row) => /hop_anchor/i.test(row)));
|
|
18
|
+
});
|
|
19
|
+
test('validateLiveEvidenceRows passes when authenticity schema is complete', () => {
|
|
20
|
+
const result = validateLiveEvidenceRows([
|
|
21
|
+
{
|
|
22
|
+
timestamp: '2026-04-01T10:00:00.000Z',
|
|
23
|
+
command: 'node gitnexus/dist/cli/index.js query -r neonspark-core "Reload"',
|
|
24
|
+
flags: { confidenceFields: 'on', unity_resources: 'on', unity_hydration: 'parity' },
|
|
25
|
+
request_excerpt: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
26
|
+
response_excerpt: 'confidence=low verification_hint.action=rerun_parity_hydration',
|
|
27
|
+
segment: 'runtime',
|
|
28
|
+
hop_anchor: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs:CheckReload',
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
assert.equal(result.valid, true);
|
|
32
|
+
assert.equal(result.errors.length, 0);
|
|
33
|
+
});
|
|
@@ -200,6 +200,17 @@ async function loadSerializedTypeEdgeCount(runner, repoAlias) {
|
|
|
200
200
|
}
|
|
201
201
|
return count;
|
|
202
202
|
}
|
|
203
|
+
async function loadResourceSummaryEdgeCount(runner, repoAlias) {
|
|
204
|
+
const result = await runner.cypher({
|
|
205
|
+
repo: repoAlias,
|
|
206
|
+
query: "MATCH ()-[r:CodeRelation]->() WHERE r.type='UNITY_RESOURCE_SUMMARY' RETURN COUNT(r) AS summaryEdgeCount",
|
|
207
|
+
});
|
|
208
|
+
const count = extractSingleCountFromCypherResult(result);
|
|
209
|
+
if (count === null) {
|
|
210
|
+
throw new Error('Unable to parse UNITY_RESOURCE_SUMMARY edge count from cypher result');
|
|
211
|
+
}
|
|
212
|
+
return count;
|
|
213
|
+
}
|
|
203
214
|
function selectClassUid(contextOutput, expectedSymbol) {
|
|
204
215
|
const candidates = Array.isArray(contextOutput?.candidates) ? contextOutput.candidates : [];
|
|
205
216
|
const expectedLower = expectedSymbol.toLowerCase();
|
|
@@ -325,8 +336,10 @@ export async function runNeonsparkU2E2E(options) {
|
|
|
325
336
|
results.push(await runSymbolScenario(runner, scenario, repoAlias));
|
|
326
337
|
}
|
|
327
338
|
const serializedTypeEdgeCount = await loadSerializedTypeEdgeCount(runner, repoAlias);
|
|
328
|
-
|
|
329
|
-
|
|
339
|
+
const resourceSummaryEdgeCount = await loadResourceSummaryEdgeCount(runner, repoAlias);
|
|
340
|
+
const retrievalWarnings = [];
|
|
341
|
+
if (serializedTypeEdgeCount <= 0 && resourceSummaryEdgeCount <= 0) {
|
|
342
|
+
retrievalWarnings.push('UNITY edge persistence counts are 0; using query-time Unity resource evidence gates for this run');
|
|
330
343
|
}
|
|
331
344
|
const characterListContext = await loadCharacterListContext(runner, repoAlias);
|
|
332
345
|
const characterBindings = Array.isArray(characterListContext?.resourceBindings)
|
|
@@ -334,12 +347,14 @@ export async function runNeonsparkU2E2E(options) {
|
|
|
334
347
|
: [];
|
|
335
348
|
const characterListAssetRefSprite = summarizeCharacterListAssetRefSprite(characterBindings);
|
|
336
349
|
if (characterListAssetRefSprite.spriteAssetRefInstances <= 0) {
|
|
337
|
-
|
|
350
|
+
retrievalWarnings.push('CharacterList AssetRef sprite instances are 0; proceeding with runtime confidence calibration checks only');
|
|
338
351
|
}
|
|
352
|
+
const baseRetrievalSummary = summarizeRetrieval(results);
|
|
339
353
|
state.retrievalResults = results;
|
|
340
354
|
state.retrievalSummary = {
|
|
341
|
-
...
|
|
355
|
+
...baseRetrievalSummary,
|
|
342
356
|
serializedTypeEdgeCount,
|
|
357
|
+
resourceSummaryEdgeCount,
|
|
343
358
|
characterListAssetRefSprite: {
|
|
344
359
|
extractedAssetRefInstances: characterListAssetRefSprite.extractedAssetRefInstances,
|
|
345
360
|
nonEmptyAssetRefInstances: characterListAssetRefSprite.nonEmptyAssetRefInstances,
|
|
@@ -347,6 +362,10 @@ export async function runNeonsparkU2E2E(options) {
|
|
|
347
362
|
spriteRatioInNonEmpty: characterListAssetRefSprite.spriteRatioInNonEmpty,
|
|
348
363
|
uniqueSpriteAssets: characterListAssetRefSprite.uniqueSpriteAssets,
|
|
349
364
|
},
|
|
365
|
+
failures: [
|
|
366
|
+
...(baseRetrievalSummary.failures || []),
|
|
367
|
+
...retrievalWarnings,
|
|
368
|
+
],
|
|
350
369
|
};
|
|
351
370
|
return state.retrievalSummary;
|
|
352
371
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RuntimeChainResult } from '../../mcp/local/runtime-chain-verify.js';
|
|
2
|
+
export interface AnchorValidationRow {
|
|
3
|
+
anchor: string;
|
|
4
|
+
valid: boolean;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ReloadAcceptanceArtifact {
|
|
8
|
+
generatedAt: string;
|
|
9
|
+
repoAlias: string;
|
|
10
|
+
repoPath: string;
|
|
11
|
+
status: {
|
|
12
|
+
indexedCommit: string;
|
|
13
|
+
currentCommit: string;
|
|
14
|
+
upToDate: boolean;
|
|
15
|
+
raw: string;
|
|
16
|
+
};
|
|
17
|
+
commands: Array<{
|
|
18
|
+
command: string;
|
|
19
|
+
output: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
runtime_chain: RuntimeChainResult;
|
|
22
|
+
anchor_validation: AnchorValidationRow[];
|
|
23
|
+
}
|
|
24
|
+
export declare function containsPlaceholderText(value: unknown): boolean;
|
|
25
|
+
export declare function validateAnchorAuthenticity(repoPath: string, hop: {
|
|
26
|
+
anchor?: string;
|
|
27
|
+
snippet?: string;
|
|
28
|
+
}): Promise<AnchorValidationRow>;
|
|
29
|
+
export declare function validateReloadAcceptanceArtifact(artifact: ReloadAcceptanceArtifact): Promise<{
|
|
30
|
+
ok: boolean;
|
|
31
|
+
failures: string[];
|
|
32
|
+
anchorValidation: AnchorValidationRow[];
|
|
33
|
+
}>;
|
|
34
|
+
export declare function buildReloadAcceptanceArtifact(input: {
|
|
35
|
+
repoAlias: string;
|
|
36
|
+
requireStatusMatch?: boolean;
|
|
37
|
+
}): Promise<ReloadAcceptanceArtifact>;
|
|
38
|
+
export declare function writeReloadAcceptanceArtifact(outPath: string, artifact: ReloadAcceptanceArtifact): Promise<void>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { listRegisteredRepos } from '../../storage/repo-manager.js';
|
|
5
|
+
import { getCurrentCommit } from '../../storage/git.js';
|
|
6
|
+
import { LocalBackend } from '../../mcp/local/local-backend.js';
|
|
7
|
+
const PLACEHOLDER_RE = /TODO|TBD|placeholder|<symbol-or-query>/i;
|
|
8
|
+
export function containsPlaceholderText(value) {
|
|
9
|
+
return PLACEHOLDER_RE.test(String(value || ''));
|
|
10
|
+
}
|
|
11
|
+
function parseAnchor(anchor) {
|
|
12
|
+
const match = String(anchor || '').match(/^(.*):(\d+)$/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
return { filePath: match[1], line: Number(match[2]) };
|
|
16
|
+
}
|
|
17
|
+
export async function validateAnchorAuthenticity(repoPath, hop) {
|
|
18
|
+
const anchor = String(hop.anchor || '').trim();
|
|
19
|
+
if (!anchor)
|
|
20
|
+
return { anchor, valid: false, reason: 'anchor missing' };
|
|
21
|
+
if (containsPlaceholderText(anchor))
|
|
22
|
+
return { anchor, valid: false, reason: 'anchor contains placeholder text' };
|
|
23
|
+
const parsed = parseAnchor(anchor);
|
|
24
|
+
if (!parsed)
|
|
25
|
+
return { anchor, valid: false, reason: 'anchor format invalid' };
|
|
26
|
+
const resolvedPath = path.isAbsolute(parsed.filePath)
|
|
27
|
+
? parsed.filePath
|
|
28
|
+
: path.join(repoPath, parsed.filePath);
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = await fs.readFile(resolvedPath, 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { anchor, valid: false, reason: 'anchor file does not exist' };
|
|
35
|
+
}
|
|
36
|
+
const lines = raw.split(/\r?\n/);
|
|
37
|
+
if (parsed.line < 1 || parsed.line > lines.length) {
|
|
38
|
+
return { anchor, valid: false, reason: 'anchor line out of range' };
|
|
39
|
+
}
|
|
40
|
+
const expectedSnippet = String(hop.snippet || '').trim();
|
|
41
|
+
if (expectedSnippet && !lines[parsed.line - 1].includes(expectedSnippet)) {
|
|
42
|
+
return { anchor, valid: false, reason: 'anchor snippet mismatch' };
|
|
43
|
+
}
|
|
44
|
+
return { anchor, valid: true };
|
|
45
|
+
}
|
|
46
|
+
export async function validateReloadAcceptanceArtifact(artifact) {
|
|
47
|
+
const failures = [];
|
|
48
|
+
const chain = artifact.runtime_chain;
|
|
49
|
+
const hops = Array.isArray(chain?.hops) ? chain.hops : [];
|
|
50
|
+
if (containsPlaceholderText(JSON.stringify(chain))) {
|
|
51
|
+
failures.push('placeholder text leaked into runtime_chain');
|
|
52
|
+
}
|
|
53
|
+
const hopTypes = new Set(hops.map((hop) => hop.hop_type));
|
|
54
|
+
for (const required of ['resource', 'guid_map', 'code_loader', 'code_runtime']) {
|
|
55
|
+
if (!hopTypes.has(required)) {
|
|
56
|
+
failures.push(`missing required ${required} hop`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (chain?.status === 'verified_full' && hops.length === 0) {
|
|
60
|
+
failures.push('verified_full requires non-empty hops');
|
|
61
|
+
}
|
|
62
|
+
const loaderHops = hops.filter((hop) => hop.hop_type === 'code_loader');
|
|
63
|
+
if (loaderHops.length > 0) {
|
|
64
|
+
const hasCurGunGraphAssignmentAnchor = loaderHops.some((hop) => /\bCurGunGraph\b\s*=/i.test(String(hop.snippet || '')));
|
|
65
|
+
if (!hasCurGunGraphAssignmentAnchor) {
|
|
66
|
+
failures.push('loader semantic closure missing CurGunGraph assignment anchor');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const runtimeHops = hops.filter((hop) => hop.hop_type === 'code_runtime');
|
|
70
|
+
if (runtimeHops.length > 0) {
|
|
71
|
+
const hasRuntimeGraphAnchor = runtimeHops.some((hop) => /RegisterEvents|StartRoutineWithEvents/i.test(String(hop.snippet || '')));
|
|
72
|
+
const hasRuntimeReloadAnchor = runtimeHops.some((hop) => /GetValue|CheckReload|ReloadRoutine/i.test(String(hop.snippet || '')));
|
|
73
|
+
if (!hasRuntimeGraphAnchor || !hasRuntimeReloadAnchor) {
|
|
74
|
+
failures.push('runtime semantic closure missing RegisterEvents/StartRoutineWithEvents and GetValue|CheckReload|ReloadRoutine anchors');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const anchorValidation = await Promise.all(hops.map((hop) => validateAnchorAuthenticity(artifact.repoPath, hop)));
|
|
78
|
+
anchorValidation
|
|
79
|
+
.filter((row) => !row.valid)
|
|
80
|
+
.forEach((row) => failures.push(row.reason || 'invalid anchor'));
|
|
81
|
+
return {
|
|
82
|
+
ok: failures.length === 0,
|
|
83
|
+
failures,
|
|
84
|
+
anchorValidation,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function buildStatus(repoPath, indexedCommit) {
|
|
88
|
+
const currentCommit = String(getCurrentCommit(repoPath) || '').trim();
|
|
89
|
+
const upToDate = indexedCommit === currentCommit;
|
|
90
|
+
const raw = [
|
|
91
|
+
`Repository: ${repoPath}`,
|
|
92
|
+
`Indexed commit: ${indexedCommit}`,
|
|
93
|
+
`Current commit: ${currentCommit}`,
|
|
94
|
+
`Status: ${upToDate ? '✅ up-to-date' : '⚠️ stale (re-run gitnexus analyze)'}`,
|
|
95
|
+
].join('\n');
|
|
96
|
+
return { indexedCommit, currentCommit, upToDate, raw };
|
|
97
|
+
}
|
|
98
|
+
async function runBackendCommand(backend, command, method, params) {
|
|
99
|
+
return {
|
|
100
|
+
command,
|
|
101
|
+
output: await backend.callTool(method, params),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export async function buildReloadAcceptanceArtifact(input) {
|
|
105
|
+
const repos = await listRegisteredRepos({ validate: false });
|
|
106
|
+
const repo = repos.find((entry) => entry.name === input.repoAlias);
|
|
107
|
+
if (!repo) {
|
|
108
|
+
throw new Error(`Repo alias not found: ${input.repoAlias}`);
|
|
109
|
+
}
|
|
110
|
+
const distCli = path.resolve('gitnexus/dist/cli/index.js');
|
|
111
|
+
const env = { ...process.env, GITNEXUS_UNITY_PROCESS_CONFIDENCE_FIELDS: 'on' };
|
|
112
|
+
const status = buildStatus(repo.path, repo.lastCommit);
|
|
113
|
+
if (input.requireStatusMatch && !status.upToDate) {
|
|
114
|
+
throw new Error(`status mismatch for ${input.repoAlias}`);
|
|
115
|
+
}
|
|
116
|
+
const backend = new LocalBackend();
|
|
117
|
+
const ready = await backend.init();
|
|
118
|
+
if (!ready) {
|
|
119
|
+
throw new Error('LocalBackend failed to initialize for acceptance runner');
|
|
120
|
+
}
|
|
121
|
+
const commands = await Promise.all([
|
|
122
|
+
runBackendCommand(backend, `GITNEXUS_UNITY_PROCESS_CONFIDENCE_FIELDS=on node ${distCli} query -r ${input.repoAlias} --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand "Reload NEON.Game.Graph.Nodes.Reloads"`, 'query', {
|
|
123
|
+
repo: input.repoAlias,
|
|
124
|
+
query: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
125
|
+
unity_resources: 'on',
|
|
126
|
+
unity_hydration_mode: 'parity',
|
|
127
|
+
runtime_chain_verify: 'on-demand',
|
|
128
|
+
}),
|
|
129
|
+
runBackendCommand(backend, `GITNEXUS_UNITY_PROCESS_CONFIDENCE_FIELDS=on node ${distCli} query -r ${input.repoAlias} --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand "PickItUp EquipWithEvent WeaponPowerUp Equip CurGunGraph"`, 'query', {
|
|
130
|
+
repo: input.repoAlias,
|
|
131
|
+
query: 'PickItUp EquipWithEvent WeaponPowerUp Equip CurGunGraph',
|
|
132
|
+
unity_resources: 'on',
|
|
133
|
+
unity_hydration_mode: 'parity',
|
|
134
|
+
runtime_chain_verify: 'on-demand',
|
|
135
|
+
}),
|
|
136
|
+
runBackendCommand(backend, `GITNEXUS_UNITY_PROCESS_CONFIDENCE_FIELDS=on node ${distCli} context -r ${input.repoAlias} --file Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand Reload`, 'context', {
|
|
137
|
+
repo: input.repoAlias,
|
|
138
|
+
name: 'Reload',
|
|
139
|
+
file_path: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs',
|
|
140
|
+
unity_resources: 'on',
|
|
141
|
+
unity_hydration_mode: 'parity',
|
|
142
|
+
runtime_chain_verify: 'on-demand',
|
|
143
|
+
}),
|
|
144
|
+
]);
|
|
145
|
+
const runtime_chain = commands[0].output?.runtime_chain;
|
|
146
|
+
if (!runtime_chain) {
|
|
147
|
+
throw new Error('runtime_chain missing from reload query');
|
|
148
|
+
}
|
|
149
|
+
const artifact = {
|
|
150
|
+
generatedAt: new Date().toISOString(),
|
|
151
|
+
repoAlias: input.repoAlias,
|
|
152
|
+
repoPath: repo.path,
|
|
153
|
+
status,
|
|
154
|
+
commands: [
|
|
155
|
+
{
|
|
156
|
+
command: `node ${distCli} status`,
|
|
157
|
+
output: status.raw,
|
|
158
|
+
},
|
|
159
|
+
...commands,
|
|
160
|
+
],
|
|
161
|
+
runtime_chain,
|
|
162
|
+
anchor_validation: [],
|
|
163
|
+
};
|
|
164
|
+
const validation = await validateReloadAcceptanceArtifact(artifact);
|
|
165
|
+
artifact.anchor_validation = validation.anchorValidation;
|
|
166
|
+
if (!validation.ok) {
|
|
167
|
+
throw new Error(validation.failures.join('\n'));
|
|
168
|
+
}
|
|
169
|
+
return artifact;
|
|
170
|
+
}
|
|
171
|
+
export async function writeReloadAcceptanceArtifact(outPath, artifact) {
|
|
172
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
173
|
+
await fs.writeFile(outPath, JSON.stringify(artifact, null, 2));
|
|
174
|
+
}
|
|
175
|
+
async function main(argv) {
|
|
176
|
+
const verifyOnlyIndex = argv.indexOf('--verify-only');
|
|
177
|
+
if (verifyOnlyIndex >= 0) {
|
|
178
|
+
const inputPath = path.resolve(argv[verifyOnlyIndex + 1] || '');
|
|
179
|
+
const artifact = JSON.parse(await fs.readFile(inputPath, 'utf-8'));
|
|
180
|
+
const validation = await validateReloadAcceptanceArtifact(artifact);
|
|
181
|
+
if (!validation.ok) {
|
|
182
|
+
throw new Error(validation.failures.join('\n'));
|
|
183
|
+
}
|
|
184
|
+
process.stdout.write(`reload acceptance verification passed: ${inputPath}\n`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const repoIndex = argv.indexOf('--repo');
|
|
188
|
+
const outIndex = argv.indexOf('--out');
|
|
189
|
+
const requireStatusMatch = argv.includes('--require-status-match');
|
|
190
|
+
const repoAlias = String(argv[repoIndex + 1] || '').trim();
|
|
191
|
+
const outPath = path.resolve(String(argv[outIndex + 1] || '').trim());
|
|
192
|
+
if (!repoAlias || !outPath) {
|
|
193
|
+
throw new Error('Usage: node dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js --repo <alias> --out <path> [--require-status-match] | --verify-only <path>');
|
|
194
|
+
}
|
|
195
|
+
const artifact = await buildReloadAcceptanceArtifact({ repoAlias, requireStatusMatch });
|
|
196
|
+
await writeReloadAcceptanceArtifact(outPath, artifact);
|
|
197
|
+
process.stdout.write(`reload acceptance artifact written: ${outPath}\n`);
|
|
198
|
+
}
|
|
199
|
+
const entryPath = process.argv[1] ? path.resolve(process.argv[1]) : '';
|
|
200
|
+
const thisPath = fileURLToPath(import.meta.url);
|
|
201
|
+
if (entryPath === thisPath) {
|
|
202
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
203
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { containsPlaceholderText, validateAnchorAuthenticity, validateReloadAcceptanceArtifact, } from './reload-v1-acceptance-runner.js';
|
|
6
|
+
const { test: rawTest } = process.env.VITEST
|
|
7
|
+
? await import('vitest')
|
|
8
|
+
: await import('node:test');
|
|
9
|
+
const test = rawTest;
|
|
10
|
+
test('v1 reload acceptance rejects placeholders and missing required segments', async () => {
|
|
11
|
+
const artifact = {
|
|
12
|
+
repoPath: process.cwd(),
|
|
13
|
+
runtime_chain: {
|
|
14
|
+
status: 'verified_full',
|
|
15
|
+
hops: [
|
|
16
|
+
{ hop_type: 'resource', anchor: 'placeholder:1', snippet: 'placeholder' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
const validation = await validateReloadAcceptanceArtifact(artifact);
|
|
21
|
+
assert.equal(validation.ok, false);
|
|
22
|
+
assert.equal(validation.failures.some((failure) => /placeholder/i.test(failure)), true);
|
|
23
|
+
assert.equal(validation.failures.some((failure) => /missing required guid_map/i.test(failure)), true);
|
|
24
|
+
assert.equal(validation.failures.some((failure) => /missing required code_runtime/i.test(failure)), true);
|
|
25
|
+
});
|
|
26
|
+
test('v1 anchor authenticity validates file existence, line range, and snippet match', async () => {
|
|
27
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'reload-v1-'));
|
|
28
|
+
const filePath = path.join(tempDir, 'Anchor.cs');
|
|
29
|
+
await fs.writeFile(filePath, 'line one\nneedle line\nline three\n');
|
|
30
|
+
const valid = await validateAnchorAuthenticity(tempDir, {
|
|
31
|
+
anchor: `${filePath}:2`,
|
|
32
|
+
snippet: 'needle line',
|
|
33
|
+
});
|
|
34
|
+
assert.deepEqual(valid, { anchor: `${filePath}:2`, valid: true });
|
|
35
|
+
const lineOut = await validateAnchorAuthenticity(tempDir, {
|
|
36
|
+
anchor: `${filePath}:9`,
|
|
37
|
+
snippet: 'needle line',
|
|
38
|
+
});
|
|
39
|
+
assert.equal(lineOut.valid, false);
|
|
40
|
+
assert.match(lineOut.reason || '', /line out of range/i);
|
|
41
|
+
const mismatch = await validateAnchorAuthenticity(tempDir, {
|
|
42
|
+
anchor: `${filePath}:2`,
|
|
43
|
+
snippet: 'missing snippet',
|
|
44
|
+
});
|
|
45
|
+
assert.equal(mismatch.valid, false);
|
|
46
|
+
assert.match(mismatch.reason || '', /snippet mismatch/i);
|
|
47
|
+
});
|
|
48
|
+
test('containsPlaceholderText detects placeholder leakage', () => {
|
|
49
|
+
assert.equal(containsPlaceholderText('TODO later'), true);
|
|
50
|
+
assert.equal(containsPlaceholderText('real anchor'), false);
|
|
51
|
+
});
|
|
52
|
+
test('v1 reload acceptance enforces loader/runtime semantic anchors', async () => {
|
|
53
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'reload-v1-semantic-'));
|
|
54
|
+
const filePath = path.join(tempDir, 'Chain.cs');
|
|
55
|
+
await fs.writeFile(filePath, 'resource line\nguid line\nloader line\nruntime line\n');
|
|
56
|
+
const artifact = {
|
|
57
|
+
repoPath: tempDir,
|
|
58
|
+
runtime_chain: {
|
|
59
|
+
status: 'verified_full',
|
|
60
|
+
hops: [
|
|
61
|
+
{ hop_type: 'resource', anchor: `${filePath}:1`, snippet: 'resource line', note: 'resource ok' },
|
|
62
|
+
{ hop_type: 'guid_map', anchor: `${filePath}:2`, snippet: 'guid line', note: 'guid ok' },
|
|
63
|
+
{ hop_type: 'code_loader', anchor: `${filePath}:3`, snippet: 'loader line', note: 'loader ok' },
|
|
64
|
+
{ hop_type: 'code_runtime', anchor: `${filePath}:4`, snippet: 'runtime line', note: 'runtime ok' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const validation = await validateReloadAcceptanceArtifact(artifact);
|
|
69
|
+
assert.equal(validation.ok, false);
|
|
70
|
+
assert.equal(validation.failures.some((failure) => /loader.*curgungraph/i.test(failure)), true);
|
|
71
|
+
assert.equal(validation.failures.some((failure) => /runtime.*closure/i.test(failure)), true);
|
|
72
|
+
});
|
|
@@ -44,6 +44,7 @@ export function buildRetrievalSummaryMarkdown(summary) {
|
|
|
44
44
|
`- Total Tokens (est): ${summary.tokenSummary?.totalTokensEst ?? 0}`,
|
|
45
45
|
`- Total Duration: ${formatMs(summary.tokenSummary?.totalDurationMs)}`,
|
|
46
46
|
`- UNITY_SERIALIZED_TYPE_IN Edges: ${typeof summary.serializedTypeEdgeCount === 'number' ? summary.serializedTypeEdgeCount : 'n/a'}`,
|
|
47
|
+
`- UNITY_RESOURCE_SUMMARY Edges: ${typeof summary.resourceSummaryEdgeCount === 'number' ? summary.resourceSummaryEdgeCount : 'n/a'}`,
|
|
47
48
|
`- CharacterList AssetRef Sprite Instances: ${summary.characterListAssetRefSprite?.spriteAssetRefInstances ?? 'n/a'}`,
|
|
48
49
|
`- CharacterList AssetRef Sprite Ratio: ${typeof summary.characterListAssetRefSprite?.spriteRatioInNonEmpty === 'number'
|
|
49
50
|
? `${(summary.characterListAssetRefSprite.spriteRatioInNonEmpty * 100).toFixed(2)}%`
|
|
@@ -87,6 +88,7 @@ export function buildFinalVerdictMarkdown(input) {
|
|
|
87
88
|
`- Total Tokens (est): ${summary?.tokenSummary?.totalTokensEst ?? 0}`,
|
|
88
89
|
`- Total Duration: ${formatMs(summary?.tokenSummary?.totalDurationMs)}`,
|
|
89
90
|
`- UNITY_SERIALIZED_TYPE_IN Edges: ${typeof summary?.serializedTypeEdgeCount === 'number' ? summary.serializedTypeEdgeCount : 'n/a'}`,
|
|
91
|
+
`- UNITY_RESOURCE_SUMMARY Edges: ${typeof summary?.resourceSummaryEdgeCount === 'number' ? summary.resourceSummaryEdgeCount : 'n/a'}`,
|
|
90
92
|
`- CharacterList AssetRef Sprite Instances: ${summary?.characterListAssetRefSprite?.spriteAssetRefInstances ?? 'n/a'}`,
|
|
91
93
|
`- CharacterList AssetRef Sprite Ratio: ${typeof summary?.characterListAssetRefSprite?.spriteRatioInNonEmpty === 'number'
|
|
92
94
|
? `${(summary.characterListAssetRefSprite.spriteRatioInNonEmpty * 100).toFixed(2)}%`
|
|
@@ -18,4 +18,38 @@ export interface SymbolScenarioResult {
|
|
|
18
18
|
failures: string[];
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
export interface Phase5ConfidenceCalibrationCounts {
|
|
22
|
+
totalEvaluated: number;
|
|
23
|
+
falseNegativeCount: number;
|
|
24
|
+
falseConfidenceCount: number;
|
|
25
|
+
lowConfidenceHintCovered: number;
|
|
26
|
+
lowConfidenceCount: number;
|
|
27
|
+
fallbackCovered: number;
|
|
28
|
+
}
|
|
29
|
+
export interface Phase5BaselineSnapshot extends Partial<Phase5ConfidenceCalibrationCounts> {
|
|
30
|
+
artifactPath: string;
|
|
31
|
+
gitCommit: string;
|
|
32
|
+
sha256: string;
|
|
33
|
+
}
|
|
34
|
+
export interface Phase5ConfidenceCalibrationSummary {
|
|
35
|
+
lowConfidenceHintCoverage: number;
|
|
36
|
+
falseConfidenceFailures: number;
|
|
37
|
+
falseNegativeFallbackCoverage: number;
|
|
38
|
+
falseNegativeRateBaselinePct: number;
|
|
39
|
+
falseNegativeRateCurrentPct: number;
|
|
40
|
+
falseNegativeRateDeltaPct: number;
|
|
41
|
+
falseConfidenceRateBaselinePct: number;
|
|
42
|
+
falseConfidenceRateCurrentPct: number;
|
|
43
|
+
falseConfidenceRateDeltaPct: number;
|
|
44
|
+
baseline: {
|
|
45
|
+
artifactPath: string;
|
|
46
|
+
gitCommit: string;
|
|
47
|
+
sha256: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export declare function containsPlaceholderLeak(value: unknown): boolean;
|
|
51
|
+
export declare function summarizePhase5ConfidenceCalibration(input: {
|
|
52
|
+
current: Phase5ConfidenceCalibrationCounts;
|
|
53
|
+
baseline: Phase5BaselineSnapshot;
|
|
54
|
+
}): Phase5ConfidenceCalibrationSummary;
|
|
21
55
|
export declare function runSymbolScenario(runner: ToolRunner, scenario: SymbolScenario, repo?: string): Promise<SymbolScenarioResult>;
|