Skip to content

[Bug]: dependency graph empty for src-layout repositories (module name canonicalization mismatch) #53

Description

@orenlab

Metadata

Field Value
CodeClone version 2.1.0a1 (reproduced on current main)
Affected area Quality metrics / health

What happened?

For repositories using the standard PEP src-layout (src/pkg/... on sys.path), CodeClone builds a dependency graph with zero edges. HTML/CLI show "graph unavailable", fan-in/out 0/0, while per-file metrics (LOC, complexity) still render.

Root cause is a canonicalization mismatch between node names and import targets:

  1. Node / source names come from module_name_from_path (codeclone/scanner/__init__.py), which derives dotted names relative to the repository root, so src/pkg/mod.pysrc.pkg.mod.
  2. Edge targets come from _resolve_import_target (codeclone/analysis/_module_walk.py), which returns Python-resolved module names (e.g. from pkg.b import xpkg.b) — without the src. prefix.
  3. Edge filtering in build_dep_graph_internal_roots / _is_internal_target (codeclone/metrics/dependencies.py) computes internal roots from node names → {src, tests}. Target pkg.b fails because pkg ∉ {src, tests}. All internal edges are dropped.

Flat layout (pkg/mod.py at repo root) works: nodes are pkg.*, roots are {pkg}, edges survive.

What did you expect instead?

  • src/pkg/a.py importing pkg.b should produce nodes pkg.a, pkg.b and edge pkg.a → pkg.b.
  • Same import in flat layout pkg/a.py should produce the identical graph (already works today).
  • Module Map / dependency metrics should reflect real internal imports for src-layout repos.

Minimal reproduction

src-layout (broken):

repo/
  src/
    pkg/
      __init__.py
      a.py      # from pkg import b  OR  from pkg.b import something
      b.py
  pyproject.toml

Observed:

NODE names      : src.pkg.a | src.pkg.b
EDGE target     : pkg.b
internal_roots  : {'src'}
target internal?: False
dep_graph edges : 0

Flat layout (control — works):

repo/
  pkg/
    __init__.py
    a.py
    b.py

Observed:

NODE names      : pkg.a | pkg.b
internal_roots  : {'pkg'}
target internal?: True
dep_graph edges : 1

Same import statement; only the presence of a top-level src/ directory toggles the graph from working to empty.

Proposed fix (single seam)

Fix module_name_from_path to emit dotted names relative to a detected source root, not the repo root.

Preferred detection order:

  1. Auto-detect src/ — strip top-level src/ when it contains packages (zero-config).
  2. [tool.codeclone] source_roots and/or [tool.setuptools] package-dir from pyproject.toml for non-standard layouts.

module_name_from_path call sites: codeclone/core/worker.py, codeclone/metrics/overloaded_modules.py.

Regression test shape

Layout Import Expected nodes Expected edge
src/pkg/a.py import pkg.b pkg.a, pkg.b pkg.a → pkg.b
pkg/a.py (flat) import pkg.b pkg.a, pkg.b pkg.a → pkg.b

Blast radius / migration notes

Area Impact
Clone fingerprints Safe — CFG-based SHA-1; qualname is debug-only. No BASELINE_FINGERPRINT_VERSION change.
qualname / unit identity Shifts for src-layout repos (units.py → dead-code IDs, memory subject paths). Baselines remap under new keys — note in CHANGELOG.
Dependency graph / Module Map Primary fix target.
Overloaded modules table Fixed via shared module_name_from_path.

Additional context

  • Diagnosed read-only: traced scanner/, _module_walk.py, dependencies.py; reproduced with synthetic repos.
  • Not an SVG renderer issue — edges extracted then discarded as "external".
  • No source-root / package-dir concept in scanner/ or config/ today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions