From e1d844bfed7396fdea0636cda44f371d33917fcc Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Fri, 24 Jul 2026 07:54:07 -0400 Subject: [PATCH] fix(bootstrap): path-shaped branches ancestry without isdir (#850 review #551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve_canonical_repo_root fallback now uses commonpath only — no string split and no os.path.isdir gate — so MCP project_root = branches/ still resolves to the repo root when the path is not yet on disk (#274 / review #551 regression). --- author_mutation_worktree.py | 27 ++++++++++++++------------ tests/test_author_mutation_worktree.py | 7 +++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/author_mutation_worktree.py b/author_mutation_worktree.py index ee396e8..d0d6551 100644 --- a/author_mutation_worktree.py +++ b/author_mutation_worktree.py @@ -93,9 +93,10 @@ def resolve_canonical_repo_root(workspace_path: str, fallback_project_root: str) pass # Fallback when git metadata is unavailable. Never string-split on - # "/branches/" (review #531 Finding 2 / F-6): recover the repo root only - # via resolved-path commonpath ancestry against a parent that owns a - # real ``branches`` directory containing the fallback path. + # "/branches/" (review #531 F2 / #551): recover the repo root only via + # resolved-path commonpath ancestry. Do **not** require on-disk isdir — + # MCP may launch with project_root = branches/ before that path + # exists, and #274 path-shaped worktree-as-project-root must still resolve. fallback = os.path.realpath(fallback_project_root or workspace_path or ".") cur = fallback for _ in range(64): @@ -103,16 +104,18 @@ def resolve_canonical_repo_root(workspace_path: str, fallback_project_root: str) if parent == cur: break branches_dir = os.path.realpath(os.path.join(parent, "branches")) - if os.path.isdir(branches_dir): + try: + # Path-shaped: fallback is under parent/branches/ (commonpath). + if os.path.commonpath([branches_dir, fallback]) == branches_dir: + return parent + except ValueError: + pass + # Fallback path itself is the branches directory. + if os.path.basename(os.path.realpath(cur)) == "branches": try: - if os.path.commonpath([branches_dir, fallback]) == branches_dir: - return parent - except ValueError: - pass - # Also accept fallback itself being the branches directory. - if os.path.basename(cur) == "branches" and os.path.isdir(cur): - try: - if os.path.commonpath([cur, fallback]) == cur: + if os.path.commonpath([os.path.realpath(cur), fallback]) == os.path.realpath( + cur + ): return parent except ValueError: pass diff --git a/tests/test_author_mutation_worktree.py b/tests/test_author_mutation_worktree.py index d44ac98..006a88f 100644 --- a/tests/test_author_mutation_worktree.py +++ b/tests/test_author_mutation_worktree.py @@ -86,6 +86,13 @@ class TestAssessAuthorMutationWorktree(unittest.TestCase): self.assertTrue(result["proven"]) self.assertFalse(result["block"]) + def test_path_shaped_branches_ancestry_without_isdir(self): + """Review #551: commonpath recovery must not require on-disk isdir.""" + fake_wt = "/repo/Gitea-Tools/branches/issue-274" + root = amw.resolve_canonical_repo_root(fake_wt, fake_wt) + self.assertEqual(root, "/repo/Gitea-Tools") + self.assertNotIn('split("/branches/")', open(amw.__file__).read()) + class TestPreflightIntegration(unittest.TestCase): def test_verify_preflight_blocks_control_checkout_with_test_porcelain(self):