Skip to content

feat(skills): publish the agent skills as their own npm package#899

Open
mlennie wants to merge 1 commit into
mainfrom
monty/agent-skills-package
Open

feat(skills): publish the agent skills as their own npm package#899
mlennie wants to merge 1 commit into
mainfrom
monty/agent-skills-package

Conversation

@mlennie

@mlennie mlennie commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What this solves

Publisher's 28 agent skills reach an npm user as MCP prompts, and only as MCP prompts. Those prompt bodies point at 25 reference/*.md files that no npm consumer can open: 19 live reference/... pointers name files the host has no way to read. The publish root is packages/server/, so skills/ sits above it and nothing there can reach the tarball. Any host that reads skill files from disk (Claude Code, the Publisher plugin, a scaffolder) gets no files at all.

A skills content edit also has to ride a joint sdk+app+server version bump. 3 of the last 10 commits touching skills/ were pure content edits whose only change under packages/server/src was the regenerated bundle.

What it does

Adds @malloy-publisher/skills: the 28 skills and their reference/ directories as real files, on their own version line.

  • Exports skillsDir and listSkills(), so a consumer can locate the files without hardcoding a node_modules layout. listSkills() parses frontmatter from the shipped files, so there is no generated manifest to drift. The exports map keeps ./package.json and ./skills/* reachable, without which require.resolve('pkg/package.json'), the usual way to find a package root, fails outright.
  • skills/ stays at the repo root and prepack copies it in. That is not a style preference: a symlink named in files packs to package.json alone, with exit 0 and no warning, so the copy is load-bearing.
  • check-pack packs for real and compares the tarball against the source tree, deriving every expectation instead of hardcoding a count. An empty tarball is otherwise completely silent.
  • One shared predicate drives the copy, the audit and the tests, so they cannot disagree about what may ship.

What it does not do

It does not make MCP any fresher. mcp/server.ts statically imports the bundle and the bundler inlines it into dist/server.mjs, so newer skills still need a server build. This buys the file channel and its own semver, nothing more.

Also in here

Replaces skills_bundle.spec.ts's "at least 24 skills" floor, which sat 4 behind reality and would have passed a four-skill regression, with a regenerate-and-compare against skills/.

Publishing

Publishing is a separate manual dispatch (skills-npm.yml), deliberately outside the sdk/app/server lockstep release, since a skill edit should not need a server release. One thing worth flagging for whoever cuts it: npm configures trusted publishing per package, on the package's own settings page, so the entry cannot exist until the package does. The first publish of this name has to be done by hand by an @malloy-publisher owner, with scripts enabled, and the workflow takes over from the next version. The workflow comment spells out the order.

Verification

  • Full gate green: typecheck, lint, prettier, and 1341 tests (1127 unit, 207 integration, 7 skills), 0 failures.
  • Live: the built server boots and serves all 28 skills as MCP prompts, with examples serving 3/3 packages.
  • The packed tarball installs with npm and yields 28 skills plus 25 readable reference/ files from node_modules under Node 24.
  • Both new gates were mutation-tested rather than assumed: dropping one skill from the bundle fails the regen-diff (the old floor passed that), and a copy that ships nothing fails the pack audit.

The npm publish root is packages/server, so skills/ sits above it and cannot
reach the tarball. Skill bodies do reach npx users, inlined into the MCP prompt
bundle, but no skill files do, and the 25 reference/ files reach nobody: 19 live
reference/ pointers inside those prompt bodies name files the host cannot open.
A skills content edit also has to ride a joint sdk+app+server version bump,
because the only thing that carries it is the regenerated bundle.

@malloy-publisher/skills packs the 28 skills and their reference/ directories as
real files, on its own version line. It exports skillsDir and listSkills() so a
consumer can locate them without hardcoding a node_modules layout; listSkills()
parses frontmatter from the shipped files, so there is no generated manifest to
drift. The exports map keeps ./package.json and ./skills/* reachable, without
which require.resolve('pkg/package.json'), the usual way to find a package root,
fails outright.

skills/ stays at the repo root and prepack copies it in. A symlink named in
"files" packs to package.json alone, with exit 0 and no warning, so the copy is
load-bearing rather than a convenience, and check-pack packs for real and
compares the tarball against the source tree because that failure is otherwise
silent. The same shared predicate drives the copy, the audit and the tests, so
they cannot disagree about what may ship.

This does not make MCP any fresher. The server statically imports the bundle and
the bundler inlines it, so newer skills still need a server build. The package
buys the file channel and its own semver, nothing more.

Also replaces skills_bundle.spec.ts's "at least 24 skills" floor, which sat 4
behind reality and would pass a four-skill regression, with a regenerate-and-
compare against skills/.

Publishing is a separate manual dispatch and needs an npm trusted-publisher
entry, which cannot exist until the package does; the workflow documents the
order.

Signed-off-by: Monty Lennie <[email protected]>
Comment on lines +27 to +49
name: Verify the published tarball
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}

- name: Install dependencies
run: bun install --frozen-lockfile

# A skills/ dir that failed to copy packs as nothing at all, with exit 0
# and no warning, so this is the only thing standing between a bad refactor
# and publishing an empty package.
- name: Check pack contents
run: bun run --cwd packages/skills check-pack

- name: Test
run: bun run --cwd packages/skills test

publish:
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.

2 participants