Sync Skills Lock
ルート直下の `skills-lock.json` の `computedHash` を upstream リポジトリの最新状態と照合して更新する。`source` が `Fandhe-AI/<repo>` に完全一致しないエントリは clone せず skip (安全弁)。submodule 配下の `skills-lock.json` は触らない。contribute-skill のマージ後や upstream 同期後、「ハッシュ更新」「skills-lock 同期」などで使用。
- Skill ID
- fandhe-ai/agent-cli-skills/sync-skills-lock
- Publisher
- fandhe-ai
- Repository
- agent-cli-skills
- Installs
- 0
- Files
- 16
- 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/sync-skills-lockInstalls these files- SKILL.md
- scripts/skills-lock-update.sh
- tests/checker-outside-state.test.mjs
- tests/checker-run-verify.test.mjs
- tests/errexit-fence.test.mjs
- tests/path-state-dirnames.test.mjs
- tests/preview-untracked.test.mjs
- tests/restore-backup-failure-checker-lsfiles.test.mjs
- tests/restore-backup-failure-npx-path.test.mjs
- tests/restore-backup-failure-status-path.test.mjs
- tests/restore-backup-failure.test.mjs
- tests/restore-help-order.test.mjs
- tests/restore-ignored-failure-revert-display.test.mjs
- tests/scope-compromised-revert-display.test.mjs
- tests/scope-guard.test.mjs
- tests/version-pin.test.mjs
What this skill tells the agent
sync-skills-lock
ルート直下の skills-lock.json の computedHash を、upstream リポジトリの現状と照合して更新する。
対象ファイル
- ルート: 呼び出し元リポジトリ直下の
skills-lock.json— このスキルが唯一編集するファイル - 除外: submodule 配下の
skills-lock.json— submodule 境界を跨がないため 絶対に触らない
前提条件
ghCLI がインストールされ、認証済みであることnode/npxが利用可能であること(npx skills addを使用するため)。skillsCLI は固定版(SKILLS_CLI_VERSION)で実行する。値と更新手順は「skills CLI のバージョン固定と更新手順」節を参照python3が利用可能であること(状態署名〔path_state/index_state_signature〕と復元処理〔既存 ignored ファイルの比較・復元等〕で使用。主要 Linux / macOS には標準搭載されている。無い環境では Step 4 フェンスが npx 実行前に fail-closed で停止するため、導入してから再実行すること)fileCLI が利用可能であること(未追跡バイナリファイルの種別表示に使用。未導入の環境では 種別がfile コマンド未検出として表示され、承認前にサイズ・git blob ハッシュのみで 判断することになる。macOS / 主要 Linux ディストリビューションには標準搭載されている)- ルート直下の
skills-lock.jsonが存在すること - 実行前に `skills-lock.json` に未コミットの変更がないこと(ステージ済み・未ステージ問わず)。本スキルの実行中に発生する変更は sync 由来のみとなり、
git add skills-lock.jsonで全体をステージしても無関係な変更が混入しない - 対象スキルの `.agents/skills/<name>/` に未コミット変更がないこと。
npx skills addは.agents/skills/<name>/を upstream の最新版で上書きするため、そのディレクトリに WIP が存在すると即座に失われる。git checkoutで戻せるのは「最後にコミットされた状態」のみであり、npx 実行前の未コミット編集は復元できない。未追跡ファイルとして存在する WIP も対象であり、git status --porcelainで検出する - 消費側リポジトリが commit 済み local patch を持つ場合: vendored skill(
.agents/skills/配下)へ commit 済みの local patch を適用しているリポジトリは、その検証・再適用の入口として repository-owned checkerscripts/check-skill-local-patches.sh(無引数 = check /applyの 2 モード)と台帳.agents/skills/LOCAL-PATCHES.mdを持つ。commit 済み patch は上記 clean ガードでは保護できないため、checker が存在する場合は同期の前後(Step 4 の pre-check・Step 5.5 の apply + 最終検証)での成功が必須(非 0 は fail-closed で同期・stage しない)。台帳があるのに checker が無い状態も検証不能として fail-closed で停止する。checker(apply)の書き込み先は当該スキルディレクトリ・skills-lock.json・durable patch 置き場scripts/local-patches/に限る契約とし、範囲外の変更は各実行直後の digest 比較で fail-closed に検出する(検出範囲は Git が追跡・列挙する対象に限る best-effort であり、書き込み制限の保証ではない。保証はユーザーによる checker 内容レビュー + blob hash 承認が担う。詳細は Step 4 の「検出範囲の限界」コメント)。checker は消費側が配置する実行可能コードのため「存在するだけ」では実行せず、symlink ではない regular file(HEAD 側 mode も 100644/100755)であり、HEAD に commit 済みで worktree と一致し、かつユーザーへ由来・内容を提示して blob hash 単位の明示承認を得た場合のみ実行する(Step 4 で機械検証)。実行対象は worktree のファイルではなく承認済み HEAD blob を取り出した一時ファイル(CHECKER_EXEC)とし、hash 確認後に worktree の checker を差し替える TOCTOU 経路を断つ - 通常構成のメイン worktree で実行すること。linked worktree(
git worktree addで作られた作業ツリー)では.gitが gitdir を指す通常ファイルになり、実 Git ディレクトリ(.git/worktrees/<name>/と共有側のrefs・logs・config・objects)が状態署名の対象外になるため、Step 4 フェンスが npx 実行前にgit rev-parse --absolute-git-dir/--git-common-dirの不一致で検出して fail-closed で拒否する。同様に、実 Git ディレクトリが作業ツリー外にある構成(git clone --separate-git-dir・submodule checkout・.gitが symlink)も、「実 Git ディレクトリ = 作業ツリー直下の.git実体ディレクトリ」の検証(--show-toplevelとの厳密一致 + lstat)で npx 実行前に fail-closed で拒否する
フロー
Step 1: 引数を確認し、事前条件を検証する
TARGET="$ARGUMENTS" # 空なら全スキル対象
# 引数指定時は kebab-case のみ許可(パストラバーサル防止)
if [[ -n "${TARGET}" && ! "${TARGET}" =~ ^[a-z][a-z0-9-]+$ ]]; then
echo "エラー: スキル名は小文字 kebab-case のみ許可されています: ${TARGET}"
exit 1
fi引数ありの場合は該当スキルのみ処理、なしの場合は skills-lock.json の全エントリを対象にする。
次に skills-lock.json の clean 状態を確認する。未コミット変更(ステージ済み・未ステージ問わず)があれば中止する。
# skills-lock.json に未コミット変更があれば中止(sync 由来以外の変更の混入を防ぐ)
# git diff 系は untracked を検出しないため porcelain を使う
if [[ -n "$(git status --porcelain -- skills-lock.json)" ]]; then
echo "エラー: skills-lock.json に未コミットの変更があります。コミットまたは退避してから再実行してください。"
exit 1
fiStep 2: upstream 一覧を集計する
skills-lock.json を読み、source フィールドごとにスキルをグルーピングする(同一リポへの処理を 1 回にまとめるため)。
Fandhe-AI/agent-cli-skills:
- create-commit
- create-issue
- ...Step 3: source を検証する
安全弁: 処理前に必ず source フィールドが Fandhe-AI/<repo> に完全一致することを確認する。前方一致では ../ を含む値が通過し、clone 時の URL パス正規化で組織外リポジトリを対象にできてしまうため、OWNER/REPO へ正規化後に厳密な正規表現で検証する。想定外の source は skip してユーザーに警告する。skills-lock.json の改ざん・誤設定によって untrusted リポジトリから clone することを防ぐためである。
REPO_SLUG="${SOURCE#https://github.com/}"
REPO_SLUG="${REPO_SLUG%.git}"
if [[ ! "$REPO_SLUG" =~ ^Fandhe-AI/[A-Za-z0-9._-]+$ ]] \
|| [[ "$REPO_SLUG" == "Fandhe-AI/." || "$REPO_SLUG" == "Fandhe-AI/.." ]]; then
echo "警告: 想定外の source: $SOURCE — このスキルは skip します"
continue
fiStep 4–7: 対象スキルを1つずつ処理する(ループ)
対象スキルそれぞれについて、次の 4→5→6→7 を順に実行し、1スキル完了後に次スキルへ進む。全スキル sync であっても同時に複数スキルを処理せず、1スキルずつ完結させること。
Step 4: npx skills add で computedHash を更新する
sha256sum などで手動計算するのではなく、npx skills add に計算を任せる。これにより CLI の内部アルゴリズムと完全に一致する。
# 当該スキルの install ツリーに未コミット変更があれば npx が上書きするため skip
# git diff 系は untracked を検出しないため porcelain を使う(未追跡 WIP も保護対象)
if [[ -n "$(git status --porcelain -- ".agents/skills/${SKILL_NAME}/")" ]]; then
echo "警告: .agents/skills/${SKILL_NAME}/ に未コミット変更(未追跡含む)があります。npx の上書きで失われるため skip します。"
continue
fi
# python3 の存在確認(PR #412 codex P1 指摘): 状態署名(path_state /
# index_state_signature)と復元処理(restore_preexisting_ignored 等)が python3 を
# 必須実行する。無いまま進むと npx 実行後の検査・復元の途中で command-not-found に
# なり、スコープ外検査が中途半端なまま停止する(fail-open 経路)。python3 の不在は
# スキル単位の事情ではなく環境全体の条件のため、skip(continue)ではなくループ全体を
# npx 実行前に exit 1 で停止する(fail-closed。この時点では npx 未実行のため残置なし)。
if ! command -v python3 >/dev/null 2>&1; then
echo "エラー: python3 が見つかりません。状態署名・復元処理で python3 が必要です。導入してから再実行してください(fail-closed)。" >&2
exit 1
fi
# linked worktree の実行拒否(PR #412 codex P0 指摘): linked worktree
# (git worktree add で作られた作業ツリー)では .git が gitdir を指す通常ファイルで、
# 実 Git ディレクトリ(.git/worktrees/<name>/ と common dir 側の refs・logs・config・
# objects)は作業ツリーの外にある。repo_state_signature の走査(path_state .)は
# 作業ツリー内しか対象にせず、index_state_signature も index の論理状態しか補わない
# ため、npx が git update-ref 等で共有リポジトリの参照・設定を改変しても porcelain・
# ツリー署名・index 署名のすべてが前後一致し成功扱いになる。メイン worktree でのみ
# 成立する「--absolute-git-dir と --git-common-dir の一致」を一次根拠に判定し、
# 不一致(linked worktree のほか、実 Git ディレクトリが署名対象に収まらない
# submodule 等の変則構成すべて)は npx 未実行のまま fail-closed で拒否する。
# --git-common-dir は相対パスを返し得る(--path-format=absolute は git 2.31+ のため
# 使わない)ので、cd + pwd -P で双方を物理パスへ正規化してから比較する。
# rev-parse・正規化の失敗も「メイン worktree と確認できない」であって
# 「メインである」ではないため中止する。実行場所の異常はスキル単位の事情ではなく
# リポジトリ全体の条件のため、skip(continue)ではなくループ全体を exit 1 で
# 停止する(この時点では npx 未実行のため残置なし)。
if ! GIT_DIR_ABS="$(git rev-parse --absolute-git-dir)" \
|| ! GIT_COMMON_DIR_RAW="$(git rev-parse --git-common-dir)" \
|| ! GIT_DIR_PHYS="$(cd "${GIT_DIR_ABS}" && pwd -P)" \
|| ! GIT_COMMON_DIR_PHYS="$(cd "${GIT_COMMON_DIR_RAW}" && pwd -P)"; then
echo "エラー: Git ディレクトリの解決(git rev-parse --absolute-git-dir / --git-common-dir)に失敗しました。メイン worktree での実行と確認できないため中止します(fail-closed)。" >&2
exit 1
fi
if [[ "${GIT_DIR_PHYS}" != "${GIT_COMMON_DIR_PHYS}" ]]; then
echo "エラー: linked worktree(git worktree add で作られた作業ツリー)では実 Git ディレクトリ(.git/worktrees/<name>/ と共有側の refs・logs・config・objects)が状態署名の対象外になり、npx による共有リポジトリの改変を検出できないため実行を拒否します(fail-closed)。メイン worktree で実行し直してください。" >&2
exit 1
fi
# git-dir と common-dir の一致は linked worktree を除外するだけで、「実 Git
# ディレクトリが署名対象の作業ツリー内にある」ことまでは保証しない
# (PR #412 Bugbot High 指摘)。git clone --separate-git-dir(.git が gitdir を
# 指す gitfile)・submodule checkout(.git/modules/<name>/ を指す gitfile)・
# .git が symlink の構成では両者が一致したまま実体が作業ツリー外にあり、
# path_state の走査(起点 .)が refs・hooks・objects を含まないため、npx の
# 改変が porcelain・ツリー署名・index 署名のすべてをすり抜ける。そこで実 Git
# ディレクトリが「作業ツリー直下の .git 実体ディレクトリ」であることまで検証
# する: toplevel を上と同じ手法(cd + pwd -P)で物理パスへ正規化し、
# (1) GIT_DIR_PHYS が <toplevel>/.git と厳密一致、(2) <toplevel>/.git を lstat
# して symlink ではなく directory であること(gitfile = 通常ファイル・symlink
# はいずれも拒否)、の両方を要求する。不成立は npx 未実行のまま中止する。
if ! TOPLEVEL_RAW="$(git rev-parse --show-toplevel)" \
|| ! TOPLEVEL_PHYS="$(cd "${TOPLEVEL_RAW}" && pwd -P)"; then
echo "エラー: 作業ツリールートの解決(git rev-parse --show-toplevel)に失敗しました。実 Git ディレクトリが作業ツリー内の .git ディレクトリであることを確認できないため中止します(fail-closed)。" >&2
exit 1
fi
if [[ "${GIT_DIR_PHYS}" != "${TOPLEVEL_PHYS}/.git" ]] \
|| [[ -L "${TOPLEVEL_PHYS}/.git" ]] \
|| [[ ! -d "${TOPLEVEL_PHYS}/.git" ]]; thenMore from fandhe-ai
Use this skill in a project
Open any RiverX project, search the Skills panel for fandhe-ai/agent-cli-skills/sync-skills-lock, and install it in one click.
