Module: Pikuri::Workspace::ProjectRoot

Defined in:
lib/pikuri/workspace/project_root.rb

Overview

Climb from a working directory toward the project root, looking for a VCS marker (+.git+ / .hg / .svn). Host binaries use it to decide the Filesystem project_root: — the writable containment ceiling and (under the Bubblewrap sandbox) the bind-mount root, so +git+/+make+ can climb inside the sandbox to find <repo>/.git / <repo>/Makefile.

Why VCS-only markers

Build-system fallbacks (+Gemfile+, pyproject.toml, package.json, pom.xml, build.gradle, ...) were rejected: they aren't reliable root markers in multi-module setups (a Gradle subproject, a Maven module, a yarn-workspaces package each has its own), so climbing for them lands on the nearest subtree root — and a wrong project_root means the sandbox bind-mounts the wrong tree and git can't see the real .git. VCS markers sit only at the actual root (or a submodule root, the user's intended boundary when they cd'd in).

If no marker is found within the safe-climb bounds, ProjectRoot.find returns nil and the caller falls back to the launch cwd (losing visibility to sibling code; git init or invoke from the project root to fix).

Safety bounds

The climb refuses to enter the home directory or a system root (DENIED_CLIMB_ROOTS) — reaching either means "left the project tree" and the climb stops. The starting cwd is always considered even if banned (the user invoked there); only ancestors are gated.

Priority

All markers are checked at each ancestor before climbing, so the nearest VCS marker wins regardless of type.

Constant Summary collapse

DENIED_CLIMB_ROOTS =

Climb-up boundary. Overlaps Filesystem::DENIED_PROJECT_ROOTS but kept separate — different semantics (climb boundary vs. project_root rejection), may drift apart.

%w[
  / /etc /var /proc /sys /dev /boot /root
  /usr /opt /lib /lib64 /bin /sbin /tmp
].map { |p| Pathname.new(p) }.freeze
VCS_MARKERS =

VCS metadata dirs/files. .git is the common case; +.hg+/+.svn+ for the rare users on those. File.exist? matches both a directory (normal checkout) and a regular file (+.git+ in a worktree/submodule).

%w[.git .hg .svn].freeze

Class Method Summary collapse

Class Method Details

.find(cwd:, markers: VCS_MARKERS) ⇒ Pathname?

Find the nearest ancestor of cwd containing any of markers. See the class header for the safety bounds and priority.

Parameters:

  • cwd (String, Pathname)

    starting dir; +realpath+'d so symlinked checkouts don't bounce out of the tree.

  • markers (Array<String>) (defaults to: VCS_MARKERS)

    VCS marker names; default VCS_MARKERS.

Returns:

  • (Pathname, nil)

    the matching ancestor, or nil if none found within the safe-climb bounds (caller falls back to cwd).



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/pikuri/workspace/project_root.rb', line 61

def self.find(cwd:, markers: VCS_MARKERS)
  current = Pathname.new(cwd).realpath
  home = Pathname.new(Dir.home).realpath
  loop do
    return current if markers.any? { |m| current.join(m).exist? }

    parent = current.parent
    return nil if parent == current     # at the filesystem root
    return nil if parent == home        # crossing into $HOME
    return nil if DENIED_CLIMB_ROOTS.include?(parent)

    current = parent
  end
end