Ctrl K
ComunidadGuías…

packages/code-indexer/bench/dump-edges.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
// Gauntlet tooling (P1a): walk+parse+resolve a repo with the code-indexer library
// and dump one JSON line per edge (CALLS by default, or every edge type with
// --all-edges), each carrying enough symbol metadata (file + line range + kind)
// for downstream precision scoring (ts-oracle.mjs, judge-packet.mjs) and the
// gauntlet report (bench/gauntlet/code_kb.py in the bench repo).
//
// Usage: node bench/dump-edges.mjs <repoPath> [--lang py|ts|rust] [--out edges.jsonl] [--all-edges]
import fs from 'node:fs';
import path from 'node:path';
import { walkRepo, extractFile, buildRepoIndex, fileOutputs } from '../dist/index.js';

const LANG_GROUPS = {
  py: new Set(['python']),
  ts: new Set(['typescript', 'tsx', 'javascript']),
  rust: new Set(['rust']),
};

function parseArgs(argv) {
  const args = { repoPath: null, lang: null, out: 'edges.jsonl', allEdges: false };
  const positional = [];
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '--lang') args.lang = argv[++i];
    else if (a === '--out') args.out = argv[++i];
    else if (a === '--all-edges') args.allEdges = true;
    else if (a === '--help' || a === '-h') args.help = true;
    else positional.push(a);
  }
  args.repoPath = positional[0] ?? null;
  return args;
}

function usage() {
  console.error('usage: node bench/dump-edges.mjs <repoPath> [--lang py|ts|rust] [--out edges.jsonl] [--all-edges]');
}

// Resolve a fully-qualified symbol name ("file::qualname") to its own metadata by
// looking it up in the repo-wide symbolsByFile index built from buildRepoIndex.
// Works for both same-file and cross-file symbols since symbolsByFile covers every
// parsed file, not just the one currently being emitted. Returns null for names
// that aren't a symbol reference at all (e.g. a bare external module name in an
// unresolved IMPORTS edge, which has no "::").
// A name with no "::" is either a bare file path (CONTAINS/IMPORTS head/tail can be
// the file itself, e.g. a top-level symbol's CONTAINS head) or a bare external
// module name (an unresolved IMPORTS tail, e.g. "os"). Only the former is a real
// repo file.
function findMeta(idx, fullName) {
  const sep = fullName.indexOf('::');
  if (sep < 0) {
    if (idx.allPaths.has(fullName)) return { file: fullName, kind: 'file', line_start: null, line_end: null };
    return null;
  }
  const file = fullName.slice(0, sep);
  const qualname = fullName.slice(sep + 2);
  const syms = idx.symbolsByFile.get(file);
  if (!syms) return null;
  const s = syms.find((s) => s.qualname === qualname);
  if (!s) return null;
  return { file, kind: s.kind, line_start: s.line_start, line_end: s.line_end };
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help || !args.repoPath) { usage(); process.exit(args.help ? 0 : 1); }
  const allowedLangs = args.lang ? LANG_GROUPS[args.lang] : null;
  if (args.lang && !allowedLangs) { console.error(`unknown --lang '${args.lang}' (expected py|ts|rust)`); process.exit(1); }

  const repoPath = path.resolve(args.repoPath);
  if (!fs.existsSync(repoPath)) { console.error(`repoPath does not exist: ${repoPath}`); process.exit(1); }

  const t0 = Date.now();
  const walked = await walkRepo(repoPath);
  const parsed = [];
  const parseWarnings = [];
  for (const w of walked) {
    if (w.lang === 'other') continue;
    const source = fs.readFileSync(w.abs, 'utf8');
    try { parsed.push({ walked: w, ex: await extractFile(w.lang, source) }); }
    catch (e) { parseWarnings.push(`parse failed ${w.path}: ${e.message}`); }
  }
  const idx = buildRepoIndex(parsed);

  const outPath = path.resolve(args.out);
  const out = fs.createWriteStream(outPath);

  let filesEmitted = 0;
  let symbolsTotal = 0;
  const byType = new Map();
  const callsByLangTier = new Map(); // lang -> tier -> count

  const tierOf = (confidence) => {
    if (confidence === 1) return '1.0';
    if (confidence === 0.6) return '0.6';
    if (confidence === 0.4) return '0.4';
    return String(confidence);
  };

  for (const p of parsed) {
    if (allowedLangs && !allowedLangs.has(p.walked.lang)) continue;
    filesEmitted++;
    symbolsTotal += p.ex.symbols.length;
    const { edges } = fileOutputs(idx, p.walked.path);
    for (const e of edges) {
      if (!args.allEdges && e.type !== 'CALLS') continue;
      byType.set(e.type, (byType.get(e.type) ?? 0) + 1);
      const headMeta = findMeta(idx, e.head);
      const tailMeta = findMeta(idx, e.tail);
      const rec = {
        type: e.type,
        head: e.head,
        tail: e.tail,
        confidence: e.confidence,
        resolution: e.resolution,
        lang: p.walked.lang,
        headFile: headMeta ? headMeta.file : p.walked.path,
        headLineStart: headMeta ? headMeta.line_start : null,
        headLineEnd: headMeta ? headMeta.line_end : null,
        tailFile: tailMeta ? tailMeta.file : (e.tail.includes('::') ? e.tail.split('::')[0] : null),
        tailLineStart: tailMeta ? tailMeta.line_start : null,
        tailLineEnd: tailMeta ? tailMeta.line_end : null,
        tailKind: tailMeta ? tailMeta.kind : null,
      };
      out.write(JSON.stringify(rec) + '\n');
      if (e.type === 'CALLS') {
        const key = p.walked.lang;
        if (!callsByLangTier.has(key)) callsByLangTier.set(key, new Map());
        const m = callsByLangTier.get(key);
        const tier = tierOf(e.confidence);
        m.set(tier, (m.get(tier) ?? 0) + 1);
      }
    }
  }
  out.end();
  await new Promise((resolve, reject) => { out.on('finish', resolve); out.on('error', reject); });

  const dtSec = ((Date.now() - t0) / 1000).toFixed(1);
  console.error(`dump-edges: ${filesEmitted} files parsed, ${symbolsTotal} symbols, wrote ${outPath} in ${dtSec}s`);
  console.error(`edge types: ${JSON.stringify(Object.fromEntries(byType))}`);
  const langSummary = {};
  for (const [lang, tiers] of callsByLangTier) langSummary[lang] = Object.fromEntries(tiers);
  console.error(`CALLS edges by lang/tier: ${JSON.stringify(langSummary)}`);
  if (parseWarnings.length) {
    console.error(`${parseWarnings.length} parse warnings (first 5):`);
    for (const w of parseWarnings.slice(0, 5)) console.error(`  ${w}`);
  }
}

main().catch((e) => { console.error(e.stack || e.message || e); process.exit(1); });