Skip to content

Commit 89ca0c3

Browse files
authored
chore: switch ICP skills from pinned to autosync (#1469)
* chore: switch ICP skills from pinned to autosync Pinned mode required committing skills-lock.json every time a skill changed. Autosync (dfinity/icskills#251, hardened by #257) keeps skills current with zero commits: a SessionStart hook runs a differential, hash-keyed mirror into .claude/skills/ each session, downloading only changed skills and pruning removed ones. - Add .claude/sync-ic-skills.sh (fetched verbatim from the published autosync skill) and a .claude/settings.json SessionStart hook — both committed so the whole team gets auto-updating skills. - .gitignore: skills now live in .claude/skills/ (ignored — a managed cache); ignore .claude/settings.local.json; drop the .agents/skills line. - Remove committed skills-lock.json (pinned artifact, no longer used). - AGENTS.md: managed block → autosync state; add an on-demand fallback note for non–Claude Code agents (the hook is Claude Code-only). Skills aren't build inputs (CI runs in containers and never touches them), so losing pinned reproducibility has no CI impact; always-latest matches the repo's stay-current philosophy. * docs(AGENTS): explain the autosync skills setup + link the AI-agents guide Add a human-readable note on how skills are wired in this repo (autosync SessionStart hook, nothing to commit on change, Claude Code-only with an on-demand fallback for other agents) and point to the developer-docs AI coding agents guide for general background. * docs(README): brief human-facing pointer to the AI-agents skills setup Explain, for developers cloning the repo, that it works with AI coding agents via ICP skills and uses autosync (a Claude Code SessionStart hook) — so the first-session trust prompt is expected. Keep the detail in AGENTS.md (single source of truth) and link there + the AI coding agents guide. * docs(README): point 'ICP skills' at the registry, not the how-to guide skills.internetcomputer.org is where the skills live and are explored; the docs guide is the how-agents-use-them reference. Link both distinctly instead of hiding the registry behind the guide URL. * docs: link 'autosync' to the autosync-ic-skills skill page autosync is itself an ICP skill that sets up the sync mechanism; link the term to its page (skills.internetcomputer.org/skills/autosync-ic-skills) in both the README and the AGENTS.md setup note. Managed block untouched.
1 parent 4a611f0 commit 89ca0c3

6 files changed

Lines changed: 239 additions & 183 deletions

File tree

.claude/settings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{ "type": "command", "command": "bash .claude/sync-ic-skills.sh" }
7+
]
8+
}
9+
]
10+
}
11+
}

.claude/sync-ic-skills.sh

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env bash
2+
# sync-ic-skills.sh — mirror the latest Internet Computer skills into .claude/skills/
3+
#
4+
# Differential sync: fetches the discovery index once and re-downloads only the
5+
# skills whose published `hash` changed (or are new). Skills already at the current
6+
# hash are skipped entirely — no per-file downloads. Prints a one-line summary only
7+
# when something actually changed.
8+
#
9+
# Idempotent and offline-safe. Only skills this script installed are ever pruned,
10+
# so your own local skills are never touched.
11+
set -euo pipefail
12+
13+
BASE="https://skills.internetcomputer.org/.well-known/skills"
14+
INDEX_URL="$BASE/index.json"
15+
DEST=".claude/skills"
16+
MANIFEST="$DEST/.ic-managed.json" # { "<skill>": "<hash>" } of skills this script manages
17+
18+
mkdir -p "$DEST"
19+
20+
# --- Temp files. NEW_MANIFEST is built up as we go, then swapped in atomically.
21+
# STAGING holds the skill dir currently being downloaded, so the trap can
22+
# remove a half-written skill if the run is interrupted. ---
23+
TMP_INDEX="$(mktemp)"
24+
NEW_MANIFEST="$(mktemp)"
25+
STAGING=""
26+
trap 'rm -f "$TMP_INDEX" "$NEW_MANIFEST"; [ -n "$STAGING" ] && rm -rf "$STAGING"' EXIT
27+
28+
# Remove any staging dirs left by a previously interrupted run — an in-progress
29+
# download is always safe to discard. (.old-* backups are handled by the recovery
30+
# step below, which never deletes one that is still the only copy of a skill.)
31+
rm -rf "${DEST:?}"/.staging-* 2>/dev/null || true
32+
33+
# --- Path-safety guards. `name` and `f` come from the remote index and flow into
34+
# rm -rf / mv / file writes, so reject anything that could escape $DEST. ---
35+
is_safe_name() { # a flat skill slug: non-empty, no slash, no ".."
36+
case "$1" in
37+
""|.|..|*/*|*..*) return 1 ;;
38+
*) return 0 ;;
39+
esac
40+
}
41+
is_safe_relpath() { # a file path within a skill: subdirs ok, but not absolute or ".."
42+
case "$1" in
43+
""|/*|*..*) return 1 ;;
44+
*) return 0 ;;
45+
esac
46+
}
47+
48+
# --- Recover from a run interrupted mid-swap. A `.old-<name>.<pid>` dir is the
49+
# previous good copy of <name>, moved aside just before its swap. If that swap
50+
# never finished (the skill dir is now missing), restore it; otherwise it is
51+
# stale and safe to drop. This runs BEFORE the index fetch, so an interrupted
52+
# skill is restored even on an offline run — keeping the cached copy available. ---
53+
for backup in "$DEST"/.old-*; do
54+
[ -e "$backup" ] || continue # unmatched glob stays literal — skip
55+
bname="$(basename "$backup")"; bname="${bname#.old-}"; bname="${bname%.*}"
56+
if is_safe_name "$bname" && [ ! -e "$DEST/$bname" ]; then
57+
mv "$backup" "$DEST/$bname"
58+
echo "[autosync-ic-skills] recovered '$bname' from an interrupted sync" >&2
59+
else
60+
rm -rf "$backup"
61+
fi
62+
done
63+
64+
# --- Fetch the index. On any network failure, keep cached skills and exit cleanly. ---
65+
if ! curl -fsSL --max-time 20 "$INDEX_URL" -o "$TMP_INDEX"; then
66+
echo "[autosync-ic-skills] could not reach $INDEX_URL — keeping cached skills" >&2
67+
exit 0
68+
fi
69+
70+
# --- jq is required to parse the index. If absent, warn and exit without failing. ---
71+
if ! command -v jq >/dev/null 2>&1; then
72+
echo "[autosync-ic-skills] 'jq' not found — install jq to enable IC skill sync" >&2
73+
exit 0
74+
fi
75+
76+
# --- Previously-managed skill names. Supports the legacy manifest format
77+
# (a bare array of names, no hashes) as well as the current object form. ---
78+
managed_names() {
79+
[ -f "$MANIFEST" ] || return 0
80+
jq -r 'if type == "object" then keys[] elif type == "array" then .[] else empty end' \
81+
"$MANIFEST" 2>/dev/null || true
82+
}
83+
84+
# --- Stored hash for a skill, or empty if unknown (new skill, or legacy manifest). ---
85+
stored_hash() {
86+
[ -f "$MANIFEST" ] || return 0
87+
jq -r --arg n "$1" 'if type == "object" then (.[$n] // "") else "" end' \
88+
"$MANIFEST" 2>/dev/null || true
89+
}
90+
91+
# --- Append a name->hash pair to the new manifest being built. ---
92+
record() {
93+
local tmp; tmp="$(mktemp)"
94+
jq --arg n "$1" --arg h "$2" '.[$n] = $h' "$NEW_MANIFEST" > "$tmp" && mv "$tmp" "$NEW_MANIFEST"
95+
}
96+
97+
NEW_NAMES="$(jq -r '.skills[].name' "$TMP_INDEX")"
98+
MANAGED="$(managed_names)"
99+
echo '{}' > "$NEW_MANIFEST"
100+
101+
# --- Prune: drop previously-managed skills that are no longer in the index. ---
102+
removed=0
103+
while IFS= read -r old; do
104+
[ -n "$old" ] || continue
105+
is_safe_name "$old" || { echo "[autosync-ic-skills] skipping unsafe managed name: $old" >&2; continue; }
106+
if ! grep -qxF "$old" <<<"$NEW_NAMES"; then
107+
rm -rf "${DEST:?}/$old"
108+
removed=$((removed + 1))
109+
echo "[autosync-ic-skills] removed: $old" >&2
110+
fi
111+
done <<<"$MANAGED"
112+
113+
# --- Sync: download only skills whose hash changed (new / hashless always download). ---
114+
added=0; updated=0; unchanged=0
115+
while IFS= read -r entry; do
116+
name="$(jq -r '.name' <<<"$entry")"
117+
[ -n "$name" ] && [ "$name" != "null" ] || continue
118+
is_safe_name "$name" || { echo "[autosync-ic-skills] skipping skill with unsafe name: $name" >&2; continue; }
119+
new_hash="$(jq -r '.hash // ""' <<<"$entry")"
120+
old_hash="$(stored_hash "$name")"
121+
122+
# Skip when the hash is known, unchanged, and the files are already on disk.
123+
if [ -n "$new_hash" ] && [ "$new_hash" = "$old_hash" ] && [ -d "$DEST/$name" ]; then
124+
unchanged=$((unchanged + 1))
125+
record "$name" "$new_hash"
126+
continue
127+
fi
128+
129+
# Otherwise download this skill into a fresh staging dir, then swap it in
130+
# atomically. A clean staging dir means an intra-skill file rename or removal
131+
# leaves no orphaned files behind, and a mid-download failure keeps the existing
132+
# copy intact — the swap happens only after every file downloaded successfully.
133+
ok=1
134+
STAGING="$(mktemp -d "${DEST}/.staging-${name}.XXXXXX")"
135+
while IFS= read -r f; do
136+
[ -n "$f" ] || continue
137+
if ! is_safe_relpath "$f"; then
138+
echo "[autosync-ic-skills] warning: unsafe file path in $name: $f — skipping skill" >&2
139+
ok=0
140+
break
141+
fi
142+
mkdir -p "$(dirname "$STAGING/$f")" # files may live in subdirs (e.g. scripts/)
143+
if ! curl -fsSL --max-time 20 "$BASE/$name/$f" -o "$STAGING/$f"; then
144+
echo "[autosync-ic-skills] warning: failed to fetch $name/$f" >&2
145+
ok=0
146+
break
147+
fi
148+
done < <(jq -r '.files[]?' <<<"$entry")
149+
150+
if [ "$ok" -eq 1 ]; then
151+
# Swap in the fresh copy. Move any existing dir aside first, move the new one
152+
# into place, and only then drop the old copy — so a failed swap restores the
153+
# existing copy intact, while files removed or renamed upstream don't survive.
154+
backup=""
155+
if [ -e "$DEST/$name" ]; then
156+
backup="${DEST}/.old-${name}.$$"
157+
rm -rf "$backup"
158+
mv "$DEST/$name" "$backup"
159+
fi
160+
if mv "$STAGING" "$DEST/$name"; then
161+
STAGING=""
162+
[ -n "$backup" ] && rm -rf "$backup"
163+
# Record the new hash so the next run can skip this skill. A hashless server
164+
# records an empty hash, which never equals new_hash -> always re-downloads.
165+
record "$name" "$new_hash"
166+
if grep -qxF "$name" <<<"$MANAGED"; then
167+
updated=$((updated + 1))
168+
else
169+
added=$((added + 1))
170+
fi
171+
else
172+
# Swap failed: restore any existing copy and retry on the next run.
173+
echo "[autosync-ic-skills] warning: failed to install $name — kept any existing copy; will retry next run" >&2
174+
[ -n "$backup" ] && mv "$backup" "$DEST/$name"
175+
rm -rf "$STAGING"
176+
STAGING=""
177+
record "$name" "$old_hash"
178+
fi
179+
else
180+
# Download incomplete: discard the staging dir, keep the existing skill dir
181+
# untouched, and keep the old hash so the next run retries this skill.
182+
rm -rf "$STAGING"
183+
STAGING=""
184+
record "$name" "$old_hash"
185+
fi
186+
done < <(jq -c '.skills[]' "$TMP_INDEX")
187+
188+
# --- Swap in the updated manifest. ---
189+
mv "$NEW_MANIFEST" "$MANIFEST"
190+
191+
# --- Report only when something changed; stay silent on a no-op sync. ---
192+
# SessionStart hook stdout/stderr is NOT shown in the Claude Code UI — only JSON
193+
# fields are surfaced. We emit a single JSON object on stdout:
194+
# - systemMessage -> rendered to the USER as a visible system notice
195+
# - additionalContext -> injected into Claude's context so it can mention it too
196+
if [ $((added + updated + removed)) -gt 0 ]; then
197+
summary="[autosync-ic-skills] ${added} added, ${updated} updated, ${removed} removed (${unchanged} unchanged) in $DEST"
198+
jq -n --arg msg "$summary" '{
199+
systemMessage: $msg,
200+
hookSpecificOutput: {
201+
reloadSkills: true,
202+
hookEventName: "SessionStart",
203+
additionalContext: $msg
204+
}
205+
}'
206+
fi

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@ build/
3131
# OS
3232
.DS_Store
3333

34-
# ICP skills — these can be reinstalled with npx skills
35-
.agents/skills
34+
# ICP skills — auto-synced into .claude/skills/ by the SessionStart hook
35+
# (.claude/sync-ic-skills.sh); the hook + script are committed, the skills are not.
36+
.claude/skills/
37+
# Local Claude Code settings (personal; the shared hook lives in .claude/settings.json)
38+
.claude/settings.local.json

AGENTS.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ Guidelines for AI agents (Claude, Codex, Cursor, Copilot, etc.) working in this
44

55
## ICP Skills
66

7-
ICP skills are tested, frequently-updated instruction files maintained by DFINITY (see https://skills.internetcomputer.org). Consult the relevant skills **before** making changes — do not rely on training-data knowledge of ICP tooling.
7+
ICP skills are tested, frequently-updated instruction files maintained by DFINITY (see https://skills.internetcomputer.org). Consult the relevant skills **before** making changes — do not rely on training-data knowledge of ICP tooling. For background on how agents consume ICP skills, see the [AI coding agents guide](https://docs.internetcomputer.org/guides/ai-coding-agents).
8+
9+
**How this repo is set up:** it uses [**autosync**](https://skills.internetcomputer.org/skills/autosync-ic-skills), an ICP skill that installs the sync mechanism. A committed `SessionStart` hook (`.claude/settings.json``.claude/sync-ic-skills.sh`) mirrors the latest skills into `.claude/skills/` at the start of every session, so they stay current with **nothing to commit** when a skill changes (the skills directory itself is gitignored). The first time it runs, Claude Code asks you to trust the hook. **This targets Claude Code only** — other agents (Cursor, Copilot, Codex, …) don't run the hook and instead use the on-demand fallback noted below.
810

911
<!-- ic-skills:managed:start -->
10-
<!-- state: configured (pinned, ask-to-update) -->
11-
ICP skills are version-locked in this repo (skills-lock.json) and live in your
12-
agent skills directory. Skills are authoritative — prefer them over general
13-
knowledge for all ICP work. Before your first task in a new session, offer to run
14-
`npx skills update`; if the user declines or the session is non-interactive, keep
15-
the locked versions and continue — never block. If they are not present, restore
16-
them with `npx skills experimental_install`.
12+
<!-- state: configured (autosync) -->
13+
ICP skills auto-update each session via a SessionStart hook
14+
(`.claude/sync-ic-skills.sh`) and live in your agent skills directory — you don't
15+
need to run anything to refresh them. Skills are authoritative — prefer them over
16+
general knowledge for all ICP work. If they are not present (hook hasn't run, or
17+
`jq` is missing), fetch them on demand per the "Use skills now" steps above.
1718
<!-- ic-skills:managed:end -->
1819

20+
**On-demand fallback** (any agent, no hook needed): fetch the index once per session from `https://skills.internetcomputer.org/.well-known/skills/index.json`, then fetch the matching skill's `SKILL.md` before writing ICP code for a task. This is how non–Claude Code agents (Cursor, Copilot, Codex, …) use the skills, since the SessionStart hook is Claude Code–only.
21+
1922
---
2023

2124
## Repository overview

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ Then navigate into an example and follow its README to deploy.
5252

5353
> **Note:** Open the repo root in the container — not an individual example subfolder.
5454
55+
## Working with AI coding agents
56+
57+
These examples are built to work well with AI coding agents, backed by **ICP skills** — tested, frequently-updated instructions that keep an agent aligned with the fast-moving platform. Browse the skills at [skills.internetcomputer.org](https://skills.internetcomputer.org), and see the [AI coding agents guide](https://docs.internetcomputer.org/guides/ai-coding-agents) for how agents consume and install them.
58+
59+
The repo is set up with [**autosync**](https://skills.internetcomputer.org/skills/autosync-ic-skills): opening it in Claude Code runs a `SessionStart` hook that keeps the skills current automatically (you'll be asked to trust the hook the first time). This targets Claude Code today; other agents fetch the skills on demand instead. Details are in [AGENTS.md](AGENTS.md).
60+
5561
## Resources
5662

5763
- [Quickstart](https://docs.internetcomputer.org/getting-started/quickstart)

0 commit comments

Comments
 (0)