Packages

fnord

0.9.36

AI code archaeology

Current section

Files

Jump to
fnord lib resolve_project.ex
Raw

lib/resolve_project.ex

# This module determines the current project based on the current working directory
# or falls back to git worktree and repository roots to ensure the most specific project
# context is identified.
defmodule ResolveProject do
@moduledoc """
Project resolution from the current working directory.
Behavior:
- With no explicit cwd, the effective directory is the session's project
root override (`Settings.get_project_root_override/0`) when set, else
the real cwd. In prod the override is always nil at resolution time;
see the comment in `resolve/1`.
- If inside a git worktree, `resolve/1` will map the cwd to the repository root.
- Select the configured project whose root contains that directory, choosing the one with the deepest (longest) root path.
Returns `{:ok, project_name}` or `{:error, :not_in_project}`.
"""
@type project_name :: binary
# Find the configured project whose root most specifically contains the provided directory.
@spec resolve(cwd :: binary | nil) :: {:ok, project_name} | {:error, :not_in_project}
def resolve(cwd \\ nil) do
projects =
Settings.new()
|> Settings.get_projects()
|> Enum.flat_map(fn
{name, %{"root" => nil}} ->
UI.warn("Project '#{name}' missing 'root' configuration, skipping.")
[]
{name, %{"root" => root}} ->
[{Path.absname(root), name}]
{name, _project_config} ->
UI.warn("Project '#{name}' missing 'root' configuration, skipping.")
[]
end)
# Resolution base: an explicit cwd argument wins; otherwise honor the
# session's project-root override before falling back to the real cwd.
# In prod the override is always nil here - resolution runs once at CLI
# parse time, and every override writer (ask's worktree machinery) runs
# later - so consulting it changes nothing today. It exists for
# consistency (if resolution ever re-runs with an override set, the
# worktree mapping below converges on the same project) and as the
# async-safe alternative to File.cd! in tests.
base_dir =
case cwd do
nil -> Settings.get_project_root_override() || File.cwd!()
dir -> dir
end
# If we are inside a git worktree, prefer resolving at the primary repo
# root so that project selection reflects the clone from which the
# worktree was created. Skipped when an explicit cwd was passed,
# preserving resolve/1's literal-directory contract for that form.
repo_root_in_ctx =
case cwd do
nil -> GitCli.primary_root_at(base_dir)
_ -> nil
end
cwd_base = repo_root_in_ctx || base_dir
cwd_abs = Path.absname(cwd_base)
# Filter to projects whose root contains the current working directory.
matching_projects =
projects
|> Enum.filter(fn {root_abs, _name} -> Util.path_within_root?(cwd_abs, root_abs) end)
# Decide outcome based on whether any matching projects were found.
case matching_projects do
# No matching project roots found, return error.
[] ->
{:error, :not_in_project}
# Select the project with the deepest root path as the most specific match.
_ ->
{_, project_name} =
Enum.max_by(matching_projects, fn {root_abs, _} -> String.length(root_abs) end)
{:ok, project_name}
end
end
end