Contribute Skill
ローカルで改修した `skills/<skill-name>/` を upstream リポジトリ (Fandhe-AI/agent-cli-skills 等) へ PR として投稿する。`skills-lock.json` の `source` を読み、`Fandhe-AI/` 以外への push は安全弁で中止。clone → 反映 → セキュリティチェック → ブランチ作成 → push → `gh pr create` を実行。マージ後は sync-skills-lock で hash 更新。「スキルを upstream に貢献」「外部リポジトリに PR」などで使用。
- Skill ID
- fandhe-ai/agent-cli-skills/contribute-skill
- Publisher
- fandhe-ai
- Repository
- agent-cli-skills
- Installs
- 0
- Files
- 2
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
fandhe-ai/agent-cli-skills/contribute-skillInstalls these files- SKILL.md
- scripts/skills-contribute.sh
What this skill tells the agent
contribute-skill
ローカルで改修した skills/<skill-name>/ を、skills-lock.json に記録された upstream リポジトリへ PR として投稿します。
前提条件
ghCLI がインストールされ認証済みであること(対象 org への push / PR 権限が必要)- 対象スキルが
skills-lock.jsonに登録されていること - 対象スキルのローカル改修が最新のコミットに含まれ、作業ツリーが clean であること
責務の分離
- create-pr: 現在のリポジトリ内でカレントブランチから base へ PR を作成する
- contribute-skill: 別リポジトリ(upstream)へ clone → 変更反映 → push → PR 作成を行う
外部リポジトリ貢献は clone / path 変換 / 異なる認証境界が関わるため、別スキルとして分離しています。
フロー
Step 1: 引数を検証する
SKILL_NAME="$ARGUMENTS"
# 空判定ガード: パス解決の前に SKILL_NAME を確定させる
if [[ -z "${SKILL_NAME}" ]]; then
echo "対象スキルを指定してください。候補:"
ls -1 skills/ 2>/dev/null
ls -1 .agents/skills/ 2>/dev/null
# .claude/skills/ は skills/ への symlink 慣習と実体配置(例: create-skill / create-agent)が
# 併存するため、実ディレクトリのみ列挙する(symlink は実体が上の 2 候補に現れる)。
# 親の .claude / .claude/skills 自体が symlink の場合も配下の実体は symlink 先で列挙されるため、
# 経路の全親が symlink でないときのみ find する(-type d は親 symlink 経由でも列挙してしまう)
[[ ! -L .claude && ! -L .claude/skills ]] && find .claude/skills -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||'
echo "(lockfile 由来のスキルは .agents/skills/ のみ、リポジトリ管理スキルは .claude/skills/ のみに存在する場合がある)"
exit 1 # ユーザーが選んだスキル名を引数に付けて再実行する
fi
# kebab-case 検証(パストラバーサル防止): パス解決より前に実施する
if [[ ! "${SKILL_NAME}" =~ ^[a-z][a-z0-9-]+$ ]]; then
echo "エラー: SKILL_NAME は小文字 kebab-case のみ許可されています: ${SKILL_NAME}"
exit 1
fi
# symlink 境界の共通判定(fail-closed): 相対経路 rel の全要素を先頭から累積検査し、
# いずれかが symlink なら該当パスを SYMLINK_COMPONENT に設定して 1 を返す。
# 第2引数 base を与えると base 配下の相対経路として検査する(省略時はカレント基準)。
# 末尾要素だけの -L 検査では親(例: .claude 自体がリポジトリ外を指す symlink)を
# すり抜けるため、候補判定・override 検証・upstream 配置判定・rm 前検証の
# すべての経路判定でこの関数を使い「どの経路要素が symlink でも候補と数えない」を一貫させる。
assert_no_symlink_components() {
local rel="$1" prefix="${2:+${2}/}" acc="" part
local -a parts
SYMLINK_COMPONENT=""
IFS='/' read -r -a parts <<< "${rel}"
for part in "${parts[@]}"; do
acc="${acc:+${acc}/}${part}"
if [[ -L "${prefix}${acc}" ]]; then
SYMLINK_COMPONENT="${prefix}${acc}"
return 1
fi
done
return 0
}
# override: 環境変数 LOCAL_SKILL_DIR が設定済みならそれを検証して使う
if [[ -n "${LOCAL_SKILL_DIR:-}" ]]; then
case "${LOCAL_SKILL_DIR}" in
"skills/${SKILL_NAME}"|".agents/skills/${SKILL_NAME}"|".claude/skills/${SKILL_NAME}") ;;
*)
echo "エラー: LOCAL_SKILL_DIR は skills/${SKILL_NAME} / .agents/skills/${SKILL_NAME} / .claude/skills/${SKILL_NAME} のいずれかを指定してください: ${LOCAL_SKILL_DIR}"
exit 1
;;
esac
# symlink(.claude/skills/<name> → skills/<name> 慣習)は差分表示が空になるため実体側を要求する。
# 末尾要素だけでなく全中間要素(例: .claude → .claude/skills)も累積検査し、
# 親ディレクトリが symlink のパス指定も fail-closed で拒否する(自動解決側と同一関数で対称)
if ! assert_no_symlink_components "${LOCAL_SKILL_DIR}"; then
echo "エラー: LOCAL_SKILL_DIR の経路に symlink が含まれています。実体側のパスを指定してください: ${SYMLINK_COMPONENT} -> $(readlink "${SYMLINK_COMPONENT}")"
exit 1
fi
if [[ ! -d "${LOCAL_SKILL_DIR}" ]]; then
echo "エラー: 指定された LOCAL_SKILL_DIR が存在しません: ${LOCAL_SKILL_DIR}"
exit 1
fi
else
# 自動解決(複数存在する場合は中止して override を促す)
have_skills=0; have_agents=0; have_claude=0
# 各候補は経路の全要素(.claude 等の最上位親を含む)が実体の場合のみ数える。
# symlink 経由の候補は実体側候補で検出されるため数えず(重複カウント防止)、
# .claude 自体がリポジトリ外を指す symlink の場合も候補と数えない(fail-closed)
[[ -d "skills/${SKILL_NAME}" ]] && assert_no_symlink_components "skills/${SKILL_NAME}" && have_skills=1
[[ -d ".agents/skills/${SKILL_NAME}" ]] && assert_no_symlink_components ".agents/skills/${SKILL_NAME}" && have_agents=1
[[ -d ".claude/skills/${SKILL_NAME}" ]] && assert_no_symlink_components ".claude/skills/${SKILL_NAME}" && have_claude=1
if (( have_skills + have_agents + have_claude > 1 )); then
echo "エラー: ${SKILL_NAME} の実体が skills/ / .agents/skills/ / .claude/skills/ の複数に存在します。"
echo "環境変数 LOCAL_SKILL_DIR にどれかを指定して再実行してください(例: LOCAL_SKILL_DIR=.agents/skills/${SKILL_NAME})。"
exit 1
elif [[ "${have_skills}" -eq 1 ]]; then
LOCAL_SKILL_DIR="skills/${SKILL_NAME}"
elif [[ "${have_agents}" -eq 1 ]]; then
LOCAL_SKILL_DIR=".agents/skills/${SKILL_NAME}"
elif [[ "${have_claude}" -eq 1 ]]; then
LOCAL_SKILL_DIR=".claude/skills/${SKILL_NAME}"
else
echo "エラー: ローカルスキルが見つかりません: skills/${SKILL_NAME} / .agents/skills/${SKILL_NAME} / .claude/skills/${SKILL_NAME}"
exit 1
fi
fi引数が空の場合はパス解決に進まず、skills/・.agents/skills/・.claude/skills/(実ディレクトリのみ。symlink は実体側の候補に現れるため除外)の候補一覧を表示して終了します。Claude はその一覧をユーザーに提示し、スキル名を選んでもらってから再実行を促してください。後続の Step では ${LOCAL_SKILL_DIR}/ を使ってローカルパスを参照します。 skills/・.agents/skills/・.claude/skills/(実ディレクトリのみ。symlink は実体側でカウント)の複数にディレクトリが存在する場合は中止し、環境変数 LOCAL_SKILL_DIR に改修対象のパス(skills/<name>・.agents/skills/<name>・.claude/skills/<name> のいずれか)を指定して再実行するよう案内します(silently に skills/ を優先しません)。環境変数 LOCAL_SKILL_DIR が設定済みの場合は、許可された3パスのいずれかであること・経路の全要素(中間の親ディレクトリ含む)が symlink でないこと・実在することを検証してから採用し、自動解決をスキップします。いずれにも存在しなければエラーで中止します。
Step 2: upstream を特定する
ルートの skills-lock.json を読み、skills.<SKILL_NAME>.source を取り出します。
# jq が使えるなら jq で取得する
SOURCE=$(jq -r ".skills[\"${SKILL_NAME}\"].source" skills-lock.json)
SOURCE_TYPE=$(jq -r ".skills[\"${SKILL_NAME}\"].sourceType" skills-lock.json)
# 安全弁: Fandhe-AI org 以外への push を拒否する
# 1) まず正規化する(URL 形式は OWNER/REPO へ変換、短縮形はそのまま採用)
case "${SOURCE}" in
https://github.com/*)
REPO_SLUG="${SOURCE#https://github.com/}"
;;
*)
REPO_SLUG="${SOURCE}"
;;
esac
# 末尾 .git の除去は両形式共通で行う
# (URL 分岐内のみで除去すると短縮形 'Fandhe-AI/<repo>.git' が .git 付きのまま
# 後段の正規表現を通過してしまうため、検証の前に必ずここで正規化する)
REPO_SLUG="${REPO_SLUG%.git}"
# 2) 正規化後の値を厳密検証する: owner は Fandhe-AI 固定、repo は単一セグメントのみ許可する
# [A-Za-z0-9._-]+ は '/'・'?'・'#'・空文字を含められないため、
# パストラバーサル(../)・余剰パスセグメント・クエリ・フラグメントをすべて拒否できる
# (前方一致 case では `Fandhe-AI/../../attacker/repo` のような値が誤って通過していた)
if [[ ! "${REPO_SLUG}" =~ ^Fandhe-AI/[A-Za-z0-9._-]+$ ]]; then
echo "エラー: source '${SOURCE}' は Fandhe-AI/<repo> 形式ではありません。中止します。"
exit 1
fi
# 3) '.'・'..' は上記正規表現を通過してしまうため repo 名として明示拒否する
# (例: 'Fandhe-AI/..git' は .git 除去後に 'Fandhe-AI/.' へ化ける)
case "${REPO_SLUG#Fandhe-AI/}" in
.|..)
echo "エラー: source '${SOURCE}' の repository 名が不正です。中止します。"
exit 1
;;
esac
# sourceType が github 以外なら中止(GitHub 以外の source は本スキルの想定外)
if [[ "${SOURCE_TYPE}" != "github" ]]; then
echo "エラー: sourceType '${SOURCE_TYPE}' は github ではありません。中止します。"
exit 1
fisourceは正規化後のOWNER/REPOが^Fandhe-AI/[A-Za-z0-9._-]+$に完全一致する場合のみ許可します(安全弁:見知らぬリポジトリへ意図せず push しないため)。../を含むパストラバーサル・クエリ(?x=1)・フラグメント(#frag)・余剰パスセグメント(/extra)を含む値は正規表現に一致せずエラーで中止します。検証は必ず.git除去などの正規化の後に行います(正規化前に検証するとFandhe-AI/..gitのような値が正規化後に別の値へ化けてすり抜けるため)。sourceTypeがgithub以外の場合も エラーで中止 します(GitHub 以外の source は本スキルの想定外であり、gh repo clone/gh pr createが正常動作しないため)。- 正規化後の
REPO_SLUGは以降の Step でgh repo clone・gh pr create --repoに利用します。
Step 3: 変更内容を確認する
git log --oneline -- "${LOCAL_SKILL_DIR}/"
# HEAD~1 の有無を明示的に判定する(git diff は差分ありでも終了コード 0 のため || で分岐しない)
if git rev-parse --verify -q HEAD~1 >/dev/null; then
git diff HEAD~1 HEAD -- "${LOCAL_SKILL_DIR}/"
else
# 親コミットがない場合(初回コミット・shallow clone 等)は空ツリーと HEAD を比較する
# (作業ツリー差分ではコミット済み・クリーンな状態で空になるため使わない)
git diff "$(git hash-object -t tree /dev/null)" HEAD -- "${LOCAL_SKILL_DIR}/"
fiユーザーに「この改修内容で upstream に PR を作ってよいか」を確認します。
Step 4: セキュリティチェック(必須)
create-pr と同様に以下をレビューします。
- 認証・認可の実装漏れ
- API キー・シークレットのハードコーディング
- XSS の可能性(ドキュメントでも外部埋め込みが含まれる場合)
- 入力バリデーションの欠如
- OWASP Top 10
問題があれば upstream 貢献を中止し、ユーザーに警告します。
Step 5: 変更を反映する
skills-contribute.sh は upstream の gh repo clone から作業ディレクトリ(WORKDIR)の作成・反映までを自己完結で行います。手動での事前 clone は不要です(機械可読な CONTRIBUTE_SKILL_WORKDIR= 等の出力を Step 6 以降で唯一の正として使う契約に一本化しています)。
このステップは手順を個別に打鍵せず、必ず本スキル自身のスクリプト(`skills-contribute.sh`)を実行してください。 同スクリプトには rm -rf 前の symlink 境界検証(TOCTOU 対策込み)が実装されており、以下の断片だけを個別に実行すると検証が欠落します。
More from fandhe-ai
Use this skill in a project
Open any RiverX project, search the Skills panel for fandhe-ai/agent-cli-skills/contribute-skill, and install it in one click.
