Skip to content

feat(rig): provision .claude/worktrees ignore via global core.excludesfile (supersedes #23)#29

Draft
alex-mextner wants to merge 2 commits into
mainfrom
rig-global-excludesfile
Draft

feat(rig): provision .claude/worktrees ignore via global core.excludesfile (supersedes #23)#29
alex-mextner wants to merge 2 commits into
mainfrom
rig-global-excludesfile

Conversation

@alex-mextner

Copy link
Copy Markdown
Owner

What & why

Rebuilds #23's gitignore feature under the CTO-chosen GLOBAL design.

#23 wrote a managed block into each repo's committed .gitignore (per-repo, needs a rig apply per repo). The CTO rejected that and chose a global git excludesfile: rig manages ONE marker block in git's global core.excludesfile, so **/.claude/worktrees/ is ignored in every repo on the machinezero per-repo commits, no per-repo rig apply.

This reuses #23's tested marker-reconcile machine (create/update/ok/conflict/io_error, the offset-based splice, drift parity, the gitignore: schema), retargeted to the global file.

Design

Global config, wired like the git-hooks dispatcher — a git config --global setting plus a managed file, living in the GLOBAL rig layer. Default ON at plan level (an absent gitignore key still provisions), so the global block is not scaffolded into committed repo rig.yaml.

Target resolution (at apply time):

  • core.excludesfile already set (this machine: ~/.gitignore) → manage the block in that file, leave git config alone (respect the user's choice).
  • core.excludesfile unset → set it to the XDG default ~/.config/git/ignore and write the block there. So on a clean machine rig init does everything itself (set the config if absent + write the block).
  • gitignore.excludesfile: override → force a specific file.

Managed block — fenced by # >>> rig-managed (do not edit) >>> / # <<< … <<<, a fixed explanatory comment, then the entries (default ["**/.claude/worktrees/"], configurable). Only the bytes between the markers are ever touched; everything else (CRLF, trailing blanks, no-final-newline) is preserved verbatim.

Strict idempotency — a re-apply is a byte-identical no-op. If a prior non-idempotent tool appended the block more than once, rig collapses the managed region to one correct block, preserving any user line that sits between duplicated blocks (splice per marker-pair, not first-begin..last-end). .serena/ is deliberately not ignored (committed shared memory).

Driftrig status flags a missing/divergent/duplicated block, and an unset core.excludesfile rig would set, in the global section. rig apply reconciles.

Tests / smoke

  • uv run pytest tests/509 passed.
  • bash tests/smoke.shexit 0 (HOME-isolated leg asserts core.excludesfile set to the XDG default, the block written, and markers=2 byte-stable across re-apply; pytest leg green).
  • No test or smoke ever runs real git config --global or writes the real ~/.gitignore: the git-config read/write seams are stubbed suite-wide and HOME/XDG are isolated.
  • Zero-churn no-op confirmed against this machine's existing ~/.gitignore block: resolve_global_excludes(...)ok (no rewrite).
  • Ran multi-model review on the diff; addressed findings (interleaved-user-line preservation, XDG test isolation, removed the global block from the committed repo scaffold, doctor→status doc fix, CRLF create-path test, SYNC note on the duplicated path-expander).

Supersedes #23

#23 should be closed as superseded — leaving that to the CTO (not closing it here).

Notes

🤖 Generated with Claude Code

alex-mextner and others added 2 commits June 17, 2026 00:32
…sfile

Rebuild #23's gitignore feature under the CTO-chosen GLOBAL design: rig owns
ONE marker-delimited block in git's global core.excludesfile so the harness's
throwaway **/.claude/worktrees/ is ignored in EVERY repo on the machine, with
zero per-repo commits and no per-repo `rig apply`.

Wired like the git-hooks dispatcher (a `git config --global` setting + a
managed file), in the GLOBAL rig layer. Target resolution at apply time:
honor an existing core.excludesfile; when unset, set it to ~/.config/git/ignore
AND write the block there (so a clean machine is fully provisioned by `rig
init` alone). Reuses #23's marker reconcile machine (create/update/ok/conflict/
io_error), retargeted, plus:
  - strict idempotency: a re-apply is a byte-identical no-op;
  - dedup of the managed region: several duplicated blocks collapse to one,
    preserving user content BETWEEN blocks (splice per marker-pair, not
    first-begin..last-end);
  - drift in the GLOBAL section flags a missing/divergent/duplicated block and
    an unset core.excludesfile rig would set.

Default ON at plan level (an absent `gitignore` key still provisions), so the
GLOBAL block is NOT scaffolded into committed repo rig.yaml. The canonical
block text (markers + fixed comment + entry) is byte-identical to what a
provisioned machine already has, making re-apply zero-churn.

Review: ran multi-model `review` on this diff; addressed findings (interleaved
user-line preservation, XDG isolation in tests, scaffold cleanup, doc fixes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tests/test_global_excludes.py: target resolution both ways (core.excludesfile
  set vs unset, with injected git-config seams so no test runs real `git config
  --global`), every reconcile state, STRICT byte-identical re-apply, the
  dedup-of-managed-region collapse (incl. preserving a user line between blocks),
  CRLF/trailing-blank/no-final-newline preservation, drift parity, disabled-but-
  installed leftover scan, and a byte-stable canonical-block regression pin.
- conftest: autouse fixtures isolate HOME + XDG_CONFIG_HOME and stub the
  git-config read/write seams suite-wide, so a full-plan e2e test can never
  touch the real ~/.gitignore or real global git config.
- smoke.sh: HOME-isolated leg exercises the real init/apply flow — asserts
  core.excludesfile is set to the XDG default, the block is written, and the
  block is byte-stable (markers=2) across a re-apply. Also makes the mcp leg
  conditional on the carrier and prefers `uv run pytest`.
- docs/config-schema.md: the `gitignore` (global core.excludesfile) section and
  validation note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant