Packages
fnord
0.8.61
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/resolve_project.ex
defmodule ResolveProject do
@moduledoc """
Project resolution from cwd or git worktree root.
Resolution order (most-specific to least-specific):
- From CWD: choose the configured project whose root contains CWD with the deepest (longest) root.
- Git fallback: build candidate roots from worktree and repo.
- Prefer exact matches first (worktree exact beats repo exact; ties break by depth then name).
- Otherwise, pick the deepest configured project whose root lies within any candidate (ties by name).
Returns `{:ok, project_name}` or `{:error, :not_in_project}`.
"""
@type project_name :: binary
@spec resolve_from_cwd(cwd :: binary | nil) :: {:ok, project_name} | {:error, :not_in_project}
def resolve_from_cwd(cwd \\ nil) do
projects =
Settings.new()
|> Settings.get_projects()
|> Enum.flat_map(fn {name, %{"root" => root}} ->
case root do
nil -> []
root_str -> [{Path.absname(root_str), name}]
end
end)
cwd_abs =
case cwd do
nil -> Path.absname(File.cwd!())
dir -> Path.absname(dir)
end
matching_projects =
projects
|> Enum.filter(fn {root_abs, _name} -> path_contains?(root_abs, cwd_abs) end)
case matching_projects do
[] ->
{:error, :not_in_project}
_ ->
{_, project_name} =
Enum.max_by(matching_projects, fn {root_abs, _} -> String.length(root_abs) end)
{:ok, project_name}
end
end
@spec resolve_from_worktree() :: {:ok, project_name} | {:error, :not_in_project}
def resolve_from_worktree() do
# Gather possible roots from git worktree and repo
candidates =
[GitCli.worktree_root(), GitCli.repo_root()]
|> Enum.filter(& &1)
|> Enum.map(&Path.absname/1)
|> Enum.uniq()
# Load configured project roots
projects =
Settings.new()
|> Settings.get_projects()
|> Enum.flat_map(fn {name, %{"root" => root}} ->
case root do
nil -> []
root_str -> [{Path.absname(root_str), name}]
end
end)
# Attempt direct match of candidate to configured roots
# Two-phase ranked project selection: exact then nested within worktree candidates
# Phase 1: exact matches
selected =
projects
|> Enum.filter(fn {root_abs, _} -> root_abs in candidates end)
result =
if selected != [] do
worktree_abs = GitCli.worktree_root() && Path.absname(GitCli.worktree_root())
repo_abs = GitCli.repo_root() && Path.absname(GitCli.repo_root())
cond do
worktree_abs && Enum.any?(selected, fn {root_abs, _} -> root_abs == worktree_abs end) ->
{_, name} = Enum.find(selected, fn {root_abs, _} -> root_abs == worktree_abs end)
{:ok, name}
repo_abs && Enum.any?(selected, fn {root_abs, _} -> root_abs == repo_abs end) ->
{_, name} = Enum.find(selected, fn {root_abs, _} -> root_abs == repo_abs end)
{:ok, name}
true ->
{_, name} =
Enum.max_by(selected, fn {root_abs, name} -> {String.length(root_abs), name} end)
{:ok, name}
end
else
# Phase 2: nested matches
nested =
projects
|> Enum.filter(fn {root_abs, _} ->
Enum.any?(candidates, fn cand -> Util.path_within_root?(root_abs, cand) end)
end)
if nested != [] do
{_, name} =
Enum.max_by(nested, fn {root_abs, name} -> {String.length(root_abs), name} end)
{:ok, name}
else
{:error, :not_in_project}
end
end
result
end
# Determines if `child` is equal to or contained within `parent`.
defp path_contains?(parent, child) do
parent_abs = Path.expand(parent)
child_abs = Path.expand(child)
Util.path_within_root?(child_abs, parent_abs)
end
end