Skip to content

feat(cli): lock, update and sync skills from skillx add; project scope, several runtimes, commit refs - #168

Merged
mrgoonie merged 4 commits into
mainfrom
claude/admiring-ritchie-umeusx
Oct 29, 2026
Merged

mrgoonie merged 4 commits into
mainfrom
claude/admiring-ritchie-umeusx

Conversation

@mrgoonie

Copy link
Copy Markdown
Contributor

Closes #166. Follows #120. Overlaps with #163 in git-source.ts, add-source.ts and the docs line saying commit SHAs are not accepted, so whichever PR merges second has to resolve those files.

What changes

Lock, update, sync

  • A skill that skillx add reads from git is now pinned as an added entry in the packages lock (skillx.lock.json). The entry records the repository URL (with no credentials), the directory, the ref to follow, the commit and the agentkit.skill-tree/v1 tree digest.
    • The contract lives in packages/contracts/src/install-lock-added.ts and is validated with the rest of the lock.
    • Packages scope only, at most 256 entries, sorted, and an alias may not also appear in packages.
  • skillx update re-reads each added skill from its recorded source.
    • It installs the skill from the directory it was installed from. If that directory is gone, it takes the only skill with that name. Two skills with that name elsewhere is a conflict.
    • A source that fails does not stop the others. They are installed and locked, then the command fails and lists every failed source in details.failures.
  • skillx sync --locked fetches each locked commit and installs the skill only if its tree digest matches the lock. Any other digest is an integrity_error.
  • Uninstalling add:<name> also drops its lock entry.
  • A local add over a skill that target had from git drops that pin. Any other local add leaves the lock alone.

Scope and runtimes

  • --runtime can be repeated or comma-separated. The source is read once, and every runtime is planned before any is written.
    • If a later runtime fails, the error reports the runtimes already installed (details.installed, details.lockPath).
  • --project installs into ./.claude/skills (Claude Code) or ./.agents/skills (Codex). It is also accepted by install, update, uninstall, list and sync.
    • Project state is kept separately under $SKILLX_HOME/projects/<hash of the real path>.
    • If .claude, .agents, the skills directory or the staging directory is a symlink or junction, the command fails with conflict. This is checked when the context is created and again under the install lock.

Commit refs

  • @<sha> and /tree/<sha> accept a full 40- or 64-hex commit. It is fetched with git init and then git fetch --depth 1 <url> <sha>.
  • The commit is read only if a branch or tag of the named repository contains it. This refuses "imposter" commits that exist only in a fork. The check tries, in order: the recorded ref, then the default branch, then every branch and tag. It fetches commits only, using --filter=tree:0.

Docs: docs/cli-reference.md (the skillx add section, lock, update and sync, runtimes and scope, the installer usage lines, and the lockfile added field).

Decisions

  • The lock is written by default, the same as install. --no-lock skips it.
    • CLIs older than this change will reject a lock that contains added entries. The docs say so.
  • Uninstalling for one runtime drops the lock entry, which matches how registry packages already behave.

Review

I ran two isolated subagent reviews.

  • The first found 1 blocker, 3 major and several minor issues. All are fixed:
    • project symlink escape;
    • update picking the skill by name;
    • imposter commits;
    • update stopping at the first failing source;
    • plus the minors.
  • The second found 1 major issue (a local add removed lock pins it had not replaced) and some minor issues. All are fixed.

Verification

  • pnpm typecheck passes for every workspace.
  • pnpm test: 156 files, 2884 passed, 3 skipped.
    • One timing test in apps/web, proposal-detector-cost, failed once while another test run was using the CPU. It passes when run alone and is unrelated to this change.
  • New tests are in add-lock-refresh.test.ts, add-update-safety.test.ts and install-lock.test.ts. They cover:
    • commits behind the tip, on a side branch, and on an annotated tag;
    • a loose (fork-like) commit being refused;
    • a remote with no HEAD;
    • symlinked .claude and skills directories;
    • partial update;
    • pin handling.
  • Live end-to-end runs of the built CLI against anthropics/skills:
    • add at a non-tip commit with --project into claude-code and codex;
    • an added entry written to the lock;
    • sync --locked restoring the skill after its state was wiped, including through a followed @main ref;
    • update --project;
    • list --project;
    • a symlinked .claude refused with conflict;
    • an unknown commit reported as not_found;
    • a tampered digest reported as integrity_error.

Generated by Claude Code

…scope, several runtimes and commit refs

- skillx.lock/v1 gains an optional `added` list in a packages lock: the
  repository (no credentials), the skill's directory, the ref to follow,
  the commit read and the skill tree digest. `skillx add` writes it for
  git sources (`--lock`, `--no-lock`); a local directory is never locked.
- `skillx update` re-reads added skills from their recorded source (all,
  or `add:<name>`), and refreshes a lock that already pins them;
  `uninstall add:<name>` drops the entry.
- `skillx sync --locked` fetches each locked commit and installs the
  skill only when its tree digest is the locked one, planned together
  with the lock's packages.
- `@ref` and `/tree/<ref>` accept a full commit id, fetched on its own
  (`init` + `fetch --depth 1 <url> <commit>`) and checked after the read.
- `--runtime` takes several runtimes for one read, planned before any is
  written; `--project` installs into ./.claude/skills or ./.agents/skills
  with installer state kept apart per project, on every installer command.

Closes #166

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32rxE1XR42zxaKTD3MpW
- Refuse a project whose .claude/.agents, skills or staging directory is a
  symlink or junction, at context creation and again under the install lock;
  key project state by the project's real path.
- Read a commit id only when a branch or tag of the named repository contains
  it (commits-only fetch of the default branch, then all branches and tags),
  so a commit pushed only to a fork is refused.
- update picks the skill from the directory it was installed from, refuses an
  ambiguous name, and keeps updating and locking the other sources when one
  fails, then reports every failure.
- add reports a later runtime's failure with the targets already installed and
  never lets the lock write mask it; a local add drops the lock entry it
  replaces.
- A lock path holding @ resolves to the whole repository; init a SHA-256
  repository for a 64-hex commit; classify an unknown commit as not_found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32rxE1XR42zxaKTD3MpW
…checks

- A local add drops a lock pin only for a skill that target had from git,
  and leaves an unreadable lock alone.
- The branch-or-tag check of a commit tries the recorded ref first, then the
  default branch, and falls through to every branch and tag when a remote
  has no HEAD.
- update reports a non-CLI failure as unexpected_error and names the owners
  it did update; a failed lock write on a partial add is reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32rxE1XR42zxaKTD3MpW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): close the accepted design gaps of skillx add (lock, update, commit refs)

2 participants