@iderouter/index-mcp 0.2.0-beta.1

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 (69) hide show
  1. package/README.md +93 -0
  2. package/package.json +26 -0
  3. package/scripts/benchmark-all.mjs +177 -0
  4. package/scripts/benchmark-auto-continuation.mjs +188 -0
  5. package/scripts/benchmark-background-fine-resume.mjs +245 -0
  6. package/scripts/benchmark-background-fine-wait.mjs +76 -0
  7. package/scripts/benchmark-background-fine.mjs +132 -0
  8. package/scripts/benchmark-clean-snapshot.mjs +83 -0
  9. package/scripts/benchmark-coarse-ready-search.mjs +161 -0
  10. package/scripts/benchmark-deferred.mjs +62 -0
  11. package/scripts/benchmark-first-semantic-visible.mjs +151 -0
  12. package/scripts/benchmark-gate.mjs +107 -0
  13. package/scripts/benchmark-generic-resumed-single-chunk-embed.mjs +104 -0
  14. package/scripts/benchmark-noop.mjs +24 -0
  15. package/scripts/benchmark-priority-ready-search.mjs +165 -0
  16. package/scripts/benchmark-repeat-search.mjs +148 -0
  17. package/scripts/benchmark-resumed-retry-burst.mjs +187 -0
  18. package/scripts/benchmark-resumed-single-chunk-success.mjs +154 -0
  19. package/scripts/benchmark-resumed-single-chunk.mjs +146 -0
  20. package/scripts/benchmark-single-priority-chunk-embed.mjs +145 -0
  21. package/scripts/benchmark-small-change.mjs +146 -0
  22. package/scripts/benchmark-stage-summary.mjs +88 -0
  23. package/scripts/lib/auto-continuation-state.mjs +34 -0
  24. package/scripts/lib/benchmark-query-packs.mjs +123 -0
  25. package/scripts/lib/benchmark-snapshot.mjs +109 -0
  26. package/scripts/lib/mcp-bench.mjs +455 -0
  27. package/src/architecture-query-fallback.js +50 -0
  28. package/src/background-definition-chunks.js +199 -0
  29. package/src/background-embedding-profile.js +64 -0
  30. package/src/background-fine-budget.js +18 -0
  31. package/src/background-fine-runtime.js +179 -0
  32. package/src/background-fine-selection.js +332 -0
  33. package/src/checkpoint-policy.js +16 -0
  34. package/src/conflict-policy.js +17 -0
  35. package/src/deferred-retry-delay.js +14 -0
  36. package/src/deferred-retry-status.js +10 -0
  37. package/src/embedding-attempt-ordinal.js +17 -0
  38. package/src/embedding-failure-penalty.js +60 -0
  39. package/src/embedding-failure-policy.js +52 -0
  40. package/src/embedding-flush-timeout.js +33 -0
  41. package/src/embedding-inflight-status.js +18 -0
  42. package/src/embedding-model-policy.js +44 -0
  43. package/src/embedding-next-switch.js +18 -0
  44. package/src/embedding-request-status-detail.js +25 -0
  45. package/src/embedding-request-status.js +22 -0
  46. package/src/embedding-selection-order.js +23 -0
  47. package/src/fine-run-queue.js +14 -0
  48. package/src/index.js +7970 -0
  49. package/src/job-supersession.js +25 -0
  50. package/src/priority-progress.js +20 -0
  51. package/src/priority-ready-anchor-coverage-normalize.js +18 -0
  52. package/src/priority-ready-anchor-coverage.js +23 -0
  53. package/src/priority-ready-hotspots.js +344 -0
  54. package/src/priority-ready-status.js +30 -0
  55. package/src/priority-ready-targets.js +45 -0
  56. package/src/priority-usable-attempt-plan.js +44 -0
  57. package/src/priority-usable-attempt-timeout.js +18 -0
  58. package/src/priority-usable-fast-path.js +11 -0
  59. package/src/priority-usable-probe-order.js +34 -0
  60. package/src/remote-strategy-failure-cache.js +55 -0
  61. package/src/resume-seed.js +9 -0
  62. package/src/semantic-first-checkpoint.js +8 -0
  63. package/src/semantic-slow-path.js +10 -0
  64. package/src/single-chunk-attempt-timeout.js +13 -0
  65. package/src/single-chunk-embedding-content.js +26 -0
  66. package/src/single-chunk-embedding-policy.js +18 -0
  67. package/src/single-chunk-provider-order.js +12 -0
  68. package/src/single-chunk-provider-policy.js +63 -0
  69. package/src/worker-lock-retry.js +24 -0
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+
8
+ import { backgroundBucketKey } from "../src/background-fine-selection.js";
9
+ import { benchRepoId, cleanupBenchmarkRun, installBenchmarkSignalCleanup, parseDiagnostics, readIndexMeta, readJobStatus, sleep, spawnMcpClient } from "./lib/mcp-bench.mjs";
10
+ import { createBenchmarkSnapshot } from "./lib/benchmark-snapshot.mjs";
11
+
12
+ const TARGET_PATH = path.resolve(process.argv[2] || process.cwd());
13
+ const POLL_MS = Number(process.env.IDEROUTER_BENCH_POLL_MS || 1000);
14
+ const ROUNDS = Math.max(2, Number(process.env.IDEROUTER_BENCH_RESUME_ROUNDS || 3));
15
+ const ROUND_TIMEOUT_MS = Number(process.env.IDEROUTER_BENCH_RESUME_TIMEOUT_MS || 180000);
16
+
17
+ function bucketFamily(relativePath) {
18
+ const key = backgroundBucketKey(relativePath);
19
+ return key.startsWith("relay_channel:") ? "relay_channel" : key;
20
+ }
21
+
22
+ function summarize(text) {
23
+ const diagnostics = parseDiagnostics(text);
24
+ return {
25
+ status: text.split("\n")[0],
26
+ diagnostics,
27
+ deferred: /^Priority semantic search is ready\b/.test(text) && /background fine rebuild .*deferred/i.test(text),
28
+ full_ready: /^Indexed\b/.test(text),
29
+ failed: /^Last index job .* failed\b/.test(text),
30
+ };
31
+ }
32
+
33
+ function coverageFromMeta(meta) {
34
+ const files = Array.isArray(meta?.files) ? meta.files.map((item) => item.relativePath).filter(Boolean) : [];
35
+ const familyCounts = new Map();
36
+ for (const relativePath of files) {
37
+ const family = bucketFamily(relativePath);
38
+ familyCounts.set(family, (familyCounts.get(family) || 0) + 1);
39
+ }
40
+ return {
41
+ files,
42
+ fine_semantic_files: Number(meta?.fineSemanticFileCount || 0),
43
+ fine_semantic_chunks: Number(meta?.fineSemanticChunkCount || 0),
44
+ background_bucket_cursor: String(meta?.backgroundBucketCursor || ""),
45
+ priority_ready_anchor_coverage: meta?.priorityReadyAnchorCoverage || null,
46
+ family_counts: Object.fromEntries(Array.from(familyCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]))),
47
+ };
48
+ }
49
+
50
+ async function readSemanticCoverage(indexHome, targetPath) {
51
+ try {
52
+ const filePath = path.join(indexHome, "indexes", `${benchRepoId(targetPath)}.json`);
53
+ const content = await fs.readFile(filePath, "utf8");
54
+ const index = JSON.parse(content);
55
+ const semanticChunkCounts = new Map();
56
+ const semanticFiles = new Set();
57
+ for (const chunk of Array.isArray(index?.chunks) ? index.chunks : []) {
58
+ const hasVector = Array.isArray(chunk?.vector)
59
+ ? chunk.vector.length > 0
60
+ : typeof chunk?.vectorBase64 === "string" && chunk.vectorBase64.length > 0;
61
+ if (!hasVector || !chunk?.relativePath) continue;
62
+ semanticFiles.add(chunk.relativePath);
63
+ semanticChunkCounts.set(chunk.relativePath, (semanticChunkCounts.get(chunk.relativePath) || 0) + 1);
64
+ }
65
+ const familyCounts = new Map();
66
+ for (const relativePath of semanticFiles) {
67
+ const family = bucketFamily(relativePath);
68
+ familyCounts.set(family, (familyCounts.get(family) || 0) + 1);
69
+ }
70
+ return {
71
+ semantic_files: Array.from(semanticFiles).sort(),
72
+ semantic_family_counts: Object.fromEntries(Array.from(familyCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]))),
73
+ semantic_chunk_counts: Object.fromEntries(Array.from(semanticChunkCounts.entries()).sort((a, b) => a[0].localeCompare(b[0]))),
74
+ };
75
+ } catch {
76
+ return {
77
+ semantic_files: [],
78
+ semantic_family_counts: {},
79
+ semantic_chunk_counts: {},
80
+ };
81
+ }
82
+ }
83
+
84
+ async function waitForRound(client, targetPath, indexHome) {
85
+ const startedAt = Date.now();
86
+ let lastText = "";
87
+ while (Date.now() - startedAt < ROUND_TIMEOUT_MS) {
88
+ lastText = await client.callTool("get_indexing_status", { path: targetPath });
89
+ if (
90
+ /^Indexed\b/.test(lastText) ||
91
+ /^Last index job .* failed\b/.test(lastText) ||
92
+ (/^Priority semantic search is ready\b/.test(lastText) && /background fine rebuild .*deferred/i.test(lastText))
93
+ ) {
94
+ const meta = await readIndexMeta(indexHome, targetPath);
95
+ return {
96
+ settled: true,
97
+ wall_ms: Date.now() - startedAt,
98
+ text: lastText,
99
+ summary: summarize(lastText),
100
+ meta,
101
+ };
102
+ }
103
+ await sleep(POLL_MS);
104
+ }
105
+ return {
106
+ settled: false,
107
+ wall_ms: Date.now() - startedAt,
108
+ text: lastText,
109
+ summary: summarize(lastText),
110
+ meta: await readIndexMeta(indexHome, targetPath),
111
+ };
112
+ }
113
+
114
+ async function runRound(round, targetPath, indexHome, baseEnv) {
115
+ const env = {
116
+ ...baseEnv,
117
+ IDEROUTER_INDEX_HOME: indexHome,
118
+ IDEROUTER_AUTO_CONTINUE_BACKGROUND_BENCH: "false",
119
+ };
120
+ const { child, client } = spawnMcpClient(env);
121
+ const uninstallSignalCleanup = installBenchmarkSignalCleanup(() => ({
122
+ child,
123
+ indexHome,
124
+ targetPath,
125
+ }));
126
+ try {
127
+ await client.initialize();
128
+ const startText = await client.callTool("index_codebase", { path: targetPath, wait: false, cloud: false });
129
+ const settled = await waitForRound(client, targetPath, indexHome);
130
+ const job = await readJobStatus(indexHome, targetPath);
131
+ return {
132
+ round,
133
+ start_status: startText.split("\n")[0],
134
+ job,
135
+ ...settled,
136
+ };
137
+ } finally {
138
+ uninstallSignalCleanup();
139
+ await cleanupBenchmarkRun({
140
+ child,
141
+ targetPath,
142
+ });
143
+ }
144
+ }
145
+
146
+ function diffFiles(previousFiles, currentFiles) {
147
+ const previous = new Set(previousFiles);
148
+ return currentFiles.filter((item) => !previous.has(item));
149
+ }
150
+
151
+ async function main() {
152
+ const snapshot = await createBenchmarkSnapshot(TARGET_PATH, process.env);
153
+ const snapshotDir = snapshot.snapshotDir;
154
+ const isolatedIndexHome = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-home-"));
155
+ const rounds = [];
156
+ const cumulativeFiles = new Set();
157
+ const cumulativeFamilies = new Set();
158
+
159
+ try {
160
+ for (let round = 1; round <= ROUNDS; round += 1) {
161
+ const result = await runRound(round, snapshotDir, isolatedIndexHome, process.env);
162
+ const coverage = coverageFromMeta(result.meta);
163
+ const semanticCoverage = await readSemanticCoverage(isolatedIndexHome, snapshotDir);
164
+ const newFiles = diffFiles(Array.from(cumulativeFiles), semanticCoverage.semantic_files);
165
+ const newFamilies = Array.from(new Set(newFiles.map((item) => bucketFamily(item)))).sort();
166
+ for (const file of semanticCoverage.semantic_files) cumulativeFiles.add(file);
167
+ for (const family of Object.keys(semanticCoverage.semantic_family_counts)) cumulativeFamilies.add(family);
168
+ rounds.push({
169
+ round,
170
+ settled: result.settled,
171
+ wall_ms: result.wall_ms,
172
+ start_status: result.start_status,
173
+ final_status: result.summary.status,
174
+ deferred: result.summary.deferred,
175
+ full_ready: result.summary.full_ready,
176
+ failed: result.summary.failed,
177
+ fine_semantic_files: coverage.fine_semantic_files,
178
+ fine_semantic_chunks: coverage.fine_semantic_chunks,
179
+ background_bucket_cursor: coverage.background_bucket_cursor,
180
+ priority_ready_anchor_coverage: coverage.priority_ready_anchor_coverage,
181
+ manifest_family_counts: coverage.family_counts,
182
+ semantic_family_counts: semanticCoverage.semantic_family_counts,
183
+ new_files_count: newFiles.length,
184
+ new_families: newFamilies,
185
+ new_files: newFiles,
186
+ new_file_chunk_counts: Object.fromEntries(
187
+ newFiles
188
+ .map((relativePath) => [relativePath, Number(semanticCoverage.semantic_chunk_counts[relativePath] || 0)])
189
+ .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])),
190
+ ),
191
+ diagnostics: {
192
+ background_fine_target_files: Number(result.summary.diagnostics.background_fine_target_files || 0),
193
+ background_fine_target_chunks: Number(result.summary.diagnostics.background_fine_target_chunks || 0),
194
+ background_fine_completed_files: Number(result.summary.diagnostics.background_fine_completed_files || 0),
195
+ embedding_request_ms_total: Number(result.summary.diagnostics.embedding_request_ms_total || 0),
196
+ embedding_requests: Number(result.summary.diagnostics.embedding_requests || 0),
197
+ },
198
+ job_observed: {
199
+ embedding_model_source: result.job?.embeddingModelSource || result.job?.diagnostics?.embeddingModelSource || "",
200
+ background_embedding_profile: result.job?.backgroundEmbeddingProfile || null,
201
+ previous_background_embedding_profile: result.job?.previousBackgroundEmbeddingProfile || null,
202
+ previous_diagnostics: result.job?.previousDiagnostics
203
+ ? {
204
+ embedding_request_ms_total: Number(result.job.previousDiagnostics.embeddingRequestMsTotal || 0),
205
+ embedding_requests: Number(result.job.previousDiagnostics.embeddingRequests || 0),
206
+ embedding_batch_max_size: Number(result.job.previousDiagnostics.embeddingBatchMaxSize || 0),
207
+ }
208
+ : null,
209
+ },
210
+ });
211
+ if (result.summary.full_ready || result.summary.failed) break;
212
+ }
213
+
214
+ process.stdout.write(
215
+ `${JSON.stringify(
216
+ {
217
+ path: TARGET_PATH,
218
+ snapshot_path: snapshotDir,
219
+ snapshot_mode: snapshot.mode,
220
+ isolated_index_home: isolatedIndexHome,
221
+ rounds,
222
+ summary: {
223
+ rounds_run: rounds.length,
224
+ deferred_rounds: rounds.filter((item) => item.deferred).length,
225
+ full_ready_rounds: rounds.filter((item) => item.full_ready).length,
226
+ cursor_sequence: rounds.map((item) => item.background_bucket_cursor),
227
+ priority_ready_anchor_coverage_sequence: rounds.map((item) => item.priority_ready_anchor_coverage),
228
+ cumulative_families: Array.from(cumulativeFamilies).sort(),
229
+ cumulative_file_count: cumulativeFiles.size,
230
+ },
231
+ },
232
+ null,
233
+ 2,
234
+ )}\n`,
235
+ );
236
+ } finally {
237
+ await fs.rm(snapshotDir, { recursive: true, force: true });
238
+ await fs.rm(isolatedIndexHome, { recursive: true, force: true });
239
+ }
240
+ }
241
+
242
+ main().catch((error) => {
243
+ console.error(error instanceof Error ? error.message : String(error));
244
+ process.exitCode = 1;
245
+ });
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { promisify } from "node:util";
9
+ import { cleanupBenchmarkRun, installBenchmarkSignalCleanup, normalizeBenchmarkResult, spawnMcpClient } from "./lib/mcp-bench.mjs";
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const TARGET_PATH = path.resolve(process.argv[2] || process.cwd());
13
+
14
+ async function createSnapshotDir() {
15
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-bgwait-"));
16
+ await execFileAsync("git", ["clone", "--shared", "--no-checkout", TARGET_PATH, tempDir], {
17
+ cwd: process.cwd(),
18
+ maxBuffer: 8 * 1024 * 1024,
19
+ });
20
+ await execFileAsync("git", ["-C", tempDir, "checkout", "-f", "HEAD"], {
21
+ cwd: process.cwd(),
22
+ maxBuffer: 8 * 1024 * 1024,
23
+ });
24
+ return tempDir;
25
+ }
26
+
27
+ async function main() {
28
+ const snapshotDir = await createSnapshotDir();
29
+ const isolatedIndexHome = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-home-"));
30
+ const env = {
31
+ ...process.env,
32
+ IDEROUTER_INDEX_HOME: isolatedIndexHome,
33
+ };
34
+ const { child, client } = spawnMcpClient(env);
35
+ const uninstallSignalCleanup = installBenchmarkSignalCleanup(() => ({
36
+ child,
37
+ indexHome: isolatedIndexHome,
38
+ targetPath: snapshotDir,
39
+ snapshotDir,
40
+ }));
41
+
42
+ try {
43
+ await client.initialize();
44
+ const startedAt = Date.now();
45
+ const finalText = await client.callTool("index_codebase", { path: snapshotDir, wait: true, cloud: false });
46
+ const wallMs = Date.now() - startedAt;
47
+ process.stdout.write(
48
+ `${JSON.stringify(
49
+ {
50
+ path: TARGET_PATH,
51
+ snapshot_path: snapshotDir,
52
+ isolated_index_home: isolatedIndexHome,
53
+ wall_ms: wallMs,
54
+ final_status: finalText.split("\n")[0],
55
+ parsed: normalizeBenchmarkResult(snapshotDir, finalText, wallMs),
56
+ final_text: finalText,
57
+ },
58
+ null,
59
+ 2,
60
+ )}\n`,
61
+ );
62
+ } finally {
63
+ uninstallSignalCleanup();
64
+ await cleanupBenchmarkRun({
65
+ child,
66
+ indexHome: isolatedIndexHome,
67
+ targetPath: snapshotDir,
68
+ snapshotDir,
69
+ });
70
+ }
71
+ }
72
+
73
+ main().catch((error) => {
74
+ console.error(error instanceof Error ? error.message : String(error));
75
+ process.exitCode = 1;
76
+ });
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { promisify } from "node:util";
9
+ import { cleanupBenchmarkRun, installBenchmarkSignalCleanup, parseDiagnostics, sleep, spawnMcpClient } from "./lib/mcp-bench.mjs";
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const TARGET_PATH = path.resolve(process.argv[2] || process.cwd());
13
+ const POLL_MS = Number(process.env.IDEROUTER_BENCH_POLL_MS || 1000);
14
+ const PRIORITY_TIMEOUT_MS = Number(process.env.IDEROUTER_BENCH_PRIORITY_TIMEOUT_MS || 180000);
15
+ const FULL_TIMEOUT_MS = Number(process.env.IDEROUTER_BENCH_FULL_TIMEOUT_MS || 300000);
16
+
17
+ async function createSnapshotDir() {
18
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-bg-"));
19
+ await execFileAsync("git", ["clone", "--shared", "--no-checkout", TARGET_PATH, tempDir], {
20
+ cwd: process.cwd(),
21
+ maxBuffer: 8 * 1024 * 1024,
22
+ });
23
+ await execFileAsync("git", ["-C", tempDir, "checkout", "-f", "HEAD"], {
24
+ cwd: process.cwd(),
25
+ maxBuffer: 8 * 1024 * 1024,
26
+ });
27
+ return tempDir;
28
+ }
29
+
30
+ function summarize(text) {
31
+ const diagnostics = parseDiagnostics(text);
32
+ return {
33
+ status: text.split("\n")[0],
34
+ diagnostics,
35
+ deferred: /^Priority semantic search is ready\b/.test(text) && /background fine rebuild .*deferred/i.test(text),
36
+ full_ready: /^Indexed\b/.test(text),
37
+ priority_ready: /^Priority semantic search is ready\b/.test(text),
38
+ failed: /^Last index job .* failed\b/.test(text),
39
+ };
40
+ }
41
+
42
+ async function waitFor(client, targetPath, predicate, timeoutMs) {
43
+ const startedAt = Date.now();
44
+ let lastText = "";
45
+ while (Date.now() - startedAt < timeoutMs) {
46
+ lastText = await client.callTool("get_indexing_status", { path: targetPath });
47
+ if (predicate(lastText)) {
48
+ return {
49
+ settled: true,
50
+ wall_ms: Date.now() - startedAt,
51
+ text: lastText,
52
+ summary: summarize(lastText),
53
+ };
54
+ }
55
+ await sleep(POLL_MS);
56
+ }
57
+ return {
58
+ settled: false,
59
+ wall_ms: Date.now() - startedAt,
60
+ text: lastText,
61
+ summary: summarize(lastText),
62
+ };
63
+ }
64
+
65
+ async function main() {
66
+ const snapshotDir = await createSnapshotDir();
67
+ const isolatedIndexHome = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-home-"));
68
+ const env = {
69
+ ...process.env,
70
+ IDEROUTER_INDEX_HOME: isolatedIndexHome,
71
+ };
72
+ const { child, client } = spawnMcpClient(env);
73
+ const uninstallSignalCleanup = installBenchmarkSignalCleanup(() => ({
74
+ child,
75
+ indexHome: isolatedIndexHome,
76
+ targetPath: snapshotDir,
77
+ snapshotDir,
78
+ }));
79
+
80
+ try {
81
+ await client.initialize();
82
+ const startText = await client.callTool("index_codebase", { path: snapshotDir, wait: false, cloud: false });
83
+ const priorityReady = await waitFor(
84
+ client,
85
+ snapshotDir,
86
+ (text) => /^Priority semantic search is ready\b/.test(text) || /^Indexed\b/.test(text),
87
+ PRIORITY_TIMEOUT_MS,
88
+ );
89
+
90
+ let fullReady = null;
91
+ if (priorityReady.settled && !priorityReady.summary.full_ready) {
92
+ fullReady = await waitFor(
93
+ client,
94
+ snapshotDir,
95
+ (text) =>
96
+ /^Indexed\b/.test(text) ||
97
+ /^Background fine rebuild is temporarily paused\b/.test(text) ||
98
+ (/^Priority semantic search is ready\b/.test(text) && /background fine rebuild .*deferred/i.test(text)) ||
99
+ /^Last index job .* failed\b/.test(text),
100
+ FULL_TIMEOUT_MS,
101
+ );
102
+ }
103
+
104
+ process.stdout.write(
105
+ `${JSON.stringify(
106
+ {
107
+ path: TARGET_PATH,
108
+ snapshot_path: snapshotDir,
109
+ isolated_index_home: isolatedIndexHome,
110
+ start_status: startText.split("\n")[0],
111
+ priority_ready: priorityReady,
112
+ full_ready: fullReady,
113
+ },
114
+ null,
115
+ 2,
116
+ )}\n`,
117
+ );
118
+ } finally {
119
+ uninstallSignalCleanup();
120
+ await cleanupBenchmarkRun({
121
+ child,
122
+ indexHome: isolatedIndexHome,
123
+ targetPath: snapshotDir,
124
+ snapshotDir,
125
+ });
126
+ }
127
+ }
128
+
129
+ main().catch((error) => {
130
+ console.error(error instanceof Error ? error.message : String(error));
131
+ process.exitCode = 1;
132
+ });
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { promisify } from "node:util";
9
+ import { isUsableIndexStatus, waitForIndexStatus } from "./lib/mcp-bench.mjs";
10
+ import { createBenchmarkSnapshot } from "./lib/benchmark-snapshot.mjs";
11
+
12
+ const execFileAsync = promisify(execFile);
13
+ const TARGET_PATH = path.resolve(process.argv[2] || process.cwd());
14
+ const SCRIPT_DIR = path.resolve("scripts");
15
+ const TIMEOUT_MS = Number(process.env.IDEROUTER_BENCH_TIMEOUT_MS || 30000);
16
+ const POLL_MS = Number(process.env.IDEROUTER_BENCH_POLL_MS || 500);
17
+
18
+ async function runScript(scriptName, targetPath, env) {
19
+ const scriptPath = path.join(SCRIPT_DIR, scriptName);
20
+ const { stdout } = await execFileAsync(process.execPath, [scriptPath, targetPath], {
21
+ cwd: process.cwd(),
22
+ env,
23
+ maxBuffer: 8 * 1024 * 1024,
24
+ });
25
+ return JSON.parse(String(stdout || "").trim());
26
+ }
27
+
28
+ async function main() {
29
+ const snapshot = await createBenchmarkSnapshot(TARGET_PATH, {
30
+ ...process.env,
31
+ IDEROUTER_BENCH_SNAPSHOT_MODE: "head",
32
+ });
33
+ const snapshotDir = snapshot.snapshotDir;
34
+ const isolatedIndexHome = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-home-"));
35
+ const env = {
36
+ ...process.env,
37
+ IDEROUTER_BENCH_TIMEOUT_MS: String(TIMEOUT_MS),
38
+ IDEROUTER_BENCH_POLL_MS: String(POLL_MS),
39
+ IDEROUTER_INDEX_HOME: isolatedIndexHome,
40
+ };
41
+
42
+ try {
43
+ const warmup = await runScript("benchmark-noop.mjs", snapshotDir, env);
44
+ const warmupReady = await waitForIndexStatus({
45
+ targetPath: snapshotDir,
46
+ timeoutMs: Math.max(TIMEOUT_MS, 180000),
47
+ pollMs: POLL_MS,
48
+ env,
49
+ isSettled: isUsableIndexStatus,
50
+ });
51
+ const measure = warmupReady.settled
52
+ ? await runScript("benchmark-noop.mjs", snapshotDir, env)
53
+ : null;
54
+ process.stdout.write(
55
+ `${JSON.stringify(
56
+ {
57
+ path: TARGET_PATH,
58
+ snapshot_path: snapshotDir,
59
+ snapshot_mode: snapshot.mode,
60
+ isolated_index_home: isolatedIndexHome,
61
+ skipped: false,
62
+ api_key_present: Boolean(process.env.IDEROUTER_API_KEY),
63
+ strategy_mode: process.env.IDEROUTER_API_KEY ? "remote_or_cached" : "local_fallback",
64
+ comparable_to_auggie: Boolean(process.env.IDEROUTER_API_KEY && warmupReady.settled),
65
+ warmup_not_ready: !warmupReady.settled,
66
+ warmup,
67
+ warmup_ready: warmupReady,
68
+ measure,
69
+ },
70
+ null,
71
+ 2,
72
+ )}\n`,
73
+ );
74
+ } finally {
75
+ await fs.rm(snapshotDir, { recursive: true, force: true });
76
+ await fs.rm(isolatedIndexHome, { recursive: true, force: true });
77
+ }
78
+ }
79
+
80
+ main().catch((error) => {
81
+ console.error(error instanceof Error ? error.message : String(error));
82
+ process.exitCode = 1;
83
+ });
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+
8
+ import {
9
+ cleanupBenchmarkRun,
10
+ installBenchmarkSignalCleanup,
11
+ normalizeBenchmarkResult,
12
+ readIndexMeta,
13
+ sleep,
14
+ spawnMcpClient,
15
+ } from "./lib/mcp-bench.mjs";
16
+ import { getBenchmarkQueryPack } from "./lib/benchmark-query-packs.mjs";
17
+ import { createBenchmarkSnapshot } from "./lib/benchmark-snapshot.mjs";
18
+
19
+ const TARGET_PATH = path.resolve(process.argv[2] || process.cwd());
20
+ const POLL_MS = Number(process.env.IDEROUTER_BENCH_POLL_MS || 500);
21
+ const TIMEOUT_MS = Number(process.env.IDEROUTER_BENCH_TIMEOUT_MS || 30000);
22
+ const QUERY_PACK = String(process.env.IDEROUTER_BENCH_QUERY_PACK || "core").trim().toLowerCase();
23
+ const QUERIES = getBenchmarkQueryPack(QUERY_PACK);
24
+
25
+ function extractResultPaths(text) {
26
+ return String(text || "")
27
+ .split("\n")
28
+ .map((line) => line.trim())
29
+ .filter((line) => /^\d+\.\s+/.test(line))
30
+ .map((line) => {
31
+ const match = line.match(/^\d+\.\s+(.+?):\d+-\d+\s+score=/);
32
+ return match ? match[1] : null;
33
+ })
34
+ .filter(Boolean);
35
+ }
36
+
37
+ function evaluatePaths(paths, expected) {
38
+ const top1 = paths[0] || "";
39
+ const top3 = paths.slice(0, 3);
40
+ return {
41
+ top1,
42
+ top3,
43
+ top1_hit: expected.includes(top1),
44
+ top3_hit: top3.some((item) => expected.includes(item)),
45
+ };
46
+ }
47
+
48
+ async function waitUntilCoarseReady(client, targetPath, timeoutMs, pollMs, indexHome) {
49
+ const startedAt = Date.now();
50
+ const deadline = startedAt + timeoutMs;
51
+ let finalText = "";
52
+ while (Date.now() < deadline) {
53
+ const text = await client.callTool("get_indexing_status", { path: targetPath });
54
+ finalText = text;
55
+ const meta = indexHome ? await readIndexMeta(indexHome, targetPath) : null;
56
+ const coarseReadyFromMeta = Boolean(meta?.coarseReady);
57
+ if (
58
+ /^Coarse lexical index is ready\b/.test(text) ||
59
+ /^Priority semantic search is ready\b/.test(text) ||
60
+ /^Indexed\b/.test(text) ||
61
+ coarseReadyFromMeta
62
+ ) {
63
+ return {
64
+ settled: true,
65
+ wallMs: Date.now() - startedAt,
66
+ finalText,
67
+ meta,
68
+ parsed: normalizeBenchmarkResult(targetPath, finalText, Date.now() - startedAt),
69
+ };
70
+ }
71
+ await sleep(pollMs);
72
+ }
73
+ return {
74
+ settled: false,
75
+ wallMs: Date.now() - startedAt,
76
+ finalText,
77
+ meta: indexHome ? await readIndexMeta(indexHome, targetPath) : null,
78
+ parsed: normalizeBenchmarkResult(targetPath, finalText, Date.now() - startedAt),
79
+ };
80
+ }
81
+
82
+ async function main() {
83
+ const snapshot = await createBenchmarkSnapshot(TARGET_PATH, process.env);
84
+ const snapshotDir = snapshot.snapshotDir;
85
+ const isolatedIndexHome = await fs.mkdtemp(path.join(os.tmpdir(), "iderouter-bench-coarse-home-"));
86
+ const env = {
87
+ ...process.env,
88
+ IDEROUTER_INDEX_HOME: isolatedIndexHome,
89
+ };
90
+ const { child, client } = spawnMcpClient(env);
91
+ const uninstallSignalCleanup = installBenchmarkSignalCleanup(() => ({
92
+ child,
93
+ indexHome: isolatedIndexHome,
94
+ targetPath: snapshotDir,
95
+ snapshotDir,
96
+ }));
97
+
98
+ try {
99
+ await client.initialize();
100
+ const startText = await client.callTool("index_codebase", { path: snapshotDir, wait: false, cloud: false });
101
+ const ready = await waitUntilCoarseReady(
102
+ client,
103
+ snapshotDir,
104
+ Math.max(TIMEOUT_MS, 30000),
105
+ POLL_MS,
106
+ isolatedIndexHome,
107
+ );
108
+
109
+ const searches = [];
110
+ if (ready.settled) {
111
+ for (const item of QUERIES) {
112
+ const startedAt = Date.now();
113
+ const text = await client.callTool("search_code", {
114
+ path: snapshotDir,
115
+ query: item.query,
116
+ limit: 5,
117
+ });
118
+ const wallMs = Date.now() - startedAt;
119
+ const resultPaths = extractResultPaths(text);
120
+ searches.push({
121
+ name: item.name,
122
+ query: item.query,
123
+ wall_ms: wallMs,
124
+ result_paths: resultPaths,
125
+ ...evaluatePaths(resultPaths, item.expect_any),
126
+ raw_status: text.split("\n")[0],
127
+ });
128
+ }
129
+ }
130
+
131
+ process.stdout.write(
132
+ `${JSON.stringify(
133
+ {
134
+ path: TARGET_PATH,
135
+ snapshot_path: snapshotDir,
136
+ snapshot_mode: snapshot.mode,
137
+ isolated_index_home: isolatedIndexHome,
138
+ start_status: startText.split("\n")[0],
139
+ query_pack: QUERY_PACK,
140
+ coarse_ready: ready,
141
+ searches,
142
+ },
143
+ null,
144
+ 2,
145
+ )}\n`,
146
+ );
147
+ } finally {
148
+ uninstallSignalCleanup();
149
+ await cleanupBenchmarkRun({
150
+ child,
151
+ indexHome: isolatedIndexHome,
152
+ targetPath: snapshotDir,
153
+ snapshotDir,
154
+ });
155
+ }
156
+ }
157
+
158
+ main().catch((error) => {
159
+ console.error(error instanceof Error ? error.message : String(error));
160
+ process.exitCode = 1;
161
+ });