refactor: dogfood bundles via symlinks instead of byte-identical copies#1357
Open
tschm wants to merge 3 commits into
Open
refactor: dogfood bundles via symlinks instead of byte-identical copies#1357tschm wants to merge 3 commits into
tschm wants to merge 3 commits into
Conversation
Make bundles/ the single source of truth for the mother repo's own dogfood files. 68 root files (.rhiza/**, .claude/commands/*, top-level Makefile/ruff.toml/cliff.toml/LICENSE/pytest.ini, etc.) are now relative symlinks into their owning bundle, so editing the bundle file propagates automatically — no more hand-mirroring both sides to satisfy the byte-identity tests. A few files cannot be symlinks and stay as real copies, guarded by tests/bundles/test_bundle_*_sync.py rather than by symlink: - .github/* platform config — GitHub reads blobs directly and does not follow symlinks (Dependabot, Actions workflows, release.yml, secret_scanning.yml, PR template, rulesets). - .rhiza/.gitignore — git opens .gitignore with O_NOFOLLOW (ELOOP). - .rhiza/utils/*.py — coverage canonicalises symlinks to realpath, so `make rhiza-test`'s --cov=.rhiza/utils would match nothing. Downstream consumers are unaffected: rhiza-cli sparse-checks-out a bundle and dereferences symlinks on copy, so synced projects always receive real files (guarded by test_no_symlinks_in_*). Bundle files therefore stay self-contained real files; only root files symlink into bundles. - add utils/link_dogfood.py + `make sync-self` to (re)create the links - add tests/bundles/test_bundle_github_sync.py for the .github copies - document the model in CLAUDE.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors Rhiza’s “dogfooded” root-level template files to be relative symlinks into their authoritative bundles/<name>/... sources, reducing the need for manual byte-identical duplication while preserving exceptions where symlinks are unsupported (notably .github/ and coverage-sensitive paths).
Changes:
- Add
utils/link_dogfood.pyplus amake sync-selftarget to (re)create idempotent relative symlinks from root dogfood files intobundles/. - Add a new test to guard that root
.github/platform-config files remain real copies and stay byte-identical tobundles/github/.github/(excluding workflows). - Document the dogfooding/symlink model and exceptions in
CLAUDE.md.
Reviewed changes
Copilot reviewed 71 out of 140 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| utils/link_dogfood.py | New mother-repo utility to convert eligible root dogfood copies into relative symlinks into bundles/. |
| tests/bundles/test_bundle_github_sync.py | New test ensuring .github/ non-workflow platform-config copies stay in sync with bundles/github/.github/. |
| CLAUDE.md | Documentation of the dogfooding model, symlink strategy, and exceptions. |
| .rhiza/make.d/bundles.mk | Adds make sync-self target to run the relinking script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+139
to
+142
| bundles_dir = root / "bundles" | ||
| if not bundles_dir.is_dir(): | ||
| sys.exit(f"{YELLOW}No bundles/ directory found at {root} — run from the rhiza repo root.{RESET}") | ||
|
|
Comment on lines
+159
to
+163
| owners = index.get(rel) | ||
| if not owners: | ||
| continue | ||
| root_bytes = (root / rel).read_bytes() | ||
| identical = [o for o in owners if o.read_bytes() == root_bytes] |
| target = os.path.relpath(source, start=link.parent) | ||
| if link.is_symlink() and os.readlink(link) == target: | ||
| return False | ||
| link.unlink() |
Comment on lines
+126
to
+131
| def relink(root: Path) -> int: | ||
| """Convert every eligible root dogfood copy into a relative symlink. | ||
|
|
||
| A root file is eligible when it is tracked by git, not in ``_EXCLUDE``, and | ||
| byte-identical to exactly one bundle source. Ambiguous matches (identical to | ||
| more than one bundle) are skipped with a warning rather than guessed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Rhiza dogfoods its own templates: every bundle file in
bundles/<name>/...also lived as a byte-identical copy at the repo root, kept in sync bytest_bundle_*_sync.py. Any edit to a bundle file had to be hand-mirrored into the root copy ormake testfailed the whole matrix. This makesbundles/the single source of truth and removes that double-editing burden.What
68 root dogfood files → relative symlinks into their owning bundle (
.rhiza/**,.claude/commands/*, top-levelMakefile/ruff.toml/cliff.toml/LICENSE/pytest.ini, …). Edit the bundle file; the root reflects it automatically.A few files cannot be symlinks and stay as real copies, guarded by tests rather than by symlink:
.github/*(Dependabot, release.yml, secret_scanning.yml, PR template, rulesets).github/workflows/*.rhiza/.gitignore.gitignorewithO_NOFOLLOW→ ELOOP warning + ignored rules.rhiza/utils/*.pymake rhiza-test's--cov=.rhiza/utilswould match nothingPlus intentional mother-repo overrides that deliberately diverge (
.claude/commands/rhiza_quality.md, root.gitignore,.pre-commit-config.yaml,.python-version,SECURITY.md,renovate.json).Changes
utils/link_dogfood.py+make sync-self— idempotent (re)creation of the linkstests/bundles/test_bundle_github_sync.py— guards the.githubreal copies (previously unguarded)test_bundle_rhiza_sync.py/test_bundle_claude_commands_sync.py(still guard the remaining real copies; symlinks pass trivially)CLAUDE.mdDownstream consumers are unaffected
rhiza-clidoesgit sparse-checkoutof the requested bundle thenos.walk(followlinks=True)+shutil.copy2, so it dereferences symlinks on copy — synced projects always receive real files (guarded bytest_no_symlinks_in_*). This is also why the "inversion" (bundle files symlinking to root) was rejected: a bundle symlink pointing outside its sparse-checkout would dangle and crash sync. Only root files symlink into bundles; bundle files stay self-contained.Verification
make test— 1067 passedmake rhiza-test— 212 passed, 100% coverage (symlinked test files collect fine;.rhiza/utilskept real so coverage attributes correctly)make fmt,make typecheck,make deptry— all greentest_no_symlinks_*and the new.githubguardmake sync-selfis idempotent🤖 Generated with Claude Code