--- description: Sync forked skills and commands with their upstreams via 3-way merge against a committed baseline. Discovers forks from upstreams/*/manifest.json in the rulesets repo, runs scripts/update-skills.py check per fork (clones upstream to a cache, classifies every file as unchanged / upstream-changed / local-only / both-changed / upstream-new / local-new / upstream-deleted), then walks the user through applying upstream changes — per-file confirmation first, per-hunk keep-local / take-upstream / both / skip prompts when a 3-way merge conflicts. Updates last_synced_commit and refreshes the baseline on completion. Use to pull upstream fixes into arch-decide, playwright-js, playwright-py, or any future fork without losing local modifications. Do NOT use to create a new fork (write the manifest + run bootstrap by hand), to sync .ai/ templates (startup's rsync owns that), or outside the rulesets repo. disable-model-invocation: true --- # /update-skills — Sync forks with their upstreams Pull upstream changes into forked skills/commands deliberately: classify, confirm per file, resolve conflicts per hunk, never lose a local modification silently. ## Usage ``` /update-skills [FORK ...] [--dry-run] ``` - `FORK` — one or more fork names (`arch-decide`, `playwright-js`, `playwright-py`). Default: all forks registered under `upstreams/`. - `--dry-run` — run the classification and report what a sync would do; write nothing. ## Layout Each fork is registered at `upstreams//`: - `manifest.json` — upstream `url`, `ref`, `subpath`, repo-relative `target`, optional `files` map (upstream-path → target-path, for forks whose files were renamed, like arch-decide's `SKILL.md` → `arch-decide.md`), `license`, `last_synced_commit`. - `baseline/` — committed snapshot of the upstream at the last sync, mirroring the *target* layout. This is the 3-way merge base; without it every both-changed file degrades to side-by-side review. The helper is `scripts/update-skills.py`. It never writes a fork's target files — it classifies (`check`), merges to stdout (`merge-file`), and maintains the manifest + baseline (`bootstrap`, `mark-synced`). All target writes happen in this command's flow, with the user's per-file confirmation. ## Flow ### 1. Discover and check ``` scripts/update-skills.py list scripts/update-skills.py check --json ``` Run `check` for each requested fork. Network is required; if a clone fails, report the fork as unreachable and continue with the rest (degrade, don't abort the batch). Present a per-fork classification table. If everything is `unchanged` (or only `local-only`/`local-new`, which need no sync), say so and skip to the next fork. `--dry-run` stops here. ### 2. Apply upstream changes, per file Process only the statuses that carry upstream movement, walking each file with a confirmation (yes / no / show-diff) before touching it: - `upstream-changed` — local matches baseline, upstream moved. Safe fast path: copy the upstream version from the cache checkout (`/tmp/update-skills///...`) over the target file. - `upstream-new` — new file upstream. Offer to add it at the target path. - `both-changed` — run the 3-way merge: ``` scripts/update-skills.py merge-file ``` Exit 0: clean merge — show the result, write it to the target on confirmation. Exit 1: conflict — the output carries `<<<<<<< local` / `=======` / `>>>>>>> upstream` markers. Walk each conflicting hunk with the user, showing the local and upstream sides (and the baseline text when it clarifies): 1. keep local 2. take upstream 3. both (local then upstream) 4. skip — leave the markers for manual resolution later Assemble the resolved hunks and write the final file. A skipped hunk leaves its markers in place; flag every skipped file in the summary so it isn't forgotten. - `upstream-deleted` — upstream removed a file the baseline had. Ask: delete locally, or keep (it becomes `local-new` at the next baseline refresh). Never delete without asking. Statuses needing no action: `unchanged`, `local-only` (our modification, preserved), `local-new` (our addition, preserved), `local-deleted` (we removed it deliberately), `no-baseline` (see below). ### 3. Mark synced When every file in a fork is resolved (no conflict markers left anywhere): ``` scripts/update-skills.py mark-synced ``` This refreshes `baseline/` to the checked upstream commit and stamps `last_synced_commit`. Skip this step if any file still carries markers — the baseline must only advance past fully-absorbed upstream states. ### 4. Summarize and commit Per fork: upstream commit synced to, files taken / merged / conflicted / skipped, anything left for manual follow-up. Then commit the result (targets + `upstreams/` baselines + manifests together) through the normal review-and-publish flow — one commit per fork when changes are substantial, one batch commit when they're small. ## Fallback: missing baseline `no-baseline` statuses mean the fork was never bootstrapped (or its baseline was deleted). No 3-way merge is possible. Offer two paths: 1. Side-by-side review: show local vs upstream per file and let the user pick, then `bootstrap` to seed the baseline going forward. 2. Seed first: `scripts/update-skills.py bootstrap ` snapshots the *current* upstream as the baseline. Local differences from it become `local-only` — correct from then on, but upstream changes made before the seed are invisible to future merges. ## Notes - Baselines were first seeded 2026-06-11 from the then-current upstream HEADs, so each fork's pre-existing local modifications are tracked as `local-only` from that point. - Persistent `local-new` files are normal (e.g. playwright-js's `package-lock.json` and repo-root-copied `LICENSE`); they never block a sync. - Upstream renames surface as `upstream-deleted` + `upstream-new`. Handle as a pair: take the new file, then decide the old one's deletion. For mapped forks (arch-decide), update the manifest's `files` map instead.