Agent skills install from the npm packages the project already has - #219
Agent skills install from the npm packages the project already has#219wmadden-electric wants to merge 62 commits into
Conversation
|
Important Review skippedToo many files! This PR contains 128 files, which is 28 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (128)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
8842b4c to
4350bb3
Compare
Adds `prisma skills sync` and `prisma skills list`, and the staleness
check every other command runs.
Skills now travel inside the Prisma packages a project installs, so a
copy in a harness skill directory is current only when its
`library_version` stamp matches the version of the package it came
from. Sync resolves the allowlisted packages by name from the project
root and from each declared workspace member, copies each skill tree
into the four harness directories, and removes copies whose source
package is gone. It never scans node_modules — the allowlist states why
that is permanent.
The check lives in main.ts after dispatch: every mounted family runs
through that one call, so the ORM and Composer families need no copy of
it. It writes one stderr line, never changes the exit code, and is
silenced by --quiet, --json/--format json, PRISMA_SKILLS_CHECK=0, CI,
`skills: { check: false }` in prisma.config.ts, and
`skills sync --disable`.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Fixture projects in the layouts npm and pnpm produce, a workspace with two members pinning different versions, and a Yarn PnP fixture that patches Node's resolver and the filesystem module the way .pnp.cjs does — so the tests fail if the sync ever builds a node_modules path itself or reads through an API the PnP layer does not patch. Covers every state a copy can be in (in sync, stale, never synced, orphaned), pruning on package removal, leaving skills from other packages alone, exit 0 whenever there is nothing to do, and each of the check's off switches. The fixtures clear NODE_PATH first: vitest points it at this repository's pnpm store, which would otherwise make every fixture project look like it had two allowlisted packages installed. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The staleness notice is not TTY-gated, which the update-notification section would otherwise imply is the rule for advisory stderr lines, so its own section says why and lists every way to silence it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
`prisma --config <path> skills list` invokes the command that fixes stale skills, so the check must recognise the group even when shared flags come first. Also trims sync's help to the two examples the style guide asks for. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
A `packages/**` workspace made member enumeration walk the whole working tree — dist, coverage, .git, every source directory — and the resolver was then pointed at each one, four package names at a time. The staleness check runs that on every command, so an ordinary workspace pattern cost roughly a second per invocation instead of the milliseconds the design budgets. The walk now stops at a directory holding a package.json, because that directory is the member and everything below it is the package's own contents, and it never enters a dot-directory. Only directories with a package.json are returned, so `**` answers with packages rather than with directories. The new test counts directory reads rather than timing them: on a workspace with two built members it reads `packages` and `packages/group` and nothing else. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
`skills sync` printed `check: enabled` in a project whose
prisma.config.ts sets `skills: { check: false }`, contradicting
`skills list` and the check itself. It now needs the same config
section and combines it with the persisted opt-out the same way.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The Agent Skills spec defines no custom top-level frontmatter keys; extensions live under `metadata`, a map of strings. Slices 1 and 4 are stamping `metadata.library` and `metadata.library_version`, so the reader follows them there and nowhere else. No fallback to the old top-level spelling: nothing has shipped one, and accepting both would let a skill claim a stamp the spec has no place for. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Operator decision: the published binary is `prisma`, so CLI_NAME — the one place the user-facing name lives — now says `prisma`, and every command string, notice, error next step, help line and sample output follows it. `prisma-cli` survives only where it names something that really is still called that: the `@prisma/cli` package's own bin (its README and the update check's entrypoint detection), the update-check cache directory, the repository URL, the sign-in campaign tag, and the legacy error copy the service group rewrites. That rewriting is why one behavioural change came with the rename: `fromLegacyCliError` turned a legacy `nextSteps` line into a run-command action only when it began `prisma-cli `, and dropped every other line. Legacy builders written with the new spelling would have lost their next steps, so the mapper now recognises both spellings and renames `<name> app ` to `<name> service ` either way. The feedback client's user-agent follows CLI_NAME too, so it now reports `prisma/<version>`; it identifies this binary, and this binary is called prisma. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
`/docs/orm/tools/prisma-cli` is the path that 308-redirects to the ORM CLI reference, which is the whole reason the comment cites it. The sweep matched it because the path was followed by a space, and output-conventions.md kept the right spelling, so the two disagreed. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The mapper's current-spelling branch does have a producer — `computeConfigErrorToCliError` writes `prisma service <command>` into nextSteps, and `resolveComputeManagementContext` maps it — so removing the branch fails two tests in service-compute-config. What it lacked was a test that says so directly: those two fail for reasons that read as compute-config behaviour. These drive `renameAppCopy` and `fromLegacyCliError` with one spelling each, and pin the asymmetry that makes this worth covering — a command line the mapper does not recognise is dropped from nextActions rather than passed through, so an unrecognised spelling costs the user their next step with nothing to show for it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Round-4 review fixes (S2-R3-1, S2-R3-2), committed at session halt; suites not re-run. Includes drive project artifacts up to this point. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The branch's only cli-engine change was two doc-comment lines renamed in a prisma-cli -> prisma sweep. That trips the engine-version check because 0.2.0 is already published, and publishing the engine for comments is not worth it. Restore the file to origin/main byte-for-byte. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Both failures were in test fixtures, not production code — the skills library joins every path with path.join, which is correct on Windows. skills-pnp: the fake PnP layer remapped virtual paths with a startsWith check against a forward-slash prefix, but on Windows path.join hands it backslash paths, so the remap missed and sync found no packages. The fixture now compares in forward-slash form. skills-workspace-scan: the recorded readdir paths carry native separators, so the expected relative paths did not match on Windows. The assertion now normalizes separators before comparing; the set of directories it pins is unchanged. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…eout flake Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The new base made resolveStateDir synchronous; the .prisma-directory anchor and compute-config fallback both walk the filesystem, so the function stays async and its two callers await it with the request signal. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
One retry command in project show and two test expectations still said prisma-cli; both sides' lines merged cleanly past the rename commit. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
prisma init now writes the real postinstall hook, so the advisory next-action was dead weight. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Installed skills are ordinary files git can see. A resync removes the .gitignore older CLI versions wrote, because replaceTree rewrites the whole tree. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The old v6/v7 skills installer that shelled out to npx skills@latest is replaced by the skills group. The post-login tip now points at skills sync, driven by the same status read the skills commands use, and fires only when installed Prisma packages have out-of-date skill copies. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
A name collision with a hand-written skill, or with one stamped by a package outside the allowlist, used to parse as an absent target and get silently deleted and replaced. Such a directory is now reported as unmanaged: sync leaves it untouched and warns, and it no longer counts as out of date. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Read the persisted opt-out before any scan and skip the orphan scan on the notice path; validate the skills config section with its own validator; exempt --version like the update check; stop reading flags past a bare --; honor an explicit --config path when loading the config. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
A purely local repository setup: it writes "postinstall": "prisma skills sync || exit 0" into package.json (never touching a script the user wrote) and runs the skills sync through the same status/sync path as prisma skills sync, presented with the shared sync presentation. Every degraded outcome — no package.json, a foreign postinstall, a sync failure — is a diagnostic on a successful exit 0, and reruns report each step as already done. --no-postinstall and --no-skills skip one step each. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
An EACCES on the write and a non-object scripts value both become warn diagnostics on a zero exit, and a BOM or CRLF manifest survives the rewrite byte-compatibly. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
init now emits the unmanaged-directory diagnostics, and the shared sync presentation renders a refused table plus an up-to-date summary clause so neither sync nor list claims everything is current while a directory was refused. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…elf-heals Unmanaged is reserved for an existing SKILL.md that is unstamped or foreign-stamped, and sync also removes the .gitignore an older CLI left inside a copy that is already current. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Its status read is guarded and skips the orphan scan the tip never reads. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Its readers and writers were deleted with the agent group; the parser ignores unknown keys, so an older state file still reads cleanly and an older CLI defaults the missing field to null. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…s read readSkillsStatus accepts them as options so the ancestor walk and the opt-out file are not read twice on the path that does not exit early. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
output-conventions describes the refusal rule and the summary clause, command-principles adds skills and init to the preview scope, and init's help says the hook lands in the current directory while the skills land at the workspace root. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Closes INIT-R4-1 and INIT-R4-2: only ENOENT on the SKILL.md stat reads as absent, so an EACCES or ENOTDIR parent classifies unmanaged and sync declines instead of dying on the rm. The init sync-failure test now forces its failure with a read-only parent, since a file squatting on .claude is refused gracefully now. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
575a441 to
8e1b0a6
Compare
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
In a project that has
@prisma/orm-postgresinstalled, this now works — no network, no extra tooling:And after
pnpm up @prisma/orm-postgresbumps the package but nobody re-runs sync, everyprismacommand appends one line to stderr:The decision
Agent skills now travel inside the npm packages they describe, and this PR adds the CLI half of that:
prisma skills synccopies them into place,prisma skills listreports status,prisma initsets a project up once, and a staleness notice after every command keeps the copies from silently rotting.An agent skill is a directory with a
SKILL.md— instructions that teach a coding agent (Claude Code, Cursor, Codex, Windsurf) how to use a library. Each harness auto-indexes skills from a known project directory (.claude/skills/,.cursor/skills/,.agents/skills/,.windsurf/skills/); a skill anywhere else is invisible. So a skill is only useful if something copies it into those directories and keeps the copy matching the installed package version. Until now that something wasprisma orm initshelling out to a third-party CLI that cloned our skills from GitHub at a ref named after the package version — a convention, not a guarantee — and nothing ever detected that the copies had gone stale.How sync decides what to install
Sync resolves a hardcoded allowlist of four packages —
@prisma/orm-postgres,@prisma/orm-sqlite,@prisma/orm-mongo,@prisma/composer— by name from the project root and each workspace member directory. It never scansnode_modules: a skill is instructions an agent will follow, so a scanner would let any transitive dependency inject instructions into the agent. That invariant is stated (and marked permanent) at the allowlist declaration, and this PR's trust boundary is exactly the code's — the only skills installed come from packages the user deliberately installed.Each shipped
SKILL.mdcarries a version stamp in its frontmatter, under the Agent Skills spec'smetadatamap (custom top-level keys are not spec-defined):Sync compares the installed package's version against the stamp in each harness copy and re-copies on mismatch. Pruning removes only copies whose stamp names an allowlisted package that is no longer installed — a skill some other tool put there is never touched. The synced copies are ordinary files git tracks; sync removes the
*ignore file an earlier revision wrote into its copies, but leaves any.gitignorea user authored in place. If two workspace members pin different versions of one package, the highest wins and sync warns. Yarn PnP works because all reads go through Node's resolution and fs layers (there is a test that fakes the PnP zip filesystem to prove it).Sync refuses what it does not own. A target directory whose
SKILL.mdis unstamped, unreadable, or stamped by a non-allowlisted package — a hand-written skill colliding on name — is left byte-for-byte intact, reported in arefusedarray and aSKILLS.UNMANAGED_DIRECTORYdiagnostic, and the summary line never claims "up to date" without naming it. A directory whoseSKILL.mdis genuinely absent (an interrupted copy) is repaired.prisma initprisma initinitializes a repository for Prisma development, purely locally — no platform calls. Two steps, each with an opt-out (--no-postinstall,--no-skills), always exit 0, never prompts, idempotent:"postinstall": "prisma skills sync || exit 0"to the current directory'spackage.json, so skills resync on every install and upgrade. A different existing postinstall script is never clobbered or chained — init reports it and tells the user what to append. BOM, CRLF, indentation, and trailing-newline style are preserved; an unreadable, unwritable, or malformed manifest is a diagnostic, not a failure.This is a new command reusing a retired name: #218 deleted the old compute-config wizard, and nothing from it returns. Sync itself still never touches
package.json(a test pins byte-identity across a sync run) — writing the hook is exclusively the act of a user runninginit.The
prisma agentgroup is deletedprisma agent install|update|status— the old installer that shelled out tonpx skills@latestfor the v6/v7 skills — is gone (operator ruling, 2026-08-21). The post-login tip that advertised it now offersprisma skills syncinstead, only when installed packages actually have stale copies, and can no longer fail a login that already succeeded.The staleness notice
It prints to stderr, after the command's own output, never changes the exit code, and is deliberately not TTY-dependent — its main reader is an agent running the CLI without one. Off switches:
--quiet,--json/--format json,--version,PRISMA_SKILLS_CHECK=0,CI/GITHUB_ACTIONS,skills: { check: false }inprisma.config.ts(an explicit--config <path>is honored), a persistent opt-out written byprisma skills sync --disable(stored in.prisma/skills.json), and anyskillscommand itself. Flag scanning stops at a bare--. The per-command cost is stat calls and small file reads;prisma.config.tsis only evaluated after staleness is already established. With the init-written postinstall as the primary trigger, the notice is the backstop for projects that never raninitor removed the script.Pin and state dir find the project root
.prisma/local.json(the link pin) and the state dir are now discovered by walking up from the cwd to the nearest directory containing.prisma/— a pure filesystem check, no config file is read — so a repo linked at its root works fromapps/api/. Nearest wins deliberately: a subdirectory linked to a different project beats the root. Commands that rewrite or delete the pin operate on the file they found, not on the cwd. When no.prisma/exists, behavior is unchanged.The rename:
CLI_NAMEis nowprismaThe published package (
prisma) has installed a bin namedprismasince 8.0.0-rc.3, but every string the CLI printed still saidprisma-cli. This PR movesCLI_NAME, so help text, error guidance, and the notice above all name the binary users actually have. There is no compatibility layer: the CLI is pre-rc and owes old spellings nothing, so every producer of error guidance was fixed to emit the current commands directly, and the display-time rewriter that used to patch up legacy spellings (renameAppCopyand friends) was deleted rather than extended.fromLegacyCliErrorsurvives only as a structural converter (legacy error shape →CliError); it no longer rewrites or filters any copy. Deliberate survivals, so nobody "finishes" the rename by mistake: the@prisma/clipackage's own bin and README, the update-check entrypoint matcher and cache directory,git@github.com:prisma/prisma-cli.gitrepo URLs, and theutm_source/utm_campaignsign-in tags.Tests
90+ new tests: npm/pnpm/Yarn-PnP fixture projects, every sync/check state (stale, never-synced, in-sync, refused, opted out), the collision and unreadable-skill refusals (chmod-based), pruning on package removal, a two-member monorepo with the version-conflict warning, every off switch, init's full package.json edge-case matrix (foreign script, non-object
scripts, BOM, CRLF, read-only file, missing manifest), a credential-free e2e driving the built binary throughinittwice, areaddir-count regression test bounding workspace-glob expansion, and the legacy-error mapper suite.For other owners
prisma-cli/<version>toprisma/<version>— wire-visible; whoever reads that dashboard should know.isLikelyGlobalNpmEntrypoint(update-check) still matches onlyprisma-cliinstall paths, so a globally installedprismagets the docs-link fallback instead of a concrete update command. Pre-existing, untouched here, newly conspicuous.npx skills add prisma/skillscopy button — the last surface promoting the retired installer; its removal is pending an operator decision.orm init) drops its scaffold-time sync call per operator ruling — follow-up on that PR.Merge order: #218 is merged; this PR next, then prisma/prisma#30096 (packaging) → #30097, and prisma/composer#251's npm release — all ship skills only this CLI can read.
Alternatives considered
npx skills add(status quo): version matched by ref-name convention only, unmanaged copies, network access during init, and an unpinned third-party CLI in our init path.node_modules: prompt-injection by construction — any transitive dependency could plant instructions. Permanently rejected.node_modulesinstead of copies: nonode_modulesunder Yarn PnP, symlink creation needs elevation on Windows, and only Claude Code documents following symlinked skills. Version-stamped, checked, auto-resynced copies are a managed cache.AGENTS.mdtelling agents to run sync each session: rejected by the team — agents shouldn't carry maintenance duties.postinstallin our own packages: dependency lifecycle scripts are blocked by default in pnpm 10+, bun, and Deno. Permanently rejected.postinstallfromprisma orm initor from sync itself (the original design): rejected — a routine command silently editing yourpackage.json, or re-adding a script you removed, is not acceptable. The operator's final ruling: the hook is written only byprisma init, a command whose whole point the user invokes deliberately; nothing automatic ever edits the manifest, and the staleness notice covers everyone else.🤖 Generated with Claude Code