Skip to content

skill install: handle a repository that is a collection of skills - #437

Merged
ralyodio merged 1 commit into
mainfrom
worktree-skill-install-collections
Aug 28, 2026
Merged

skill install: handle a repository that is a collection of skills#437
ralyodio merged 1 commit into
mainfrom
worktree-skill-install-collections

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The bug

skill install assumes a repository is one skill. It clones the source into <skills-dir>/<name> and stops.

Claude and Kimi discover skills by scanning exactly one level deep. So a repository whose skills live in subdirectories — a collection — lands every one of them a level too deep, where nothing will ever find them.

git clone still exits 0, so moshcode reports the install succeeded. The user gets a green summary and zero usable skills, with nothing to suggest otherwise. That silence is the part worth fixing: a loud failure here would have been harmless.

Verified against a real 13-skill collection before the change:

before settle:
one level deep (what engines scan): 0 <- installed, undiscoverable

The change

  • skillCollection(dir) reports what a clone actually contains: single (a SKILL.md at the root), collection (subdirectories holding one), or empty.
  • settleSkillClone(dir) resolves a clone into the shape engines scan:
    • single is left exactly as cloned — the existing behaviour, unchanged.
    • collection has each skill moved up beside its siblings and the wrapper removed. The wrapper holds the repository's README, tooling and CI, none of which is a skill.
    • empty is removed rather than left as a directory that can never resolve.
  • The claude and kimi actions carry their clone target as settle, and the runner resolves it after a successful clone. Gemini installs natively and is never settled.
  • A clone containing no SKILL.md anywhere is now reported as failed, with that reason, instead of counting as installed.
  • Results carry kind, skills and kept, so the summary can say what actually landed.

After:

after settle:
kind: collection | installed: 13 | kept: 0
one level deep (what engines scan): 13
wrapper removed: true

Safety

A skill whose name is already taken is left alone and reported in kept. This runs inside the user's real skills directory, so a name collision must never silently replace a skill they already had. There's a test pinning exactly that.

On the test changes

settle is injectable alongside run, matching how the suite already stubs subprocesses.

Three existing tests stubbed run without it. With a stubbed clone nothing ever lands, so the real settle correctly found an empty directory and reported failure — the new strictness working as intended, on a fixture that was never meant to exercise it. Those tests now stub both. The skillInstallAction deep-equals were updated to assert the new settle field rather than ignore it.

Verification

  • New suite: test/skill-install-collections.test.mjs, 12 tests covering shape detection, settling, collision safety, and that a failed clone is never settled.
  • Full suite green: 2171 pass, 0 fail (282 pre-existing skips).
  • End to end against a real collection: 0 discoverable before, 13 after, wrapper removed.

Extends prd/0003, which set out the fan-out but assumed one repository is one skill.


🤖 Generated with Claude Code

https://claude.ai/code/session_01B7KVQWbU17PR2mCUn8jjpH

`skill install` assumed a repository is one skill: it clones the source into
`<skills-dir>/<name>` and stops. Claude and Kimi discover skills by scanning
exactly one level deep, so a repository whose skills live in subdirectories
lands every one of them a level too deep, where nothing will ever find them.
`git clone` still exits 0, so moshcode reports the install succeeded. The user
gets a green summary and zero usable skills, with nothing to suggest otherwise.
- `skillCollection(dir)` reports what a clone actually contains: `single` (a
SKILL.md at the root), `collection` (subdirectories holding one), or `empty`
- `settleSkillClone(dir)` resolves a clone into the shape engines scan. A
single skill is left alone; a collection has each skill moved up beside its
siblings and the wrapper removed, since the wrapper holds the repository's
README, tooling and CI, none of which is a skill; an empty clone is removed
rather than left as a directory that can never resolve
- claude and kimi actions carry the clone target as `settle`, so the runner
resolves them after a successful clone. Gemini installs natively and is
never settled
- a clone containing no SKILL.md anywhere is now reported as failed with that
reason, instead of counting as installed
- results carry `kind`, `skills` and `kept`, so the summary can say what landed
A skill whose name is already taken is left alone and reported in `kept`. This
runs inside the user's real skills directory, so a name collision must never
silently replace a skill they already had.
`settle` is injectable alongside `run`, matching how the suite already stubs
subprocesses. Three existing tests stubbed `run` without it, so the real settle
correctly found an empty directory where a stubbed clone never landed; they now
stub both.
Verified end to end against a real 13-skill collection: 0 discoverable before,
13 after, wrapper removed. Full suite green (2171 pass, 0 fail).
Extends prd/0003.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B7KVQWbU17PR2mCUn8jjpH
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

0 finding(s) in the 6 file(s) this pull request changes.

Nothing in the files this pull request changes.

59 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 48 | **LOW**: 6

Not introduced by this pull request. The full set is in the Security tab.

SeverityRuleLocation
HIGHjs-ssrf-outbound-requestapps/pwa/public/sw.js:45
HIGHtls-verification-disabledapps/pwa/src/lib/moshpit-gateway.mjs:299
HIGHsh-remote-script-executioninstall.sh:79
HIGHsh-remote-script-executioninstall.sh:83
HIGHtls-verification-disabledsrc/dns.mjs:766
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:74
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:88
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:114
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:301
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:305
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:350
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:574
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:755
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:757
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:816
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:862
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:932
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:1035
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:1058
MEDIUMsql-template-interpolationapps/pwa/src/moshpit.mjs:1080

…and 39 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit e7f58ab into mainAug 28, 2026
6 checks passed
@ralyodio
ralyodio deleted the worktree-skill-install-collections branch August 28, 2026 21:45
@ralyodioralyodio mentioned this pull request Aug 28, 2026
Sign up for freeto 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.

1 participant

@ralyodio