update · git:20260525.9519f39 · 2026-05-25 · sha256 9c7e1866e3a0d742

update git:20260525.9519f39A

Immutable. This exact content is served forever at /api/v1/blob/9c7e1866e3a0d742.

---
name: update
description: |
  Safe cc10x upgrade that preserves local modifications.
  Stashes diffs, pulls upstream, rebuilds cache, rebases patches.

  Use this skill when: updating cc10x, upgrading, pulling latest cc10x,
  syncing plugin, refreshing cache, or checking for new versions.

  Triggers: update cc10x, upgrade cc10x, pull cc10x, sync plugin, refresh cc10x,
  check for updates, new version, update plugin, upgrade plugin.
allowed-tools: Read, Bash, AskUserQuestion
---

# cc10x Update

Safe upgrade that preserves your local modifications to cached skill files.

**Workflow:** Discover versions → Stash local diffs → Pull upstream → Rebuild cache → Rebase patches → Verify.

---

## Paths (Resolve Once)

Resolve these paths at the start and reuse throughout. All paths are deterministic from the plugin registry.

```bash
REGISTRY="$HOME/.claude/plugins/installed_plugins.json"
KNOWN_MARKETPLACES="$HOME/.claude/plugins/known_marketplaces.json"
CACHE_ROOT="$HOME/.claude/plugins/cache/cc10x/cc10x"
BACKUP_DIR="$HOME/.claude/plugins/cache/cc10x/_backup_$(date +%Y%m%d_%H%M%S)"
```

**Pre-flight check:**

```
Read(file_path="~/.claude/plugins/installed_plugins.json")
```

Extract the `cc10x@cc10x` entry. If missing → STOP: "cc10x is not installed. Install it first with `/install cc10x`."

Then resolve the marketplace repo location:

```
Read(file_path="~/.claude/plugins/known_marketplaces.json")
```

Extract `cc10x.installLocation` → this is `MARKETPLACE_ROOT` (the local git clone of the cc10x repo).
The plugin source lives at `MARKETPLACE_ROOT/plugins/cc10x/`.

Verify the marketplace repo exists:

```
Bash(command="test -d \"$MARKETPLACE_ROOT/.git\" && echo 'OK' || echo 'MISSING'")
```

If MISSING → STOP: "Marketplace repo not found at $MARKETPLACE_ROOT. Re-add it with `claude plugins add-marketplace`."

---

## Phase 1: Discovery

### 1.1 Read installed version

From `installed_plugins.json`, extract for the `cc10x@cc10x` entry:
- `INSTALLED_VERSION` → `.version`
- `CURRENT_CACHE_PATH` → `.installPath`
- `INSTALLED_SHA` → `.gitCommitSha` (may be absent)
- `INSTALL_SCOPE` → `.scope` (user or local)

### 1.2 Read marketplace version

```
Read(file_path="$MARKETPLACE_ROOT/.claude-plugin/marketplace.json")
```

Extract `MARKETPLACE_VERSION` → `.metadata.version`

### 1.3 Check upstream for new commits

```bash
cd "$MARKETPLACE_ROOT" && git fetch origin main --quiet 2>&1
```

```bash
LOCAL_SHA=$(cd "$MARKETPLACE_ROOT" && git rev-parse HEAD)
REMOTE_SHA=$(cd "$MARKETPLACE_ROOT" && git rev-parse origin/main)
```

### 1.4 Display status

Output a status table:

```
## cc10x Update Status

| Field              | Value                    |
|--------------------|--------------------------|
| Installed version  | $INSTALLED_VERSION       |
| Marketplace version| $MARKETPLACE_VERSION     |
| Local SHA          | ${LOCAL_SHA:0:10}        |
| Remote SHA         | ${REMOTE_SHA:0:10}       |
| Cache path         | $CURRENT_CACHE_PATH      |
| Status             | Up-to-date / Update available / Registry drift |
```

If `LOCAL_SHA == REMOTE_SHA` AND the cache path exists AND matches marketplace version:
→ Report "Already up-to-date" and offer a **re-sync** option (skip to Phase 3 to rebuild cache from current marketplace source).

If `LOCAL_SHA != REMOTE_SHA`:
→ Show changelog preview (top 10 new commits):

```bash
cd "$MARKETPLACE_ROOT" && git log --oneline HEAD..origin/main | head -10
```

### 1.5 Gate: Proceed?

```
AskUserQuestion(questions=[{
  "question": "Proceed with cc10x update?",
  "header": "Update",
  "options": [
    {"label": "Yes, update", "description": "Stash local changes, pull upstream, rebuild cache"},
    {"label": "Re-sync only", "description": "Rebuild cache from current marketplace source (no git pull)"},
    {"label": "Cancel", "description": "Do nothing"}
  ],
  "multiSelect": false
}])
```

- **Cancel** → STOP with "Update cancelled."
- **Re-sync only** → Skip Phase 3.1 (git pull), go directly to 3.2.
- **Yes, update** → Continue to Phase 2.

---

## Phase 2: Stash Local Modifications

**Critical timing:** Diff cache against the *current* (un-pulled) marketplace source. This isolates user changes from upstream changes.

### 2.1 Enumerate cache files

```bash
find "$CURRENT_CACHE_PATH" -type f | sort
```

### 2.2 Diff each file against marketplace source

For each file in the cache, compute its relative path and diff against the marketplace counterpart:

```bash
MARKETPLACE_SOURCE="$MARKETPLACE_ROOT/plugins/cc10x"

for CACHE_FILE in $(find "$CURRENT_CACHE_PATH" -type f | sort); do
  REL_PATH="${CACHE_FILE#$CURRENT_CACHE_PATH/}"
  MARKET_FILE="$MARKETPLACE_SOURCE/$REL_PATH"

  if [ -f "$MARKET_FILE" ]; then
    DIFF=$(diff -u "$MARKET_FILE" "$CACHE_FILE" 2>/dev/null)
    if [ -n "$DIFF" ]; then
      # File was locally modified
      mkdir -p "$BACKUP_DIR/patches/$(dirname "$REL_PATH")"
      mkdir -p "$BACKUP_DIR/originals/$(dirname "$REL_PATH")"
      echo "$DIFF" > "$BACKUP_DIR/patches/$REL_PATH.patch"
      cp "$CACHE_FILE" "$BACKUP_DIR/originals/$REL_PATH"
    fi
  else
    # User-added file (no marketplace counterpart)
    mkdir -p "$BACKUP_DIR/new_files/$(dirname "$REL_PATH")"
    cp "$CACHE_FILE" "$BACKUP_DIR/new_files/$REL_PATH"
  fi
done
```

### 2.3 Report modifications

Count and list modified files:

```bash
PATCH_COUNT=$(find "$BACKUP_DIR/patches" -name "*.patch" 2>/dev/null | wc -l | tr -d ' ')
NEW_COUNT=$(find "$BACKUP_DIR/new_files" -type f 2>/dev/null | wc -l | tr -d ' ')
```

Output:
```
## Local Modifications Found

- **$PATCH_COUNT** files with local changes (patches saved)
- **$NEW_COUNT** user-added files (backed up)
- Backup location: `$BACKUP_DIR`
```

If `PATCH_COUNT == 0` and `NEW_COUNT == 0`:
→ "No local modifications detected. Clean update."

List modified files with short summary of each patch (lines added/removed).

---

## Phase 3: Pull & Rebuild Cache

### 3.1 Git pull (skip if re-sync)

```bash
cd "$MARKETPLACE_ROOT" && git pull origin main --ff-only 2>&1
```

If fast-forward fails (diverged history):
→ STOP: "Marketplace repo has diverged from upstream. Resolve manually with `cd $MARKETPLACE_ROOT && git status`."

### 3.2 Read new version

```
Read(file_path="$MARKETPLACE_ROOT/.claude-plugin/marketplace.json")
```

Extract `NEW_VERSION` from `.metadata.version`.

### 3.3 Copy to new cache directory

```bash
NEW_CACHE_PATH="$CACHE_ROOT/$NEW_VERSION"
mkdir -p "$NEW_CACHE_PATH"
cp -r "$MARKETPLACE_SOURCE/"* "$NEW_CACHE_PATH/"
```

Verify the copy:

```bash
echo "Skills: $(ls "$NEW_CACHE_PATH/skills/" 2>/dev/null | wc -l | tr -d ' ')"
echo "Agents: $(ls "$NEW_CACHE_PATH/agents/" 2>/dev/null | wc -l | tr -d ' ')"
echo "Hooks:  $(ls "$NEW_CACHE_PATH/hooks/" 2>/dev/null | wc -l | tr -d ' ')"
```

### 3.4 Update registry

Use Python3 to safely update `installed_plugins.json`:

```bash
python3 -c "
import json, sys
from datetime import datetime, timezone

registry_path = sys.argv[1]
new_version = sys.argv[2]
new_cache_path = sys.argv[3]
new_sha = sys.argv[4]

with open(registry_path) as f:
    reg = json.load(f)

entry = reg['plugins']['cc10x@cc10x'][0]
entry['version'] = new_version
entry['installPath'] = new_cache_path
entry['lastUpdated'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
if new_sha:
    entry['gitCommitSha'] = new_sha

with open(registry_path, 'w') as f:
    json.dump(reg, f, indent=2)
    f.write('\n')

print(f'Registry updated: version={new_version}, path={new_cache_path}')
" "$REGISTRY" "$NEW_VERSION" "$NEW_CACHE_PATH" "$NEW_SHA"
```

Where `NEW_SHA` is obtained from:

```bash
NEW_SHA=$(cd "$MARKETPLACE_ROOT" && git rev-parse HEAD)
```

### 3.5 Gate: Old cache cleanup

If `CURRENT_CACHE_PATH != NEW_CACHE_PATH` and the old cache directory still exists:

```
AskUserQuestion(questions=[{
  "question": "Remove old cache version ($INSTALLED_VERSION)?",
  "header": "Cleanup",
  "options": [
    {"label": "Move to backup", "description": "Move old cache to $BACKUP_DIR/old_cache/ (safe, can restore later)"},
    {"label": "Keep it", "description": "Leave old version in place alongside new version"},
    {"label": "Delete", "description": "Permanently remove old cache directory"}
  ],
  "multiSelect": false
}])
```

- **Move to backup** → `mv "$CURRENT_CACHE_PATH" "$BACKUP_DIR/old_cache/"`
- **Keep it** → No action. Mark with `.orphaned_at` timestamp: `date -u > "$CURRENT_CACHE_PATH/.orphaned_at"`
- **Delete** → `rm -rf "$CURRENT_CACHE_PATH"`

---

## Phase 4: Rebase Patches

Skip this phase entirely if no patches were saved in Phase 2.

### 4.1 Apply each patch

For each `.patch` file in `$BACKUP_DIR/patches/`:

```bash
REL_PATH="${PATCH_FILE#$BACKUP_DIR/patches/}"    # e.g., skills/cc10x-router/SKILL.md.patch
TARGET_REL="${REL_PATH%.patch}"                    # e.g., skills/cc10x-router/SKILL.md
TARGET_FILE="$NEW_CACHE_PATH/$TARGET_REL"
```

**Step 1: Dry run**

```bash
patch --dry-run "$TARGET_FILE" < "$PATCH_FILE" 2>&1
```

**Step 2: Apply or handle conflict**

If dry run succeeds (exit code 0):
→ Apply for real: `patch "$TARGET_FILE" < "$PATCH_FILE"`
→ Log: "Patch applied cleanly: $TARGET_REL"

If dry run fails (exit code != 0):
→ Save reject file: `patch "$TARGET_FILE" < "$PATCH_FILE"` (will create `.rej`)
→ Show the conflict context to the user
→ Gate per conflict:

```
AskUserQuestion(questions=[{
  "question": "Patch conflict on $TARGET_REL. How to resolve?",
  "header": "Conflict",
  "options": [
    {"label": "Show diff", "description": "Display the patch content so I can decide"},
    {"label": "Skip patch", "description": "Use upstream version (discard local change)"},
    {"label": "Use backup", "description": "Replace with full backup copy of your modified file"}
  ],
  "multiSelect": false
}])
```

- **Show diff** → Read and display the patch file content, then re-ask with Skip/Use backup options.
- **Skip patch** → No action on this file. Log: "Skipped: $TARGET_REL"
- **Use backup** → `cp "$BACKUP_DIR/originals/$TARGET_REL" "$TARGET_FILE"`

### 4.2 Restore user-added files

If user-added files were saved in Phase 2:

For each file in `$BACKUP_DIR/new_files/`:

```bash
REL_PATH="${NEW_FILE#$BACKUP_DIR/new_files/}"
TARGET="$NEW_CACHE_PATH/$REL_PATH"

if [ ! -f "$TARGET" ]; then
  mkdir -p "$(dirname "$TARGET")"
  cp "$NEW_FILE" "$TARGET"
  echo "Restored user file: $REL_PATH"
else
  echo "Conflict: upstream now has $REL_PATH (your version backed up)"
fi
```

---

## Phase 5: Verify & Report

### 5.1 Verify cache structure

```bash
for DIR in skills agents hooks; do
  if [ -d "$NEW_CACHE_PATH/$DIR" ]; then
    echo "$DIR/ — $(ls "$NEW_CACHE_PATH/$DIR" | wc -l | tr -d ' ') items"
  else
    echo "$DIR/ — MISSING (warning)"
  fi
done
```

### 5.2 Verify registry

```bash
python3 -c "
import json, sys
with open(sys.argv[1]) as f:
    reg = json.load(f)
entry = reg['plugins']['cc10x@cc10x'][0]
print(f\"Registry version:  {entry['version']}\")
print(f\"Registry path:     {entry['installPath']}\")
print(f\"Registry SHA:      {entry.get('gitCommitSha', 'N/A')}\")
print(f\"Last updated:      {entry['lastUpdated']}\")
" "$REGISTRY"
```

### 5.3 Final summary

Output a summary table:

```
## Update Complete

| Field                 | Before               | After                |
|-----------------------|----------------------|----------------------|
| Version               | $INSTALLED_VERSION   | $NEW_VERSION         |
| Cache path            | $CURRENT_CACHE_PATH  | $NEW_CACHE_PATH      |
| Commit SHA            | ${INSTALLED_SHA:0:10}| ${NEW_SHA:0:10}      |
| Patches applied       | N/A                  | $APPLIED_COUNT       |
| Patches conflicted    | N/A                  | $CONFLICT_COUNT      |
| Patches skipped       | N/A                  | $SKIPPED_COUNT       |
| User files restored   | N/A                  | $RESTORED_COUNT      |
| Backup location       | N/A                  | $BACKUP_DIR          |
```

### 5.4 Changelog highlights

```bash
cd "$MARKETPLACE_ROOT" && git log --oneline -10
```

### 5.5 Next steps

Output:
```
**Restart Claude Code** to pick up the new version.

If something went wrong, your backup is at:
  $BACKUP_DIR

To restore from backup:
  cp -r $BACKUP_DIR/originals/* $NEW_CACHE_PATH/
```

---

## Edge Cases

| Scenario | Handling |
|----------|----------|
| Marketplace repo missing | STOP with instructions to re-add marketplace |
| `git fetch` fails (network) | STOP with error message, suggest retry |
| `git pull --ff-only` fails | STOP — diverged history needs manual resolution |
| Registry missing `cc10x@cc10x` | STOP — cc10x not installed |
| Cache path in registry doesn't exist | Warn, proceed with rebuild (registry drift) |
| No local modifications | Skip Phase 2 stash + Phase 4 rebase entirely |
| User-added file conflicts with new upstream file | Warn, keep upstream, backup available |
| `python3` not available | Extremely unlikely (Claude Code requires it), but STOP with error |
| Same version but different SHA | Offer re-sync (content changed without version bump) |
| Multiple entries in `cc10x@cc10x` array | Use the first entry (primary installation) |

## Anti-Patterns

| Don't | Do Instead |
|-------|------------|
| `rm -rf` any directory | `mv` to timestamped backup |
| Diff after `git pull` | Diff before pull (Phase 2 before Phase 3) |
| Use `jq` for JSON | Use `python3 -c 'import json; ...'` (portable) |
| Hardcode paths | Resolve from registry + known_marketplaces |
| Skip dry-run for patches | Always `patch --dry-run` first |
| Auto-resolve conflicts | Ask user per conflict via `AskUserQuestion` |
| Modify marketplace repo files | Only modify cache and registry |
| Use `git stash` on cache | Cache isn't a git repo — use `diff`/`patch` |