feat(worktree): seed the absent children of an existing seed dir (#235)#257
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughDirectory seeding now copies missing children beneath existing directories without clobbering existing content. Provisioning distinguishes partial applications from total no-ops, preserves git exclusion behavior, and documents the updated sequencing and staging protection. ChangesWorktree seeding
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant provision_worktree
participant _copy_traversable
participant WorktreeFilesystem
participant LocalGitExclude
provision_worktree->>_copy_traversable: seed existing directory
_copy_traversable->>WorktreeFilesystem: copy absent children
_copy_traversable-->>provision_worktree: report copied or total no-op
provision_worktree->>LocalGitExclude: shield partially seeded path
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A `worktree_seed` entry naming a DIRECTORY was a total no-op whenever its destination existed — and it always does, because a worktree checks out the tracked children. The gitignored children that are absent, and would clobber nothing, were skipped along with them (bmad-code-org#230). bmad-code-org#233 made that skip visible; this makes it stop happening. `_copy_traversable` gains an opt-in `skip_existing` guard so the no-clobber property holds at FILE granularity, and returns whether it wrote anything. The guard is opt-in rather than baked in because `install_into --force-skills` rmtree's the destination precisely to overwrite it; only the seeding call passes it, so every other call site is byte-identical. Reporting follows the same rule as before, one level down: an entry that seeded even one child is applied configuration and is no longer reported skipped-whole, while an entry that still copied nothing stays reported. Per-child skips are not reported — the checkout is expected to carry its tracked children, the same routine-noise argument that excluded globs in bmad-code-org#233. A partially-seeded entry still gets its exclude pattern written, so the newly seeded children stay out of the unit's `git add -A`. `seed_globs` deliberately keeps its whole-entry behaviour: bmad-code-org#235 scopes this to `worktree_seed`. Fixes bmad-code-org#235.
1524b45 to
6361515
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/bmad_loop/install.py`:
- Around line 403-410: Update _copy_traversable’s directory branch to initialize
copied based on whether dst existed before mkdir, so creating a missing empty
directory returns True; retain child propagation for non-empty directories. Add
a regression test covering an empty child directory created during
provision_worktree and verify it is reported as applied rather than skipped.
- Around line 578-585: The _copy_traversable call for directory sources must
skip existing file targets without recursing. Before recursing, distinguish
destination types: treat an existing non-directory dst as a skipped no-op, and
invoke _copy_traversable only when dst is absent or dst.is_dir(), preserving
skip_existing no-clobber behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 79007fc3-586d-4d5e-af10-87d922570307
📒 Files selected for processing (3)
docs/FEATURES.mdsrc/bmad_loop/install.pytests/test_install.py
PR bmad-code-org#265 moved provision_worktree out of install.py into worktree_flow.py after this branch was cut, so the seeding-loop half of the change conflicted. Resolution: install.py keeps only the _copy_traversable changes (the helper still lives there and worktree_flow imports it); the provision_worktree docstring + dir-entry seeding branch are re-applied verbatim in worktree_flow.py. Tests and docs merged clean.
… count created dirs (bmad-code-org#235) Two review findings on the skip_existing path, plus a variant the review missed: - A directory entry whose destination is a FILE crashed provisioning: _copy_traversable ran dst.mkdir(exist_ok=True) on the file and raised FileExistsError. The call site now skips the type mismatch whole (reported like any existing destination), and the helper guards the same mismatch on NESTED children — the call-site check alone cannot see those. - A missing empty child directory was created by mkdir but not counted as written, so an entry whose only effect was creating directories was misreported as a total no-op. copied now initializes from whether dst existed before the mkdir. Four regression tests, each proven to bite with the fix hunks stashed.
|
@Polloinfilzato thanks — reviewed, and this is exactly the shape #235 asked for: file-granular no-clobber with the pinned invariant test untouched, the #233 reporting rule carried one level down, the exclude still written for partial seeds, and the opt-in flag keeping To get it merged promptly I pushed two commits to your branch (maintainer edits) rather than round-tripping:
Full suite green locally (2772 passed / 7 skipped), |
|
@coderabbitai review |
✅ Action performedReview finished.
|
What
worktree_seedentries naming a directory now seed the children the worktree is missing,instead of skipping the entry whole. Option B from #230, as scoped in #235.
Why
A worktree checks out tracked files, so a seed directory with any tracked child always exists —
and copy-when-absent then skipped the entry entirely, including the gitignored children that are
absent and would clobber nothing. #233 made that skip visible; this makes it stop happening.
How
_copy_traversablegains an opt-inskip_existingguard, so no-clobber holds at filegranularity, and returns whether it wrote anything.
Opt-in rather than baked into the helper because
install_into --force-skillsrmtrees thedestination precisely to overwrite it (
install.py:455); a guard in the helper would havesilently regressed it. Only the seeding call passes the flag — the other four call sites are
byte-identical, and
test_install_skills_forcestill pins the force path. The guard sits aheadof both write branches, so it holds for the zip-imported
Traversablesource too, not justshutil.copy2.Reporting follows the rule from #233 one level down:
the same routine-noise argument that excluded globs in fix: report worktree_seed entries skipped as no-ops #233;
patterns, so the children just written stay out of theunit's
git add -A. Excluding the whole dir is safe: an exclude does not untrack the trackedchildren that were already there.
test_provision_worktree_reports_seed_dir_skipped_wholecarried a# documents today's behaviourassertion; it is inverted and renamed here, which is the behaviour change #235 askedfor.
test_provision_worktree_seed_does_not_clobber_existingpasses unmodified.Testing
LC_ALL=C LANG=C).trunk fmt && trunk check --no-progressclean.install.pystashed, 5 of the 5 new tests fail (the6th,
reports_seed_dir_with_nothing_to_copy, passes either way by design — it pins theunchanged case).
total no-op still reported; exclude pattern written for a partial seed in a real git worktree;
skip_existingon the zipTraversablebranch; theFalsereturn on a total no-op.Note, not addressed here
seed_globshas the same whole-entry behaviour for a directory match (install.py:572). I leftit alone deliberately — #235 scopes this to
worktree_seed, and changing the glob path wouldalter behaviour for plugin-authored seeds that were not part of the report. Happy to extend it
in a follow-up if you'd rather the two paths share one semantic.
Also carrying forward your non-blocking note on #233: the skipped-entry signal is not specific to
hand-written
worktree_seedentries, since the seed list also carries adapter-profileseed_files. I have not repeated that claim here.Fixes #235.
Summary by CodeRabbit
Bug Fixes
Documentation