feat(skills): versioned skill cache paths and repo-level version inheritance#99
Merged
Conversation
Skill.version is now non-optional — omitting it in a Lucafile throws SkillDecodingError.missingVersion, which SpecLoader wraps as SpecLoaderError.invalidSpec. All fixture files and tests updated accordingly; the previously-optional behaviour test is removed.
…dividual installs SkillSet.version is now String (non-optional). SkillsInfoFactory throws SkillsInfoFactoryError.missingVersion when an individual install is attempted without a ref. InstallCommand.validate() provides an early CLI error for that case. All test callsites updated to supply a version.
…ned cache folder
Symlink source path gains a /{version} component:
.luca/skills/{name}/{version}/ (project-local)
~/.luca/skills/{name}/{version}/ (global)
…lready installed
Skills are now cached at .luca/skills/{name}/{version}/ (mirroring tool
cache paths). Named skills that already have their versioned folder skip
the git clone entirely and only re-create symlinks. A migration step
removes any old flat-layout SKILL.md before writing the versioned folder.
…lledCommand shows versions installedSkills() now returns [String: [String]] (skill name → sorted version list) by iterating the two-level name/version directory tree. InstalledCommand displays each skill name followed by its installed versions, matching the tools listing style.
…sions repos: entries now support an object form with url + version fields. Skills referencing an alias without their own version: inherit the repo's version (skill-level overrides repo-level). If two skills from the same resolved URL end up with conflicting versions, SkillsInfoFactory throws versionConflict rather than silently dropping the second version. Fixes the silent-discard bug where mixed per-skill versions on the same repository caused one SHA to be silently ignored.
…at different SHAs Two different repo aliases pointing to the same URL but at different commit SHAs previously triggered a spurious versionConflict error. Skills are now grouped by a (repository, version) composite key so each unique (URL, SHA) pair produces an independent SkillSet — checked out and cached separately. Removes SkillsInfoFactoryError.versionConflict entirely; the restriction was wrong because independent aliases intentionally express separate checkouts.
albertodebortoli
force-pushed
the
skill-versioning
branch
from
July 22, 2026 10:57
74fda3a to
1d5aa36
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR introduces versioned skill management — skills are now pinned to a specific git ref (tag, branch, or commit SHA), stored and cached under a versioned path, and that version can be declared once at the repository level rather than repeated on every skill entry.
Motivation
Without versioning, every
luca installhad to re-clone a skill repository to determine whether anything had changed, because there was no record of which revision was installed. This made installs slow and unreliable. It also made it impossible to have two sets of skills from the same repository at different refs (e.g. a stable tag for most skills and a specific SHA for one experimental skill).No lockfile
A lockfile was considered and deliberately rejected. Luca's original design philosophy argues against lockfiles for managed tools: skills are symlinked directly into each agent's folder (
.claude/skills/, etc.), so agents always access the exact installed version. Users never need to know which SHA is live — the symlink is the truth. Introducing a lockfile would replicate the same information in a second place without adding safety, while adding friction to the workflow. The versioning pattern here mirrors how tool versions are managed (declared in the Lucafile, cached under~/.luca/tools/{name}/{version}/), keeping the mental model consistent.What changed
Version is now mandatory for skills.
Every skill must resolve to a version — either declared inline on the skill entry or inherited from the
repos:dictionary entry. Attempting to install without one is an error with a clear message.Skills are cached at versioned paths.
Global cache:
~/.luca/skills/{name}/{version}/Project-local cache:
.luca/skills/{name}/{version}/If the versioned folder already exists, the download is skipped entirely. Old flat-layout installations (
.luca/skills/{name}/SKILL.md) are auto-migrated on the next install.Symlinks target versioned folders.
.claude/skills/{name}→.luca/skills/{name}/{version}/The symlink name is unchanged so agents see no difference.
Repo aliases can carry a
versionfield.All skills referencing the alias inherit its version, with per-skill version taking precedence:
Same repo, different SHAs — now supported.
To avoid two skills from the same repository at different refs to trigger a spurious version-conflict error, the grouping key is
(repository, version)so each unique(URL, SHA)pair produces an independentSkillSet— cloned and cached separately:luca installed --skillsshows versions.Output now lists each installed version per skill name:
Type of Change
How Has This Been Tested?
Checklist
Breaking Changes?
versionis now required on every skill (or on its repo alias).Migration: Add a
version:field to each skill, or promote it to the repo alias entry to avoid repetition. The flat → versioned cache migration is transparent: on the firstluca installafter upgrading, any existing~/.luca/skills/{name}/SKILL.mdfile is removed before the versioned copy is written. No manual cleanup required.