Support declaring skill locations beyond the top-level skills/ directory
#17827
Replies: 2 comments
|
This already works with pnpm's own git subdirectory syntax. pnpm uses pnpm add 'gitlab:gitlab-org/cli#v1.124.0&path:/internal/commands'permissions:
"@gitlab-org/cli@git+https://gitlab.com/gitlab-org/cli.git":
skills: trueVerified on pnpm 12.19.0. The install reports the package as awaiting approval. After approval, Why it works: with On the options:
The case this doesn't cover is a repo whose skills have no ancestor directory named Written by an agent (Claude Code, claude-opus-5-5). |
|
Follow-up: #17976 makes pnpm accept the npm spelling too, so Written by an agent (Claude Code, claude-opus-5-5). |
Uh oh!
There was an error while loading. Please reload this page.
Contribution
Describe the user story
Agent Skills sometimes ship inside repositories whose primary purpose is something else, and whose root layout is already taken. A concrete example: the official
glabskill lives in gitlab-org/cli atThe Agent Skills spec allows
SKILL.mdanywhere in a package, but pnpm only discovers skills under the package's top-levelskills/directory (plus category subpaths, up to three levels). So this perfectly valid, upstream-maintained skill is invisible topnpm approve, while other skill tools that accept a subpath install it fine.Verified on pnpm 12.17.0: a git dependency whose skill sits at a nested path installs cleanly but is never offered for approval; move the identical
SKILL.mdunderskills/and it's detected immediately. Upstreams like GitLab aren't going to restructure a Go CLI's source tree to satisfy an npm-package convention they don't otherwise use — and consumers fall back to vendoring copies, which loses exactly the provenance and lockfile pinning that make pnpm-managed skills worth using.Describe the solution you'd like
Akin to @vercel-labs/skills, a client-side solution. One of three options:
A. Path-scoped entries in the existing
permissions.skillslistpermissions.<pkg>.skillscurrently takestrue | false | [names], where names are directories underskills/. Extend it: an entry containing/is treated as a path (glob allowed) inside the package, and pnpm scans exactly there — requiringSKILL.mdto exist at that path:glab(basename of the skill directory), collision rules unchanged.pnpm-workspace.yaml), covering content the lockfile integrity already pins.B. Consumer-declared discovery map (variant of A)
Keep approval and discovery separate: a
skills.pathsmap keyed by package matcher, next toskills.dirs:C. Implement npm's
::path:git fragment, with a root-level skill scan for path-selected packagesnpm already supports selecting a subdirectory of a git repo as the package root (npm-package-arg#91):
pnpm parses git specs with a fork of
npm-package-arg, so the fragment grammar is already understood at the boundary — but the subdirectory is not honored on fetch. Today, any colon in the fragment makes resolution fail outright:(verified on pnpm 12.17.0; npm 11 extracts the subdirectory and reads its
package.json)The proposal has two halves.
1. Core: implement
gitSubdirend-to-end — fetch the subdirectory as the package root, and keep the::path:selector intact through committish resolution and in the lockfile.2. Skills, one small rule: when a package was fetched via a consumer-chosen
::path:fragment, the subdirectory root is itself a skills root — directories directly under it containingSKILL.mdare discovered as skills, alongside the existingskills/scan.With both, glab installs and links as if it had been built for pnpm:
gitlab:gitlab-org/cli#v1.124.0::path:internal/commands/skills/bundled/assetsassets/directory, which directly containsglab/SKILL.md→ discovered, no dependency on where an ancestor happens to be namedskills/permissionskey, pinned by the existing lockfile integrity, linked asglabThe root-scan rule is deliberately narrower than it sounds: it activates only for packages whose spec carries an explicit
::path:— i.e. the consumer already declared "this exact directory is what I want installed", which is a stronger, more reviewable signal than any post-hoc path list. Packages without the fragment keep today's behavior verbatim.This is still mostly a compatibility gap being closed, not a skills feature being invented: a whole class of "valid skill, wrong layout" repos becomes installable, and pnpm gains git-subdir dependencies for any use, skills or not.
Describe the drawbacks of your solution
A. Path-scoped entries in
permissions.skills/is a silent semantic switch. It collides with the existing category layout:engineering/glabis already expressible today as a discovered category path — is that an approval for a discovered skill, or a directive to scan a new location? Ambiguity in a security-relevant field is a bad trade.permissionsrecords verdicts; this makes it also encode where to look. Consequences are visible in tooling:pnpm approvewrites entries programmatically, and generated name-entries would be indistinguishable from hand-written path-entries — bad for review clarity, bad for revocation semantics ("a skill not in the list is never offered again" gets weird when the list mixes names and globs).@gitlab-org/cli), and thepermissionskey must match that derived name — an artifact of pnpm's fallback, not anything upstream declared. Host mirrors or owner renames silently orphan the approval.node_modulesfor one Markdown file; the path only selects what to scan, not what to download.B. Consumer-declared discovery map
skills.paths+permissions), and the two grammars differ:permissionsneeds thename@git+urlrepo form (a bare name does not match a git dependency — verified), while the map keys on a plain name. Name-only keying also drops provenance: it can't express which@gitlab-org/clisource, and cloning the repo elsewhere is a different trust decision with the same key.skills: falsebut present inskills.paths; a discovered path the approval list doesn't cover. Each pair needs a defined precedence, orpnpm approvestarts nagging on every install.permissionsreads exactly like "configure paths, get skills, no approval" to a reviewer — even if the approval still gates linking. A is worse on paper for trust but has fewer such states because there's only one gate to configure.C. npm's
::path:git fragment::path:, installing the repo root under an unchanged dependency entry (npm/pacote#513). For skills, the silent-wrong-content version of that bug means instructions changing under an approval nobody re-granted. Mitigable — test that::path:round-trips through resolution, which is exactly what npm had to learn — but it's a regression pnpm would have to avoid by contract.package.jsoninside the selected subdirectory; glab has none anywhere in the tree. Implement npm's strictness and the motivating example stays broken; extend pnpm's existing manifest-less fallback (derived@owner/reponames) to subdirs and git specs inpackage.jsongrow a pnpm-only dialect npm rejects. Either outcome is a deliberate spec decision that belongs in the docs, not an implementation detail.name@git+urlapproval key — comes from pnpm's host-derived fallback. It's an artifact no author declared: owner renames, host mirrors, or later adding a manifest with a different name silently orphans (or re-targets) the approval. Pre-existing in the manifest-less fallback, but C is the first feature to lean on it as a happy path.skills/-only (everything else), plus their interactions: category rules at a root that is already the skills root, collision naming, and straySKILL.mdfixtures a repo keeps at its root becoming discoverable skills. The activation condition is clean, but the rule is one more branch in the discovery story, and it's skills-feature work after all — C is not "zero skills changes."glab-skills@…::path:…,xyz@…::path:…), with two lockfile entries for one repository. A/B take lists within one dependency.package.json. The path lives in the dependency spec, so path churn (upstream moves the directory) dirties manifests and lockfiles across all consumers — whereas A/B confine that maintenance to the workspace policy file.Describe alternatives you've considered
Author-declared paths like:
{ "name": "@glab/cli", "skills": ["internal/commands/skills/bundled/assets/*"] }Needs buy-in from the skill authors, which is more work, but might be a much cleaner solution in the long term.
All reactions