--- name: contribute-skill description: Use this skill when contributing local improvements to an installed skill back to its source repo via PR. --- # Contribute Skill When you have improved a skill you installed from another repo, this skill guides you through contributing that improvement back to the source via a pull request. ## When to use - You modified a skill (global `~/.agents/skills//` or repo-internal `.agents/skills//`) and want to send the improvement upstream - The skill was installed from a public repo tracked in `skills-lock.json` Do NOT use for repo-native skills under this repo's `skills//` — if you're the author here, this repo IS the source. ## Source repository scope (required) Only update files under the source repo's canonical skills tree: ```text /skills// SKILL.md scripts/ ← include when changed … ← any other files in that skill folder ``` Rules: - **Always** map upstream paths to `skills//…`, even when `skills-lock.json` `skillPath` points at `.agents/skills/…` or another layout in the consumer repo. - **Never** update `.agents/skills/`, duplicate trees, or paths outside `skills//` in the source repository. - Include every changed file under that skill folder (e.g. `scripts/*.mjs`), not only `SKILL.md`. - Never include `SKILL.local.md` — machine-local augmentations stay local. Derive paths: | Input | Upstream path | | --- | --- | | Lock key / folder name `skillify` | `skills//` | | `skillPath`: `.agents/skills/skillify/SKILL.md` | `skills//SKILL.md` | | `skillPath`: `skills/fix-security-pr/SKILL.md` | `skills/fix-security-pr/SKILL.md` | ## Steps ### 1. Identify the skill(s) to patch From context or ask the user. Look up origin using this skill's own `scripts/source.mjs` script, which resolves in a fixed order: the repo-local lock, then the global lock, then `npx skills find`, returning the FIRST hit: ```bash node "/scripts/source.mjs" --format json ``` Output is JSON: `{ name, source, sourceUrl, skillPath, foundIn }`. Extract `source` (`owner/repo`). `foundIn` tells you which rung resolved it (`repo`, `global`, or `npx-skills`). If the script exits non-zero (`foundIn: null`), fall back to reading the lockfiles by hand, in the same order: ```bash # 1. Repo-local (committed, version 1 schema): jq '.skills[""]' skills-lock.json # 2. Global (version 3 schema, has sourceUrl): jq '.skills[""]' ~/.agents/.skill-lock.json # 3. npx skills find ``` If none of the three resolve, ask the user for `owner/repo` and the skill folder name — never guess. For multiple related skills (user confirms one PR), repeat file discovery for each `skills//` directory. ### 2. Find the local skill directory | Kind | Local directory | | --- | --- | | Global | `~/.agents/skills//` | | Repo internal | `.agents/skills//` | Collect files to contribute (exclude machine-local files): ```bash find "" -type f ! -name 'SKILL.local.md' | sort ``` ### 3. Diff against source For each local file, map to `skills//` and compare to upstream on the default branch. List upstream skill folder (optional sanity check): ```bash gh api "repos///contents/skills/?ref=" \ --jq '.[] | .path' # If scripts/ exists, list nested files too: gh api "repos///contents/skills//scripts?ref=" \ --jq '.[]? | .path' 2>/dev/null ``` Per-file diff: ```bash UPSTREAM="skills//SKILL.md" gh api "repos///contents/${UPSTREAM}" --jq '.content' | base64 -d > /tmp/upstream.md diff /tmp/upstream.md "/SKILL.md" ``` - **No diffs** across all mapped files → nothing to contribute; stop - **Any diff** → show unified diffs and get user confirmation before proceeding ### 4. Check write access ```bash gh api repos// --jq '{push: .permissions.push, default: .default_branch}' ``` - `push: true` → branch on `owner/repo` - `push: false` → `gh repo fork / --clone=false`, then use `/` ### 5. Create branch and push (single commit — Git Data API) Use the **Git Data API** so all files land in **one commit**. Do not use `PUT /contents/{path}` per file (that creates one commit per file). Set variables: ```bash OWNER= REPO= REPO_FULL="${OWNER}/${REPO}" DEFAULT_BRANCH=main # from step 4 BRANCH="patch/-" # or shared slug for multi-skill PR MSG="fix(): " SKILL_NAME= LOCAL_DIR="" ``` #### 5a. Create branch ref from default branch tip ```bash BASE_SHA=$(gh api "repos/${REPO_FULL}/git/refs/heads/${DEFAULT_BRANCH}" --jq '.object.sha') BASE_TREE=$(gh api "repos/${REPO_FULL}/git/commits/${BASE_SHA}" --jq '.tree.sha') gh api "repos/${REPO_FULL}/git/refs" \ --method POST \ --field ref="refs/heads/${BRANCH}" \ --field sha="${BASE_SHA}" ``` If the branch already exists, skip creation and set `BASE_SHA` to the branch tip instead (for amending, prefer a fresh branch). #### 5b. Create blobs and tree entries for each local file Build a JSON array of tree items. Each local file maps to `skills/${SKILL_NAME}/`. **Use temp files for `--input`**, not inline `$(jq …)`. Embedding file content in the shell argument hits `ARG_MAX` and fails with errors like `file name too long` on typical `SKILL.md` sizes. ```bash TREE_ITEMS='[]' while IFS= read -r -d '' file; do rel="${file#${LOCAL_DIR}/}" upstream_path="skills/${SKILL_NAME}/${rel}" jq -n --rawfile content "$file" '{content: $content, encoding: "utf-8"}' > /tmp/patch-blob-input.json blob_sha=$(gh api "repos/${REPO_FULL}/git/blobs" \ --method POST \ --input /tmp/patch-blob-input.json \ --jq '.sha') TREE_ITEMS=$(jq -n \ --argjson items "$TREE_ITEMS" \ --arg path "$upstream_path" \ --arg sha "$blob_sha" \ '$items + [{path: $path, mode: "100644", type: "blob", sha: $sha}]') done < <(find "$LOCAL_DIR" -type f ! -name 'SKILL.local.md' -print0) ``` Repeat the loop for additional skills in the same PR (append to `TREE_ITEMS` with each skill's `SKILL_NAME` and `LOCAL_DIR`). #### 5c. Create tree, commit, update branch ref Write tree and commit payloads to temp files as well (large multi-skill `TREE_ITEMS` can also exceed shell limits): ```bash jq -n \ --arg base "$BASE_TREE" \ --argjson tree "$TREE_ITEMS" \ '{base_tree: $base, tree: $tree}' > /tmp/patch-tree-input.json NEW_TREE=$(gh api "repos/${REPO_FULL}/git/trees" \ --method POST \ --input /tmp/patch-tree-input.json \ --jq '.sha') jq -n \ --arg msg "$MSG" \ --arg tree "$NEW_TREE" \ --arg parent "$BASE_SHA" \ '{message: $msg, tree: $tree, parents: [$parent]}' > /tmp/patch-commit-input.json COMMIT_SHA=$(gh api "repos/${REPO_FULL}/git/commits" \ --method POST \ --input /tmp/patch-commit-input.json \ --jq '.sha') gh api "repos/${REPO_FULL}/git/refs/heads/${BRANCH}" \ --method PATCH \ --field sha="${COMMIT_SHA}" ``` Result: exactly **one commit** on the patch branch containing all updated paths under `skills//`. ### 6. Open PR ```bash gh pr create \ --repo "${REPO_FULL}" \ --base "${DEFAULT_BRANCH}" \ --head "${BRANCH}" \ --title "fix(): " \ --body "$(cat <<'EOF' ## Summary /`> ## Test plan - [ ] Run improve-skill / `audit validate` against the updated skill(s) - [ ] Confirm no files outside `skills/` were modified 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )" ``` ### 7. Report Output the PR URL. After merge, run `npx skills update` in the consumer repo to refresh `skills-lock.json` hashes. ## What NOT to do - Do not include `SKILL.local.md` content in the PR - Do not push without showing diffs and getting user confirmation - Do not create a PR if every mapped file is identical to upstream - Do not update `.agents/skills/` or any path outside `skills//` in the source repo - Do not use Contents API `PUT` per file (multi-commit noise); use Git Data API (section 5)