Changelog

[1.3.0] — 2026-10-20

Added

  • test/sample_package/ — a checked-in fixture project the whole suite runs against. It holds the curve balls on purpose: an import cycle, a file with a syntax error, a file with non-UTF-8 bytes, an empty file, a filename with a space, a filename with an accent, @property/@setter/@deleter sharing one name, @typing.overload stubs sharing one name, relative, parent-relative and absolute imports, a subpackage, a module-scope entry point, and a callable referenced without being called. A new edge case is a new file in that directory, not a new test
  • Tests that walk analyze()’s own stages in order, so a failure names the stage that broke instead of “the output changed”. The expected module set is derived from the files on disk, so adding a fixture file needs no test change
  • Tests for the git history the fixture cannot provide, since it lives inside this repository. Each one builds its own repository and pins a previously-broken behaviour: author ordering, paths with spaces, an analyzed root below the repository root, a repository without commits, a directory outside any repository, and a missing git executable
  • make run-sample — analyze the fixture and print the JSON to stdout, warnings included, as a one-command smoke test
  • The publish workflow now runs lint, format check and the test suite before building. Forgejo has no cross-workflow needs:, so tests and publishing were racing and a red commit could reach PyPI

Changed

  • Git statistics come from a single git log --numstat over the whole repository instead of two subprocesses per module. On a 2 933-module project: 51.51 s to 1.32 s, a 39x speedup, and 5 866 subprocess calls become one
  • The git log invocation is hardened against a user’s own git configuration, so its output does not change shape depending on who runs it
  • The analysis is now reproducible: the same project analyzed twice serializes identically, which makes the JSON diffable between commits
  • Documentation restructured. Software Design is now Design: six phases, each documenting the strings that go in and the strings that come out, with a clickable package index. Roadmap is now Constraints, holding the rules the tool keeps on purpose next to the gaps it has not closed. This page’s landing page now carries the full CLI and API reference instead of two example lines

Fixed

  • A malformed file no longer aborts the run. The file is named in a warning and skipped, and the analysis returns an answer for everything else. Covers syntax errors, non-UTF-8 bytes, NUL bytes and files that cannot be read
  • last_author reported the oldest author of a file. git log is newest-first, and the field was being reassigned on every row after the first
  • Paths containing a space were dropped from git statistics entirely — --numstat is tab-delimited, and splitting on whitespace turned src/pkg/my mod.py into four fields instead of three
  • Git statistics were silently empty whenever the analyzed directory was not the repository root, because git reports repository-root-relative paths however deep the analysis starts
  • Rendered output was not reproducible between runs: set iteration order leaked into package order, module order, edge lists, callable lists and cycles. Sorting dictionary keys hid none of it, and cycles needed more than sorting — a cycle is reported from wherever the traversal entered it, so each is rotated to start at its smallest node

Known limitations

  • Analyzing a package directory instead of its parent silently produces a near-empty graph. Measured on a 2 933-module project: 251 module edges from the wrong root versus 3 146 from the correct one, with no warning either way
  • Dead-callable detection is unreliable on libraries. On a large one, 47% of callables came back flagged: a third were dunders invoked by the interpreter, and most of the rest were public API with no internal callers
  • from pkg import module, where the imported name is itself a module, loses the edge and degrades to the package node. A large project can hold hundreds of imports in this shape
  • Every __init__.py reports zero fan-in, DependaMan’s own included
  • A file named .py that is actually binary gets no git history
  • Each module is read and parsed twice per analysis — once to collect definitions, once to resolve references against them
  • analyze() is public API but is not exported from the package root; the import is from dependaman.core import analyze

[1.2.0] — 2026-10-18

Added

  • A real command line interface — --help, --version, -f/--format, -o/--output-path and --no-browser. Before this, the first argument was read directly as a path, so dependaman --help tried to analyse a directory called --help
  • --format json — the complete analysis as JSON, for piping into other tools, CI checks, or anything that wants the data rather than the graph: dependaman --format json -o - | jq '.cycles'
  • -o - writes to stdout; any other value is used as the output file exactly as given
  • analyze(path) — the analysis on its own, returning a dictionary, without rendering, writing a file or opening a browser. Useful for serving the graph from a web handler or gating CI on the result
  • Tests for the command line interface and one regression test for every previously fixed bug
  • Continuous integration now lints, checks formatting and runs the test suite on Python 3.12, 3.13 and 3.14

Changed

  • Breaking: every dependaman() argument after path is keyword-only. dependaman(".", True) becomes dependaman(".", in_memory=True)
  • Breaking: the default output file is Dependaman.html (or Dependaman.json) rather than output.html
  • An explicit output path is used exactly as given — its suffix is never rewritten, so -o report.txt writes report.txt
  • An invalid --format is rejected before any analysis runs, with a usage message instead of a traceback
  • dependaman() now composes the analysis and a renderer rather than doing the work itself, and accepts output_format, output_path and open_browser

Fixed

  • File URIs are built with Path.as_uri(), which fixes opening a graph whose path contains spaces

Known limitations

  • Pointing DependaMan at a package directory instead of its parent silently produces a nearly empty graph, because imports resolve against the top-level package name. On a 2 933-module project this was 251 module edges from the wrong root against 3 146 from the correct one, with no warning either way
  • A single file that cannot be parsed aborts the whole run. One deliberate syntax-error fixture in a 2 933-file project was enough to stop the analysis of all of them
  • Dead-callable detection is unreliable for libraries — on a large one, 47% of callables came back reported dead. Around a third of those are dunder methods the interpreter calls, and most of the rest are public API with no internal callers. For a library, “nothing here calls it” does not mean dead

[1.1.1] — 2026-05-24

Added

  • Search by module or function/class name — results show the containing module as a faint suffix; selecting a callable jumps to its module and flashes the callable in the detail panel
  • Same-module link label in the Callables section — palegoldenrod links scroll within the open modal instead of navigating; now labelled
  • Dead callable highlighting — dead callable names and left border use the accent colour; live callables remain neutral

Changed

  • Visual redesign: colour palette aligned with the project design language — black canvas, whitesmoke text, salmon accent for dead code, palegoldenrod for selection and same-module links, Wiphala-derived blue→green→red commit-frequency gradient at lower lightness for readability
  • Fit-to-screen now offsets the graph centre by half the legend width so the graph clears the legend panel
  • No external font requests — the output HTML is fully self-contained; system monospace font used instead of a Google Fonts CDN call

[1.1.0] — 2026-05-24

Added

  • Callable graph layer — function/method/class-level dependency tracking alongside the existing module graph, joined by canonical prefix (mod::Name belongs to module mod)
  • ast.Attribute chain walker — resolves qualified calls (np.linalg.norm()-style) by trying every split point against project definitions
  • Extended alias mapper — every import shape (import x, import a.b, import a as b, from . import x, etc.) now contributes a local_name → canonical entry; local definitions are added too so same-module calls resolve
  • Inheritance edges from ClassDef.bases — class B(A) produces an edge B → A
  • Decorator edges from decorator_list — handles bare @dec and parameterized @dec(arg) the same way
  • Synthetic mod::<module> source for module-scope calls — entry points like cli() in __main__.py are no longer falsely dead
  • Scoped naive name-matching fallback for obj.method() when obj has no type info
  • Call-aware dead-callable detection — a callable is dead when callable-graph fan-in is zero AND its canonical doesn’t appear in the project import set
  • Per-module “Callables” section in the modal — every function/method/class with its fan-in, fan-out, calls, called-by, and dead flag
  • Function references passed as Call arguments / keyword arguments tracked as edges — covers callbacks handed to executors, sorted(key=...), map, decorators-by-name
  • Dashed red ring around any module circle that contains at least one dead callable

Changed

  • Single-pass file parsing — each module is now read and AST-parsed once instead of three times; ~30% wall-clock speedup on a 1 354-file project (3.48s → 2.44s)
  • parse_calls is now caller-scoped — returns call_edges: dict[source, set[target]] instead of a flat call set
  • symbols_to_review_detector replaced by project-wide dead_callables_detector
  • Module payload field symbols_to_review replaced by with_symbols_to_review (boolean)

Fixed

  • discover() no longer emits duplicate Package entries for directories with many .py files
  • Property getter / setter / deleter no longer collapse into one canonical — each role gets a distinct canonical (mod::Class.bar@get, @set, @del)

Known limitations

  • obj.method() resolution falls back to naive name matching when obj is a local variable without type information — precise resolution via type annotations is slated for v1.2
  • Reference tracking covers callable-as-argument; other reference contexts (assignment, list literals, returns) are still missed — full reference tracking is a v1.2 item

[1.0.3] — 2026-04-05

Added

  • Hierarchical circle packing layout — packages contain sub-packages with no node overlap
  • Color encoding — node fill by commit frequency (blue→red), package border brightness by nesting depth
  • Test filter toggle — hide/show all nodes, edges, and packages with “test” in the name
  • Click node to select and open detail modal; click package circle to zoom into it
  • Fuzzy search bar — type a module name, jump to and highlight it (/ to focus)
  • Keyboard shortcuts: F zoom to fit, / focus search, Esc deselect/close

Changed

  • Middle-click to pan canvas
  • Escape closes modal and deselects node in one press
  • Minimum arrow length to avoid invisible edges between close nodes

Fixed

  • Panning and zoom now rAF-throttled — smooth at any display refresh rate
  • Package draw order precomputed once instead of sorted on every frame

[1.0.2] — 2026-03-26

Fixed

  • git stats now run from the correct project root (cwd=root)
  • module.full_path used for file reading so CLI works from any directory

[1.0.1] — 2026-03-26

Fixed

  • HTML and JS template files now included in the wheel
  • git stats no longer crash on repos with no commits (graceful fallback)
  • CLI entry point now correctly reads sys.argv via cli() function

[1.0.0] — 2026-03-25

Added

  • Initial release
  • Dependency graph visualization
  • Dead code detection, cycle detection, hotspot analysis
  • Git integration (commit frequency, churn, last author)
  • Unused symbol detection
  • CLI and Python API
  • GIL-aware parallel processing