@veewo/gitnexus 1.3.4

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 (231) hide show
  1. package/README.md +234 -0
  2. package/dist/benchmark/agent-context/evaluators.d.ts +9 -0
  3. package/dist/benchmark/agent-context/evaluators.js +196 -0
  4. package/dist/benchmark/agent-context/evaluators.test.d.ts +1 -0
  5. package/dist/benchmark/agent-context/evaluators.test.js +39 -0
  6. package/dist/benchmark/agent-context/io.d.ts +2 -0
  7. package/dist/benchmark/agent-context/io.js +23 -0
  8. package/dist/benchmark/agent-context/io.test.d.ts +1 -0
  9. package/dist/benchmark/agent-context/io.test.js +19 -0
  10. package/dist/benchmark/agent-context/report.d.ts +2 -0
  11. package/dist/benchmark/agent-context/report.js +59 -0
  12. package/dist/benchmark/agent-context/report.test.d.ts +1 -0
  13. package/dist/benchmark/agent-context/report.test.js +85 -0
  14. package/dist/benchmark/agent-context/runner.d.ts +46 -0
  15. package/dist/benchmark/agent-context/runner.js +111 -0
  16. package/dist/benchmark/agent-context/runner.test.d.ts +1 -0
  17. package/dist/benchmark/agent-context/runner.test.js +79 -0
  18. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -0
  19. package/dist/benchmark/agent-context/tool-runner.js +18 -0
  20. package/dist/benchmark/agent-context/tool-runner.test.d.ts +1 -0
  21. package/dist/benchmark/agent-context/tool-runner.test.js +11 -0
  22. package/dist/benchmark/agent-context/types.d.ts +40 -0
  23. package/dist/benchmark/agent-context/types.js +1 -0
  24. package/dist/benchmark/analyze-runner.d.ts +16 -0
  25. package/dist/benchmark/analyze-runner.js +51 -0
  26. package/dist/benchmark/analyze-runner.test.d.ts +1 -0
  27. package/dist/benchmark/analyze-runner.test.js +37 -0
  28. package/dist/benchmark/evaluators.d.ts +6 -0
  29. package/dist/benchmark/evaluators.js +10 -0
  30. package/dist/benchmark/evaluators.test.d.ts +1 -0
  31. package/dist/benchmark/evaluators.test.js +12 -0
  32. package/dist/benchmark/io.d.ts +7 -0
  33. package/dist/benchmark/io.js +25 -0
  34. package/dist/benchmark/io.test.d.ts +1 -0
  35. package/dist/benchmark/io.test.js +35 -0
  36. package/dist/benchmark/neonspark-candidates.d.ts +19 -0
  37. package/dist/benchmark/neonspark-candidates.js +94 -0
  38. package/dist/benchmark/neonspark-candidates.test.d.ts +1 -0
  39. package/dist/benchmark/neonspark-candidates.test.js +43 -0
  40. package/dist/benchmark/neonspark-materialize.d.ts +19 -0
  41. package/dist/benchmark/neonspark-materialize.js +111 -0
  42. package/dist/benchmark/neonspark-materialize.test.d.ts +1 -0
  43. package/dist/benchmark/neonspark-materialize.test.js +124 -0
  44. package/dist/benchmark/neonspark-sync.d.ts +3 -0
  45. package/dist/benchmark/neonspark-sync.js +53 -0
  46. package/dist/benchmark/neonspark-sync.test.d.ts +1 -0
  47. package/dist/benchmark/neonspark-sync.test.js +20 -0
  48. package/dist/benchmark/report.d.ts +1 -0
  49. package/dist/benchmark/report.js +7 -0
  50. package/dist/benchmark/runner.d.ts +48 -0
  51. package/dist/benchmark/runner.js +302 -0
  52. package/dist/benchmark/runner.test.d.ts +1 -0
  53. package/dist/benchmark/runner.test.js +50 -0
  54. package/dist/benchmark/scoring.d.ts +16 -0
  55. package/dist/benchmark/scoring.js +27 -0
  56. package/dist/benchmark/scoring.test.d.ts +1 -0
  57. package/dist/benchmark/scoring.test.js +24 -0
  58. package/dist/benchmark/tool-runner.d.ts +6 -0
  59. package/dist/benchmark/tool-runner.js +17 -0
  60. package/dist/benchmark/types.d.ts +36 -0
  61. package/dist/benchmark/types.js +1 -0
  62. package/dist/cli/ai-context.d.ts +22 -0
  63. package/dist/cli/ai-context.js +184 -0
  64. package/dist/cli/ai-context.test.d.ts +1 -0
  65. package/dist/cli/ai-context.test.js +30 -0
  66. package/dist/cli/analyze-multi-scope-regression.test.d.ts +1 -0
  67. package/dist/cli/analyze-multi-scope-regression.test.js +22 -0
  68. package/dist/cli/analyze-options.d.ts +7 -0
  69. package/dist/cli/analyze-options.js +56 -0
  70. package/dist/cli/analyze-options.test.d.ts +1 -0
  71. package/dist/cli/analyze-options.test.js +36 -0
  72. package/dist/cli/analyze.d.ts +14 -0
  73. package/dist/cli/analyze.js +384 -0
  74. package/dist/cli/augment.d.ts +13 -0
  75. package/dist/cli/augment.js +33 -0
  76. package/dist/cli/benchmark-agent-context.d.ts +29 -0
  77. package/dist/cli/benchmark-agent-context.js +61 -0
  78. package/dist/cli/benchmark-agent-context.test.d.ts +1 -0
  79. package/dist/cli/benchmark-agent-context.test.js +80 -0
  80. package/dist/cli/benchmark-unity.d.ts +15 -0
  81. package/dist/cli/benchmark-unity.js +31 -0
  82. package/dist/cli/benchmark-unity.test.d.ts +1 -0
  83. package/dist/cli/benchmark-unity.test.js +18 -0
  84. package/dist/cli/claude-hooks.d.ts +22 -0
  85. package/dist/cli/claude-hooks.js +97 -0
  86. package/dist/cli/clean.d.ts +10 -0
  87. package/dist/cli/clean.js +60 -0
  88. package/dist/cli/eval-server.d.ts +30 -0
  89. package/dist/cli/eval-server.js +372 -0
  90. package/dist/cli/index.d.ts +2 -0
  91. package/dist/cli/index.js +182 -0
  92. package/dist/cli/list.d.ts +6 -0
  93. package/dist/cli/list.js +33 -0
  94. package/dist/cli/mcp.d.ts +8 -0
  95. package/dist/cli/mcp.js +34 -0
  96. package/dist/cli/repo-manager-alias.test.d.ts +1 -0
  97. package/dist/cli/repo-manager-alias.test.js +40 -0
  98. package/dist/cli/scope-filter.test.d.ts +1 -0
  99. package/dist/cli/scope-filter.test.js +49 -0
  100. package/dist/cli/serve.d.ts +4 -0
  101. package/dist/cli/serve.js +6 -0
  102. package/dist/cli/setup.d.ts +8 -0
  103. package/dist/cli/setup.js +311 -0
  104. package/dist/cli/setup.test.d.ts +1 -0
  105. package/dist/cli/setup.test.js +31 -0
  106. package/dist/cli/status.d.ts +6 -0
  107. package/dist/cli/status.js +27 -0
  108. package/dist/cli/tool.d.ts +40 -0
  109. package/dist/cli/tool.js +94 -0
  110. package/dist/cli/version.test.d.ts +1 -0
  111. package/dist/cli/version.test.js +19 -0
  112. package/dist/cli/wiki.d.ts +15 -0
  113. package/dist/cli/wiki.js +361 -0
  114. package/dist/config/ignore-service.d.ts +1 -0
  115. package/dist/config/ignore-service.js +210 -0
  116. package/dist/config/supported-languages.d.ts +12 -0
  117. package/dist/config/supported-languages.js +15 -0
  118. package/dist/core/augmentation/engine.d.ts +26 -0
  119. package/dist/core/augmentation/engine.js +213 -0
  120. package/dist/core/embeddings/embedder.d.ts +60 -0
  121. package/dist/core/embeddings/embedder.js +251 -0
  122. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  123. package/dist/core/embeddings/embedding-pipeline.js +329 -0
  124. package/dist/core/embeddings/index.d.ts +9 -0
  125. package/dist/core/embeddings/index.js +9 -0
  126. package/dist/core/embeddings/text-generator.d.ts +24 -0
  127. package/dist/core/embeddings/text-generator.js +182 -0
  128. package/dist/core/embeddings/types.d.ts +87 -0
  129. package/dist/core/embeddings/types.js +32 -0
  130. package/dist/core/graph/graph.d.ts +2 -0
  131. package/dist/core/graph/graph.js +66 -0
  132. package/dist/core/graph/types.d.ts +61 -0
  133. package/dist/core/graph/types.js +1 -0
  134. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  135. package/dist/core/ingestion/ast-cache.js +34 -0
  136. package/dist/core/ingestion/call-processor.d.ts +15 -0
  137. package/dist/core/ingestion/call-processor.js +327 -0
  138. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  139. package/dist/core/ingestion/cluster-enricher.js +170 -0
  140. package/dist/core/ingestion/community-processor.d.ts +39 -0
  141. package/dist/core/ingestion/community-processor.js +312 -0
  142. package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
  143. package/dist/core/ingestion/entry-point-scoring.js +260 -0
  144. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  145. package/dist/core/ingestion/filesystem-walker.js +80 -0
  146. package/dist/core/ingestion/framework-detection.d.ts +39 -0
  147. package/dist/core/ingestion/framework-detection.js +235 -0
  148. package/dist/core/ingestion/heritage-processor.d.ts +20 -0
  149. package/dist/core/ingestion/heritage-processor.js +197 -0
  150. package/dist/core/ingestion/import-processor.d.ts +38 -0
  151. package/dist/core/ingestion/import-processor.js +778 -0
  152. package/dist/core/ingestion/parsing-processor.d.ts +15 -0
  153. package/dist/core/ingestion/parsing-processor.js +291 -0
  154. package/dist/core/ingestion/pipeline.d.ts +5 -0
  155. package/dist/core/ingestion/pipeline.js +323 -0
  156. package/dist/core/ingestion/process-processor.d.ts +51 -0
  157. package/dist/core/ingestion/process-processor.js +309 -0
  158. package/dist/core/ingestion/scope-filter.d.ts +25 -0
  159. package/dist/core/ingestion/scope-filter.js +100 -0
  160. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  161. package/dist/core/ingestion/structure-processor.js +36 -0
  162. package/dist/core/ingestion/symbol-table.d.ts +33 -0
  163. package/dist/core/ingestion/symbol-table.js +38 -0
  164. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -0
  165. package/dist/core/ingestion/tree-sitter-queries.js +398 -0
  166. package/dist/core/ingestion/utils.d.ts +10 -0
  167. package/dist/core/ingestion/utils.js +50 -0
  168. package/dist/core/ingestion/workers/parse-worker.d.ts +59 -0
  169. package/dist/core/ingestion/workers/parse-worker.js +672 -0
  170. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  171. package/dist/core/ingestion/workers/worker-pool.js +120 -0
  172. package/dist/core/kuzu/csv-generator.d.ts +29 -0
  173. package/dist/core/kuzu/csv-generator.js +336 -0
  174. package/dist/core/kuzu/kuzu-adapter.d.ts +101 -0
  175. package/dist/core/kuzu/kuzu-adapter.js +753 -0
  176. package/dist/core/kuzu/schema.d.ts +53 -0
  177. package/dist/core/kuzu/schema.js +407 -0
  178. package/dist/core/search/bm25-index.d.ts +23 -0
  179. package/dist/core/search/bm25-index.js +95 -0
  180. package/dist/core/search/hybrid-search.d.ts +49 -0
  181. package/dist/core/search/hybrid-search.js +118 -0
  182. package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
  183. package/dist/core/tree-sitter/parser-loader.js +44 -0
  184. package/dist/core/wiki/generator.d.ts +110 -0
  185. package/dist/core/wiki/generator.js +786 -0
  186. package/dist/core/wiki/graph-queries.d.ts +80 -0
  187. package/dist/core/wiki/graph-queries.js +238 -0
  188. package/dist/core/wiki/html-viewer.d.ts +10 -0
  189. package/dist/core/wiki/html-viewer.js +297 -0
  190. package/dist/core/wiki/llm-client.d.ts +40 -0
  191. package/dist/core/wiki/llm-client.js +162 -0
  192. package/dist/core/wiki/prompts.d.ts +53 -0
  193. package/dist/core/wiki/prompts.js +174 -0
  194. package/dist/lib/utils.d.ts +1 -0
  195. package/dist/lib/utils.js +3 -0
  196. package/dist/mcp/core/embedder.d.ts +27 -0
  197. package/dist/mcp/core/embedder.js +108 -0
  198. package/dist/mcp/core/kuzu-adapter.d.ts +34 -0
  199. package/dist/mcp/core/kuzu-adapter.js +231 -0
  200. package/dist/mcp/local/local-backend.d.ts +160 -0
  201. package/dist/mcp/local/local-backend.js +1646 -0
  202. package/dist/mcp/resources.d.ts +31 -0
  203. package/dist/mcp/resources.js +407 -0
  204. package/dist/mcp/server.d.ts +23 -0
  205. package/dist/mcp/server.js +251 -0
  206. package/dist/mcp/staleness.d.ts +15 -0
  207. package/dist/mcp/staleness.js +29 -0
  208. package/dist/mcp/tools.d.ts +24 -0
  209. package/dist/mcp/tools.js +195 -0
  210. package/dist/server/api.d.ts +10 -0
  211. package/dist/server/api.js +344 -0
  212. package/dist/server/mcp-http.d.ts +13 -0
  213. package/dist/server/mcp-http.js +100 -0
  214. package/dist/storage/git.d.ts +6 -0
  215. package/dist/storage/git.js +32 -0
  216. package/dist/storage/repo-manager.d.ts +125 -0
  217. package/dist/storage/repo-manager.js +257 -0
  218. package/dist/types/pipeline.d.ts +34 -0
  219. package/dist/types/pipeline.js +18 -0
  220. package/hooks/claude/gitnexus-hook.cjs +135 -0
  221. package/hooks/claude/pre-tool-use.sh +78 -0
  222. package/hooks/claude/session-start.sh +42 -0
  223. package/package.json +92 -0
  224. package/skills/gitnexus-cli.md +82 -0
  225. package/skills/gitnexus-debugging.md +89 -0
  226. package/skills/gitnexus-exploring.md +78 -0
  227. package/skills/gitnexus-guide.md +64 -0
  228. package/skills/gitnexus-impact-analysis.md +97 -0
  229. package/skills/gitnexus-refactoring.md +121 -0
  230. package/vendor/leiden/index.cjs +355 -0
  231. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { listRegisteredRepos } from '../storage/repo-manager.js';
5
+ import { closeKuzu, executeQuery, initKuzu } from '../mcp/core/kuzu-adapter.js';
6
+ const ALLOWED_PREFIXES = ['Assets/NEON/Code/', 'Packages/com.veewo.', 'Packages/com.neonspark.'];
7
+ export function filterNeonsparkPaths(rows) {
8
+ return rows.filter((r) => {
9
+ const p = (r.file_path || '').replace(/\\/g, '/');
10
+ return ALLOWED_PREFIXES.some((prefix) => p.startsWith(prefix));
11
+ });
12
+ }
13
+ export function toCandidateRow(row) {
14
+ return {
15
+ symbol_uid: String(row.symbol_uid),
16
+ file_path: String(row.file_path),
17
+ symbol_name: String(row.symbol_name),
18
+ symbol_type: String(row.symbol_type),
19
+ start_line: Number(row.start_line || 0),
20
+ end_line: Number(row.end_line || 0),
21
+ };
22
+ }
23
+ export async function extractCandidates(repoName, outFile) {
24
+ const repos = await listRegisteredRepos({ validate: true });
25
+ const repo = repos.find((r) => r.name === repoName);
26
+ if (!repo)
27
+ throw new Error(`repo not indexed: ${repoName}`);
28
+ await initKuzu(repoName, path.join(repo.storagePath, 'kuzu'));
29
+ try {
30
+ const rows = await executeQuery(repoName, `
31
+ MATCH (s:Class)
32
+ RETURN s.id AS symbol_uid,
33
+ s.filePath AS file_path,
34
+ s.name AS symbol_name,
35
+ 'Class' AS symbol_type,
36
+ COALESCE(s.startLine, 0) AS start_line,
37
+ COALESCE(s.endLine, 0) AS end_line
38
+ UNION
39
+ MATCH (s:Interface)
40
+ RETURN s.id AS symbol_uid,
41
+ s.filePath AS file_path,
42
+ s.name AS symbol_name,
43
+ 'Interface' AS symbol_type,
44
+ COALESCE(s.startLine, 0) AS start_line,
45
+ COALESCE(s.endLine, 0) AS end_line
46
+ UNION
47
+ MATCH (s:Method)
48
+ RETURN s.id AS symbol_uid,
49
+ s.filePath AS file_path,
50
+ s.name AS symbol_name,
51
+ 'Method' AS symbol_type,
52
+ COALESCE(s.startLine, 0) AS start_line,
53
+ COALESCE(s.endLine, 0) AS end_line
54
+ UNION
55
+ MATCH (s:Function)
56
+ RETURN s.id AS symbol_uid,
57
+ s.filePath AS file_path,
58
+ s.name AS symbol_name,
59
+ 'Function' AS symbol_type,
60
+ COALESCE(s.startLine, 0) AS start_line,
61
+ COALESCE(s.endLine, 0) AS end_line
62
+ `);
63
+ const normalized = filterNeonsparkPaths(rows.map(toCandidateRow));
64
+ const jsonl = normalized.map((r) => JSON.stringify(r)).join('\n') + '\n';
65
+ await fs.mkdir(path.dirname(outFile), { recursive: true });
66
+ await fs.writeFile(outFile, jsonl, 'utf-8');
67
+ return normalized.length;
68
+ }
69
+ finally {
70
+ await closeKuzu(repoName);
71
+ }
72
+ }
73
+ export function parseCandidatesCliArgs(argv) {
74
+ if (argv.length !== 2) {
75
+ throw new Error('usage: node dist/benchmark/neonspark-candidates.js <repoName> <outFile>');
76
+ }
77
+ const [repoName, outFile] = argv;
78
+ return { repoName, outFile };
79
+ }
80
+ export async function mainCandidatesCli(argv = process.argv.slice(2), runExtract = extractCandidates) {
81
+ const { repoName, outFile } = parseCandidatesCliArgs(argv);
82
+ const written = await runExtract(repoName, outFile);
83
+ return written;
84
+ }
85
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
86
+ mainCandidatesCli()
87
+ .then((written) => {
88
+ console.log(`wrote ${written} candidate rows`);
89
+ })
90
+ .catch((error) => {
91
+ console.error(String(error?.message || error));
92
+ process.exitCode = 1;
93
+ });
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { filterNeonsparkPaths, mainCandidatesCli, parseCandidatesCliArgs, toCandidateRow } from './neonspark-candidates.js';
4
+ test('filterNeonsparkPaths keeps code and allowed package prefixes', () => {
5
+ const rows = [
6
+ { file_path: 'Assets/NEON/Code/Game/A.cs' },
7
+ { file_path: 'Packages/com.veewo.stat/Runtime/Stat.cs' },
8
+ { file_path: 'Packages/com.neonspark.inspector-navigator/Editor/NavigatorMenu.cs' },
9
+ { file_path: 'Packages/com.unity.inputsystem/Runtime/InputAction.cs' },
10
+ ];
11
+ const filtered = filterNeonsparkPaths(rows);
12
+ assert.equal(filtered.length, 3);
13
+ });
14
+ test('toCandidateRow normalizes required fields', () => {
15
+ const row = toCandidateRow({
16
+ symbol_uid: 'Method:Assets/NEON/Code/Game/A.cs:Tick',
17
+ file_path: 'Assets/NEON/Code/Game/A.cs',
18
+ symbol_name: 'Tick',
19
+ symbol_type: 'Method',
20
+ start_line: 11,
21
+ end_line: 22,
22
+ });
23
+ assert.equal(row.symbol_name, 'Tick');
24
+ assert.equal(row.start_line, 11);
25
+ });
26
+ test('parseCandidatesCliArgs parses repoName and outFile', () => {
27
+ const parsed = parseCandidatesCliArgs(['neonspark-v1', '/tmp/candidates.jsonl']);
28
+ assert.equal(parsed.repoName, 'neonspark-v1');
29
+ assert.equal(parsed.outFile, '/tmp/candidates.jsonl');
30
+ });
31
+ test('parseCandidatesCliArgs rejects missing required args', () => {
32
+ assert.throws(() => parseCandidatesCliArgs(['neonspark-v1']), /usage/i);
33
+ assert.throws(() => parseCandidatesCliArgs([]), /usage/i);
34
+ });
35
+ test('mainCandidatesCli parses args and forwards to extractor', async () => {
36
+ const calls = [];
37
+ const written = await mainCandidatesCli(['neonspark-v1', '/tmp/candidates.jsonl'], async (repoName, outFile) => {
38
+ calls.push({ repoName, outFile });
39
+ return 42;
40
+ });
41
+ assert.equal(written, 42);
42
+ assert.deepEqual(calls, [{ repoName: 'neonspark-v1', outFile: '/tmp/candidates.jsonl' }]);
43
+ });
@@ -0,0 +1,19 @@
1
+ export interface BuildSymbolRowsOptions {
2
+ minSelected?: number;
3
+ maxSelected?: number;
4
+ }
5
+ export declare function buildSymbolRows(candidates: any[], selectedUids: string[], options?: BuildSymbolRowsOptions): {
6
+ symbol_uid: string;
7
+ file_path: string;
8
+ symbol_name: string;
9
+ symbol_type: string;
10
+ start_line: number;
11
+ end_line: number;
12
+ }[];
13
+ export interface MaterializeCliArgs extends Required<BuildSymbolRowsOptions> {
14
+ candidatesFile: string;
15
+ selectedFile: string;
16
+ outFile: string;
17
+ }
18
+ export declare function parseMaterializeCliArgs(argv: string[]): MaterializeCliArgs;
19
+ export declare function mainMaterializeCli(argv?: string[]): Promise<number>;
@@ -0,0 +1,111 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ export function buildSymbolRows(candidates, selectedUids, options = {}) {
5
+ const minSelected = options.minSelected ?? 20;
6
+ const maxSelected = options.maxSelected ?? 20;
7
+ for (const [key, value] of [['minSelected', minSelected], ['maxSelected', maxSelected]]) {
8
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
9
+ throw new Error(`${key} must be a finite non-negative integer, got ${value}`);
10
+ }
11
+ }
12
+ if (minSelected > maxSelected) {
13
+ throw new Error(`invalid selected symbol range: minSelected (${minSelected}) exceeds maxSelected (${maxSelected})`);
14
+ }
15
+ if (selectedUids.length < minSelected || selectedUids.length > maxSelected) {
16
+ if (minSelected === 20 && maxSelected === 20) {
17
+ throw new Error(`selected symbol count must be exactly 20, got ${selectedUids.length}`);
18
+ }
19
+ throw new Error(`selected symbol count must be between ${minSelected} and ${maxSelected}, got ${selectedUids.length}`);
20
+ }
21
+ const byUid = new Map(candidates.map((c) => [String(c.symbol_uid), c]));
22
+ return selectedUids.map((uid) => {
23
+ const row = byUid.get(uid);
24
+ if (!row)
25
+ throw new Error(`selected uid not found in candidates: ${uid}`);
26
+ return {
27
+ symbol_uid: String(row.symbol_uid),
28
+ file_path: String(row.file_path),
29
+ symbol_name: String(row.symbol_name),
30
+ symbol_type: String(row.symbol_type),
31
+ start_line: Number(row.start_line || 0),
32
+ end_line: Number(row.end_line || 0),
33
+ };
34
+ });
35
+ }
36
+ function parseNonNegativeInteger(value, flagName) {
37
+ if (!/^\d+$/.test(value)) {
38
+ throw new Error(`${flagName} must be a finite non-negative integer, got ${value}`);
39
+ }
40
+ const parsed = Number(value);
41
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 0) {
42
+ throw new Error(`${flagName} must be a finite non-negative integer, got ${value}`);
43
+ }
44
+ return parsed;
45
+ }
46
+ export function parseMaterializeCliArgs(argv) {
47
+ const positional = [];
48
+ let minSelected = 20;
49
+ let maxSelected = 20;
50
+ for (let i = 0; i < argv.length; i += 1) {
51
+ const arg = argv[i];
52
+ if (arg === '--min-selected' || arg === '--max-selected') {
53
+ const value = argv[i + 1];
54
+ if (value == null) {
55
+ throw new Error(`${arg} requires a value`);
56
+ }
57
+ const parsed = parseNonNegativeInteger(value, arg);
58
+ if (arg === '--min-selected')
59
+ minSelected = parsed;
60
+ if (arg === '--max-selected')
61
+ maxSelected = parsed;
62
+ i += 1;
63
+ continue;
64
+ }
65
+ if (arg.startsWith('--')) {
66
+ throw new Error(`unknown option: ${arg}`);
67
+ }
68
+ positional.push(arg);
69
+ }
70
+ if (positional.length !== 3) {
71
+ throw new Error('usage: node dist/benchmark/neonspark-materialize.js <candidatesFile> <selectedFile> <outFile> [--min-selected N] [--max-selected N]');
72
+ }
73
+ if (minSelected > maxSelected) {
74
+ throw new Error(`invalid selected symbol range: minSelected (${minSelected}) exceeds maxSelected (${maxSelected})`);
75
+ }
76
+ const [candidatesFile, selectedFile, outFile] = positional;
77
+ return { candidatesFile, selectedFile, outFile, minSelected, maxSelected };
78
+ }
79
+ function parseJsonlRows(raw) {
80
+ return raw
81
+ .split(/\r?\n/)
82
+ .map((line) => line.trim())
83
+ .filter(Boolean)
84
+ .map((line) => JSON.parse(line));
85
+ }
86
+ function parseSelectedUids(raw) {
87
+ return raw
88
+ .split(/\r?\n/)
89
+ .map((line) => line.trim())
90
+ .filter(Boolean);
91
+ }
92
+ export async function mainMaterializeCli(argv = process.argv.slice(2)) {
93
+ const { candidatesFile, selectedFile, outFile, minSelected, maxSelected } = parseMaterializeCliArgs(argv);
94
+ const candidatesRaw = await fs.readFile(candidatesFile, 'utf-8');
95
+ const selectedRaw = await fs.readFile(selectedFile, 'utf-8');
96
+ const rows = buildSymbolRows(parseJsonlRows(candidatesRaw), parseSelectedUids(selectedRaw), { minSelected, maxSelected });
97
+ const jsonl = rows.length > 0 ? `${rows.map((row) => JSON.stringify(row)).join('\n')}\n` : '';
98
+ await fs.mkdir(path.dirname(outFile), { recursive: true });
99
+ await fs.writeFile(outFile, jsonl, 'utf-8');
100
+ return rows.length;
101
+ }
102
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
103
+ mainMaterializeCli()
104
+ .then((written) => {
105
+ console.log(`wrote ${written} symbol rows`);
106
+ })
107
+ .catch((error) => {
108
+ console.error(String(error?.message || error));
109
+ process.exitCode = 1;
110
+ });
111
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { buildSymbolRows, mainMaterializeCli, parseMaterializeCliArgs } from './neonspark-materialize.js';
7
+ test('buildSymbolRows enforces exactly 20 selected uids', () => {
8
+ const candidates = [{ symbol_uid: 'a' }];
9
+ assert.throws(() => buildSymbolRows(candidates, ['a']), /exactly 20/i);
10
+ });
11
+ test('buildSymbolRows maps selected uids to candidate rows', () => {
12
+ const c = [
13
+ { symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 },
14
+ { symbol_uid: 'u2', file_path: 'Assets/NEON/Code/B.cs', symbol_name: 'B', symbol_type: 'Class', start_line: 1, end_line: 9 },
15
+ ];
16
+ const ids = [...Array(20)].map((_, i) => i < 19 ? 'u1' : 'u2');
17
+ const rows = buildSymbolRows(c, ids);
18
+ assert.equal(rows.length, 20);
19
+ });
20
+ test('buildSymbolRows supports ranged selected uid counts', () => {
21
+ const c = [
22
+ { symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 },
23
+ { symbol_uid: 'u2', file_path: 'Assets/NEON/Code/B.cs', symbol_name: 'B', symbol_type: 'Class', start_line: 1, end_line: 9 },
24
+ ];
25
+ const ids = [...Array(40)].map((_, i) => i < 39 ? 'u1' : 'u2');
26
+ const rows = buildSymbolRows(c, ids, { minSelected: 40, maxSelected: 60 });
27
+ assert.equal(rows.length, 40);
28
+ });
29
+ test('buildSymbolRows rejects selection below minSelected', () => {
30
+ const c = [{ symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 }];
31
+ const ids = [...Array(39)].map(() => 'u1');
32
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 40, maxSelected: 60 }), /between 40 and 60/i);
33
+ });
34
+ test('buildSymbolRows rejects selection above maxSelected', () => {
35
+ const c = [{ symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 }];
36
+ const ids = [...Array(61)].map(() => 'u1');
37
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 40, maxSelected: 60 }), /between 40 and 60/i);
38
+ });
39
+ test('buildSymbolRows rejects minSelected greater than maxSelected', () => {
40
+ const c = [{ symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 }];
41
+ const ids = [...Array(40)].map(() => 'u1');
42
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 60, maxSelected: 40 }), /invalid selected symbol range/i);
43
+ });
44
+ test('buildSymbolRows still validates selected uid existence when range options are used', () => {
45
+ const c = [{ symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 }];
46
+ const ids = [...Array(40)].map((_, i) => i < 39 ? 'u1' : 'missing');
47
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 40, maxSelected: 60 }), /selected uid not found in candidates: missing/i);
48
+ });
49
+ test('buildSymbolRows validates minSelected and maxSelected as finite non-negative integers', () => {
50
+ const c = [{ symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 }];
51
+ const ids = [...Array(40)].map(() => 'u1');
52
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: -1, maxSelected: 60 }), /non-negative integer/i);
53
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 40.5, maxSelected: 60 }), /non-negative integer/i);
54
+ assert.throws(() => buildSymbolRows(c, ids, { minSelected: 40, maxSelected: Number.POSITIVE_INFINITY }), /finite/i);
55
+ });
56
+ test('parseMaterializeCliArgs parses positional args and default selected range', () => {
57
+ const parsed = parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl']);
58
+ assert.equal(parsed.candidatesFile, 'candidates.jsonl');
59
+ assert.equal(parsed.selectedFile, 'selected.txt');
60
+ assert.equal(parsed.outFile, 'symbols.jsonl');
61
+ assert.equal(parsed.minSelected, 20);
62
+ assert.equal(parsed.maxSelected, 20);
63
+ });
64
+ test('parseMaterializeCliArgs parses --min-selected and --max-selected', () => {
65
+ const parsed = parseMaterializeCliArgs([
66
+ 'candidates.jsonl',
67
+ 'selected.txt',
68
+ 'symbols.jsonl',
69
+ '--min-selected',
70
+ '40',
71
+ '--max-selected',
72
+ '60',
73
+ ]);
74
+ assert.equal(parsed.minSelected, 40);
75
+ assert.equal(parsed.maxSelected, 60);
76
+ });
77
+ test('parseMaterializeCliArgs rejects invalid CLI flags and values', () => {
78
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', '-1']), /non-negative integer/i);
79
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--max-selected']), /requires a value/i);
80
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--unknown', '1']), /unknown option/i);
81
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', '']), /non-negative integer/i);
82
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', ' ']), /non-negative integer/i);
83
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', '1e2']), /non-negative integer/i);
84
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', '0x10']), /non-negative integer/i);
85
+ });
86
+ test('parseMaterializeCliArgs rejects minSelected greater than maxSelected', () => {
87
+ assert.throws(() => parseMaterializeCliArgs(['candidates.jsonl', 'selected.txt', 'symbols.jsonl', '--min-selected', '60', '--max-selected', '40']), /invalid selected symbol range/i);
88
+ });
89
+ test('mainMaterializeCli reads candidates and selected files and writes symbols jsonl', async () => {
90
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'neonspark-materialize-'));
91
+ const candidatesFile = path.join(tmp, 'candidates.jsonl');
92
+ const selectedFile = path.join(tmp, 'selected.txt');
93
+ const outFile = path.join(tmp, 'symbols.jsonl');
94
+ const candidates = [
95
+ { symbol_uid: 'u1', file_path: 'Assets/NEON/Code/A.cs', symbol_name: 'A', symbol_type: 'Class', start_line: 1, end_line: 9 },
96
+ { symbol_uid: 'u2', file_path: 'Assets/NEON/Code/B.cs', symbol_name: 'B', symbol_type: 'Class', start_line: 10, end_line: 20 },
97
+ ];
98
+ try {
99
+ await fs.writeFile(candidatesFile, `${JSON.stringify(candidates[0])}\n${JSON.stringify(candidates[1])}\n`, 'utf-8');
100
+ await fs.writeFile(selectedFile, 'u2\n', 'utf-8');
101
+ const written = await mainMaterializeCli([
102
+ candidatesFile,
103
+ selectedFile,
104
+ outFile,
105
+ '--min-selected',
106
+ '1',
107
+ '--max-selected',
108
+ '2',
109
+ ]);
110
+ assert.equal(written, 1);
111
+ const output = await fs.readFile(outFile, 'utf-8');
112
+ const rows = output
113
+ .split('\n')
114
+ .map((line) => line.trim())
115
+ .filter(Boolean)
116
+ .map((line) => JSON.parse(line));
117
+ assert.equal(rows.length, 1);
118
+ assert.equal(rows[0].symbol_uid, 'u2');
119
+ assert.equal(rows[0].symbol_name, 'B');
120
+ }
121
+ finally {
122
+ await fs.rm(tmp, { recursive: true, force: true });
123
+ }
124
+ });
@@ -0,0 +1,3 @@
1
+ export declare function parseManifest(raw: string): string[];
2
+ export declare function shouldIncludeRelativePath(relPath: string, roots: string[]): boolean;
3
+ export declare function syncFixture(sourceRoot: string, fixtureRoot: string, manifestPath: string): Promise<number>;
@@ -0,0 +1,53 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export function parseManifest(raw) {
4
+ return raw
5
+ .split(/\r?\n/)
6
+ .map((s) => s.trim())
7
+ .filter((s) => s && !s.startsWith('#'));
8
+ }
9
+ function wildcardPrefix(rule) {
10
+ return rule.endsWith('*') ? rule.slice(0, -1) : rule;
11
+ }
12
+ export function shouldIncludeRelativePath(relPath, roots) {
13
+ const normalized = relPath.replace(/\\/g, '/');
14
+ if (!normalized.endsWith('.cs'))
15
+ return false;
16
+ return roots.some((rule) => {
17
+ const normalizedRule = rule.replace(/\\/g, '/');
18
+ const prefix = wildcardPrefix(normalizedRule);
19
+ if (normalizedRule.endsWith('*')) {
20
+ return normalized.startsWith(prefix);
21
+ }
22
+ return normalized === prefix || normalized.startsWith(`${prefix}/`);
23
+ });
24
+ }
25
+ async function walk(dir, base, out) {
26
+ const entries = await fs.readdir(dir, { withFileTypes: true });
27
+ for (const e of entries) {
28
+ const full = path.join(dir, e.name);
29
+ if (e.isDirectory()) {
30
+ if (['.git', 'Library', 'Logs', 'Temp', 'Obj', 'UserSettings'].includes(e.name))
31
+ continue;
32
+ await walk(full, base, out);
33
+ continue;
34
+ }
35
+ const rel = path.relative(base, full).replace(/\\/g, '/');
36
+ out.push(rel);
37
+ }
38
+ }
39
+ export async function syncFixture(sourceRoot, fixtureRoot, manifestPath) {
40
+ const manifest = await fs.readFile(manifestPath, 'utf-8');
41
+ const roots = parseManifest(manifest);
42
+ const allFiles = [];
43
+ await walk(sourceRoot, sourceRoot, allFiles);
44
+ const selected = allFiles.filter((rel) => shouldIncludeRelativePath(rel, roots));
45
+ await fs.rm(fixtureRoot, { recursive: true, force: true });
46
+ for (const rel of selected) {
47
+ const src = path.join(sourceRoot, rel);
48
+ const dst = path.join(fixtureRoot, rel);
49
+ await fs.mkdir(path.dirname(dst), { recursive: true });
50
+ await fs.copyFile(src, dst);
51
+ }
52
+ return selected.length;
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseManifest, shouldIncludeRelativePath } from './neonspark-sync.js';
4
+ test('parseManifest strips comments and blank lines', () => {
5
+ const roots = parseManifest(`
6
+ # main gameplay
7
+ Assets/NEON/Code
8
+
9
+ Packages/com.veewo.*
10
+ Packages/com.neonspark.*
11
+ `);
12
+ assert.deepEqual(roots, ['Assets/NEON/Code', 'Packages/com.veewo.*', 'Packages/com.neonspark.*']);
13
+ });
14
+ test('shouldIncludeRelativePath keeps only .cs under allowed roots', () => {
15
+ const roots = ['Assets/NEON/Code', 'Packages/com.veewo.*', 'Packages/com.neonspark.*'];
16
+ assert.equal(shouldIncludeRelativePath('Assets/NEON/Code/Game/A.cs', roots), true);
17
+ assert.equal(shouldIncludeRelativePath('Packages/com.veewo.stat/Runtime/Stat.cs', roots), true);
18
+ assert.equal(shouldIncludeRelativePath('Packages/com.unity.inputsystem/Runtime/X.cs', roots), false);
19
+ assert.equal(shouldIncludeRelativePath('Assets/NEON/Code/Game/A.prefab', roots), false);
20
+ });
@@ -0,0 +1 @@
1
+ export declare function writeReports(reportDir: string, jsonReport: unknown, markdown: string): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function writeReports(reportDir, jsonReport, markdown) {
4
+ await fs.mkdir(reportDir, { recursive: true });
5
+ await fs.writeFile(path.join(reportDir, 'benchmark-report.json'), JSON.stringify(jsonReport, null, 2), 'utf-8');
6
+ await fs.writeFile(path.join(reportDir, 'benchmark-summary.md'), markdown, 'utf-8');
7
+ }
@@ -0,0 +1,48 @@
1
+ import type { RelationCase, SymbolCase, TaskCase, Thresholds } from './types.js';
2
+ interface Dataset {
3
+ thresholds: Thresholds;
4
+ symbols: SymbolCase[];
5
+ relations: RelationCase[];
6
+ tasks: TaskCase[];
7
+ }
8
+ interface ProfileConfig {
9
+ maxSymbols: number;
10
+ maxTasks: number;
11
+ }
12
+ interface RunBenchmarkOptions {
13
+ repo?: string;
14
+ repoAlias?: string;
15
+ targetPath?: string;
16
+ profile: ProfileConfig;
17
+ reportDir?: string;
18
+ extensions: string;
19
+ scopeManifest?: string;
20
+ scopePrefix?: string[];
21
+ skipAnalyze: boolean;
22
+ }
23
+ export interface BenchmarkResult {
24
+ pass: boolean;
25
+ failures: string[];
26
+ metrics: {
27
+ queryPrecision: number;
28
+ queryRecall: number;
29
+ contextImpactF1: number;
30
+ smokePassRate: number;
31
+ perfRegressionPct: number;
32
+ };
33
+ triage: Array<{
34
+ kind: string;
35
+ count: number;
36
+ }>;
37
+ analyze?: {
38
+ totalSeconds: number;
39
+ nodes: number;
40
+ edges: number;
41
+ };
42
+ reportDir: string;
43
+ }
44
+ export declare function resolveBenchmarkRepoName(options: Pick<RunBenchmarkOptions, 'repo' | 'repoAlias' | 'targetPath'>): string | undefined;
45
+ export declare function hasRequiredHitFuzzy(expectedUid: string, hitUids: string[], hitNames: string[]): boolean;
46
+ export declare function hasForbiddenUidHitStrict(forbiddenUid: string, hitUids: string[]): boolean;
47
+ export declare function runBenchmark(ds: Dataset, options: RunBenchmarkOptions): Promise<BenchmarkResult>;
48
+ export {};