Packages
portfolio_coder
0.1.0
Code Intelligence Platform built on the Portfolio RAG ecosystem. Repository analysis, semantic code search, dependency graphs, and AI-powered code understanding with multi-language support.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/code.deps.ex
defmodule Mix.Tasks.Code.Deps do
@moduledoc """
Analyze code dependencies.
Build and query dependency graphs for code repositories.
## Usage
mix code.deps COMMAND PATH [OPTIONS]
## Commands
* `build` - Build a dependency graph
* `show` - Show dependencies of an entity
* `reverse` - Show reverse dependencies (dependents)
* `cycles` - Find circular dependencies
## Options
* `--graph` - Name of the graph (default: "deps")
* `--language` - Project language (auto-detected)
* `--depth` - Traversal depth for queries (default: 1)
## Examples
mix code.deps build ./my_project
mix code.deps show my_module --graph my_project
mix code.deps reverse utils --depth 2
mix code.deps cycles --graph my_project
"""
use Mix.Task
@shortdoc "Analyze code dependencies"
@impl Mix.Task
def run(args) do
{:ok, _} = Application.ensure_all_started(:portfolio_coder)
{opts, positional, _} =
OptionParser.parse(args,
strict: [
graph: :string,
language: :string,
depth: :integer,
help: :boolean
],
aliases: [g: :graph, l: :language, d: :depth, h: :help]
)
if opts[:help] do
shell_info(@moduledoc)
else
case positional do
["build", path | _] -> build_graph(path, opts)
["show", entity | _] -> show_deps(entity, opts)
["reverse", entity | _] -> show_reverse_deps(entity, opts)
["cycles" | _] -> find_cycles(opts)
_ -> shell_error("Unknown command. Use --help for usage.")
end
end
end
defp build_graph(path, opts) do
path = Path.expand(path)
graph_id = opts[:graph] || "deps"
shell_info("Building dependency graph for: #{path}")
build_opts =
[]
|> maybe_add(:language, parse_language(opts[:language]))
case PortfolioCoder.build_dependency_graph(graph_id, path, build_opts) do
{:ok, stats} ->
shell_info("""
Dependency graph built!
Graph ID: #{graph_id}
Nodes: #{stats[:nodes] || "unknown"}
Edges: #{stats[:edges] || "unknown"}
""")
{:error, reason} ->
shell_error("Error: #{inspect(reason)}")
exit({:shutdown, 1})
end
end
defp show_deps(entity, opts) do
graph_id = opts[:graph] || "deps"
depth = opts[:depth] || 1
shell_info("Dependencies of #{entity} (depth: #{depth}):\n")
case PortfolioCoder.get_dependencies(graph_id, entity, depth: depth) do
{:ok, deps} ->
if deps == [] do
shell_info("No dependencies found.")
else
Enum.each(deps, &print_dep/1)
end
{:error, reason} ->
shell_error("Error: #{inspect(reason)}")
exit({:shutdown, 1})
end
end
defp show_reverse_deps(entity, opts) do
graph_id = opts[:graph] || "deps"
depth = opts[:depth] || 1
shell_info("Dependents of #{entity} (depth: #{depth}):\n")
case PortfolioCoder.get_dependents(graph_id, entity, depth: depth) do
{:ok, deps} ->
if deps == [] do
shell_info("No dependents found.")
else
Enum.each(deps, &print_dep/1)
end
{:error, reason} ->
shell_error("Error: #{inspect(reason)}")
exit({:shutdown, 1})
end
end
defp find_cycles(opts) do
graph_id = opts[:graph] || "deps"
shell_info("Finding circular dependencies in graph: #{graph_id}\n")
{:ok, cycles} = PortfolioCoder.find_cycles(graph_id)
print_cycles(cycles)
end
defp print_cycles([]) do
shell_info("No circular dependencies found!")
end
# dialyzer:no_return - cycles detection is a stub that always returns []
@dialyzer {:nowarn_function, print_cycles: 1}
defp print_cycles(cycles) do
shell_info("Found #{length(cycles)} circular dependencies:\n")
Enum.each(cycles, fn cycle ->
path = Enum.join(cycle, " -> ")
shell_info(" Warning: #{path}")
end)
end
defp print_dep(dep) when is_map(dep) do
name = dep[:id] || dep[:name] || "unknown"
type = dep[:type] || ""
shell_info(" * #{name} #{type}")
end
defp print_dep(dep), do: shell_info(" * #{dep}")
defp shell_info(message), do: IO.puts(message)
defp shell_error(message), do: IO.puts(:stderr, message)
defp maybe_add(opts, _key, nil), do: opts
defp maybe_add(opts, key, value), do: Keyword.put(opts, key, value)
defp parse_language(nil), do: nil
defp parse_language(lang), do: String.to_existing_atom(lang)
end