feat(platform): list knowledge skills in the marketplace and install them to AutoPilot - #14431
feat(platform): list knowledge skills in the marketplace and install them to AutoPilot#14431Pwuts wants to merge 23 commits into
Conversation
…d a category filter Marketplace listings are now filed under one of eight canonical categories, shoppers can filter the front page by them, and verified listings rank above unverified ones everywhere the store is browsed. SECRT-2594. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g and the required category Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…it matches The same three-line choice — asked-for category, else the canonical set when the setting is on, else no filter — was spelled out at all three query sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Other" was one of the ten legacy options and is not in the canonical eight, so the publish happy path could no longer find it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both shipped untested, which put the backend patch coverage at 69% against an 80% target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A skill is instructions and examples, not a runnable graph, so it cannot ride StoreListing: that model's agentGraphId is required and the StoreAgent view exposes the graph as non-null. SkillListing and SkillListingVersion mirror the store's shape — slug, version, submission status, review fields, isVerified, canonical categories — and hold the SKILL.md instead. owningUserId is nullable so the platform can publish starter listings with no creator profile, the way a roster Expert has no owner until it is hired, and slug is globally unique so such a listing needs no creator segment in its URL. No accompanying SQL view: StoreAgent exists to aggregate run counts and review stats, and a skill has neither, so its install count sits on the listing row. Schema and migration only; nothing reads these tables yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds GET /api/store/skills, GET /api/store/skills/{slug} and
POST /api/store/skills/{slug}/install, plus two platform-authored starter
listings so the catalogue is not empty before creator publishing ships.
A listing's slug is both its marketplace URL segment and the name the skill
takes once installed, so the two cannot drift and a re-install picks up a newer
approved version in place. Install goes through store_user_skill, the same
function the copilot's store_skill tool and the SKILL.md upload endpoint use,
so a marketplace skill is validated, capped and stored exactly like any other.
Browse is rooted at the version rather than the listing so verified-first
ordering reads its own column. The starter skills' content is a real SKILL.md
under starter_skills/, parsed with parse_skill_markdown, so a seeded skill
cannot drift from the format an installed one has.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mutation testing found both absence tests passing for the wrong reason: they set the listing's hasApprovedVersion false, so the listing-level filter hid the row before the version's status was ever consulted. hasApprovedVersion means some version was approved once, not that the live one still is, so a version rejected on re-review is exactly the case the status check exists for and nothing covered it. The detail and install cases are parametrized over both shapes, and the ordering test now creates the verified listing first so recency ordering alone cannot satisfy it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The marketplace now reads Experts to hire, Workflows to install, Skills to
teach. A skill card opens /marketplace/skills/{slug}, which shows the
instructions the AutoPilot will follow and adds it in one click — two from the
front page.
Integrations the skill assumes are shown as "Works with …" beside the button
and, after the install has already succeeded, as a connect step for the ones
not yet connected. Connecting is the normal next step rather than a
precondition, so nothing here is styled or worded as a warning and the install
never waits on it.
Behind the skills-hub flag; off hides the shelf and issues no request for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…itors The listing body was a <pre>, so a reader judging a skill before installing it saw "## Before writing anything" instead of a heading. It renders as markdown now, with the file's own heading levels demoted under the page's. A signed-out visitor got a button that called an authenticated endpoint. The listing is public, so the call to action stays and links to log in rather than disappearing the way the agent install button does — a listing with no CTA is the wrong trade for a marketplace shelf aimed at people who have not signed up. Provider names on the card go through formatProviderName, so a card no longer says "Works with google" beside a page that says "Google". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
🤖 Evidence for this PR: what I executed, what the mutations proved, and what I could not capture. Screenshots. Both are real browser renders at 1440px, signed out, against a local fixture backend. The signed-out state is why the call to action reads as a link rather than a button — that is the change, not an artifact. Tests executed. Backend suites ran against a throwaway
The migration was verified, not eyeballed. I applied every migration to the throwaway database and diffed the result against Mutations. Every guard below was broken on purpose and the named test watched to fail.
Two of those did not fail on the first attempt, and both were real gaps rather than harmless ones. The ordering test created the verified listing second, so recency ordering alone satisfied it — it proved nothing about verified-first until the fixtures were reordered. And both absence tests set the listing's What I did not execute. The post-install connect step in a real browser. Installing calls an authenticated endpoint and my local fixture stack cannot issue a signed-in session, so that state rests on the integration test and the two mutations above rather than a screenshot. Everything else in the Verified paragraph I ran. Backend CI is hand-fired. This PR is based on 🤖 The two screenshots below are superseded. They crop tightly to the new surface and show nothing of where it sits in the Marketplace; the fuller set is in #14431 (comment). Everything above still stands. |
…ules Neither module logs anything; the logger and its import were carried over from the store modules they sit beside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nhandled `mutateAsync` rejects on failure, and awaiting it inside an onClick made every failed install an unhandled rejection even with `onError` set — reverting this change fails the vitest run with "Unhandled Rejection", which is what the new test pins. `mutate` with `onSuccess` does the same work with nothing to catch. `pendingConnections` also carried an `id` no caller read; it is a list of display names now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## pwuts/secrt-2594 #14431 +/- ##
====================================================
- Coverage 81.44% 81.37% -0.07%
====================================================
Files 3550 3538 -12
Lines 265163 264074 -1089
Branches 24598 24465 -133
====================================================
- Hits 215956 214890 -1066
- Misses 43789 43827 +38
+ Partials 5418 5357 -61
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Patch coverage came in at 63.63% against a 70% target, and the uncovered lines were the ones worth testing rather than filler: the page module was untested outright, SkillBody's heading, list, bold and code renderers never ran because the fixture body had only a paragraph and an ordered list, and the card's unverified and no-integrations branches had no case. Adds the "gone from the marketplace" state, the empty shelf, and a generateMetadata test that pins the tab title to the listing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🤖 Backend CI, hand-fired, and the coverage gap that followed the first push.
The frontend patch first came in at 63.63%, and the uncovered lines were worth testing rather than filler, so I wrote tests instead of arguing the target down. The page module had no test at all; Two of those additions are behaviour rather than coverage padding — the "this skill is gone from the marketplace" state, and an empty shelf rendering nothing. The second caught a race in my own test: the section renders its heading over skeletons while loading, so an immediate Three superseded backend runs were cancelled rather than left to finish, so the earlier run links in this thread are stale — the one above is the current head. |
`shows the compatibility line only for a skill that needs one` went straight to findByRole after render, so its single 1s testing-library wait had to cover the request and the query together. That holds locally and not on CI, where it failed on #14432 while passing here. Reproduced by delaying the handler 900ms: the original fails, and so does a plain waitFor, because that default is 1s too. The first data-gated await in each of these tests now carries an explicit timeout, which passes under the same delay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label and every chip were siblings in one wrapping flex container, so the chips wrapped around the label one at a time and a single stray chip landed on the second line. Grouping the chips into their own flex item makes the list move down as a unit, and it only breaks within itself once it has a line of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`isVerified` reached the query layer but never the API, so a shopper had no way to tell a reviewed listing from any other. `StoreAgent` now carries it on all three read paths — the Prisma browse, the hybrid search and its lexical fallback — and each marketplace card states which it is: Verified, or Community for everything else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…roduct Product descoped Verified on 2026-09-08: all listings keep manual human review, so a separate verified state buys nothing until the marketplace allows automated listings. Out go the `isVerified` column and its migration, `StoreAgent.verified` and the API field, `verified DESC` leading the browse ordering, featured requiring verified, and the badge on the marketplace card. Canonical categories, the classifier backfill and the category filter are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mit and an unreachable category list Three review findings. The classifier read `response.choices[0]` outside its try, so a completion with no choices raised IndexError out of the backfill's `asyncio.gather` and took the whole run down — the module's docstring already promised it never raises. `--limit` took any int, and a negative one sliced `pending[:-1]`, classifying everything but the last listing. And the publish and edit forms fed a required category select from an API call whose loading and error states neither read, so a slow or failed request left the field empty with nothing said; it is now disabled and says which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both tests waited on the combobox, which the loading state disables too, so neither actually proved the error path — the edit one would have passed with the request still in flight. They now wait for the copy only the failed state renders. Making the hook ignore `isError` turns both red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Note Superseded — six of the eight shots below show the Verified pill, which Product descoped on 2026-09-08. 🤖 Fuller screenshots of the Skills Hub in place, replacing the crops in the earlier evidence comment — those were tight on the new surface and showed nothing of where it sits. Everything here is a real browser render, signed out, at 1440 and at 430. The fixture backend behind them populates the whole marketplace rather than only the skills endpoints, which is why the earlier shots had no context to show. The Skills Hub is the third shelf on the Marketplace home: experts to hire, then workflows to install, then skills to teach, then creators. The path in is the card. The whole card is the link to Where the skill page diverges from an agent listingNaming these rather than changing them — whether the Skills Hub should match the agent pages is a decision above this PR.
At 430px the shelf stacks to one column and keeps its position between the workflow grid and Featured Creators. What is fixture dataThe agents and creators are local fixtures. The two skills are the real platform starter skills, |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…descoped by Product Product descoped Verified on 2026-09-08: every listing already goes through manual human review, so a separate verified state buys nothing until the marketplace admits automated listings. Out go `SkillListingVersion.isVerified` and its column in the skill-listing migration, the API field, the badge on the skill card and the skill page, and the seed marking starter skills verified. Browse now orders on `updatedAt DESC` alone. The test that pinned verified-first becomes one that pins that ordering, creating its rows oldest-first so insertion order alone fails it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🤖 Design and interface audit — findings only, nothing actionedThe Vercel web-design-guidelines audit of the ten Skills Hub files, plus a comparison against the agent and expert surfaces. Nothing here has been actioned — per Reinier, items get posted as they come in and worked through once they are all in. The visual side is in the screenshot comment, not repeated here. Findings the Verified descope made moot are dropped. 🟠 Should Fix
Should the Skills Hub match the agent pages?One decision, not 24 fixes. Where the surfaces differ it is usually the newer one that is thinner. Grouped as the audit groups them: header (three different back-navigation treatments across three sibling pages, Two divergences run the other way and are worth keeping: the skill card is a real 🟡 Nice to Have
🔵 Nits
|
|
🤖 Re-captured from the descoped head ( The Skills Hub is the third shelf on the Marketplace home: experts to hire, then workflows to install, then skills to teach, then creators. The path in is the card. The whole card is the link to Where the skill page diverges from an agent listingNaming these rather than changing them — whether the Skills Hub should match the agent pages is a decision above this PR, and the design audit comment groups all 24 of them as that one decision.
At 430px the shelf stacks to one column and keeps its position between the workflow grid and Featured Creators. What is fixture dataThe agents and creators are local fixtures. The two skills are the real platform starter skills, |
|
/review |
|
🤖 Reviewing |
|
/review |
|


















Why / What / How
The marketplace now lists knowledge skills as their own kind of thing, next to Experts to hire and Workflows to install, and a signed-in user can add one to their AutoPilot in two clicks from the front page.
A skill is instructions and examples the copilot reads before doing a job — a brand voice guide, an outreach playbook. It is not a runnable graph. The product already has skills: the copilot distils them with
store_skill, they live asSKILL.mdfiles in the user's workspace, and/library/skillslists them. What it had no way to do was get one from anywhere but your own chat history or a file on your disk.Worse, the one place that offered a marketplace skill did not deliver one. The "Add a skill" dialog on an expert says "Pick one from your library or install one from the marketplace", and its Marketplace tab is typed
StoreAgent[]— picking an entry appends the workflow listing's name toExpert.skillsas a display chip. No content moves, and nothing readsExpert.skillsinto a prompt:expert_context.pybuilds an expert's block from name, role, identity, voice and boundaries only. This PR does not touch that path (it isupdate_skills, which #14414 is rewriting); it builds the real thing beside it, and the chip path goes when both have landed.Why a separate listing type.
StoreListing.agentGraphIdisNOT NULLand unique, and theStoreAgentview exposes the graph as non-null, so a skill cannot ride the agent tables without changing the busiest table and view in the product to gain a discriminator we would then branch on everywhere.SkillListing/SkillListingVersionmirror the store's shape — slug, version, submission status, review fields, the canonical categories from #14428 — and hold theSKILL.mdinstead. No accompanying SQL view:StoreAgentexists to aggregate run counts and review stats, and a skill has neither.Install goes through the existing capability.
POST /api/store/skills/{slug}/installcallsstore_user_skill, the same function thestore_skilltool and theSKILL.mdupload endpoint use, so a marketplace skill is validated, capped at the same 50 and stored exactly like any other. The listing's slug is also the installed skill's name, so the two cannot drift and a re-install picks up a newer approved version in place.Compatibility is a declared list of integrations the instructions assume, shown as "Works with Google" on the card and the page. When one is not connected it appears after the install has already succeeded, as a connect step — not a warning, not a blocker. People connect an integration when they first need it, and the install never waits on it.
Changes 🏗️
SkillListing/SkillListingVersion+ migration.owningUserIdis nullable so the platform can publish starter listings with no creator profile, the way a rosterExperthas no owner until it is hired;slugis globally unique so such a listing needs no creator segment in its URL.GET /api/store/skills,GET /api/store/skills/{slug},POST /api/store/skills/{slug}/install. Browse is rooted at the version rather than the listing because every filter and the ordering read version columns, and orders onupdatedAt DESC.python -m backend.api.features.store.skill_seed, so the shelf is not empty before creator publishing ships. Their content is a realSKILL.mdunderstarter_skills/, parsed withparse_skill_markdown, so a seeded skill cannot drift from the format an installed one has.SkillsSection,/marketplace/skills/{slug}, and the install panel with its connect step. Behind theskills-hubflag; off hides the shelf and issues no request for it.Scope
This is the first of two. Creator publishing and admin review are the follow-up — with no way to submit a listing yet, there is nothing for a reviewer to review, so both belong together in the second PR. Also deliberately out: installing a marketplace skill onto an expert rather than personal AutoPilot, public expert publishing (the ticket excludes it), skill listings in unified search, and multi-file skill bundles (nothing can create sibling files today).
Verified badges are out, descoped by Product on 2026-09-08. Every listing already goes through manual human review, so a separate verified state buys nothing until the marketplace admits automated listings. There is no
isVerifiedcolumn, no API field and no badge; browse orders onupdatedAt DESCalone. #14428 dropped the same thing on the agent side.Expert-owned install is one keyword away. #14414 gives
store_user_skillan optional trailingexpert_id; this PR has exactly one call site for it, so the follow-up passes the target through rather than restructuring anything. That is why this is built ondevrather than stacked on #14414.Verification
I ran the backend suites against a throwaway Postgres of my own with this branch's migrations applied — the shared dev database was never touched, and in fact cannot be: it has no
SkillListingtables, because this branch's migration is unmerged and was never applied there. 14 skill-listing tests, the full store suite (165),architecture_test.py(3) andblocks/test/test_block.py(1,647 passed, 84 skipped) are green, as are 13 frontend marketplace test files (53 tests) andtsc --noEmit.I verified the migration by applying all 197 migrations to that database and diffing the result against
schema.prisma. Zero drift on these tables; the 21 remaining lines are pre-existingdevdrift (trigram indexes,organizationIdindexes, a droppedStoreListingVersion.searchcolumn) that CI carries too. The same check caught an index name Postgres truncates at 63 characters.I then broke guards on purpose and watched the right test go red. Two absence tests were passing because the listing-level filter hid the row before the version's status was consulted, and awaiting
mutateAsyncin an onClick made every failed install an unhandled rejection. Both are fixed and each now has a test that fails without the fix. For the browse ordering I removedorder=[{"updatedAt": "desc"}]and confirmed the test fails on['older-one', 'newer-one'] == ['newer-one', 'older-one']— it creates its rows oldest-first, so insertion order alone cannot satisfy it.What I did not execute: the post-install connect step in a real browser — it needs a signed-in session my local fixture stack cannot issue, so it rests on the integration test and its two mutations. Backend CI is hand-fired because this PR is stacked on #14428 and a stacked base never triggers
platform-backend-ci.yml.Closes SECRT-2593.
Agents and large language models used
Claude Code with Claude Opus 5
Checklist 📋
For code changes: