@veewo/gitnexus 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +9 -4
  2. package/dist/benchmark/runner.test.js +1 -1
  3. package/dist/benchmark/u2-e2e/analyze-parser.d.ts +22 -0
  4. package/dist/benchmark/u2-e2e/analyze-parser.js +89 -0
  5. package/dist/benchmark/u2-e2e/analyze-parser.test.d.ts +1 -0
  6. package/dist/benchmark/u2-e2e/analyze-parser.test.js +13 -0
  7. package/dist/benchmark/u2-e2e/characterlist-assetref.d.ts +19 -0
  8. package/dist/benchmark/u2-e2e/characterlist-assetref.js +80 -0
  9. package/dist/benchmark/u2-e2e/characterlist-assetref.test.d.ts +1 -0
  10. package/dist/benchmark/u2-e2e/characterlist-assetref.test.js +108 -0
  11. package/dist/benchmark/u2-e2e/config.d.ts +25 -0
  12. package/dist/benchmark/u2-e2e/config.js +86 -0
  13. package/dist/benchmark/u2-e2e/config.test.d.ts +1 -0
  14. package/dist/benchmark/u2-e2e/config.test.js +29 -0
  15. package/dist/benchmark/u2-e2e/metrics.d.ts +20 -0
  16. package/dist/benchmark/u2-e2e/metrics.js +34 -0
  17. package/dist/benchmark/u2-e2e/metrics.test.d.ts +1 -0
  18. package/dist/benchmark/u2-e2e/metrics.test.js +13 -0
  19. package/dist/benchmark/u2-e2e/neonspark-full-e2e.d.ts +33 -0
  20. package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +439 -0
  21. package/dist/benchmark/u2-e2e/neonspark-full-e2e.test.d.ts +1 -0
  22. package/dist/benchmark/u2-e2e/neonspark-full-e2e.test.js +40 -0
  23. package/dist/benchmark/u2-e2e/report.d.ts +58 -0
  24. package/dist/benchmark/u2-e2e/report.js +130 -0
  25. package/dist/benchmark/u2-e2e/report.test.d.ts +1 -0
  26. package/dist/benchmark/u2-e2e/report.test.js +58 -0
  27. package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +21 -0
  28. package/dist/benchmark/u2-e2e/retrieval-runner.js +166 -0
  29. package/dist/benchmark/u2-e2e/retrieval-runner.test.d.ts +1 -0
  30. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +145 -0
  31. package/dist/benchmark/u2-performance-sampler.d.ts +33 -0
  32. package/dist/benchmark/u2-performance-sampler.js +178 -0
  33. package/dist/benchmark/u2-performance-sampler.test.d.ts +1 -0
  34. package/dist/benchmark/u2-performance-sampler.test.js +34 -0
  35. package/dist/cli/analyze-custom-modules-regression.test.d.ts +1 -0
  36. package/dist/cli/analyze-custom-modules-regression.test.js +75 -0
  37. package/dist/cli/analyze-modules-diagnostics.test.d.ts +1 -0
  38. package/dist/cli/analyze-modules-diagnostics.test.js +36 -0
  39. package/dist/cli/analyze-multi-scope-regression.test.js +10 -0
  40. package/dist/cli/analyze-summary.d.ts +7 -0
  41. package/dist/cli/analyze-summary.js +37 -0
  42. package/dist/cli/analyze-summary.test.d.ts +1 -0
  43. package/dist/cli/analyze-summary.test.js +58 -0
  44. package/dist/cli/analyze.js +11 -6
  45. package/dist/cli/benchmark-u2-e2e.d.ts +9 -0
  46. package/dist/cli/benchmark-u2-e2e.js +35 -0
  47. package/dist/cli/benchmark-u2-e2e.test.d.ts +1 -0
  48. package/dist/cli/benchmark-u2-e2e.test.js +7 -0
  49. package/dist/cli/index.js +20 -0
  50. package/dist/cli/setup.js +24 -3
  51. package/dist/cli/setup.test.js +6 -4
  52. package/dist/cli/tool.d.ts +3 -0
  53. package/dist/cli/tool.js +2 -0
  54. package/dist/cli/unity-bindings.d.ts +8 -0
  55. package/dist/cli/unity-bindings.js +33 -0
  56. package/dist/cli/unity-bindings.test.d.ts +1 -0
  57. package/dist/cli/unity-bindings.test.js +24 -0
  58. package/dist/core/graph/types.d.ts +1 -1
  59. package/dist/core/ingestion/modules/assignment-engine.d.ts +33 -0
  60. package/dist/core/ingestion/modules/assignment-engine.js +179 -0
  61. package/dist/core/ingestion/modules/assignment-engine.test.d.ts +1 -0
  62. package/dist/core/ingestion/modules/assignment-engine.test.js +111 -0
  63. package/dist/core/ingestion/modules/config-loader.d.ts +2 -0
  64. package/dist/core/ingestion/modules/config-loader.js +186 -0
  65. package/dist/core/ingestion/modules/config-loader.test.d.ts +1 -0
  66. package/dist/core/ingestion/modules/config-loader.test.js +57 -0
  67. package/dist/core/ingestion/modules/rule-matcher.d.ts +12 -0
  68. package/dist/core/ingestion/modules/rule-matcher.js +63 -0
  69. package/dist/core/ingestion/modules/rule-matcher.test.d.ts +1 -0
  70. package/dist/core/ingestion/modules/rule-matcher.test.js +58 -0
  71. package/dist/core/ingestion/modules/types.d.ts +44 -0
  72. package/dist/core/ingestion/modules/types.js +2 -0
  73. package/dist/core/ingestion/pipeline.d.ts +2 -4
  74. package/dist/core/ingestion/pipeline.js +12 -0
  75. package/dist/core/ingestion/unity-resource-processor.d.ts +26 -0
  76. package/dist/core/ingestion/unity-resource-processor.js +363 -0
  77. package/dist/core/ingestion/unity-resource-processor.test.d.ts +1 -0
  78. package/dist/core/ingestion/unity-resource-processor.test.js +599 -0
  79. package/dist/core/kuzu/kuzu-adapter.d.ts +6 -0
  80. package/dist/core/kuzu/kuzu-adapter.js +18 -7
  81. package/dist/core/kuzu/schema.d.ts +2 -2
  82. package/dist/core/kuzu/schema.js +22 -1
  83. package/dist/core/kuzu/schema.test.d.ts +1 -0
  84. package/dist/core/kuzu/schema.test.js +17 -0
  85. package/dist/core/unity/meta-index.d.ts +5 -0
  86. package/dist/core/unity/meta-index.js +113 -0
  87. package/dist/core/unity/meta-index.test.d.ts +1 -0
  88. package/dist/core/unity/meta-index.test.js +11 -0
  89. package/dist/core/unity/options.d.ts +2 -0
  90. package/dist/core/unity/options.js +9 -0
  91. package/dist/core/unity/options.test.d.ts +1 -0
  92. package/dist/core/unity/options.test.js +10 -0
  93. package/dist/core/unity/override-merger.d.ts +27 -0
  94. package/dist/core/unity/override-merger.js +35 -0
  95. package/dist/core/unity/override-merger.test.d.ts +1 -0
  96. package/dist/core/unity/override-merger.test.js +47 -0
  97. package/dist/core/unity/resolver.d.ts +79 -0
  98. package/dist/core/unity/resolver.js +384 -0
  99. package/dist/core/unity/resolver.test.d.ts +1 -0
  100. package/dist/core/unity/resolver.test.js +244 -0
  101. package/dist/core/unity/resource-hit-scanner.d.ts +10 -0
  102. package/dist/core/unity/resource-hit-scanner.js +60 -0
  103. package/dist/core/unity/resource-hit-scanner.test.d.ts +1 -0
  104. package/dist/core/unity/resource-hit-scanner.test.js +20 -0
  105. package/dist/core/unity/scan-context.d.ts +23 -0
  106. package/dist/core/unity/scan-context.js +318 -0
  107. package/dist/core/unity/scan-context.test.d.ts +1 -0
  108. package/dist/core/unity/scan-context.test.js +118 -0
  109. package/dist/core/unity/serialized-type-index.d.ts +10 -0
  110. package/dist/core/unity/serialized-type-index.js +105 -0
  111. package/dist/core/unity/serialized-type-index.test.d.ts +1 -0
  112. package/dist/core/unity/serialized-type-index.test.js +34 -0
  113. package/dist/core/unity/u2-thresholds.test.d.ts +1 -0
  114. package/dist/core/unity/u2-thresholds.test.js +71 -0
  115. package/dist/core/unity/yaml-object-graph.d.ts +9 -0
  116. package/dist/core/unity/yaml-object-graph.js +92 -0
  117. package/dist/core/unity/yaml-object-graph.test.d.ts +1 -0
  118. package/dist/core/unity/yaml-object-graph.test.js +49 -0
  119. package/dist/mcp/local/cluster-aggregation.d.ts +20 -0
  120. package/dist/mcp/local/cluster-aggregation.js +48 -0
  121. package/dist/mcp/local/cluster-aggregation.test.d.ts +1 -0
  122. package/dist/mcp/local/cluster-aggregation.test.js +22 -0
  123. package/dist/mcp/local/local-backend.js +12 -1
  124. package/dist/mcp/local/unity-enrichment.d.ts +6 -0
  125. package/dist/mcp/local/unity-enrichment.js +91 -0
  126. package/dist/mcp/local/unity-enrichment.test.d.ts +1 -0
  127. package/dist/mcp/local/unity-enrichment.test.js +130 -0
  128. package/dist/mcp/tools.js +12 -0
  129. package/dist/types/pipeline.d.ts +7 -0
  130. package/dist/types/pipeline.js +2 -0
  131. package/hooks/check-release-path-hygiene.mjs +108 -0
  132. package/package.json +14 -7
@@ -0,0 +1,439 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { loadE2EConfig } from './config.js';
5
+ import { parseAnalyzeSummary, compareEstimate } from './analyze-parser.js';
6
+ import { runSymbolScenario } from './retrieval-runner.js';
7
+ import { summarizeDurations } from './metrics.js';
8
+ import { writeU2E2EReports } from './report.js';
9
+ import { summarizeCharacterListAssetRefSprite } from './characterlist-assetref.js';
10
+ import { createAgentContextToolRunner } from '../agent-context/tool-runner.js';
11
+ const GATE_ORDER = [
12
+ 'preflight',
13
+ 'build',
14
+ 'pipeline-profile',
15
+ 'analyze',
16
+ 'estimate-compare',
17
+ 'retrieval',
18
+ 'final-report',
19
+ ];
20
+ function nowStamp() {
21
+ const now = new Date();
22
+ const yyyy = now.getUTCFullYear();
23
+ const mm = String(now.getUTCMonth() + 1).padStart(2, '0');
24
+ const dd = String(now.getUTCDate()).padStart(2, '0');
25
+ const hh = String(now.getUTCHours()).padStart(2, '0');
26
+ const min = String(now.getUTCMinutes()).padStart(2, '0');
27
+ const ss = String(now.getUTCSeconds()).padStart(2, '0');
28
+ return `${yyyy}${mm}${dd}-${hh}${min}${ss}`;
29
+ }
30
+ export function createRunId(prefix = 'neonspark-u2-e2e') {
31
+ return `${prefix}-${nowStamp()}`;
32
+ }
33
+ async function defaultWriteCheckpoint(reportDir, payload) {
34
+ await fs.mkdir(reportDir, { recursive: true });
35
+ await fs.writeFile(path.join(reportDir, 'checkpoint.json'), JSON.stringify(payload, null, 2), 'utf-8');
36
+ }
37
+ async function pathExists(targetPath) {
38
+ try {
39
+ await fs.access(targetPath);
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ async function resolveRepoRoot(cwd) {
47
+ if (await pathExists(path.join(cwd, 'benchmarks')) && await pathExists(path.join(cwd, 'gitnexus'))) {
48
+ return cwd;
49
+ }
50
+ const parent = path.resolve(cwd, '..');
51
+ if (await pathExists(path.join(parent, 'benchmarks')) && await pathExists(path.join(parent, 'gitnexus'))) {
52
+ return parent;
53
+ }
54
+ throw new Error(`Unable to resolve repo root from cwd=${cwd}`);
55
+ }
56
+ function sanitizeAlias(input) {
57
+ return input.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 120);
58
+ }
59
+ async function execCommand(command, args, cwd) {
60
+ return new Promise((resolve, reject) => {
61
+ const started = Date.now();
62
+ const child = spawn(command, args, { cwd, env: process.env });
63
+ let stdout = '';
64
+ let stderr = '';
65
+ child.stdout.on('data', (buf) => {
66
+ stdout += buf.toString();
67
+ });
68
+ child.stderr.on('data', (buf) => {
69
+ stderr += buf.toString();
70
+ });
71
+ child.on('error', reject);
72
+ child.on('close', (code) => {
73
+ resolve({
74
+ code,
75
+ stdout,
76
+ stderr,
77
+ durationMs: Date.now() - started,
78
+ });
79
+ });
80
+ });
81
+ }
82
+ function scopePrefixArgs(prefixes) {
83
+ const out = [];
84
+ for (const prefix of prefixes) {
85
+ out.push('--scope-prefix', prefix);
86
+ }
87
+ return out;
88
+ }
89
+ async function runRequiredCommand(command, args, cwd) {
90
+ const result = await execCommand(command, args, cwd);
91
+ if (result.code !== 0) {
92
+ const msg = `Command failed (${result.code}): ${command} ${args.join(' ')}\n${result.stderr || result.stdout}`;
93
+ throw new Error(msg.trim());
94
+ }
95
+ return result;
96
+ }
97
+ function summarizeRetrieval(results) {
98
+ const symbols = results.map((result) => {
99
+ const durationMs = result.steps.reduce((sum, step) => sum + step.durationMs, 0);
100
+ const totalTokensEst = result.steps.reduce((sum, step) => sum + step.totalTokensEst, 0);
101
+ return {
102
+ symbol: result.symbol,
103
+ pass: result.assertions.pass,
104
+ stepCount: result.steps.length,
105
+ durationMs: Number(durationMs.toFixed(1)),
106
+ totalTokensEst,
107
+ failures: result.assertions.failures,
108
+ };
109
+ });
110
+ const allSteps = results.flatMap((result) => result.steps);
111
+ const totalTokensEst = allSteps.reduce((sum, step) => sum + step.totalTokensEst, 0);
112
+ const durationSeries = allSteps.map((step) => step.durationMs);
113
+ const durationStats = summarizeDurations(durationSeries);
114
+ const totalDurationMs = allSteps.reduce((sum, step) => sum + step.durationMs, 0);
115
+ const failures = symbols.flatMap((row) => (row.failures || []).map((item) => `${row.symbol}: ${item}`));
116
+ return {
117
+ symbols,
118
+ tokenSummary: {
119
+ totalTokensEst,
120
+ totalDurationMs: Number(totalDurationMs.toFixed(1)),
121
+ },
122
+ failures: [
123
+ ...failures,
124
+ `duration.min=${durationStats.minMs}ms median=${durationStats.medianMs}ms max=${durationStats.maxMs}ms`,
125
+ ],
126
+ };
127
+ }
128
+ function extractPipelineMeanMs(profile) {
129
+ if (!profile)
130
+ return 0;
131
+ const metrics = profile.metrics;
132
+ const pipelineTotalMs = metrics?.pipelineTotalMs;
133
+ const mean = pipelineTotalMs?.mean;
134
+ return typeof mean === 'number' && Number.isFinite(mean) ? mean : 0;
135
+ }
136
+ function parseNumericCount(raw) {
137
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
138
+ return raw;
139
+ }
140
+ if (typeof raw === 'string') {
141
+ const cleaned = raw.trim().replace(/,/g, '');
142
+ if (!cleaned)
143
+ return null;
144
+ const parsed = Number(cleaned);
145
+ if (Number.isFinite(parsed)) {
146
+ return parsed;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+ export function extractSingleCountFromCypherResult(result) {
152
+ if (Array.isArray(result) && result.length > 0) {
153
+ const first = result[0];
154
+ if (first && typeof first === 'object') {
155
+ return (parseNumericCount(first.serializedTypeEdgeCount)
156
+ ?? parseNumericCount(first.c)
157
+ ?? parseNumericCount(first.count)
158
+ ?? null);
159
+ }
160
+ }
161
+ if (!result || typeof result !== 'object') {
162
+ return null;
163
+ }
164
+ const row = result;
165
+ const direct = parseNumericCount(row.serializedTypeEdgeCount)
166
+ ?? parseNumericCount(row.c)
167
+ ?? parseNumericCount(row.count);
168
+ if (direct !== null) {
169
+ return direct;
170
+ }
171
+ const markdown = row.markdown;
172
+ if (typeof markdown === 'string' && markdown.trim().length > 0) {
173
+ const lines = markdown
174
+ .split(/\r?\n/)
175
+ .map((line) => line.trim())
176
+ .filter((line) => line.length > 0);
177
+ for (let index = 2; index < lines.length; index += 1) {
178
+ const cells = lines[index]
179
+ .split('|')
180
+ .map((cell) => cell.trim())
181
+ .filter((cell) => cell.length > 0);
182
+ for (const cell of cells) {
183
+ const parsed = parseNumericCount(cell);
184
+ if (parsed !== null) {
185
+ return parsed;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+ async function loadSerializedTypeEdgeCount(runner, repoAlias) {
193
+ const result = await runner.cypher({
194
+ repo: repoAlias,
195
+ query: "MATCH ()-[r:CodeRelation]->() WHERE r.type='UNITY_SERIALIZED_TYPE_IN' RETURN COUNT(r) AS serializedTypeEdgeCount",
196
+ });
197
+ const count = extractSingleCountFromCypherResult(result);
198
+ if (count === null) {
199
+ throw new Error('Unable to parse UNITY_SERIALIZED_TYPE_IN edge count from cypher result');
200
+ }
201
+ return count;
202
+ }
203
+ function selectClassUid(contextOutput, expectedSymbol) {
204
+ const candidates = Array.isArray(contextOutput?.candidates) ? contextOutput.candidates : [];
205
+ const expectedLower = expectedSymbol.toLowerCase();
206
+ for (const candidate of candidates) {
207
+ const kind = String(candidate?.kind || '').toLowerCase();
208
+ if (kind !== 'class')
209
+ continue;
210
+ const name = String(candidate?.name || '').toLowerCase();
211
+ if (name !== expectedLower)
212
+ continue;
213
+ const uid = String(candidate?.uid || '').trim();
214
+ if (uid)
215
+ return uid;
216
+ }
217
+ return null;
218
+ }
219
+ async function loadCharacterListContext(runner, repoAlias) {
220
+ const input = { name: 'CharacterList', repo: repoAlias, unity_resources: 'on' };
221
+ const first = await runner.context(input);
222
+ if (first?.status !== 'ambiguous') {
223
+ return first;
224
+ }
225
+ const classUid = selectClassUid(first, 'CharacterList');
226
+ if (!classUid) {
227
+ return first;
228
+ }
229
+ return runner.context({ ...input, uid: classUid });
230
+ }
231
+ export async function runNeonsparkU2E2E(options) {
232
+ const cwd = process.cwd();
233
+ const repoRoot = await resolveRepoRoot(cwd);
234
+ const gitnexusRoot = path.join(repoRoot, 'gitnexus');
235
+ const config = await loadE2EConfig(options.configPath);
236
+ const runId = options.runId || createRunId(config.runIdPrefix || 'neonspark-u2-e2e');
237
+ const reportDir = path.resolve(options.reportDir || path.join(repoRoot, 'docs/reports', runId));
238
+ const repoAlias = sanitizeAlias(`${config.repoAliasPrefix}-${runId}`);
239
+ const state = {
240
+ repoRoot,
241
+ gitnexusRoot,
242
+ runId,
243
+ reportDir,
244
+ repoAlias,
245
+ configPath: path.resolve(options.configPath),
246
+ config,
247
+ };
248
+ return runE2E({
249
+ runId,
250
+ reportDir,
251
+ gates: {
252
+ preflight: async () => {
253
+ if (!(await pathExists(config.targetPath))) {
254
+ throw new Error(`Target path not found: ${config.targetPath}`);
255
+ }
256
+ if (!(await pathExists('/usr/bin/time'))) {
257
+ throw new Error('Missing required tool: /usr/bin/time');
258
+ }
259
+ state.preflight = {
260
+ targetPath: config.targetPath,
261
+ configPath: state.configPath,
262
+ repoAlias,
263
+ scenarioCount: config.symbolScenarios.length,
264
+ };
265
+ return state.preflight;
266
+ },
267
+ build: async () => {
268
+ const build = await runRequiredCommand('npm', ['--prefix', 'gitnexus', 'run', 'build'], repoRoot);
269
+ state.buildMs = Number(build.durationMs.toFixed(1));
270
+ return { durationMs: state.buildMs };
271
+ },
272
+ 'pipeline-profile': async () => {
273
+ const reportPath = path.join(reportDir, 'pipeline-profile.json');
274
+ const scopeArgs = scopePrefixArgs(config.scope.scriptPrefixes || []);
275
+ await runRequiredCommand('npm', [
276
+ '--prefix',
277
+ 'gitnexus',
278
+ 'run',
279
+ 'benchmark:u2:sample',
280
+ '--',
281
+ '--target-path',
282
+ config.targetPath,
283
+ '--runs',
284
+ '1',
285
+ '--report',
286
+ reportPath,
287
+ ...scopeArgs,
288
+ ], repoRoot);
289
+ const raw = await fs.readFile(reportPath, 'utf-8');
290
+ state.pipelineProfile = JSON.parse(raw);
291
+ return state.pipelineProfile;
292
+ },
293
+ analyze: async () => {
294
+ const scopeArgs = scopePrefixArgs(config.scope.scriptPrefixes || []);
295
+ const analyze = await runRequiredCommand('/usr/bin/time', [
296
+ '-p',
297
+ 'node',
298
+ 'dist/cli/index.js',
299
+ 'analyze',
300
+ '--force',
301
+ '--extensions',
302
+ '.cs',
303
+ '--repo-alias',
304
+ repoAlias,
305
+ ...scopeArgs,
306
+ config.targetPath,
307
+ ], gitnexusRoot);
308
+ const logPath = path.join(reportDir, 'analyze.log');
309
+ await fs.writeFile(logPath, `${analyze.stdout}\n${analyze.stderr}\n`, 'utf-8');
310
+ state.analyzeSummary = await parseAnalyzeSummary(logPath);
311
+ return state.analyzeSummary;
312
+ },
313
+ 'estimate-compare': async () => {
314
+ if (!state.analyzeSummary) {
315
+ throw new Error('analyze summary missing');
316
+ }
317
+ state.estimateComparison = compareEstimate(state.analyzeSummary.totalSec, config.estimateRangeSec);
318
+ return state.estimateComparison;
319
+ },
320
+ retrieval: async () => {
321
+ const runner = await createAgentContextToolRunner();
322
+ try {
323
+ const results = [];
324
+ for (const scenario of config.symbolScenarios) {
325
+ results.push(await runSymbolScenario(runner, scenario, repoAlias));
326
+ }
327
+ const serializedTypeEdgeCount = await loadSerializedTypeEdgeCount(runner, repoAlias);
328
+ if (serializedTypeEdgeCount <= 0) {
329
+ throw new Error('U3 gate failed: UNITY_SERIALIZED_TYPE_IN edge count must be > 0');
330
+ }
331
+ const characterListContext = await loadCharacterListContext(runner, repoAlias);
332
+ const characterBindings = Array.isArray(characterListContext?.resourceBindings)
333
+ ? characterListContext.resourceBindings
334
+ : [];
335
+ const characterListAssetRefSprite = summarizeCharacterListAssetRefSprite(characterBindings);
336
+ if (characterListAssetRefSprite.spriteAssetRefInstances <= 0) {
337
+ throw new Error('U3 gate failed: CharacterList AssetRef sprite instances must be > 0');
338
+ }
339
+ state.retrievalResults = results;
340
+ state.retrievalSummary = {
341
+ ...summarizeRetrieval(results),
342
+ serializedTypeEdgeCount,
343
+ characterListAssetRefSprite: {
344
+ extractedAssetRefInstances: characterListAssetRefSprite.extractedAssetRefInstances,
345
+ nonEmptyAssetRefInstances: characterListAssetRefSprite.nonEmptyAssetRefInstances,
346
+ spriteAssetRefInstances: characterListAssetRefSprite.spriteAssetRefInstances,
347
+ spriteRatioInNonEmpty: characterListAssetRefSprite.spriteRatioInNonEmpty,
348
+ uniqueSpriteAssets: characterListAssetRefSprite.uniqueSpriteAssets,
349
+ },
350
+ };
351
+ return state.retrievalSummary;
352
+ }
353
+ finally {
354
+ await runner.close();
355
+ }
356
+ },
357
+ 'final-report': async () => {
358
+ await writeU2E2EReports(reportDir, {
359
+ preflight: state.preflight,
360
+ scopeCounts: {
361
+ scriptPrefixCount: config.scope.scriptPrefixes.length,
362
+ resourcePrefixCount: config.scope.resourcePrefixes.length,
363
+ },
364
+ pipelineProfile: state.pipelineProfile,
365
+ analyzeSummary: state.analyzeSummary,
366
+ estimateComparison: state.estimateComparison,
367
+ retrievalSteps: state.retrievalResults?.flatMap((scenario) => scenario.steps.map((step) => ({
368
+ symbol: scenario.symbol,
369
+ ...step,
370
+ }))) || [],
371
+ retrievalSummary: state.retrievalSummary,
372
+ finalVerdict: {
373
+ runId,
374
+ buildTimings: {
375
+ buildMs: state.buildMs,
376
+ pipelineProfileMs: extractPipelineMeanMs(state.pipelineProfile),
377
+ analyzeSec: state.analyzeSummary?.totalSec,
378
+ },
379
+ estimateComparison: state.estimateComparison,
380
+ retrievalSummary: state.retrievalSummary,
381
+ failures: state.retrievalSummary?.failures || [],
382
+ },
383
+ });
384
+ return {
385
+ reportDir,
386
+ runId,
387
+ };
388
+ },
389
+ },
390
+ });
391
+ }
392
+ export async function runE2E(options = {}) {
393
+ const runId = options.runId || createRunId();
394
+ const reportDir = options.reportDir || path.resolve('.gitnexus/u2-e2e', runId);
395
+ const writeCheckpoint = options.writeCheckpoint || defaultWriteCheckpoint;
396
+ const gates = options.gates || {};
397
+ await fs.mkdir(reportDir, { recursive: true });
398
+ const completedGates = [];
399
+ const gateOutputs = {};
400
+ for (const gate of GATE_ORDER) {
401
+ const gateRunner = gates[gate];
402
+ if (!gateRunner) {
403
+ completedGates.push(gate);
404
+ continue;
405
+ }
406
+ try {
407
+ gateOutputs[gate] = await gateRunner();
408
+ completedGates.push(gate);
409
+ }
410
+ catch (error) {
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ const checkpointPayload = {
413
+ runId,
414
+ status: 'failed',
415
+ failedGate: gate,
416
+ completedGates,
417
+ error: message,
418
+ capturedAt: new Date().toISOString(),
419
+ };
420
+ await writeCheckpoint(reportDir, checkpointPayload);
421
+ return {
422
+ status: 'failed',
423
+ runId,
424
+ reportDir,
425
+ completedGates,
426
+ failedGate: gate,
427
+ error: message,
428
+ gateOutputs,
429
+ };
430
+ }
431
+ }
432
+ return {
433
+ status: 'passed',
434
+ runId,
435
+ reportDir,
436
+ completedGates,
437
+ gateOutputs,
438
+ };
439
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { extractSingleCountFromCypherResult, runE2E } from './neonspark-full-e2e.js';
4
+ test('runE2E stops on first gate failure and writes checkpoint', async () => {
5
+ const checkpoints = [];
6
+ const out = await runE2E({
7
+ runId: 'unit-test-run',
8
+ reportDir: '/tmp/u2-e2e-unit',
9
+ gates: {
10
+ preflight: async () => ({ ok: true }),
11
+ build: async () => {
12
+ throw new Error('build failed');
13
+ },
14
+ 'pipeline-profile': async () => ({ skipped: true }),
15
+ analyze: async () => ({ totalSec: 1 }),
16
+ 'estimate-compare': async () => ({ status: 'in-range' }),
17
+ retrieval: async () => ({ symbols: [] }),
18
+ 'final-report': async () => ({ written: true }),
19
+ },
20
+ writeCheckpoint: async (reportDir, payload) => {
21
+ checkpoints.push({ reportDir, payload });
22
+ },
23
+ });
24
+ assert.equal(out.status, 'failed');
25
+ assert.equal(out.failedGate, 'build');
26
+ assert.equal(checkpoints.length, 1);
27
+ assert.equal(checkpoints[0].reportDir, '/tmp/u2-e2e-unit');
28
+ assert.equal(checkpoints[0].payload.failedGate, 'build');
29
+ });
30
+ test('extractSingleCountFromCypherResult parses markdown-formatted cypher output', () => {
31
+ const count = extractSingleCountFromCypherResult({
32
+ markdown: '| serializedTypeEdgeCount |\n| --- |\n| 3791 |',
33
+ row_count: 1,
34
+ });
35
+ assert.equal(count, 3791);
36
+ });
37
+ test('extractSingleCountFromCypherResult parses raw row output', () => {
38
+ const count = extractSingleCountFromCypherResult([{ serializedTypeEdgeCount: 42 }]);
39
+ assert.equal(count, 42);
40
+ });
@@ -0,0 +1,58 @@
1
+ export interface U2EstimateComparison {
2
+ status: string;
3
+ inRange: boolean;
4
+ actualSec: number;
5
+ lower: number;
6
+ upper: number;
7
+ deltaSec: number;
8
+ }
9
+ export interface U2SymbolOutcome {
10
+ symbol: string;
11
+ pass: boolean;
12
+ stepCount: number;
13
+ durationMs?: number;
14
+ totalTokensEst?: number;
15
+ failures?: string[];
16
+ }
17
+ export interface U2CharacterListAssetRefSpriteSummary {
18
+ extractedAssetRefInstances: number;
19
+ nonEmptyAssetRefInstances: number;
20
+ spriteAssetRefInstances: number;
21
+ spriteRatioInNonEmpty: number | null;
22
+ uniqueSpriteAssets: number;
23
+ }
24
+ export interface U2RetrievalSummary {
25
+ symbols: U2SymbolOutcome[];
26
+ tokenSummary?: {
27
+ totalTokensEst: number;
28
+ totalDurationMs: number;
29
+ };
30
+ serializedTypeEdgeCount?: number;
31
+ characterListAssetRefSprite?: U2CharacterListAssetRefSpriteSummary;
32
+ failures?: string[];
33
+ }
34
+ export interface FinalVerdictInput {
35
+ runId: string;
36
+ buildTimings?: {
37
+ buildMs?: number;
38
+ pipelineProfileMs?: number;
39
+ analyzeSec?: number;
40
+ };
41
+ estimateComparison?: U2EstimateComparison;
42
+ retrievalSummary?: U2RetrievalSummary;
43
+ failures?: string[];
44
+ }
45
+ export interface E2EReportWriteInput {
46
+ preflight?: unknown;
47
+ scopeCounts?: unknown;
48
+ pipelineProfile?: unknown;
49
+ analyzeSummary?: unknown;
50
+ estimateComparison?: U2EstimateComparison;
51
+ retrievalSteps?: unknown[];
52
+ retrievalSummary?: U2RetrievalSummary;
53
+ finalVerdict: FinalVerdictInput;
54
+ }
55
+ export declare function buildEstimateComparisonMarkdown(estimate?: U2EstimateComparison): string;
56
+ export declare function buildRetrievalSummaryMarkdown(summary?: U2RetrievalSummary): string;
57
+ export declare function buildFinalVerdictMarkdown(input: FinalVerdictInput): string;
58
+ export declare function writeU2E2EReports(reportDir: string, input: E2EReportWriteInput): Promise<void>;
@@ -0,0 +1,130 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ function formatMs(value) {
4
+ if (typeof value !== 'number' || Number.isNaN(value))
5
+ return 'n/a';
6
+ return `${value.toFixed(1)}ms`;
7
+ }
8
+ function formatSec(value) {
9
+ if (typeof value !== 'number' || Number.isNaN(value))
10
+ return 'n/a';
11
+ return `${value.toFixed(1)}s`;
12
+ }
13
+ export function buildEstimateComparisonMarkdown(estimate) {
14
+ if (!estimate) {
15
+ return '# Estimate Comparison\n\nNo estimate comparison data.\n';
16
+ }
17
+ return [
18
+ '# Estimate Comparison',
19
+ '',
20
+ `- Status: ${estimate.status}`,
21
+ `- In Range: ${estimate.inRange ? 'YES' : 'NO'}`,
22
+ `- Actual: ${estimate.actualSec.toFixed(1)}s`,
23
+ `- Expected Range: ${estimate.lower.toFixed(1)}s - ${estimate.upper.toFixed(1)}s`,
24
+ `- Delta: ${estimate.deltaSec.toFixed(1)}s`,
25
+ '',
26
+ ].join('\n');
27
+ }
28
+ export function buildRetrievalSummaryMarkdown(summary) {
29
+ if (!summary) {
30
+ return '# Retrieval Summary\n\nNo retrieval summary data.\n';
31
+ }
32
+ const symbolRows = summary.symbols.length > 0
33
+ ? summary.symbols.map((row) => `- ${row.symbol}: ${row.pass ? 'PASS' : 'FAIL'} (steps=${row.stepCount}, duration=${formatMs(row.durationMs)}, tokens=${typeof row.totalTokensEst === 'number' ? row.totalTokensEst : 'n/a'})`)
34
+ : ['- none'];
35
+ const failures = summary.failures || [];
36
+ const failureRows = failures.length > 0 ? failures.map((row) => `- ${row}`) : ['- none'];
37
+ return [
38
+ '# Retrieval Summary',
39
+ '',
40
+ '## Symbols',
41
+ ...symbolRows,
42
+ '',
43
+ '## Token And Duration',
44
+ `- Total Tokens (est): ${summary.tokenSummary?.totalTokensEst ?? 0}`,
45
+ `- Total Duration: ${formatMs(summary.tokenSummary?.totalDurationMs)}`,
46
+ `- UNITY_SERIALIZED_TYPE_IN Edges: ${typeof summary.serializedTypeEdgeCount === 'number' ? summary.serializedTypeEdgeCount : 'n/a'}`,
47
+ `- CharacterList AssetRef Sprite Instances: ${summary.characterListAssetRefSprite?.spriteAssetRefInstances ?? 'n/a'}`,
48
+ `- CharacterList AssetRef Sprite Ratio: ${typeof summary.characterListAssetRefSprite?.spriteRatioInNonEmpty === 'number'
49
+ ? `${(summary.characterListAssetRefSprite.spriteRatioInNonEmpty * 100).toFixed(2)}%`
50
+ : 'n/a'}`,
51
+ '',
52
+ '## Failures',
53
+ ...failureRows,
54
+ '',
55
+ ].join('\n');
56
+ }
57
+ export function buildFinalVerdictMarkdown(input) {
58
+ const summary = input.retrievalSummary;
59
+ const failures = [...(input.failures || []), ...((summary?.failures || []))].filter((failure, index, all) => all.indexOf(failure) === index);
60
+ return [
61
+ '# U2 E2E Final Verdict',
62
+ '',
63
+ `- Run ID: ${input.runId}`,
64
+ '',
65
+ '## Build Timings',
66
+ `- Build: ${formatMs(input.buildTimings?.buildMs)}`,
67
+ `- Pipeline Profile: ${formatMs(input.buildTimings?.pipelineProfileMs)}`,
68
+ `- Analyze: ${formatSec(input.buildTimings?.analyzeSec)}`,
69
+ '',
70
+ '## Estimate Comparison',
71
+ ...(input.estimateComparison
72
+ ? [
73
+ `- Status: ${input.estimateComparison.status}`,
74
+ `- In Range: ${input.estimateComparison.inRange ? 'YES' : 'NO'}`,
75
+ `- Actual: ${input.estimateComparison.actualSec.toFixed(1)}s`,
76
+ `- Expected: ${input.estimateComparison.lower.toFixed(1)}s - ${input.estimateComparison.upper.toFixed(1)}s`,
77
+ `- Delta: ${input.estimateComparison.deltaSec.toFixed(1)}s`,
78
+ ]
79
+ : ['- no estimate data']),
80
+ '',
81
+ '## U2 Capability Checks by Symbol',
82
+ ...(summary?.symbols?.length
83
+ ? summary.symbols.map((row) => `- ${row.symbol}: ${row.pass ? 'PASS' : 'FAIL'} (steps=${row.stepCount}, duration=${formatMs(row.durationMs)}, tokens=${typeof row.totalTokensEst === 'number' ? row.totalTokensEst : 'n/a'})`)
84
+ : ['- none']),
85
+ '',
86
+ '## Token Consumption Summary',
87
+ `- Total Tokens (est): ${summary?.tokenSummary?.totalTokensEst ?? 0}`,
88
+ `- Total Duration: ${formatMs(summary?.tokenSummary?.totalDurationMs)}`,
89
+ `- UNITY_SERIALIZED_TYPE_IN Edges: ${typeof summary?.serializedTypeEdgeCount === 'number' ? summary.serializedTypeEdgeCount : 'n/a'}`,
90
+ `- CharacterList AssetRef Sprite Instances: ${summary?.characterListAssetRefSprite?.spriteAssetRefInstances ?? 'n/a'}`,
91
+ `- CharacterList AssetRef Sprite Ratio: ${typeof summary?.characterListAssetRefSprite?.spriteRatioInNonEmpty === 'number'
92
+ ? `${(summary.characterListAssetRefSprite.spriteRatioInNonEmpty * 100).toFixed(2)}%`
93
+ : 'n/a'}`,
94
+ '',
95
+ '## Failures and Manual Actions',
96
+ ...(failures.length > 0 ? failures.map((f) => `- ${f}`) : ['- none']),
97
+ '',
98
+ ].join('\n');
99
+ }
100
+ async function writeJson(reportDir, fileName, payload) {
101
+ await fs.writeFile(path.join(reportDir, fileName), JSON.stringify(payload, null, 2), 'utf-8');
102
+ }
103
+ export async function writeU2E2EReports(reportDir, input) {
104
+ await fs.mkdir(reportDir, { recursive: true });
105
+ if (input.preflight !== undefined) {
106
+ await writeJson(reportDir, 'preflight.json', input.preflight);
107
+ }
108
+ if (input.scopeCounts !== undefined) {
109
+ await writeJson(reportDir, 'scope-counts.json', input.scopeCounts);
110
+ }
111
+ if (input.pipelineProfile !== undefined) {
112
+ await writeJson(reportDir, 'pipeline-profile.json', input.pipelineProfile);
113
+ }
114
+ if (input.analyzeSummary !== undefined) {
115
+ await writeJson(reportDir, 'analyze-summary.json', input.analyzeSummary);
116
+ }
117
+ if (input.estimateComparison !== undefined) {
118
+ await writeJson(reportDir, 'estimate-comparison.json', input.estimateComparison);
119
+ await fs.writeFile(path.join(reportDir, 'estimate-comparison.md'), buildEstimateComparisonMarkdown(input.estimateComparison), 'utf-8');
120
+ }
121
+ if (input.retrievalSteps !== undefined) {
122
+ const jsonl = input.retrievalSteps.map((row) => JSON.stringify(row)).join('\n');
123
+ await fs.writeFile(path.join(reportDir, 'retrieval-steps.jsonl'), `${jsonl}${jsonl ? '\n' : ''}`, 'utf-8');
124
+ }
125
+ if (input.retrievalSummary !== undefined) {
126
+ await writeJson(reportDir, 'retrieval-summary.json', input.retrievalSummary);
127
+ await fs.writeFile(path.join(reportDir, 'retrieval-summary.md'), buildRetrievalSummaryMarkdown(input.retrievalSummary), 'utf-8');
128
+ }
129
+ await fs.writeFile(path.join(reportDir, 'final-verdict.md'), buildFinalVerdictMarkdown(input.finalVerdict), 'utf-8');
130
+ }
@@ -0,0 +1 @@
1
+ export {};