Skip to content

feat!: keep local edits on sync by default and add loadout adopt - #24

Draft
machado144 wants to merge 1 commit into
mainfrom
feat/drift-safe-sync
Draft

machado144 wants to merge 1 commit into
mainfrom
feat/drift-safe-sync

Conversation

@machado144

Copy link
Copy Markdown
Contributor

Refs #23 — spec 010 is pending review; this is a draft until it is approved.

What

loadout sync used to overwrite hand-edited files it had installed (drifted-local) and show them as a plain update. Now those edits are kept unless you say otherwise.

Before (v0.1.0, with a hand-edited conventions block in ~/.claude/CLAUDE.md):

Plan: 2 change(s)
  update claude-code: CLAUDE.md  (conventions)
  create claude-code: skills/other/SKILL.md  (other)

After:

Plan: 1 change(s)
  create claude-code: skills/other/SKILL.md  (other)
  ! keep   claude-code: CLAUDE.md  (conventions) — edited locally since install; not overwritten

1 file(s) have local edits that are not in the store — kept as-is.
  inspect:  loadout diff conventions --agent claude-code
  keep:     loadout adopt conventions --agent claude-code   (copies the edits into the store)
  discard:  re-run with --force           (a snapshot is taken first)

With --force, the line reads ! overwrite-local … and the file is snapshotted before the write.

Changes

  • engine: new Item.LocalEdit covers skill/command/hook-script files and managed blocks that are drifted-local, or where both the store and the disk changed. Local edits are left out of Changes() by default and come back in with OverwriteConflicts (--force). A skipped edit keeps its previous install record.
  • Shared files (several blocks in one CLAUDE.md): a held-back block no longer carries over into the planned content of its sibling blocks. Apply now re-merges each block/JSON fragment into the file as it is on disk at write time. Without this, updating block B would also have reverted block A's local edit.
  • loadout adopt <artifact> --agent <agent>: rebuilds the store content from the installed copy, shows the store diff and asks before writing (--dry-run, --yes). It snapshots the store first, so loadout restore can undo it, and refreshes the install record. It refuses when the agent rewrites the format, e.g. gemini-cli installs commands as generated skills. Correctness is checked by re-rendering the result and requiring an exact byte match with disk.
  • diff (CLI and web) now also shows held-back items. The web sync dock labels them ! keep / ! discard, and the TUI logs them separately.
  • MergeJSON targets (settings, MCP) still re-converge as before. Agents rewrite those files themselves, and the merge only touches keys loadout owns.

Behaviour change

Anyone who relied on sync to undo hand edits now has to pass --force. Spec 004 R5 is annotated as superseded. Hence feat!.

Tests

  • TDD: internal/engine/localedit_test.go first reproduced the silent overwrite (failing), then the fix made it pass. It also covers both-moved, store-only (still a plain update), forced+snapshot, and the shared-file sibling case, including a mutation check of the plan-time threading.
  • internal/engine/adopt_test.go: block and skill adopt, disk-only files ignored, the confirmation gate, refusal for an unknown or uninstalled artifact, and gemini-cli refused as not reversible.
  • integration/drift_test.go (real binary, temp HOME): dry-run marking, a second sync still protects the edit, diff shows it, --force followed by restore brings the edit back, adopt dry-run/decline/yes leaves status in-sync and the next sync a no-op, and bad invocations fail.
  • go test -race ./..., -tags integration, golangci-lint (0 issues), vitest (69 passed). The web dist was rebuilt.

🤖 Generated with Claude Code

* sync/install/switch hold back hand-edited skills, commands and managed blocks (! keep), --force overwrites them after a snapshot (! overwrite-local)
* skipped local edits keep their install record; shared files re-merge at write time so a sibling block never reverts a held-back edit
* diff (CLI + web) shows held-back items; web dock and TUI label them distinctly
* new loadout adopt <artifact> --agent <agent> copies local edits into the store, verified by a byte-exact re-render, store snapshotted first

Refs #23

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

✅ PR title follows the required format

Current title: feat!: keep local edits on sync by default and add loadout adopt

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