Skip to main content
Glama

importfix

Circular imports are easy to create and miserable to unwind by eye: the compiler names a file, not the loop. importfix takes a module graph — each module's path and the paths it imports — and works out where the loops actually are.

For every cyclic component it reports exactly one representative cycle: the exact nodes in traversal order and the edges between them, closing back to the start. It does not enumerate every elementary cycle; one exact, reproducible cycle per component is the contract. Each cycle also carries a suggested break point — the cycle edge to remove first, chosen because its target has the fewest dependents in the whole graph, so breaking it disturbs the least-imported module on the loop.

The graph rules are strict about what counts:

  • An import only becomes an edge when it names another module in the same input. Imports of anything else are external: counted in the stats, never edged.

  • Duplicate imports collapse into one edge.

  • A module importing itself is a cycle of one, reported like any other.

  • The representative cycle is deterministic: it starts at the component's lexicographically smallest module and follows neighbours in lexicographic order, so the same graph always yields the same answer.

Pure local computation: no network, no filesystem reads of your source tree — the graph is supplied by the caller.

CLI

npx @reneturcios/importfix cycles --modules modules.json
npx @reneturcios/importfix --version

modules.json contains the modules array:

[
  {"path": "src/a.ts", "imports": ["src/b.ts"]},
  {"path": "src/b.ts", "imports": ["src/a.ts", "node:fs"]}
]

The command prints the report as JSON:

{
  "has_cycles": true,
  "cycles": [
    {
      "nodes": ["src/a.ts", "src/b.ts"],
      "edges": [
        {"from": "src/a.ts", "to": "src/b.ts"},
        {"from": "src/b.ts", "to": "src/a.ts"}
      ],
      "suggested_break": {
        "from": "src/a.ts",
        "to": "src/b.ts",
        "reason": "target has the fewest dependents in the graph (1)"
      }
    }
  ],
  "stats": {
    "module_count": 2,
    "edge_count": 2,
    "external_imports": 1,
    "cyclic_module_count": 2
  }
}

Exit codes: 0 on success, 1 on a runtime error (including an unreadable or invalid modules file), 2 on a usage error.

Related MCP server: code-graph-mcp

MCP server

npx -y @reneturcios/importfix mcp

MCP client config:

{
  "mcpServers": {
    "importfix": {
      "command": "npx",
      "args": ["-y", "@reneturcios/importfix", "mcp"]
    }
  }
}

Tools

  • find_import_cycles — Detect circular imports in a module graph and report one representative cycle per cyclic component, with a suggested break point.

    • modules (array, required, at least one item) — modules as {path, imports} objects; path is the module's own path and imports lists the paths it imports (may be empty)

Docker

docker run -i ghcr.io/cloudcorprecords/importfix
docker run --rm -v "$PWD:/w" ghcr.io/cloudcorprecords/importfix cycles --modules /w/modules.json

Development

npm install
npm run build   # tsc -> dist/
npm test        # tsx --test test/*.test.ts (pure local, no network)
npm run dev     # run the CLI from source

License

MIT — Copyright (c) 2026 Rene Turcios

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Indexes a mono-repo into a knowledge graph and provides MCP tools to query code structure—packages, components, routes, HTTP calls—without file reads or grep round-trips.
    7
    22 npm
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables querying cross-repo code dependencies, HTTP routes, database tables, and queues via an MCP server using Cypher queries.
    -