What it does

PhantomDep scans a repository and reports four classes of dependency defect that mainstream scanners ignore:

  • Phantom — declared in a manifest, imported by zero source files.
  • Undeclared — imported by source code but declared nowhere; it resolves only because a parent hoisted it into node_modules.
  • Cycle — a strongly connected component of size >= 2 in the lockfile graph.
  • Tombstone — declared with *, latest, or no version at all.

How it is built

Four languages, each doing the job it is genuinely good at, and zero third-party dependencies in any of them — a reviewer clones the repository and runs one script.

Stage Language Responsibility
Orchestration Go manifest parsing, import extraction, HTTP API
Graph analysis Rust Tarjan SCC, depth, ghost scoring
Advisory intel Python licences and advisories, optional OSV.dev
Presentation vanilla JS/SVG layered dependency graph, no build step

The split is functional rather than decorative: extraction is IO-bound and string-heavy, so Go's filepath.WalkDir plus encoding/json covers it; analysis is pointer-chasing, so Rust keeps the adjacency lists allocation-light; advisory payloads are nested and irregular, so Python handles them in a few dozen lines. JSON is used only at the HTTP boundaries — the Go to Rust hop is a versioned line protocol, because a dependency-free JSON parser in Rust would be 200 lines of avoidable risk.

What is verified

Three fixtures, each documenting the exact defects it contains, so a reviewer can check the claim instead of trusting it. scripts/verify.py runs the real CLI and fails the build if any documented count drifts:

Fixture Phantom Undeclared Cycles
haunted-node 5 1 1
go-crypt 2 0 0
py-ritual 3 2 0

The Rust analyser carries its own unit tests for cycle detection, protocol validation and score bounds.

Known limitations

Stated rather than hidden, because a scanner that overstates its confidence is worse than no scanner:

  1. Python distribution names and import names diverge (pyyaml vs yaml), so both verdicts are mechanically correct and useless to a human. This is left failing on purpose as a regression case, not papered over with a hardcoded alias table.
  2. Dynamic imports (require(variable), import(computed)) are invisible.
  3. The ghost score is a heuristic, always rendered beside the raw counts that produced it.
  4. Cycle detection is npm-only today, because Go and Python have no lockfile edges.

Running it

./scripts/build.sh    # builds Rust + Go, runs tests, verifies all fixtures
./scripts/dev.sh      # starts the Python intel service and the UI on :8787

Requires Go 1.21+, Rust 1.70+, Python 3.9+. No npm install anywhere.

Built With

Share this project:

Updates

Submission history