Project awareness GenServer, modeled after Emacs projectile.
Tracks the current project root, caches the file list, and persists a
known-projects list to ~/.config/minga/known-projects so that SPC p p
works across editor sessions.
State
current_root— the active project root (detected from the first opened file)project_type— the type of project (:git,:mix,:cargo, etc.)cached_files— the file list for the current project (populated by a background Task)known_projects— list of all project roots the user has visited, persisted to diskrecent_files— per-project list of recently opened files, most recent first, persisted to diskcommand_frecency— command execution timestamps used to rank the empty command paletterebuilding?— true while a background Task is rebuilding the file cache
File cache
The cached file list lives in GenServer state (not ETS). Only one consumer
(the picker, running inside the Editor process) reads it at a time, so a
GenServer is simpler and sufficient. Cache rebuilds run in a supervised
Task to keep the GenServer responsive during the shell-out to fd or
git ls-files.
Summary
Types
Per-command execution event history (most recent first, unix seconds).
Per-file access event history (most recent first, unix seconds).
Per-project frecency map: project root => %{relative_path => access_timestamps}.
Per-project recent files map: project root => list of relative paths (most recent first).
Project GenServer state.
Functions
Adds a directory as a known project.
Finds alternate files (test <> implementation) for the given file.
Returns a specification to start this module under a supervisor.
Replaces the home directory prefix with ~ for display.
Returns the raw command frecency map (command name => timestamp list).
Returns frecency scores for command palette commands (command name => score).
Detects the project root for a file and sets it as the current project.
Detects the test runner for a project.
Expands a leading ~ to the home directory without normalizing the rest of the path.
Returns the cached file list for the current project.
Returns frecency scores for files in the current project (relative path => score).
Invalidates the file cache and triggers a rebuild.
Returns the list of known project roots.
Lists all files in the given directory, respecting .gitignore.
Returns true while a background file-cache rebuild is in progress.
Returns the list of recently opened files for the current project (relative paths, most recent first).
Records a command execution for command palette frecency ranking.
Records a file as recently opened in the current project.
Removes a project from the known-projects list.
Returns the current project root, falling back to File.cwd!().
Returns the current project root, or nil if none is detected.
Scores a file's access timestamps using frecency decay buckets.
Starts the project GenServer.
Switches to a known project root, triggering a cache rebuild.
Generates a command to run all tests.
Generates a command to run test at cursor position.
Generates a command to run tests in a file.
Types
@type command_frecency_map() :: %{required(atom()) => [non_neg_integer()]}
Per-command execution event history (most recent first, unix seconds).
@type file_accesses_map() :: %{required(String.t()) => [non_neg_integer()]}
Per-file access event history (most recent first, unix seconds).
@type frecency_events_map() :: %{required(String.t()) => file_accesses_map()}
Per-project frecency map: project root => %{relative_path => access_timestamps}.
Per-project recent files map: project root => list of relative paths (most recent first).
@type t() :: %Minga.Project{ cached_files: [String.t()], command_frecency: command_frecency_map(), current_root: String.t() | nil, events_registry: Minga.Events.registry(), frecency_events: frecency_events_map(), known_projects: [String.t()], project_type: Minga.Project.Detector.project_type() | nil, rebuild_ref: reference() | nil, rebuilding?: boolean(), recent_files: recent_files_map() }
Project GenServer state.
Functions
@spec add(GenServer.server(), String.t()) :: :ok
Adds a directory as a known project.
Finds alternate files (test <> implementation) for the given file.
Returns a specification to start this module under a supervisor.
See Supervisor.
Replaces the home directory prefix with ~ for display.
Handles both $HOME/... paths and $HOME exactly.
@spec command_frecency(GenServer.server()) :: command_frecency_map()
Returns the raw command frecency map (command name => timestamp list).
@spec command_frecency_scores(GenServer.server()) :: %{ required(atom()) => non_neg_integer() }
Returns frecency scores for command palette commands (command name => score).
@spec detect_and_set(GenServer.server(), String.t()) :: :ok
Detects the project root for a file and sets it as the current project.
Automatically adds the detected root to the known-projects list and triggers a background cache rebuild. No-op if detection finds no project markers.
@spec detect_test_runner(atom(), String.t()) :: {:ok, Minga.Project.TestRunner.Runner.t()} | :none
Detects the test runner for a project.
Expands a leading ~ to the home directory without normalizing the rest of the path.
@spec files(GenServer.server()) :: [String.t()]
Returns the cached file list for the current project.
@spec frecency_scores(GenServer.server()) :: %{ required(String.t()) => non_neg_integer() }
Returns frecency scores for files in the current project (relative path => score).
@spec invalidate(GenServer.server()) :: :ok
Invalidates the file cache and triggers a rebuild.
@spec known_projects(GenServer.server()) :: [String.t()]
Returns the list of known project roots.
Lists all files in the given directory, respecting .gitignore.
@spec rebuilding?(GenServer.server()) :: boolean()
Returns true while a background file-cache rebuild is in progress.
@spec recent_files(GenServer.server()) :: [String.t()]
Returns the list of recently opened files for the current project (relative paths, most recent first).
@spec record_command(GenServer.server(), atom()) :: :ok
Records a command execution for command palette frecency ranking.
@spec record_file(GenServer.server(), String.t()) :: :ok
Records a file as recently opened in the current project.
The file path should be absolute. It is stored relative to the project root. Most recent files appear first. Duplicates are moved to the front. No-op if no project root is set or the file is outside the current project.
@spec remove(GenServer.server(), String.t()) :: :ok
Removes a project from the known-projects list.
@spec resolve_root() :: String.t()
Returns the current project root, falling back to File.cwd!().
Safe to call even when the Project GenServer is not running (e.g., during
early startup or in tests): catches :exit from the GenServer call and
falls back to the working directory.
@spec root(GenServer.server()) :: String.t() | nil
Returns the current project root, or nil if none is detected.
@spec score_accesses([non_neg_integer()], non_neg_integer()) :: non_neg_integer()
Scores a file's access timestamps using frecency decay buckets.
@spec start_link(keyword()) :: GenServer.on_start()
Starts the project GenServer.
@spec switch(GenServer.server(), String.t()) :: :ok
Switches to a known project root, triggering a cache rebuild.
@spec test_all_command(Minga.Project.TestRunner.Runner.t()) :: String.t()
Generates a command to run all tests.
@spec test_at_point_command( Minga.Project.TestRunner.Runner.t(), String.t(), pos_integer() ) :: String.t() | nil
Generates a command to run test at cursor position.
@spec test_file_command(Minga.Project.TestRunner.Runner.t(), String.t()) :: String.t() | nil
Generates a command to run tests in a file.