|
| 1 | +--- |
| 2 | +description: Update a starter-kit project with the latest commands, hooks, skills, and rules |
| 3 | +argument-hint: [--force] |
| 4 | +allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion |
| 5 | +--- |
| 6 | + |
| 7 | +# Update Starter Kit Project |
| 8 | + |
| 9 | +Update an existing starter-kit project with the latest commands, hooks, skills, agents, and rules from the current starter kit source. Smart merge — replaces starter kit files with newer versions while preserving any custom files the user created themselves. |
| 10 | + |
| 11 | +**Arguments:** $ARGUMENTS |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## Step 0 — Resolve Source (Starter Kit) |
| 16 | + |
| 17 | +Find the starter kit source directory: |
| 18 | + |
| 19 | +1. If CWD has BOTH `claude-mastery-project.conf` AND `.claude/commands/new-project.md` → use CWD as `$SOURCE` |
| 20 | +2. Else read `~/.claude/starter-kit-source-path` → verify it still has both files |
| 21 | +3. Else ask via AskUserQuestion: "Where is the starter kit cloned?" with a text input |
| 22 | + |
| 23 | +Store as `$SOURCE`. |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## Step 1 — Registry Picker (Select Target) |
| 28 | + |
| 29 | +1. Read `~/.claude/starter-kit-projects.json` |
| 30 | + - If file doesn't exist or empty → error: "No projects found. Use `/new-project` to create one first." |
| 31 | + - If file is invalid JSON → error: "Project registry is corrupted. Check `~/.claude/starter-kit-projects.json`." |
| 32 | + |
| 33 | +2. Filter to projects whose `path` directory still exists on disk |
| 34 | + |
| 35 | +3. If no valid projects remain → error: "No registered projects found on disk. Use `/new-project` to create one." |
| 36 | + |
| 37 | +4. Display list with AskUserQuestion: |
| 38 | + - "Which project do you want to update?" |
| 39 | + - Options: up to 4 most recent projects (by `createdAt`), each showing: `name — language/framework — path` |
| 40 | + - If more than 4: the 4th option should be "Other (type a path)" |
| 41 | + |
| 42 | +5. Store selected path as `$TARGET` |
| 43 | + |
| 44 | +### Validations (all must pass — stop with clear error if any fail) |
| 45 | + |
| 46 | +1. `$TARGET` directory exists → if not: "Directory not found: $TARGET" |
| 47 | +2. `$TARGET` is a git repo → run: `git -C "$TARGET" rev-parse --is-inside-work-tree 2>/dev/null` |
| 48 | + - If not a git repo: "This project must be a git repo. Run `git init && git commit --allow-empty -m 'init'` first." |
| 49 | +3. `$TARGET` is NOT the starter kit itself (compare resolved paths of `$SOURCE` and `$TARGET`) |
| 50 | + - If same: "Cannot update the starter kit itself." |
| 51 | +4. `$TARGET` is registered in `~/.claude/starter-kit-projects.json` |
| 52 | + - If not registered: "This project isn't in the registry. Use `/convert-project-to-starter-kit` instead." |
| 53 | + |
| 54 | +Parse `--force` from `$ARGUMENTS` — if present, set `$FORCE=true` (skips confirmation prompts). |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## Step 2 — Safety Commit |
| 59 | + |
| 60 | +```bash |
| 61 | +cd "$TARGET" |
| 62 | +git status --porcelain |
| 63 | +``` |
| 64 | + |
| 65 | +- **If uncommitted changes exist** (git status --porcelain has output): |
| 66 | + ```bash |
| 67 | + cd "$TARGET" && git add -A && git commit -m "chore: pre-update snapshot (before starter kit update)" |
| 68 | + ``` |
| 69 | + |
| 70 | +- **If clean** (no uncommitted changes): |
| 71 | + ```bash |
| 72 | + cd "$TARGET" && git commit --allow-empty -m "chore: pre-update marker (before starter kit update)" |
| 73 | + ``` |
| 74 | + |
| 75 | +Store the hash: `PRE_UPDATE_HASH=$(git -C "$TARGET" rev-parse HEAD)` |
| 76 | + |
| 77 | +**STOP if git fails** (except "nothing to commit" which is fine — treat as clean). |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## Step 3 — Inventory & Diff |
| 82 | + |
| 83 | +Build a manifest of what the starter kit currently has vs what the target has. |
| 84 | + |
| 85 | +### Categories to compare |
| 86 | + |
| 87 | +| Category | Source Location | Target Location | |
| 88 | +|----------|----------------|-----------------| |
| 89 | +| Commands | `$SOURCE/.claude/commands/*.md` | `$TARGET/.claude/commands/*.md` | |
| 90 | +| Hooks | `$SOURCE/.claude/hooks/*.{sh,py}` | `$TARGET/.claude/hooks/*.{sh,py}` | |
| 91 | +| Skills | `$SOURCE/.claude/skills/*/SKILL.md` | `$TARGET/.claude/skills/*/SKILL.md` | |
| 92 | +| Agents | `$SOURCE/.claude/agents/*.md` | `$TARGET/.claude/agents/*.md` | |
| 93 | + |
| 94 | +### For each file, classify as: |
| 95 | + |
| 96 | +- **NEW** — exists in source, not in target → will be added |
| 97 | +- **UPDATED** — exists in both, content differs (compare with `diff -q`) → will be replaced |
| 98 | +- **UNCHANGED** — exists in both, content is identical → skip |
| 99 | +- **CUSTOM** — exists in target only, not in source → never touched (user-created) |
| 100 | + |
| 101 | +### Display the diff report |
| 102 | + |
| 103 | +``` |
| 104 | +=== Starter Kit Update Report === |
| 105 | +
|
| 106 | +Target: $TARGET |
| 107 | +Source: $SOURCE |
| 108 | +
|
| 109 | +Commands: |
| 110 | + + NEW: update-project.md, show-user-guide.md |
| 111 | + ↻ UPDATED: commit.md, review.md (starter kit versions changed) |
| 112 | + = UNCHANGED: help.md, progress.md, ... (12 files) |
| 113 | + ○ CUSTOM: my-custom-command.md (yours, not touched) |
| 114 | +
|
| 115 | +Hooks: |
| 116 | + + NEW: (none) |
| 117 | + ↻ UPDATED: check-branch.sh |
| 118 | + = UNCHANGED: block-secrets.py, lint-on-save.sh, ... |
| 119 | + ○ CUSTOM: (none) |
| 120 | +
|
| 121 | +Skills: (all unchanged) |
| 122 | +Agents: (all unchanged) |
| 123 | +
|
| 124 | +settings.json: hooks will be deep-merged |
| 125 | +CLAUDE.md: 3 new sections will be appended |
| 126 | +Infrastructure: .gitignore (2 lines to add), .env.example (1 key to add) |
| 127 | +
|
| 128 | +Total: N files to add, N files to update, N unchanged, N custom (untouched) |
| 129 | +``` |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Step 4 — Confirm (unless --force) |
| 134 | + |
| 135 | +If not `$FORCE`, ask via AskUserQuestion: |
| 136 | + |
| 137 | +"Apply these updates? N new files, N updated files. Your custom files won't be touched." |
| 138 | +- **Yes, update** (Recommended) |
| 139 | +- **Show me the diffs first** — for each UPDATED file, show `diff` output between source and target, then ask again |
| 140 | +- **No, cancel** |
| 141 | + |
| 142 | +If user selects "No, cancel" → stop immediately with: "Update cancelled. No changes made." |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## Step 5 — Apply Updates |
| 147 | + |
| 148 | +### 5a. Commands, Hooks, Skills, Agents |
| 149 | + |
| 150 | +For each **NEW** file: copy from source to target. |
| 151 | +For each **UPDATED** file: overwrite target with source version. |
| 152 | +For **CUSTOM** and **UNCHANGED**: skip entirely. |
| 153 | + |
| 154 | +For skills: copy the entire skill directory (e.g., `$SOURCE/.claude/skills/code-review/` → `$TARGET/.claude/skills/code-review/`). |
| 155 | + |
| 156 | +Make hooks executable: |
| 157 | +```bash |
| 158 | +chmod +x "$TARGET/.claude/hooks/"*.sh 2>/dev/null |
| 159 | +chmod +x "$TARGET/.claude/hooks/"*.py 2>/dev/null |
| 160 | +``` |
| 161 | + |
| 162 | +### 5b. settings.json — Deep Merge |
| 163 | + |
| 164 | +Same merge logic as `/convert-project-to-starter-kit`: |
| 165 | + |
| 166 | +1. Read both `$SOURCE/.claude/settings.json` and `$TARGET/.claude/settings.json` as JSON |
| 167 | +2. `permissions.deny`: merge arrays, deduplicate by value |
| 168 | +3. For each hook event type (`PreToolUse`, `PostToolUse`, `Stop`): |
| 169 | + - For each matcher entry in source: check if target already has same matcher string |
| 170 | + - Same matcher → merge the `hooks` arrays (deduplicate by `command` string) |
| 171 | + - New matcher → add entire entry to target |
| 172 | +4. NEVER remove existing entries from target |
| 173 | +5. Write merged result to `$TARGET/.claude/settings.json` |
| 174 | + |
| 175 | +If target has no `.claude/settings.json`: copy from source directly. |
| 176 | + |
| 177 | +### 5c. CLAUDE.md — Section Merge |
| 178 | + |
| 179 | +Same as `/convert-project-to-starter-kit`: |
| 180 | + |
| 181 | +1. Parse both by `## ` (h2) headers |
| 182 | +2. For each section in source not in target → append at end of target CLAUDE.md |
| 183 | +3. For Critical Rules: sub-merge by `### ` (h3) numbered rules — add missing rules, keep existing |
| 184 | +4. NEVER remove or replace existing sections |
| 185 | + |
| 186 | +If target has no `CLAUDE.md`: skip (don't create — the project should already have one from initial creation). |
| 187 | + |
| 188 | +### 5d. Infrastructure Files |
| 189 | + |
| 190 | +| File | If Missing in Target | If Exists in Target | |
| 191 | +|------|---------------------|---------------------| |
| 192 | +| `CLAUDE.local.md` | Copy from source | Skip | |
| 193 | +| `claude-mastery-project.conf` | Copy from source | Skip | |
| 194 | +| `project-docs/ARCHITECTURE.md` | Create `project-docs/` dir, copy | Skip | |
| 195 | +| `project-docs/INFRASTRUCTURE.md` | Copy | Skip | |
| 196 | +| `project-docs/DECISIONS.md` | Copy | Skip | |
| 197 | +| `.env.example` | Copy from source | Merge: read both, add lines from source whose key name (before `=`) doesn't exist in target. Append missing lines at end. | |
| 198 | +| `.gitignore` | Copy from source | Merge: add lines from source that don't exist in target. Ensure `.env`, `CLAUDE.local.md`, `_ai_temp/` are present. | |
| 199 | +| `.dockerignore` | Copy from source | Merge: add lines from source that don't exist in target. | |
| 200 | + |
| 201 | +--- |
| 202 | + |
| 203 | +## Step 6 — Update Registry |
| 204 | + |
| 205 | +1. Read `~/.claude/starter-kit-projects.json` |
| 206 | +2. Find the project entry by `path` matching `$TARGET` |
| 207 | +3. Add or update `updatedAt` field with current ISO timestamp |
| 208 | +4. Increment `updateCount` field (start at 1 if missing, increment if exists) |
| 209 | +5. Write updated registry back to `~/.claude/starter-kit-projects.json` |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Step 7 — Commit + Summary |
| 214 | + |
| 215 | +```bash |
| 216 | +cd "$TARGET" |
| 217 | +git add -A |
| 218 | +git commit -m "chore: update Claude Code Starter Kit infrastructure" |
| 219 | +``` |
| 220 | + |
| 221 | +Store: `UPDATE_HASH=$(git -C "$TARGET" rev-parse HEAD)` |
| 222 | + |
| 223 | +**If nothing to commit** (all files unchanged): skip the commit, note "Already up to date." |
| 224 | + |
| 225 | +### Display summary |
| 226 | + |
| 227 | +``` |
| 228 | +=== Starter Kit Update Complete === |
| 229 | +
|
| 230 | +Target: $TARGET |
| 231 | +
|
| 232 | +Commands: N added, N updated, N unchanged, N custom |
| 233 | +Hooks: N added, N updated, N unchanged, N custom |
| 234 | +Skills: N added, N updated, N unchanged, N custom |
| 235 | +Agents: N added, N updated, N unchanged, N custom |
| 236 | +settings.json: deep merged (N new hooks added) / unchanged / copied |
| 237 | +CLAUDE.md: N sections added, N skipped (exists) |
| 238 | +Infrastructure: N files added, N merged, N skipped |
| 239 | +
|
| 240 | +Pre-update commit: $PRE_UPDATE_HASH |
| 241 | +Update commit: $UPDATE_HASH |
| 242 | +
|
| 243 | +To undo: git revert HEAD |
| 244 | +To review: git diff $PRE_UPDATE_HASH..HEAD |
| 245 | +
|
| 246 | +Next: Run /help to see any new commands. |
| 247 | +``` |
| 248 | + |
| 249 | +--- |
| 250 | + |
| 251 | +## Edge Cases |
| 252 | + |
| 253 | +1. **Already up to date** — If all files are UNCHANGED and no infrastructure changes needed, report "Already up to date — no changes needed." and skip the update commit. |
| 254 | + |
| 255 | +2. **Target has no .claude/ directory** — This shouldn't happen for registered projects, but if it does, create the directories and treat all files as NEW. |
| 256 | + |
| 257 | +3. **Git fails** — If any git operation fails (commit, add), stop with a clear error. Never leave the project in a half-updated state. |
| 258 | + |
| 259 | +4. **Custom files with same name as starter kit** — If a user happened to create a file with the same name as a starter kit file (e.g., `help.md`), it will show as UPDATED (content differs). The diff report makes this visible before applying. |
0 commit comments