Files
vercel__vercel-plugin/tests/marketplace-skill-content.test.ts
Jathin Pranav Singaraju c9b1d8ff93 [marketplace] fix skill shadowed by command + discover-first routing (#107)
* feat(marketplace): discover-first commerce routing + provisioning guidance

Make the model reliably reach for the Marketplace when an app needs an
external service, instead of hardcoding a provider from memory.

- knowledge-update (always-injected at session start): add a discover-first
  directive — load the `marketplace` skill, then discover + provision a real
  integration BEFORE planning/writing code/asking.
- marketplace SKILL.md: broaden the description (commerce/payments/etc. as the
  catch-all for capabilities without a dedicated skill); restructure into
  discover -> install -> build with an anti-mock/anti-punt rule; add a
  Recommendations section keyed on the product-catalog test (commerce vs
  payments), with no hardcoded providers (discover names them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(marketplace): remove command that shadowed the skill; tighten routing

The `marketplace` skill and `/marketplace` command shared a name, so the
command shadowed the skill — Skill(vercel:marketplace) loaded the command's
runbook instead, and the skill's routing guidance never reached the model.

Remove the command (marketplace is skill-only, like the other capabilities)
so the skill loads, and tighten routing: discover-first flow, a preferred-
provider table (commerce -> Shopify, payments -> Stripe), and a slimmer
knowledge-update that points at the skill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(release): patch-bump plugin manifests to 0.45.1

Backwards-compatible fix (marketplace skill routing); bump the version in
all three shipped manifests (.claude-plugin, .cursor-plugin, .plugin).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 12:18:42 -07:00

56 lines
2.4 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";
// Deterministic content-contract guards for the marketplace catch-all skill.
// These don't test model behavior (that's the agent-eval layer) — they lock in
// the *routing surface* and anti-mock/anti-hardcode contract so a future edit
// can't silently regress the discover-first design.
const ROOT = resolve(import.meta.dirname, "..");
const SKILL = readFileSync(join(ROOT, "skills", "marketplace", "SKILL.md"), "utf8");
const fmMatch = SKILL.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
const frontmatter = fmMatch ? fmMatch[1] : "";
const body = fmMatch ? fmMatch[2] : SKILL;
const description = (frontmatter.match(/description:\s*(.*)/)?.[1] ?? "").toLowerCase();
/** Slice a markdown section from `### heading` up to the next `## ` heading. */
function section(heading: string): string {
const start = body.indexOf(heading);
if (start < 0) return "";
const rest = body.slice(start);
const end = rest.indexOf("\n## ");
return end >= 0 ? rest.slice(0, end) : rest;
}
describe("marketplace skill content contract", () => {
test("description routes the full catch-all capability set", () => {
for (const kw of ["commerce", "payments", "search", "cms"]) {
expect(description).toContain(kw);
}
expect(/monitoring|observability/.test(description)).toBe(true);
expect(/email|messaging/.test(description)).toBe(true);
});
test("commerce vs payments is decided by the product-catalog test, not a provider", () => {
// The distinction lives in the category→use-case map: catalog → commerce, no catalog → payments.
expect(body.toLowerCase()).toContain("catalog");
expect(body).toContain("`commerce`");
expect(body).toContain("`payments`");
// steered as CATEGORY slugs, never a named provider default
expect(body).not.toMatch(/default to (shopify|stripe)/i);
});
test("install is mandatory and mocking is explicitly forbidden", () => {
expect(body).toMatch(/required, not optional/i);
expect(body).toMatch(/substitute a mock/i);
expect(body).toContain("vercel integration add");
});
test("no provider is hardcoded as a forced default anywhere in the skill", () => {
// Provider names may appear as examples/anti-patterns, but never as "default to <provider>".
expect(body).not.toMatch(/default(s)? to (shopify|stripe|neon|upstash)\b/i);
});
});