Removal only derives a path from an exact <kind>:<key> state id with a known
kind; any other record is dropped with an error and nothing is deleted
(an id like "skill" used to remove ~/.claude/skills/skill). Invalid plugin
records are dropped too, and a failed chmod 600 on settings.json is reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Validate manifest structure up front; malformed items are skipped with a
reason instead of aborting extraction; an unreadable manifest changes
nothing (no removals).
- Empty/whitespace settings.json reads as {}; non-object settings are left
untouched; hook installs/updates/removals are reported and recorded only
once their entries are actually merged; mv failures are checked.
- Dangling symlinks at user paths count as occupied.
- Removal paths are derived from kind+key, never taken from state.json.
- A symlinked settings.json is written through, not replaced.
- mktemp failure emits a JSON report instead of exiting silently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constant POSIX sh + jq script (embedded via include_str!) that applies the
payload into ~/.claude, tracks ownership in state.json, never overwrites
user-owned files, merges hook entries surgically, drives claude plugin and
prints a JSON SyncReport. Also: settings.json kept 0600 (pre-flight N11),
payloads containing symlinks are refused, slugs parsed via @tsv.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>