All files / src run-import-graph.ts

39.68% Statements 25/63
71.42% Branches 10/14
80% Functions 8/10
39.68% Lines 25/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 641x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                              
import { execFileSync } from "node:child_process"
import * as fs from "node:fs"
import * as path from "node:path"
 
import type { GraphModel } from "./domain/graph-model.js"
import { buildFileTypeLegend, enrichImportGraphNode } from "./domain/graph-file-type.js"
import { kitRootFromPackageRoot, resolvePythonCommand } from "./load-from-sqlite.js"
 
export type ImportGraphResult =
  | { ok: true; model: GraphModel; metaLine: string; fileTypeLegend: ReturnType<typeof buildFileTypeLegend> }
  | { ok: false; error: string }
 
type ImportGraphJson = {
  nodes: Array<{ id: string; label: string }>
  edges: Array<{ from: string; to: string }>
  meta?: { nodeCount?: number; edgeCount?: number; root?: string }
}
 
export function resolveImportGraphScript(packageRoot: string): string {
  /** Active project folder (`.meridian/`); kit scripts may live at a monorepo ancestor. */
  const kitRoot = kitRootFromPackageRoot(packageRoot)
  return path.join(kitRoot, ".agent", "scripts", "meridian_import_graph.py")
}
 
export function runImportGraph(packageRoot: string, scopeRoot: string): ImportGraphResult {
  const script = resolveImportGraphScript(packageRoot)
  if (!fs.existsSync(script)) {
    return { ok: false, error: `Import graph script not found: ${script}` }
  }
  const scope = path.resolve(scopeRoot)
  const workspace = path.resolve(packageRoot)
  try {
    scope.startsWith(workspace)
  } catch {
    /* ignore */
  }
  if (!scope.startsWith(workspace + path.sep) && scope !== workspace) {
    return { ok: false, error: `Scope must be inside workspace: ${workspace}` }
  }
  try {
    const python = resolvePythonCommand()
    const stdout = execFileSync(
      python,
      [script, "--root", scope, "--workspace", workspace, "--format", "json"],
      { encoding: "utf-8", maxBuffer: 20 * 1024 * 1024 },
    )
    const parsed = JSON.parse(stdout) as ImportGraphJson
    const model: GraphModel = {
      nodes: (parsed.nodes ?? []).map((n) => enrichImportGraphNode(n)),
      edges: (parsed.edges ?? []).map((e) => ({ from: e.from, to: e.to })),
    }
    const meta = parsed.meta ?? {}
    return {
      ok: true,
      model,
      fileTypeLegend: buildFileTypeLegend(model),
      metaLine: `${meta.nodeCount ?? model.nodes.length} files ยท ${meta.edgeCount ?? model.edges.length} imports`,
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    return { ok: false, error: `Import graph CLI failed: ${message}` }
  }
}