Files
cursor-byok/benchmarks/semble/codegraph_adapter.cjs
T

126 lines
4.2 KiB
JavaScript

// Persistent-process adapter for measuring CodeGraph library search without CLI startup noise.
'use strict'
const fs = require('node:fs')
const path = require('node:path')
async function main() {
const [libraryRoot, projectRoot, repetitionsText, limitText] = process.argv.slice(2)
const repetitions = Number(repetitionsText)
const limit = Number(limitText)
const input = JSON.parse(fs.readFileSync(0, 'utf8'))
const loadStarted = process.hrtime.bigint()
const codegraphModule = require(path.join(libraryRoot, 'dist/index.js'))
const generatedModule = require(path.join(
libraryRoot,
'dist/extraction/generated-detection.js',
))
const { ToolHandler } = require(path.join(libraryRoot, 'dist/mcp/tools.js'))
const CodeGraph = codegraphModule.CodeGraph || codegraphModule.default
const graph = await CodeGraph.open(projectRoot, { sync: false, readOnly: true })
const loadMs = millisecondsSince(loadStarted)
const stats = graph.getStats()
const handler = new ToolHandler(graph)
const tracks = []
for (const track of input.tracks) {
const queries = []
for (const query of track.queries) {
queries.push(
track.name !== 'symbol'
? await runExplore(handler, query, repetitions, limit)
: runSearch(graph, generatedModule, query, repetitions, limit),
)
}
tracks.push({ name: track.name, queries })
}
graph.close()
process.stdout.write(JSON.stringify({ loadMs, stats, tracks }))
}
async function runExplore(handler, query, repetitions, limit) {
const first = await explore(handler, query.query, limit)
const durationsMs = []
for (let index = 0; index < repetitions; index += 1) {
const started = process.hrtime.bigint()
await explore(handler, query.query, limit)
durationsMs.push(millisecondsSince(started))
}
return { id: query.id, query: query.query, durationsMs, results: first }
}
async function explore(handler, query, limit) {
const response = await handler.execute('codegraph_explore', { query, maxFiles: limit })
if (response.isError) throw new Error(response.content?.[0]?.text || 'CodeGraph explore failed')
return parseExplore(response.content?.[0]?.text || '')
}
function parseExplore(text) {
const results = []
let current = null
let inCode = false
for (const line of text.split('\n')) {
const heading = line.match(/^####\s+(.+?)(?:\s+—|$)/)
if (heading) {
current = { path: heading[1].trim(), startLine: 0, endLine: 0, score: 0, lines: [] }
results.push(current)
inCode = false
continue
}
if (line.startsWith('```')) {
inCode = !inCode
continue
}
if (!inCode || !current) continue
const numbered = line.match(/^(\d+)\t/)
if (!numbered) continue
const number = Number(numbered[1])
current.lines.push(number)
if (current.startLine === 0) current.startLine = number
current.endLine = number
}
return results
.filter((result) => result.lines.length > 0)
.map((result, index) => ({ ...result, score: 1 / (index + 1) }))
}
function runSearch(graph, generatedModule, query, repetitions, limit) {
const durationsMs = []
const first = sortedResults(graph, generatedModule, query.query, limit)
for (let index = 0; index < repetitions; index += 1) {
const started = process.hrtime.bigint()
sortedResults(graph, generatedModule, query.query, limit)
durationsMs.push(millisecondsSince(started))
}
return {
id: query.id,
query: query.query,
durationsMs,
results: first.map(({ node, score }) => ({
path: node.filePath,
startLine: node.startLine,
endLine: node.endLine,
score,
lines: null,
})),
}
}
function sortedResults(graph, generatedModule, query, limit) {
const raw = graph.searchNodes(query, { limit })
return [...raw].sort((left, right) => {
const leftGenerated = generatedModule.isGeneratedFile(left.node.filePath) ? 1 : 0
const rightGenerated = generatedModule.isGeneratedFile(right.node.filePath) ? 1 : 0
return leftGenerated - rightGenerated
})
}
function millisecondsSince(started) {
return Number(process.hrtime.bigint() - started) / 1_000_000
}
main().catch((error) => {
process.stderr.write(`${error.stack || error}\n`)
process.exitCode = 1
})