Skip to content

chore: define a sync strategy for Claude Code / agentic tooling config across sibling repos #8

@kploch

Description

@kploch

Summary

Define and implement a single, durable strategy for sharing Claude Code (and other agentic-tool) configuration — .claude/agents, .claude/rules/*, .claude/skills/*, .github/skills/*, and arguably AGENTS.md / CLAUDE.md / GEMINI.md — across all sibling repositories under C:\DevNet\my\mrploch\. The current ad-hoc setup has caused real damage in ploch-data and a near-miss in ploch-common, and will keep doing so until normalised.

Background — the problem

Claude Code reads configuration from .claude/ inside each repository. The lookup is not hierarchical: a .claude/ at C:\DevNet\my\mrploch\ is ignored by Claude Code working inside C:\DevNet\my\mrploch\ploch-common\. So the canonical set of skills/rules/agents must physically appear in every repo where you want them to take effect.

The repos themselves are already federated: Directory.Build.props files in each library import shared MSBuild fragments via relative paths (<Import Project="../mrploch-development/dependencies/..."/>). mrploch-development is the established home for shared org-level dev config. Claude config is a natural extension of the same problem, but no convention has been set yet.

Current state — evidence of the gap

A snapshot taken on 2026-04-26 across ploch-common and ploch-data:

ploch-common

  • .claude/rules/*.md and .claude/skills/* and .claude/agents were all symlinks (mode 120000) into the user's local environment.
  • All of them were staged for commit by accident as part of an unrelated branch (codex/fix-not-out-of-range-flag-support). They were unstaged before commit.
  • 2 real .md files in .claude/rules/ are intentionally tracked: change-log.md, todo-tasks-execution.md.
  • AGENTS.md was also staged (real file, 330 lines). Currently kept untracked pending the strategy decision.
  • A .gitignore block has been added in this PR/commit to prevent re-staging of the symlinks (Claude config + agentic tooling paths). See linked PR.

ploch-data

  • 30 symlinks under .claude/ are already committed and pushed to origin. This is the future-cross-machine breakage we worried about — already in production. git ls-files --stage shows mode 120000 for all of them.
  • 2 real .md files in .claude/rules/ are intentionally tracked: integration-testing.md, sample-app.md.
  • A .gitignore block has been added (linked PR/commit) to stop further pollution. It does not remove the already-committed symlinks — gitignore doesn't affect tracked files. Those need a separate git rm --cached cleanup once the canonical strategy is in place.

Other repos

Probably similar — needs a sweep once the strategy is decided. At minimum: ploch-commandline, ploch-lists, ploch-groupmatters, ploch-ini-parser, ploch-tools-editorconfig, ploch-endpoints, ploch-tiny-tools, ploch-ai-tools, ploch-dotnet-templates, ploch-templates-dotnet-repository, ploch-templates-dotnet-with-common, ploch-generic-repository, ploch-github-actions, ploch-scripts.

Existing assets in mrploch-development

The repo already hosts (root level): AGENTS.md, CLAUDE.md, GEMINI.md. Folders include dependencies/, editor-config/, repository-config/, scripts/, solution-templates/, repositories/, computer-config/. There is no .claude/ directory yet but the repo is the natural home.

Why this matters

  1. Public-repo correctness. ploch-common, ploch-data, and the rest are public open-source repos. Committing personal-machine symlinks pollutes history with paths that mean nothing on any other machine — broken links on Linux/Mac, plain text files containing the link target on Windows without dev mode.
  2. Drift. Any "copy the rules/skills around" approach inevitably drifts. A rule update in one repo doesn't propagate. Different repos make different decisions silently.
  3. Onboarding for contributors. External contributors to the OSS libs shouldn't need any of this config — the agentic tooling is personal. But right now ploch-data ships with 30 broken-on-clone files, which looks unprofessional and confusing.
  4. Symmetry with existing patterns. Directory.Build.props already federates MSBuild config from mrploch-development. Solving Claude config the same way reduces cognitive load.

Strategy options

A. Symlinks/junctions + gitignore B. Sync script + committed copies C. Hybrid (junctions in dev, gitignored, bootstrap script)
Canonical source mrploch-development/.claude/ mrploch-development/.claude/ mrploch-development/.claude/
In each repo symlink (Unix) / junction (Windows) into canonical real file copies, regenerated junction into canonical
Tracked in repo? No (gitignored) Yes No (gitignored)
Edit experience Edit canonical, all repos see it instantly Edit canonical, run claude-sync.ps1, commit copies Edit canonical, all repos see it instantly
Other contributors ❌ broken (or missing config) ✅ they get the real files ❌ they don't have it (but probably don't need it)
CI ❌ doesn't see config ✅ sees real files; can verify drift ❌ doesn't see config (but probably doesn't need it)
Windows perms Symlinks: dev mode required. Junctions: directory-only, no special perms. None — plain files Junctions: directory-only, no special perms
Drift risk None — single source of truth Real (someone edits the copy) None
Setup friction (per fresh clone) Run a one-time symlink/junction creation step None (files are committed) Run bootstrap-claude.ps1 once
One-line update flow Edit and save in canonical Edit, sync, commit N repos, push N repos Edit and save in canonical

Option D — git submodule

Add mrploch-development/.claude as a submodule under each repo. Rejected: submodules pin per-repo SHAs (each repo can drift), require contributors to know git submodule update --init, and a one-line rule edit becomes a multi-step process across multiple repos. Bad fit for "shared dotfiles".

Option E — dotnet tool

Build a Ploch.ClaudeConfig.Tool that, when run in a repo directory, materialises the canonical config. Could be hosted as a dotnet tool. Overkill for personal use but the most "real product" approach. Worth keeping in mind if the user base ever grows.

Recommendation

Option C (hybrid: junctions + gitignore + bootstrap script). Best fit because:

  • You're the sole maintainer of these libraries. External contributors don't need your personal Claude tooling.
  • Junctions (Windows) and symlinks (Unix) work without special permissions for directories — easier than file-level symlinks.
  • Zero drift: edit once in mrploch-development, every repo sees it.
  • .gitignore keeps the public repos clean.
  • A small bootstrap-claude.ps1 is the only friction at fresh-clone time.

Exception: AGENTS.md. This file is public-facing documentation that non-Claude agentic tools (Codex, etc.) read from the repo root. Treat it differently:

  • Canonical template lives at mrploch-development/AGENTS.template.md.
  • bootstrap-claude.ps1 copies (not links) it into each repo's root as AGENTS.md.
  • The copy IS committed (it's public docs).
  • Drift is your responsibility to manage by re-running the sync.

Same logic applies to CLAUDE.md and GEMINI.md if you want consistent versions across repos — they're public docs, not personal tooling.

Decisions needed

Please decide before implementation:

  1. Strategy choice: A, B, C, or something else.
  2. Scope of "shared": Which directories are canonical-from-mrploch-development and which are repo-specific? Current real-file exceptions in the gitignore are:
    • ploch-common: .claude/rules/change-log.md, .claude/rules/todo-tasks-execution.md
    • ploch-data: .claude/rules/integration-testing.md, .claude/rules/sample-app.md
    • Should these be promoted to canonical (live in mrploch-development) or stay repo-specific?
  3. AGENTS.md policy: Single canonical template copied into every repo, or per-repo authored?
  4. Scope of cleanup: Just ploch-common + ploch-data for now, or sweep all 16 sibling repos?
  5. Cleanup of already-committed symlinks in ploch-data: Single bulk git rm --cached commit, or repo-by-repo as we touch them for other work?
  6. Settings files: .claude/settings.json and .claude/settings.local.json are personal — already gitignored. Confirm they stay that way and never become canonical.

Implementation plan (once strategy is chosen)

Assuming Option C is chosen:

  1. Move canonical content into mrploch-development/.claude/.

    • Create mrploch-development/.claude/{agents,rules,skills}/.
    • Copy the current canonical versions from wherever they live now (likely ~/.claude/ or already-committed copies in ploch-data).
    • Decide and document the structure.
  2. Author mrploch-development/scripts/bootstrap-claude.ps1.

    • Args: -RepoPath (default: current dir), -DryRun.
    • For each canonical directory in ../mrploch-development/.claude/<X>, create a junction at <RepoPath>/.claude/<X>.
    • For each canonical file (AGENTS.template.md, CLAUDE.template.md, etc.), copy into <RepoPath>/<filename>.
    • Idempotent: re-running replaces junctions/copies cleanly.
    • Cross-platform aside: supply a bootstrap-claude.sh equivalent if you ever want to run on Linux/Mac.
  3. Per-repo .gitignore template in mrploch-development/repository-config/gitignore-claude-block.txt so all repos use the same pattern. Wire it into your repo template (ploch-templates-dotnet-repository).

  4. Cleanup pass. For each sibling repo:

    • If symlinks are committed: git rm --cached <paths> and commit.
    • Append the gitignore block (or reference it).
    • Run bootstrap-claude.ps1 to materialise junctions.
    • Verify git status is clean.
  5. Document. Add a section to mrploch-development/README.md describing the strategy and the bootstrap command. Optional: add a one-liner to each repo's CONTRIBUTING.md saying "Claude Code config is personal tooling sourced from mrploch-development — see bootstrap-claude.ps1. External contributors can ignore this."

Acceptance criteria

  • Strategy is decided and documented in mrploch-development/README.md.
  • bootstrap-claude.ps1 (or the chosen sync mechanism) lives in mrploch-development/scripts/ and is idempotent.
  • All sibling repos under C:\DevNet\my\mrploch\ either use the new strategy or are explicitly opted out.
  • ploch-data no longer ships committed symlinks under .claude/.
  • git status in every sibling repo is clean of stray Claude/.github/skills/ entries.
  • Repo template (ploch-templates-dotnet-repository) ships the gitignore block and a pointer to bootstrap-claude.ps1.
  • AGENTS.md policy documented; copies (or per-repo originals) consistent.

Stop-gap already in place (2026-04-26)

To prevent further pollution while the strategy is decided:

  • ploch-common/.gitignore: appended a Claude/agentic-tooling block ignoring .claude/agents, .claude/skills/, .claude/rules/* (with !-exceptions for the 2 real tracked files), .github/skills/. Also some local IDE artefacts.
  • ploch-data/.gitignore: same block with the appropriate !-exceptions for that repo. Note: gitignore does not remove the 30 already-committed symlinks; that remains TODO under acceptance criteria.

References

  • Workspace CLAUDE.md describing the multi-repo layout: C:\DevNet\my\mrploch\CLAUDE.md
  • Existing federation precedent: Directory.Build.props files importing mrploch-development/dependencies/*
  • Polluted state in ploch-data: 30 mode-120000 entries under .claude/ (see git ls-files --stage filtered to that path)
  • The conversation that surfaced this (mrploch/ploch-common, branch codex/fix-not-out-of-range-flag-support): symlinks were staged accidentally as part of a Guard.cs refactor and unstaged before commit

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions