Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ghostdep πŸ‘»

A fast, cross-language phantom dependency detector.

ghostdep scans your project and finds two things:

  • Phantom dependencies β€” packages you import in code but forgot to add to your manifest
  • Unused dependencies β€” packages declared in your manifest that nothing actually imports

It works across Go, JavaScript/TypeScript, Python, Rust, and Java. Single binary, zero runtime dependencies, built in Rust.

Why does this matter?

Phantom dependencies are a real problem:

  • Your code works locally because some transitive dep happens to provide the package
  • CI breaks, or worse β€” production breaks when that transitive dep gets removed
  • Supply chain attacks can exploit undeclared dependencies

Unused dependencies are less dangerous but still annoying:

  • Bloated install times
  • Larger container images
  • Confusing dependency lists for new contributors

ghostdep catches both in milliseconds.

How it works

source files ──→ AST parser ──→ import list ──┐
                                               β”œβ”€β”€β†’ matching engine ──→ findings
manifest file ──→ manifest parser ──→ dep list β”˜
  1. Walks your project directory
  2. Parses source files using tree-sitter (Go, Python, Rust, Java) or OXC (JS/TS) to extract imports
  3. Parses your manifest file to get declared dependencies
  4. Cross-references the two lists
  5. Reports what’s missing and what’s unused

Quick example

$ ghostdep -p my-project

[phantom] axios at src/api.js:3 (confidence: high)
[unused] lodash at package.json (confidence: high)

Found 1 phantom and 1 unused dependencies (12 files scanned in 3ms)

Installation

Download the latest binary for your platform:

curl -fsSL https://raw.githubusercontent.com/ojuschugh1/ghostdep/main/install.sh | sh

This detects your OS and architecture, downloads the right binary, and installs it to /usr/local/bin.

From source

If you have Rust installed:

cargo install --path .

Or clone and build:

git clone https://github.com/ojuschugh1/ghostdep
cd ghostdep
cargo build --release
# binary is at ./target/release/ghostdep

Supported platforms

OSArchitectureBinary
Linuxx86_64ghostdep-linux-amd64
Linuxaarch64ghostdep-linux-arm64
macOSx86_64ghostdep-darwin-amd64
macOSApple Siliconghostdep-darwin-arm64
Windowsx86_64ghostdep-windows-amd64.exe

Verify installation

ghostdep --version

Quick Start

Scan your project

Just run ghostdep in your project directory:

ghostdep

Or point it at a specific path:

ghostdep -p /path/to/project

ghostdep auto-detects the language and manifest file. No configuration needed.

Read the output

[phantom] axios at src/api.js:3 (confidence: high)
[unused] lodash at package.json (confidence: high)

Found 1 phantom and 1 unused dependencies (12 files scanned in 3ms)
  • [phantom] = imported in code but not in your manifest
  • [unused] = in your manifest but never imported
  • The file and line number tell you where the import is
  • Confidence tells you how sure ghostdep is (see Confidence Scoring)

Exit codes

CodeMeaning
0No findings β€” your deps are clean
1Findings present
2Error (no manifest found, parse error, etc.)

Generate a config file

ghostdep init

Creates a .ghostdep.yaml with sensible defaults. See Configuration for details.

Fix your deps

Preview the fix commands:

ghostdep fix --dry-run

Or let ghostdep run them (with confirmation):

ghostdep fix --apply

See Auto-Fix for details.

Scanning Projects

Basic scan

ghostdep

Scans the current directory. ghostdep walks the file tree, finds manifest files, and scans source files for imports.

Scan a specific directory

ghostdep -p /path/to/project

What gets scanned

ghostdep uses the ignore crate for file walking, which respects .gitignore rules. It also skips:

  • node_modules/
  • vendor/
  • target/ (Rust build output)
  • build/
  • dist/
  • __pycache__/
  • .git/
  • Binary files

Restrict to a language

ghostdep -l python

Only scans Python files and Python manifests.

Specify a manifest

ghostdep -m path/to/package.json

Useful when your manifest isn’t in the project root.

Exclude paths

ghostdep --exclude-path "generated/**" --exclude-path "scripts/**"

Ignore specific dependencies

ghostdep -i "internal-*" -i "my-shared-lib"

Glob patterns. Matched deps won’t appear in findings.

Include/exclude dev dependencies

Dev dependencies are included by default. To exclude them:

ghostdep --dev

Parallel scanning

ghostdep scans files in parallel using Rayon. By default it uses all available CPU cores. To limit:

ghostdep --threads 4

Verbose mode

ghostdep -v

Prints progress info to stderr: number of project scopes detected, files scanned, errors skipped.

Quiet mode

ghostdep -q

Suppresses all output. Only the exit code tells you the result. Useful in CI when you just want pass/fail.

Output Formats

ghostdep supports three output formats: text (default), JSON, and SARIF.

Text (default)

ghostdep
[phantom] axios at src/api.js:3 (confidence: high)
[unused] lodash at package.json (confidence: high)

Found 1 phantom and 1 unused dependencies (12 files scanned in 3ms)

Human-readable. Each line shows the finding type, package name, location, and confidence. Summary at the end.

JSON

ghostdep -f json
{
  "findings": [
    {
      "finding_type": "Phantom",
      "package": "axios",
      "file": "src/api.js",
      "line": 3,
      "manifest": "package.json",
      "language": "JavaScript",
      "confidence": "High"
    }
  ],
  "metadata": {
    "project_root": "/path/to/project",
    "scanned_files": 12,
    "duration_ms": 3,
    "ghostdep_version": "0.1.0",
    "languages": ["JavaScript"]
  }
}

Every finding includes: finding_type, package, file, line, manifest, language, confidence. The metadata object has scan stats.

SARIF

ghostdep -f sarif

Produces a valid SARIF v2.1.0 document. SARIF is the standard format for static analysis tools and integrates with GitHub Code Scanning.

Rules:

  • GHOST001 β€” Phantom dependency (level: error)
  • GHOST002 β€” Unused dependency (level: warning)

Each result includes locations, confidence in properties, and the manifest path.

See CI Integration for how to upload SARIF to GitHub.

Configuration

Config file

Create a .ghostdep.yaml in your project root:

ghostdep init

Or write one manually:

ignore_deps:
  - "internal-*"
  - "my-shared-lib"
ignore_paths:
  - "scripts/**"
  - "generated/**"
include_dev: true
min_confidence: low
format: text
cache: false

Config options

OptionTypeDefaultDescription
ignore_depslist of globs[]Dependencies to exclude from findings
ignore_pathslist of globs[]File paths to skip during scanning
include_devbooltrueWhether to analyze dev dependencies
min_confidencelow/medium/highlowMinimum confidence threshold
formattext/json/sariftextOutput format
threadsnumber or nullnull (all cores)Max scanner threads
cacheboolfalseEnable incremental scan cache

Precedence

Configuration is resolved in three layers:

  1. Defaults β€” hardcoded sensible values
  2. Config file β€” .ghostdep.yaml overrides defaults
  3. CLI flags β€” always win over config file

ignore_deps and ignore_paths are additive across all layers. Everything else is last-writer-wins.

Example: suppress known false positives

ignore_deps:
  - "internal-*"       # internal packages in your monorepo
  - "my-codegen-lib"   # generated code imports this

Example: CI-optimized config

format: sarif
min_confidence: medium
include_dev: false
cache: true
ignore_paths:
  - "test/**"
  - "scripts/**"

Auto-Fix

ghostdep can generate package manager commands to fix detected findings.

Dry run (default)

Preview what would be run:

ghostdep fix --dry-run
npm install axios
npm uninstall lodash
pip install pandas
pip uninstall numpy

Nothing is executed. Commands are printed to stdout.

Apply fixes

Run the commands directly:

ghostdep fix --apply

ghostdep will ask for confirmation before executing anything:

Execute 4 command(s)? [y/N]

Each command’s result is reported:

running: npm install axios
  ok
running: npm uninstall lodash
  ok

Commands by language

ManifestPhantom (install)Unused (remove)
package.jsonnpm install <pkg>npm uninstall <pkg>
package.json + yarn.lockyarn add <pkg>yarn remove <pkg>
requirements.txtpip install <pkg>pip uninstall <pkg>
go.modgo get <module>go mod tidy
Cargo.tomlcargo add <crate>cargo remove <crate>
pom.xmlprints XML snippetprints removal note
build.gradleprints dependency lineprints removal note

ghostdep detects yarn.lock to choose yarn over npm automatically.

For Maven and Gradle, there’s no clean CLI command to add/remove deps, so ghostdep prints the snippets you need to paste manually.

Confidence Scoring

Not all imports are equally reliable. ghostdep assigns a confidence level to each finding based on how the import was detected.

Levels

ConfidenceWhat it meansExamples
highStatic import at top levelimport x, use x, require("x"), from x import y
mediumConditional or dynamic with string literaltry: import x, import("lodash")
lowDynamic with variable/expressionrequire(expr), importlib.import_module(var), import(getModule())

Language-specific behavior

  • Go β€” all imports are high confidence (Go has no dynamic imports)
  • Rust β€” all imports are high confidence (no dynamic imports)
  • Java β€” all imports are high confidence (Class.forName() not detected in v1)
  • JavaScript/TypeScript β€” mixed: ESM imports and require("string") are high, import("string") is medium, require(variable) is low
  • Python β€” mixed: import x and from x import y are high, imports inside try/except are medium, __import__() and importlib.import_module() are low

Filtering

Show only high-confidence findings:

ghostdep --min-confidence high

Show medium and above:

ghostdep --min-confidence medium

Default is low (show everything).

In config

min_confidence: medium

In output

Text format shows confidence inline:

[phantom] axios at src/api.js:3 (confidence: high)

JSON and SARIF include it as a field/property on each finding.

Monorepo Support

ghostdep automatically detects monorepos and handles cross-project imports correctly.

How it works

When ghostdep finds multiple manifest files in nested directories, it treats each one as a separate project scope. It builds an internal package index from the directory names and excludes cross-project imports from phantom findings.

my-monorepo/
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ package.json    ← scope 1
β”‚   β”‚   └── src/index.js
β”‚   β”œβ”€β”€ web/
β”‚   β”‚   β”œβ”€β”€ package.json    ← scope 2
β”‚   β”‚   └── src/app.js
β”‚   └── shared/
β”‚       β”œβ”€β”€ package.json    ← scope 3
β”‚       └── src/utils.js

If api/src/index.js imports @myorg/shared, ghostdep recognizes shared as an internal package and won’t flag it as phantom.

Scoping

Each project scope is analyzed independently:

  • Imports in api/ are matched against api/package.json
  • Imports in web/ are matched against web/package.json
  • Cross-project imports are excluded from phantom results

Mixed languages

Monorepos with different languages work fine:

my-monorepo/
β”œβ”€β”€ services/
β”‚   └── api/
β”‚       β”œβ”€β”€ go.mod
β”‚       └── main.go
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ package.json
β”‚   └── src/app.tsx

Each sub-project uses its own language plugin.

Limitations

  • Internal package detection uses directory names, not the name field from manifests (planned improvement)
  • Deeply nested workspaces (workspace-of-workspaces) aren’t specially handled β€” each manifest is a flat scope

CI Integration

ghostdep is designed for CI. It’s a single binary with deterministic output and meaningful exit codes.

Exit codes

CodeMeaning
0Clean β€” no findings
1Findings present
2Error

GitHub Actions

Basic check

- name: Install ghostdep
  run: curl -fsSL https://raw.githubusercontent.com/ojuschugh1/ghostdep/main/install.sh | sh

- name: Check dependencies
  run: ghostdep

With SARIF upload (GitHub Code Scanning)

- name: Install ghostdep
  run: curl -fsSL https://raw.githubusercontent.com/ojuschugh1/ghostdep/main/install.sh | sh

- name: Run ghostdep
  run: ghostdep -f sarif > ghostdep.sarif
  continue-on-error: true

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: ghostdep.sarif

This shows findings as annotations directly in pull requests.

JSON output for custom processing

- name: Run ghostdep
  run: |
    ghostdep -f json > ghostdep.json
    cat ghostdep.json | jq '.findings | length'

GitLab CI

ghostdep:
  stage: lint
  script:
    - curl -fsSL https://raw.githubusercontent.com/ojuschugh1/ghostdep/main/install.sh | sh
    - ghostdep

Pre-commit hook

#!/bin/sh
ghostdep -q

Save as .git/hooks/pre-commit and chmod +x it. The -q flag suppresses output β€” it’ll just block the commit if findings are present.

Tips

  • Use --cache for faster repeat scans in CI (cache the .ghostdep-cache/ directory between runs)
  • Use --min-confidence medium to reduce noise from dynamic imports
  • Use -q when you only care about pass/fail

Go

Manifest

go.mod β€” parses require directives (both single-line and block form). Version suffixes are stripped.

Import scanning

Uses tree-sitter with the Go grammar. Extracts from import_declaration / import_spec nodes. Handles single imports, grouped imports, aliased imports, and blank imports (_ "pkg").

All Go imports are static β€” confidence is always high.

Normalization

  • github.com/foo/bar/baz β†’ github.com/foo/bar (first 3 segments for domain-style paths)
  • golang.org/x/sync β†’ golang.org/x/sync
  • fmt β†’ fmt (stdlib, filtered out)

Stdlib detection

~150 known Go stdlib packages compiled into the binary. Includes nested paths like net/http, encoding/json, etc.

Aliases

None β€” Go packages use their module path directly.

JavaScript / TypeScript

Manifest

package.json β€” extracts dependency names from dependencies, devDependencies, peerDependencies, and optionalDependencies.

Import scanning

Uses the OXC parser (Rust-native, faster than tree-sitter for JS/TS). Handles:

PatternConfidence
import x from 'pkg'high
const x = require('pkg')high
export { x } from 'pkg'high
export * from 'pkg'high
import('pkg') with string literalmedium
require(variable)low
import(expression)low

File extensions: .js, .jsx, .ts, .tsx, .mjs, .cjs, .mts, .cts

Normalization

  • Relative imports (./, ../, /) are skipped
  • Scoped: @scope/pkg/deep/path β†’ @scope/pkg
  • Unscoped: lodash/merge β†’ lodash
  • node:fs β†’ fs (then filtered as stdlib)

Stdlib detection

All Node.js built-in modules including the node: prefix. Covers fs, path, http, crypto, stream, child_process, etc.

Aliases

Minimal β€” most JS packages use their npm name directly.

Python

Manifests

ghostdep reads dependencies from multiple Python manifest formats:

FormatSectionStatus
requirements.txtline-by-line package namesstable
pyproject.toml[project].dependencies (PEP 621)stable
pyproject.toml[project].optional-dependenciesstable
pyproject.toml[tool.poetry.dependencies]stable
pyproject.toml[tool.poetry.dev-dependencies]stable
pyproject.toml[tool.poetry.group.*.dependencies]stable
pyproject.toml[dependency-groups] (PEP 735 / uv)stable

requirements.txt

Parses line-by-line. Skips blanks, comments (#), and option lines (-r, -e, --). Strips version specifiers, extras ([extra]), and environment markers (;).

pyproject.toml

Handles PEP 621 ([project]), Poetry ([tool.poetry]), and PEP 735 ([dependency-groups]) in a single pass. Skips python = "^3.x" in Poetry deps and {include-group = "..."} directives in dependency-groups.

Package name normalization

Follows PEP 503: lowercase, replace -, _, . with a single _. So scikit-learn, Scikit_Learn, and scikit.learn all normalize to scikit_learn.

Import scanning

Uses tree-sitter with the Python grammar.

PatternConfidence
import xhigh
from x.y import zhigh
import x inside try/exceptmedium
__import__("x")low
importlib.import_module("x")low

Normalization

Takes the first segment of the dotted path:

  • from PIL.Image import open β†’ PIL
  • import os.path β†’ os

Stdlib detection

~300 Python 3.10+ stdlib modules. Includes common sub-packages like collections.abc, concurrent.futures, urllib.parse, etc.

Aliases

Python has a lot of packages where the import name differs from the pip name:

ImportPackage
PILPillow
cv2opencv-python
sklearnscikit-learn
yamlPyYAML
bs4beautifulsoup4
attrattrs
dateutilpython-dateutil
dotenvpython-dotenv
serialpyserial
Cryptopycryptodome
gitGitPython
google.protobufprotobuf

And more β€” see the source for the full list.

Rust

Manifest

Cargo.toml β€” extracts crate names from [dependencies], [dev-dependencies], and [build-dependencies]. Handles both crate = "version" and crate = { version = "...", package = "..." } forms.

When package = "actual-name" is present, the actual name is used for matching.

Import scanning

Uses tree-sitter with the Rust grammar. Extracts from:

  • use serde::Deserialize β†’ serde
  • extern crate serde β†’ serde
  • serde_json::from_str!(...) β†’ serde_json (macro invocations with :: path)

All Rust imports are static β€” confidence is always high.

Normalization

Takes the first path segment and replaces _ with - for Cargo matching:

  • use tokio::runtime::Runtime β†’ tokio
  • use serde_json::Value β†’ serde-json

Stdlib detection

std, core, alloc, proc_macro, test, plus the relative paths self, super, crate.

Aliases

Built dynamically from Cargo.toml. If you have:

[dependencies]
my_serde = { version = "1", package = "serde" }

Then use my_serde::Deserialize correctly resolves to the serde crate.

Java (beta)

Java support is in beta. The import-to-artifact mapping is inherently heuristic β€” there’s no standard 1:1 mapping between Java import paths and Maven artifact IDs. ghostdep covers ~150 common libraries and uses groupId matching as a fallback, but may miss niche libraries.

Manifests

pom.xml

Parses <dependency> elements. Extracts <groupId>, <artifactId>, and <scope>. Maps scopes to dependency groups:

Maven scopeghostdep group
compile (default)main
testdev
providedoptional
runtimemain
systemoptional

build.gradle / build.gradle.kts

Regex-based extraction. Matches Groovy ('group:artifact:version') and Kotlin DSL (("group:artifact:version")) syntax. Supported configurations:

Gradle configghostdep group
implementation, api, runtimeOnlymain
testImplementation, testCompileOnly, testRuntimeOnlydev
compileOnlyoptional
annotationProcessor, kaptbuild

Import scanning

Uses tree-sitter with the Java grammar. Extracts from import_declaration nodes, including static imports and wildcard imports (java.util.* β†’ java.util).

All Java imports are static β€” confidence is always high.

Normalization

Two-stage process:

  1. Alias table lookup β€” tries progressively shorter prefixes of the import path against a built-in table of ~150 common libraries. Example: org.apache.commons.lang3.StringUtils β†’ tries org.apache.commons.lang3 β†’ finds commons-lang3.

  2. groupId matching β€” if no alias matches, falls back to the first 3 segments as a groupId-style prefix (e.g., com.example.mylib). The matcher then checks if any declared dependency’s groupId starts with this prefix.

Stdlib detection

Any import starting with java., javax., or jdk. is standard library.

Aliases

~150 built-in mappings covering:

  • Google: gson, guava, protobuf, guice
  • Apache Commons: lang3, io, collections4, codec, text, csv, compress, etc.
  • Jackson: core, databind, annotations, dataformat, datatype
  • Spring: boot, web, data, security, context, beans, core, test, jdbc, batch, kafka, cloud
  • Testing: junit-jupiter, mockito, assertj, hamcrest, testng, testcontainers, cucumber
  • Logging: slf4j, logback, log4j
  • Database: HikariCP, mybatis, jooq, flyway, liquibase, jedis, lettuce, mongodb, elasticsearch
  • AWS: aws-sdk-java, aws-java-sdk-core, s3, sqs, dynamodb
  • And many more

PRs to expand the mapping table are very welcome.

WASM Plugins (Experimental)

ghostdep can load community-contributed language plugins compiled as WebAssembly modules.

Status: The WASM host is functional but the plugin API is not yet formally documented. If you’re interested in writing a plugin, the information below should get you started. Expect the API to stabilize in a future release.

Plugin discovery

ghostdep looks for .wasm files in two locations:

  1. plugins/ in your project root
  2. ~/.ghostdep/plugins/

Any .wasm file found is loaded automatically. If loading fails, a warning is printed and the plugin is skipped.

Expected exports

Your WASM module must export these functions:

FunctionSignatureReturns
alloc(len: i32) -> i32Pointer to allocated memory
plugin_name() -> i64Packed ptr:len string
file_extensions() -> i64Comma-separated list
manifest_filenames() -> i64Comma-separated list
parse_manifest(ptr: i32, len: i32) -> i64JSON response
scan_imports(ptr: i32, len: i32) -> i64JSON response
is_stdlib(ptr: i32, len: i32) -> i320 or 1
normalize_import(ptr: i32, len: i32) -> i64Packed ptr:len string
aliases() -> i64JSON object

String passing convention

Strings are passed through WASM linear memory. The host writes input strings using alloc, and reads output strings from packed i64 values where the high 32 bits are the pointer and the low 32 bits are the length.

JSON formats

parse_manifest receives:

{"content": "...", "path": "..."}

And returns an array of:

[{"name": "pkg", "normalized": "pkg", "group": "main"}]

scan_imports receives the same input and returns:

[{"raw": "pkg", "normalized": "pkg", "line": 1, "confidence": "high"}]

aliases returns:

{"import_name": "package_name"}

Error handling

  • If a plugin fails to load, it’s skipped with a warning
  • If a plugin panics during a function call, the file is skipped
  • Plugin errors never crash the main process

Source reference

See src/plugin/wasm_host.rs for the full implementation.

Caching

ghostdep supports incremental scanning β€” it caches parsed imports so unchanged files don’t need to be re-parsed.

Enable caching

ghostdep --cache

Or in .ghostdep.yaml:

cache: true

How it works

  1. On scan start, loads the cache from .ghostdep-cache/scan.json
  2. For each source file, computes a blake3 hash of the content
  3. If the hash matches the cached entry, reuses the cached imports (skips AST parsing)
  4. If the hash differs or the file is new, parses it and updates the cache
  5. After the scan, writes the updated cache to disk
  6. Entries for deleted files are pruned automatically

Cache location

.ghostdep-cache/
└── scan.json

In your project root. Add .ghostdep-cache/ to your .gitignore.

Cache invalidation

The cache includes a version number. When ghostdep’s import extraction logic changes between releases, the cache is automatically invalidated and rebuilt.

CI usage

Cache the .ghostdep-cache/ directory between CI runs for faster repeat scans:

# GitHub Actions
- uses: actions/cache@v4
  with:
    path: .ghostdep-cache
    key: ghostdep-${{ hashFiles('**/*.rs', '**/*.py', '**/*.js', '**/*.go', '**/*.java') }}
    restore-keys: ghostdep-

- name: Scan
  run: ghostdep --cache

Thread safety

The cache uses RwLock internally, so it’s safe to use with parallel scanning (--threads).

Contributing

We’re actively looking for contributors. All skill levels welcome.

Getting started

git clone https://github.com/ojuschugh1/ghostdep
cd ghostdep
cargo test

If all tests pass, you’re ready to go.

Ideas

  • Add support for new languages (Ruby, PHP, C#, Swift, Kotlin…)
  • Expand the Java import-to-artifact mapping table
  • Improve monorepo package name extraction (read name from manifests)
  • Write a WASM plugin for a language you use
  • Document the WASM plugin API with an example
  • Add setup.cfg / setup.py support for Python
  • Improve the fix command for Maven/Gradle (direct manifest editing)
  • Write more property-based tests
  • Performance benchmarks on large codebases

Tests

Run the full suite:

cargo test

The test suite includes:

  • Unit tests (inline in each module)
  • Integration tests (invoke the binary against fixture projects in tests/fixtures/)
  • Property-based tests (proptest, in tests/property/)

Add tests for any new functionality. Property tests are preferred for correctness properties.

Pull requests

  • Keep PRs focused β€” one feature or fix per PR
  • Include tests
  • Make sure cargo test passes
  • Brief description of what and why

License

By contributing, you agree that your contributions will be licensed under the MIT License.

Roadmap

v1 (current) β€” shipped

  • Go, JS/TS, Python, Rust, Java language support
  • AST-based import scanning with confidence scoring
  • Text, JSON, SARIF output
  • Fix command generation (dry-run and apply)
  • Monorepo support
  • Incremental caching
  • WASM plugin host (experimental)
  • Poetry and uv/PEP 735 support for Python
  • GitHub Actions release workflow
  • ~150 Java import-to-artifact mappings with groupId fallback

v1.x β€” next up

  • WASM plugin API docs + example plugin
  • Homebrew tap
  • crates.io publish
  • Better Java import-to-artifact mapping
  • Direct manifest editing for Maven/Gradle fix command
  • setup.cfg / setup.py support for Python
  • Read name field from manifests for monorepo detection

v2 β€” planned

  • Transitive dependency analysis β€” detect deps that only work because of transitive availability
  • AI hallucination detection β€” flag phantom deps that don’t exist in any public registry (npm, PyPI, crates.io, Maven Central)
  • LSP integration β€” real-time warnings in your editor
  • Lockfile reconciliation β€” cross-reference lockfiles against manifests

Changelog

0.1.0 (unreleased)

Initial release.

  • Go, JavaScript/TypeScript, Python, Rust, Java support
  • AST-based import scanning (tree-sitter + OXC)
  • Confidence scoring (high/medium/low)
  • Text, JSON, SARIF output formats
  • scan, init, fix subcommands
  • .ghostdep.yaml configuration with three-layer merging
  • Monorepo support with internal package detection
  • Incremental caching with blake3 hashing
  • WASM plugin host (experimental)
  • Poetry and uv/PEP 735 pyproject.toml support
  • ~150 Java import-to-artifact mappings
  • GitHub Actions release workflow
  • Cross-platform binaries (Linux, macOS, Windows)