Integrated repository, worktree, git stacker, and project manager
0

Configure Feed

Select the types of activity you want to include in your feed.

Add stacker_root path + config key

Phase 1 of the stacker fork needs a dedicated directory for its sqlite db
and ops-ownership sentinel. Introduce stacker_root on Paths (default
~/.stacker, overridable via [paths].stacker_root in pm config), with
derived helpers stacker_db() and stacker_ops_marker(). Extend pm_env so
every test gets an isolated stacker_root alongside repos/worktrees/projects.

Jordan Isaacs (Apr 23, 2026, 12:53 AM UTC) b65da499 0869da37

+20
+4
src/project_manager/config.py
··· 7 7 DEFAULT_REPOS = "~/.repos" 8 8 DEFAULT_WORKTREES = "~/.worktrees" 9 9 DEFAULT_PROJECTS = "~/.projects" 10 + DEFAULT_STACKER_ROOT = "~/.stacker" 10 11 11 12 12 13 def _expand(value: str) -> Path: ··· 31 32 repos = DEFAULT_REPOS 32 33 worktrees = DEFAULT_WORKTREES 33 34 projects = DEFAULT_PROJECTS 35 + stacker_root = DEFAULT_STACKER_ROOT 34 36 35 37 if config_path is not None: 36 38 with config_path.open("rb") as f: ··· 39 41 repos = paths_section.get("repos", repos) 40 42 worktrees = paths_section.get("worktrees", worktrees) 41 43 projects = paths_section.get("projects", projects) 44 + stacker_root = paths_section.get("stacker_root", stacker_root) 42 45 43 46 return Paths( 44 47 repos=_expand(repos), 45 48 worktrees=_expand(worktrees), 46 49 projects=_expand(projects), 50 + stacker_root=_expand(stacker_root), 47 51 )
+7
src/project_manager/paths.py
··· 7 7 repos: Path 8 8 worktrees: Path 9 9 projects: Path 10 + stacker_root: Path 10 11 11 12 def repo(self, name: str) -> Path: 12 13 return self.repos / name ··· 28 29 29 30 def project_db(self, project: str) -> Path: 30 31 return self.projects / project / ".pm.db" 32 + 33 + def stacker_db(self) -> Path: 34 + return self.stacker_root / "db.sqlite" 35 + 36 + def stacker_ops_marker(self) -> Path: 37 + return self.stacker_root / "ops"
+3
tests/conftest.py
··· 11 11 repos = tmp_path / "repos" 12 12 worktrees = tmp_path / "worktrees" 13 13 projects = tmp_path / "projects" 14 + stacker_root = tmp_path / "stacker" 14 15 repos.mkdir() 15 16 worktrees.mkdir() 16 17 projects.mkdir() 18 + stacker_root.mkdir() 17 19 18 20 cfg = tmp_path / "pm.toml" 19 21 cfg.write_text( ··· 21 23 f'repos = "{repos}"\n' 22 24 f'worktrees = "{worktrees}"\n' 23 25 f'projects = "{projects}"\n' 26 + f'stacker_root = "{stacker_root}"\n' 24 27 ) 25 28 monkeypatch.setenv("PM_CONFIG", str(cfg)) 26 29 return config.load()
+6
tests/test_config.py
··· 13 13 assert paths.repos == tmp_path / ".repos" 14 14 assert paths.worktrees == tmp_path / ".worktrees" 15 15 assert paths.projects == tmp_path / ".projects" 16 + assert paths.stacker_root == tmp_path / ".stacker" 17 + assert paths.stacker_db() == tmp_path / ".stacker" / "db.sqlite" 18 + assert paths.stacker_ops_marker() == tmp_path / ".stacker" / "ops" 16 19 17 20 18 21 def test_pm_config_overrides(pm_env) -> None: 19 22 assert pm_env.repos.is_dir() 20 23 assert pm_env.worktrees.is_dir() 21 24 assert pm_env.projects.is_dir() 25 + assert pm_env.stacker_root.is_dir() 22 26 23 27 24 28 def test_xdg_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: ··· 27 31 (xdg / "pm").mkdir(parents=True) 28 32 (xdg / "pm" / "config.toml").write_text( 29 33 f'[paths]\nrepos = "{tmp_path}/r"\nworktrees = "{tmp_path}/w"\nprojects = "{tmp_path}/p"\n' 34 + f'stacker_root = "{tmp_path}/s"\n' 30 35 ) 31 36 monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg)) 32 37 paths = config.load() 33 38 assert paths.repos == tmp_path / "r" 39 + assert paths.stacker_root == tmp_path / "s"