🌳
pt0/serverF/opencodeMcpF/codegraphCliAI.mts
12import { spawnSync } from 'child_process'
14import * as _ from 'lodash-es'
16export const codegraphImporters = async (filePath: string, {importerPath}: {importerPath?: string} = {}) => {
17 resetRegistryCache() // fresh registry: host may have cached it before new eps existed
18 // If specific importer given, explain the chain
19 if (importerPath) return codegraphExplainImport(filePath, importerPath)
20 // Uses grep-first optimization via getPathsThatImport (rg to find candidates, then ptMadge to verify)
21 const { importedByPathsA } = await getPathsThatImport({ importeePath: filePath })
23 // Also check deploy includes
24 const deployIncluders = await getDeployScriptsIncludingFile(filePath)
26 // Also check anchor includes (files linked via ptAnchorInclude/ptAnchorPath/epFromPath)
27 const anchorIncluders = await getAnchorersOfPath(filePath)
29 // Combine ES imports, deploy includes, and anchor includes
30 const resultA = [...importedByPathsA]
31 for (const regEpPath of deployIncluders) {
32 if (!resultA.includes(regEpPath)) {
33 resultA.push(`${regEpPath} (deploy-include)`)
34 }
35 }
36 for (const anchorer of anchorIncluders) {
37 if (!resultA.includes(anchorer)) {
38 resultA.push(`${anchorer} (anchor-include)`)
39 }
40 }
42 return resultA.join('\n')
45export const codegraphHowimports = async (filePath: string, { epDir, srcPath }: {epDir?: string, srcPath?: string} = {}) => {
46 resetRegistryCache() // fresh registry: host may have cached it before new eps existed
47 if (srcPath) {
48 if (isPkgSpecifier(srcPath)) return await codegraphHowimportsPackage(filePath, srcPath, {epDir})
49 return await codegraphExplainImport(filePath, srcPath)
50 }
51 const epA = filterEpsByDir(getAllRegisteredEps(), epDir)
53 if (!epA.length) {
54 return epDir ? `No entrypoints found in ${epDir}/` : 'No entrypoints found in registry'
55 }
57 return await getHowImportsWithDeployIncludes({epA, tgtPath: filePath, useReverseFormat: false})
60export const codegraphExplainImport = async (fileA: string, fileB: string) => {
61 resetRegistryCache() // fresh registry: host may have cached it before new eps existed
62 const madgeH = await ptMadge([fileA, fileB])
63 const traceDir = async (src: string, tgt: string) => {
64 const relevantMadgeH = reduceMadgeRelH({madgeH, relPath: tgt})
65 if (!relevantMadgeH[src] && !Object.values(relevantMadgeH).some(imports => imports.includes(src))) return null
66 const transitiveImporters = new Set(Object.keys(relevantMadgeH))
67 if (!transitiveImporters.has(src)) return null
68 const epPathsA = buildImportTreePaths({madgeH: relevantMadgeH, epA: [src], tgtPath: tgt, transitiveImporters})
69 return epPathsA.length > 0 ? formatImportTree({epPathsA, tgtPath: tgt}) : null
70 }
71 const [fwd, rev] = await allPromCalls([[fileA, fileB], [fileB, fileA]], ([src, tgt]) => traceDir(src, tgt))
72 if (fwd && rev) return `── ${fileA} → ${fileB} ──\n${fwd}\n\n── ${fileB} → ${fileA} ──\n${rev}`
73 if (fwd) return fwd
74 if (rev) return rev
75 return `No import path between ${fileA} and ${fileB}`
78const rgFindPkgImporters = (packageName: string): string[] => {
79 const escapedPkg = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
80 const bt = '`'
81 const pattern = `["'${bt}]${escapedPkg}(?:/|["'${bt}])`
82 const { stdout, status } = spawnSync('rg', ['-l', '--glob', extGlob(ptFileExtA), '-e', pattern, '.'], {
83 encoding: 'utf8', cwd: ptDir, maxBuffer: 10 * 1024 * 1024
84 })
85 if (status === 1) return []
86 if (status !== 0) throw new Error(`rg exited with status ${status}`)
87 return stdout.trim().split('\n').filter(Boolean).map(p => p.replace(/^\.\//, ''))
90export const isPkgSpecifier = (arg: string) =>
91 !arg.startsWith('.') && !arg.startsWith('/') && !isPtCodeFileExt(arg)
93export const codegraphHowimportsPackage = async (filePath: string, packageName: string, {epDir}: {epDir?: string} = {}) => {
94 resetRegistryCache() // fresh registry: host may have cached it before new eps existed
95 const pkgImporterPaths = rgFindPkgImporters(packageName)
96 if (!pkgImporterPaths.length) return `No files import ${packageName}`
98 const epA = filterEpsByDir(getAllRegisteredEps(), epDir)
99 if (!epA.length) return epDir ? `No entrypoints found in ${epDir}/` : 'No entrypoints found in registry'
102 const madgeH = await ptMadge(epA)
104 if (pkgImporterPaths.includes(filePath)) return `${filePath} ← imports ${packageName} (directly)`
106 const filesReachableFromTgt = new Set<string>()
107 const queue = [filePath]
108 while (queue.length) {
109 const cur = queue.shift()!
110 if (filesReachableFromTgt.has(cur)) continue
111 filesReachableFromTgt.add(cur)
112 for (const imp of (madgeH[cur] || [])) queue.push(imp)
113 }
115 const pkgImportersReachable = pkgImporterPaths.filter(p => filesReachableFromTgt.has(p))
116 if (!pkgImportersReachable.length) return `${filePath} does not transitively import ${packageName}`
118 const outputA: string[] = []
119 for (const pkgImporter of pkgImportersReachable) {
120 const relevantMadgeH = reduceMadgeRelH({madgeH, relPath: pkgImporter})
121 const transitiveImporters = new Set(Object.keys(relevantMadgeH))
122 if (!transitiveImporters.has(filePath)) continue
124 const epPathsA = buildImportTreePaths({madgeH: relevantMadgeH, epA: [filePath], tgtPath: pkgImporter, transitiveImporters})
125 if (!epPathsA.length) continue
127 const tree = formatImportTree({epPathsA, tgtPath: pkgImporter})
128 outputA.push(tree + ` ← imports ${packageName}`)
129 }
131 if (!outputA.length) return `${filePath} does not transitively import ${packageName}`
132 return outputA.join('\n')