From a375a5162f95914514d5a390c702088efa66a403 Mon Sep 17 00:00:00 2001 From: Karan Date: Wed, 28 Oct 2026 03:04:07 -0400 Subject: [PATCH] @genclass/mcp: the plugin's own repository, genclass-dev/genclass-plugin - packages/mcp/scripts/plugin-repo.mjs assembles it: the plugin folder at the root, marketplace files for Claude Code, Codex and Cursor that point at that root, the Gemini CLI manifest, the licence, and a workflow that re-syncs the repository daily from main. Why a second repository: Anthropic's directory stops validating a repository whose GitHub archive is over 50 MiB (ours is 51); Codex, Gemini CLI, `gh skill` and the skills CLI clone or read a whole repository for one plugin; and Gemini CLI and `gh skill` read the latest GitHub release first, which here predates the plugin. - .agents/plugins/marketplace.json: the Codex-native marketplace at this repository's root (Codex already read the Claude one). - The install lines (src/clients.ts, both READMEs) name the small repository; the skill's frontmatter names its licence (the Agent Skills field `gh skill publish` recommends). - manifests.test.ts checks the Codex file and the assembled tree: the plugin byte-identical, every version package.json's, nothing the directory blocks or holds (no symlinks, system files, files over 256 KiB). - docs/adoption/listings/mcp.md: the directory submission names genclass-plugin; a section on the skill and plugin directories, with what is automatic and what the owner submits. Co-Authored-By: Claude Fable 5.1 --- .agents/plugins/marketplace.json | 12 ++ AGENTS.md | 2 +- docs/adoption/listings/mcp.md | 57 ++++++++-- packages/mcp/CHANGELOG.md | 18 +++ packages/mcp/README.md | 27 ++++- packages/mcp/package.json | 3 +- packages/mcp/plugin/README.md | 20 +++- packages/mcp/plugin/skills/genclass/SKILL.md | 1 + packages/mcp/scripts/plugin-repo.mjs | 114 +++++++++++++++++++ packages/mcp/src/clients.ts | 10 +- packages/mcp/test/manifests.test.ts | 65 ++++++++++- skills/genclass/SKILL.md | 1 + 12 files changed, 307 insertions(+), 23 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 packages/mcp/scripts/plugin-repo.mjs diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 00000000..9261eac6 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,12 @@ +{ + "name": "genclass", + "interface": { "displayName": "GenClass" }, + "plugins": [ + { + "name": "genclass", + "source": { "source": "local", "path": "./packages/mcp/plugin" }, + "policy": { "installation": "AVAILABLE", "authentication": "ON_USE" }, + "category": "Developer Tools" + } + ] +} diff --git a/AGENTS.md b/AGENTS.md index fda7dd28..5dd91e3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Default mode is `observe` (report only); `guard` is opt-in; `heal` is experiment | `realapps/` | 128 real apps run in headless Chromium with the real runtime: training rows and the never-worse sweep | | `training/` | vocabulary pruning, curriculum (`curriculum/rt.py` mirrors the runtime's situation renderer), Azure training, ONNX export, `EVAL.md` | | `demos/` | six demo apps with a chaos backend and Playwright trials; `bench/heal/` runs them per mode | -| `packages/mcp/` | `@genclass/mcp`: the MCP server for coding agents (bridge on 127.0.0.1:47357, store, explore verdicts, replays, regression-test generation, 9 tools incl. `genclass_scan`, which runs the runtime CLI's `scan`); no runtime deps; `test/e2e/race.e2e.mjs` is the real-browser check (not in CI's main job). `plugin/` is the Claude Code plugin (also an Agent Plugins package); `server.json`, the plugin, `.claude-plugin/`, `.cursor-plugin/`, `gemini-extension.json`, `llms-install.md` and the READMEs pin its version (`npm run sync -w @genclass/mcp` after a version bump; `test/manifests.test.ts` fails otherwise) | +| `packages/mcp/` | `@genclass/mcp`: the MCP server for coding agents (bridge on 127.0.0.1:47357, store, explore verdicts, replays, regression-test generation, 9 tools incl. `genclass_scan`, which runs the runtime CLI's `scan`); no runtime deps; `test/e2e/race.e2e.mjs` is the real-browser check (not in CI's main job). `plugin/` is the Claude Code plugin (also an Agent Plugins package); `server.json`, the plugin, `.claude-plugin/`, `.cursor-plugin/`, `gemini-extension.json`, `llms-install.md` and the READMEs pin its version (`npm run sync -w @genclass/mcp` after a version bump; `test/manifests.test.ts` fails otherwise); `scripts/plugin-repo.mjs` assembles genclass-dev/genclass-plugin (the plugin at a repository root with its marketplace files, for the Claude directory's 50 MiB archive limit, Codex, Gemini CLI, `gh skill`; that repository re-syncs daily from main and is never edited by hand) | | `packages/sv/` | `@genclass/sv`: the Svelte CLI community add-on (`npx sv add @genclass`): adds the runtime, the `/auto` import first in `src/hooks.client`, the dev-only overlay and (default) the telemetry opt-out meta in `src/app.html`, all with the `genclass:init` marker so `remove` undoes it; bundles `@sveltejs/sv-utils`, `sv` is a peer; `test/e2e/` runs sv's add-on harness (pnpm, Chromium) | | `packages/playwright/` | `@genclass/playwright`: the fixture (`src/fixture.ts`, `install.ts`, `page.ts`, `chaos.ts`, `races.ts`: puts the runtime's script-tag build on every `page` in observe mode with telemetry off, seeded chaos on fetch/XHR responses with `page.route`, witnesses through the agent entry with every hold off, one `genclass-races/1` file per test in `test-results/genclass/`) and the reporter + pure summariser that read those files and write `genclass-report.md` for the PR; peer deps only (`@playwright/test`, `@genclass/runtime`); `test/e2e/run.mjs` is the real-browser check (its own workflow) | | `packages/nuxt/`, `packages/astro/` | `@genclass/nuxt` (a Nuxt module, `@nuxt/kit`, built with `@nuxt/module-builder`) and `@genclass/astro` (an Astro integration, `injectScript("page", ...)`): from options `{ mode, token?, telemetry?, devtools? }` they generate the runtime's own one line (`/auto` or `/auto/guard`) plus a `window.GENCLASS_CONFIG` module imported before it; browser only, nothing on the server; `@genclass/runtime` is a peer dependency. The generated code is pure (`src/source.ts`, `src/index.ts`) and the tests check it exactly, then build one fixture app. Workflow: `integrations.yml` (Node 24) | diff --git a/docs/adoption/listings/mcp.md b/docs/adoption/listings/mcp.md index eb2d49c9..bb31f5a6 100644 --- a/docs/adoption/listings/mcp.md +++ b/docs/adoption/listings/mcp.md @@ -25,7 +25,10 @@ submitted. `GENCLASS_DASHBOARD`. - **Repository.** Public URL: https://github.com/genclass-dev/GenClass-lib (`daybot-solutions-inc/GenClass-lib` is a mirror; submit the `genclass-dev` one everywhere). Server: `packages/mcp`. Claude Code plugin: `packages/mcp/plugin`. - Registry name: `io.github.genclass-dev/genclass`. + Registry name: `io.github.genclass-dev/genclass`. The plugin at a repository root, with its marketplace files: + https://github.com/genclass-dev/genclass-plugin (assembled from `packages/mcp/plugin` by + `packages/mcp/scripts/plugin-repo.mjs`, re-synced daily by its own workflow, never edited by hand). Submit that + one to plugin and skill directories, and GenClass-lib to MCP server directories. ### Texts to reuse @@ -41,12 +44,15 @@ submitted. ### Assets the owner supplies -- **Logo.** There is none in this repository. Cline wants a 400x400 PNG; the Anthropic directory a square PNG of 512 to - 2048 px under 2 MB at `packages/mcp/plugin/.claude-plugin/icon.png` (it becomes the listing icon only the first time - the plugin is saved in the portal; `claude plugin validate --profile directory` warns `ICON_MISSING` until then); - Cursor a logo committed to the repository. +- **Logo.** `packages/mcp/plugin/.claude-plugin/icon.png` (the genclass.dev mark, 512 by 512, also at the root of + genclass-plugin) is what the Anthropic directory reads the first time the plugin is saved in the portal. Cline + wants a 400x400 PNG; Cursor a logo committed to the repository. - **Contact email** for the forms that ask (Anthropic, mcpservers.org). PRIVACY.md names mehar@daybot.ca. -- **GitHub topic** `gemini-cli-extension` on the repository (Gemini gallery). +- **GitHub topics.** `gemini-cli-extension` (Gemini gallery), `agent-skills` (`gh skill`), `claude-code-plugin`, + `claude-plugin` and `codex-plugin` are set on genclass-plugin; GenClass-lib carries `gemini-cli-extension`, + `claude-code-plugin` and `mcp-server`. Gemini CLI and `gh skill` read the latest GitHub release before the default + branch, and GenClass-lib's latest release (v0.2.1, 2026-10-11) predates the plugin, so only genclass-plugin installs + through them. ## Order @@ -109,14 +115,16 @@ support.claude.com "Anthropic Software Directory Policy". - **Who.** A Pro, Max, Team or Enterprise plan (Team or Enterprise: an Owner, or a member with the Directory permission), with GitHub connected on claude.ai and push access to the repository. The first organization to submit a repository folder holds that listing. -- **Steps.** claude.ai/directory/manage, Submit new, Plugin bundle. Source: repository `genclass-dev/GenClass-lib`, - plugin path `packages/mcp/plugin`, branch `main`; Validate. Listing details are read from - `packages/mcp/plugin/.claude-plugin/plugin.json` and `packages/mcp/plugin/README.md`. Data handling: the plugin +- **Steps.** claude.ai/directory/manage, Submit new, Plugin bundle. Source: repository + `genclass-dev/genclass-plugin`, no plugin path (the plugin is the repository root), branch `main`; Validate. + GenClass-lib itself cannot be submitted: the portal stops validating a repository whose GitHub archive is over + 50 MiB (pre-submission checklist), and GenClass-lib's is 51 MiB. Listing details are read from + `.claude-plugin/plugin.json` and `README.md` at that root (the same files as `packages/mcp/plugin/`). Data handling: the plugin collects and sends nothing; it reads and writes `.genclass/agent/` in the project and `~/.genclass/` (the bridge token); the optional dashboard read is the only network request off the machine. Compliance: contact email and the four acknowledgements. Updates: GitHub push webhook. -- **What the checks will say.** Local run of `claude plugin validate packages/mcp/plugin --profile directory` - (Claude Code 2.1.310): passes; one warning, `ICON_MISSING`. The portal also holds pinned launchers for a reviewer: +- **What the checks will say.** Local run of `claude plugin validate --profile directory` on the assembled + repository (Claude Code 2.1.309, 2026-10-28): passes with nothing to report. The portal also holds pinned launchers for a reviewer: expect "Runs a pinned npx or uvx package" (both `.mcp.json` and the hook run `npx -y @genclass/mcp@0.2.1`), and possibly "MCP server command wasn't read" because the server is not a file inside the plugin folder. An unpinned launcher would block; `packages/mcp/test/manifests.test.ts` keeps both pins equal to `package.json`'s version. The @@ -301,3 +309,30 @@ Source: mxschmitt/awesome-playwright `README.md` (section "AI & Agents", alphabe `@genclass/playwright` (the race-file reporter) would go under `## Reporters` in the same format once it is released. + +## Skill and plugin directories (2026-10-28) + +The skill is `skills/genclass/SKILL.md`, at the root of both repositories; the plugin is the root of genclass-plugin. +Verified on this machine on 2026-10-28 from the pushed repository: `claude plugin marketplace add`, `codex plugin +marketplace add` and `codex plugin add genclass@genclass`, `gemini extensions install`, `gh skill install` (gh 2.103) +and `npx skills add` all install from `genclass-dev/genclass-plugin`. + +- **skills.sh** (Vercel's directory, what `npx skills add` reads): automatic, ranked by anonymous install counts; the + skill is listed at https://www.skills.sh/genclass-dev/genclass-lib/genclass (installs of the monorepo copy), and the + install lines now name genclass-plugin, which gets its own page the same way. Nothing to submit. +- **Smithery skills** (smithery.ai/skills): mirrors the skills CLI's index; nothing to submit. +- **Gemini CLI gallery** (geminicli.com/extensions): a daily crawl of repositories with the topic + `gemini-cli-extension` and a root `gemini-extension.json`; genclass-plugin has both. Nothing to submit. +- **Codex**: OpenAI's public plugin directory takes local servers only from approved partners + (developers.openai.com/plugins/deploy/desktop-only), so the path is `codex plugin marketplace add + genclass-dev/genclass-plugin` (Codex reads `.agents/plugins/marketplace.json`, and the Claude one as a fallback) and, + by hand, one README line in hashgraph-online/awesome-codex-plugins. +- **Claude**: the directory (section 3 above); the owner submits from the paid claude.ai account with GitHub + connected. Also claude-plugins.dev indexes public plugin repositories by topic. +- **Cursor**: cursor.com/marketplace/publish (manual review of an open-source repository) and + cursor.directory/plugins/new (sign in, paste the repository URL; it detects `skills/`, `.mcp.json` and + `hooks/hooks.json` at the root). Both by hand. +- **GitHub Copilot**: `gh skill install genclass-dev/genclass-plugin genclass` works today; the community registry is + github/awesome-copilot (a pull request adding the skill folder). By hand. +- **Tessl registry** (`tessl skill publish --public`, after approval) and VoltAgent/awesome-agent-skills ("real + community usage" required): later, once installs exist. diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 23b5cf72..8c066cdf 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## Unreleased + +- **The plugin has a repository of its own, [genclass-dev/genclass-plugin](https://github.com/genclass-dev/genclass-plugin)**: + the plugin folder at the root, with marketplace files for Claude Code (`.claude-plugin/marketplace.json`), Codex + (`.agents/plugins/marketplace.json`) and Cursor, the Gemini CLI manifest and the licence, assembled by + `node packages/mcp/scripts/plugin-repo.mjs` (`npm run plugin-repo -w @genclass/mcp`) and re-synced there daily by + its own workflow. Anthropic's directory stops validating a repository whose GitHub archive is over 50 MiB + (GenClass-lib's is 51), and Codex, Gemini CLI, `gh skill` and the skills CLI clone or read a whole repository for + one plugin (Gemini CLI and `gh skill` read the latest GitHub release first, which in GenClass-lib predates the + plugin), so the small repository is what the install lines name: `/plugin marketplace add genclass-dev/genclass-plugin`, + `codex plugin marketplace add genclass-dev/genclass-plugin` then `codex plugin add genclass@genclass`, + `gemini extensions install https://github.com/genclass-dev/genclass-plugin`, `npx skills add genclass-dev/genclass-plugin`, + `gh skill install genclass-dev/genclass-plugin genclass`. The marketplace in GenClass-lib still lists the plugin. +- **A Codex marketplace file at the repository root**, `.agents/plugins/marketplace.json` (Codex's own format; Codex + already read `.claude-plugin/marketplace.json`). +- **The skill's frontmatter names its licence** (`license: Apache-2.0`, the Agent Skills field `gh skill publish` + recommends). + ## 0.2.1 (2026-10-27) - **The plugin has an icon** (`plugin/.claude-plugin/icon.png`, the genclass.dev mark, 512 by 512), which the Anthropic directory reads on the first save. diff --git a/packages/mcp/README.md b/packages/mcp/README.md index 33d36d2b..8e2c4ad8 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -32,7 +32,7 @@ client it runs in. As a plugin (the server, a prompt hook that brings new findings to each prompt, and the `genclass` skill with the procedure): ``` -/plugin marketplace add genclass-dev/GenClass-lib +/plugin marketplace add genclass-dev/genclass-plugin /plugin install genclass@genclass ``` @@ -43,7 +43,7 @@ claude mcp add --scope project genclass -- npx -y @genclass/mcp@0.2.1 # or: npx -y @genclass/mcp@0.2.1 init --hook (.mcp.json and the skill, plus the prompt hook in .claude/settings.json) ``` -Or just the skill (no server; `skills/genclass/SKILL.md` at the repository root, for skill directories): `npx skills add genclass-dev/GenClass-lib` +Or just the skill (no server): `npx skills add genclass-dev/genclass-plugin`. (`genclass-dev/genclass-plugin` is this plugin at a repository root, assembled from this package; the marketplace in `genclass-dev/GenClass-lib` lists the same plugin.) ### Claude Desktop @@ -109,6 +109,15 @@ Or `.mcp.json` at the workspace root (the portable format; `.vscode/mcp.json` ta ### Codex CLI +As a plugin (the server and the `genclass` skill): + +```bash +codex plugin marketplace add genclass-dev/genclass-plugin +codex plugin add genclass@genclass +``` + +Or the server alone: + ```bash codex mcp add genclass -- npx -y @genclass/mcp@0.2.1 ``` @@ -124,6 +133,14 @@ startup_timeout_sec = 30 ### Gemini CLI +As an extension (the server and the `genclass` skill): + +```bash +gemini extensions install https://github.com/genclass-dev/genclass-plugin +``` + +Or the server alone: + ```bash gemini mcp add genclass npx -- -y @genclass/mcp@0.2.1 # -s user: for all projects ``` @@ -282,8 +299,10 @@ read a report, what to say when done, what to do when the tools are missing) is - `npx -y @genclass/mcp@0.2.1 init` writes it to `.claude/skills/genclass/SKILL.md` in the project (`--no-skill` leaves it out). The written file carries a `genclass:init` marker line: a rerun replaces a file with the marker and leaves one without it (yours, or edited), and says which; -- `npx skills add genclass-dev/GenClass-lib` installs the repository-root copy, `skills/genclass/SKILL.md`, through a - skill directory, with no server (the skill then says how to add one). +- `npx skills add genclass-dev/genclass-plugin` (the skills CLI: Codex, Cursor, Copilot, Gemini CLI, Claude Code and + others) or `gh skill install genclass-dev/genclass-plugin genclass` (GitHub CLI 2.90 or later, for Copilot) installs + the skill alone, with no server (the skill then says how to add one). `npx skills add genclass-dev/GenClass-lib` + installs the same file from this repository's root copy, `skills/genclass/SKILL.md`. To remove it: delete `.claude/skills/genclass/` (or the directory the skill tool wrote to); with the plugin, `/plugin uninstall genclass@genclass`. diff --git a/packages/mcp/package.json b/packages/mcp/package.json index b12c5902..9d00f1e4 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -54,7 +54,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "sync": "GENCLASS_SYNC=1 vitest run test/manifests.test.ts", - "bundle": "node scripts/mcpb.mjs" + "bundle": "node scripts/mcpb.mjs", + "plugin-repo": "node scripts/plugin-repo.mjs" }, "devDependencies": { "@anthropic-ai/mcpb": "2.1.2", diff --git a/packages/mcp/plugin/README.md b/packages/mcp/plugin/README.md index 40bfdefb..4fb59df3 100644 --- a/packages/mcp/plugin/README.md +++ b/packages/mcp/plugin/README.md @@ -1,17 +1,24 @@ -# GenClass plugin for Claude Code +# GenClass plugin -GenClass lets Claude see what happens inside the pages of your web app running on your machine (each click, request +For Claude Code, Codex, Cursor, Gemini CLI and other agents that read plugins or skills. GenClass lets the agent see what happens inside the pages of your web app running on your machine (each click, request and response, the order the responses were delivered, what each response wrote into the app's state, uncaught errors), find race conditions by re-running a page with the network answering in different orders, replay one exact order to prove a fix, and write a Playwright test that fails while the bug is there. ## Install +Claude Code (the server, a prompt hook that brings new findings to each prompt, and the `genclass` skill): + ``` -/plugin marketplace add genclass-dev/GenClass-lib +/plugin marketplace add genclass-dev/genclass-plugin /plugin install genclass@genclass ``` +Codex (the server and the skill): `codex plugin marketplace add genclass-dev/genclass-plugin`, then +`codex plugin add genclass@genclass`. Gemini CLI (the same): `gemini extensions install https://github.com/genclass-dev/genclass-plugin`. +The skill alone, in any agent the skills CLI knows: `npx skills add genclass-dev/genclass-plugin`; for GitHub Copilot, +`gh skill install genclass-dev/genclass-plugin genclass`. + Then connect your app's development build (two lines; `genclass_setup` prints them for your framework) and load a page of the dev server. The page side needs `@genclass/runtime` 0.3.0-beta.1 or later. See the [package README](https://github.com/genclass-dev/GenClass-lib/blob/main/packages/mcp/README.md). @@ -59,6 +66,13 @@ diagnostics, which `GenClass.init({ telemetry: false })` turns off ([PRIVACY.md] turns off every hook. - In the app: remove the `connectAgent(rt)` line; the page then never talks to the bridge. +## Where this comes from + +This folder is `packages/mcp/plugin` in [genclass-dev/GenClass-lib](https://github.com/genclass-dev/GenClass-lib). +The repository [genclass-dev/genclass-plugin](https://github.com/genclass-dev/genclass-plugin) is this folder at a +repository root with the marketplace files, assembled by `node packages/mcp/scripts/plugin-repo.mjs` and re-synced +daily, so issues and pull requests go to GenClass-lib. + ## License Apache-2.0. Source: [packages/mcp](https://github.com/genclass-dev/GenClass-lib/tree/main/packages/mcp). diff --git a/packages/mcp/plugin/skills/genclass/SKILL.md b/packages/mcp/plugin/skills/genclass/SKILL.md index c1b6d7ef..46ef5544 100644 --- a/packages/mcp/plugin/skills/genclass/SKILL.md +++ b/packages/mcp/plugin/skills/genclass/SKILL.md @@ -1,5 +1,6 @@ --- name: genclass +license: Apache-2.0 description: Find, reproduce and prove fixes for web-app bugs that depend on timing or layout with the GenClass MCP tools. Use when the user says something happens "sometimes", "randomly", "only in production" or "on slow connections", when a value "flips back", and for stale or old results, lost edits, double submits or a page that scrolls sideways on a phone; also after you change code that fetches, submits, autosaves, polls, debounces, paginates or listens to a socket, and before you call a page or UI change done. --- diff --git a/packages/mcp/scripts/plugin-repo.mjs b/packages/mcp/scripts/plugin-repo.mjs new file mode 100644 index 00000000..4b31e7d9 --- /dev/null +++ b/packages/mcp/scripts/plugin-repo.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node +// Assembles the plugin's own repository, genclass-dev/genclass-plugin, from this package: the plugin folder +// (packages/mcp/plugin) at the repository root, the marketplace files that point at that root (Claude Code, Codex, +// Cursor), the Gemini CLI manifest, the licence, and the workflow that re-runs this script there daily. Why a second +// repository: Anthropic's directory stops validating a repository whose GitHub archive is over 50 MiB (GenClass-lib's +// is 51), and Codex, Gemini CLI, `gh skill` and the skills CLI clone or read a whole repository for one plugin. +// Invariants: everything in that repository comes from here (its sync.yml overwrites all but .git and .github), the +// copied files are byte-identical to the package's, and every version is package.json's. No dependencies. +// Usage: node packages/mcp/scripts/plugin-repo.mjs (the directory is emptied first) +import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const repo = resolve(pkgDir, "..", ".."); +const pkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf8")); +const V = pkg.version; + +/** The repository on GitHub. */ +export const REPO = "genclass-dev/genclass-plugin"; +/** The plugin's description in the marketplace entries (the monorepo's marketplace says the same). */ +const DESCRIPTION = "The GenClass MCP server, a prompt hook that brings new findings to each prompt, and a skill for intermittent and timing-dependent frontend bugs."; + +const json = (v) => `${JSON.stringify(v, null, 2)}\n`; + +/** The files the script writes, by path in the output directory. Pure: a function of the version. */ +export function generated(version) { + return { + ".claude-plugin/marketplace.json": json({ + name: "genclass", + owner: { name: "GenClass", url: "https://genclass.dev" }, + description: "GenClass for Claude Code: see the race, replay it, prove the fix.", + plugins: [{ name: "genclass", source: "./", description: DESCRIPTION, version, category: "development" }], + }), + ".agents/plugins/marketplace.json": json({ + name: "genclass", + interface: { displayName: "GenClass" }, + plugins: [ + { + name: "genclass", + source: { source: "local", path: "./" }, + policy: { installation: "AVAILABLE", authentication: "ON_USE" }, + category: "Developer Tools", + }, + ], + }), + ".cursor-plugin/marketplace.json": json({ + name: "genclass", + owner: { name: "GenClass" }, + metadata: { description: "GenClass for coding agents: see the race, replay it, prove the fix." }, + plugins: [{ name: "genclass", source: "./", description: DESCRIPTION, version, category: "development" }], + }), + ".gitignore": ".DS_Store\nThumbs.db\n", + ".github/workflows/sync.yml": `# Keeps this repository equal to what packages/mcp/scripts/plugin-repo.mjs in genclass-dev/GenClass-lib assembles. +# Nothing here is edited by hand: this workflow overwrites everything but .git and .github (the default token cannot +# change workflow files, so this file is updated from GenClass-lib by a person). +name: Sync from GenClass-lib +on: + schedule: + - cron: "23 6 * * *" + workflow_dispatch: +permissions: + contents: write +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: genclass-dev/GenClass-lib + ref: main + path: lib + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Assemble from GenClass-lib + run: | + SHA=$(git -C lib rev-parse --short HEAD) + node lib/packages/mcp/scripts/plugin-repo.mjs out + rsync -a --delete --exclude .git --exclude .github --exclude lib --exclude out out/ ./ + rm -rf lib out + echo "SHA=$SHA" >> "$GITHUB_ENV" + - name: Commit and push when something changed + run: | + git add -A + if git diff --cached --quiet; then echo "nothing changed"; exit 0; fi + git -c user.name=genclass-sync -c user.email=noreply@genclass.dev commit -m "sync from GenClass-lib $SHA" + git push +`, + }; +} + +/** Writes the repository's tree to `out`: the plugin folder, LICENSE, gemini-extension.json, then the generated files. */ +export function assemble(out, version = V) { + rmSync(out, { recursive: true, force: true }); + mkdirSync(out, { recursive: true }); + cpSync(join(pkgDir, "plugin"), out, { recursive: true, filter: (p) => !/(^|[\\/])(\.DS_Store|Thumbs\.db)$/.test(p) }); + for (const f of ["LICENSE", "gemini-extension.json"]) cpSync(join(repo, f), join(out, f)); + for (const [file, body] of Object.entries(generated(version))) { + mkdirSync(dirname(join(out, file)), { recursive: true }); + writeFileSync(join(out, file), body); + } +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const out = process.argv[2]; + if (!out) { + console.error("usage: node packages/mcp/scripts/plugin-repo.mjs "); + process.exit(2); + } + assemble(resolve(out)); + console.log(`assembled ${REPO} ${V} in ${resolve(out)}`); +} diff --git a/packages/mcp/src/clients.ts b/packages/mcp/src/clients.ts index 2e79126c..4864f2af 100644 --- a/packages/mcp/src/clients.ts +++ b/packages/mcp/src/clients.ts @@ -81,10 +81,10 @@ export const CLIENTS: ClientInstall[] = [ block: (v) => [ "As a plugin (the server, a prompt hook that brings new findings to each prompt, and the `genclass` skill with the procedure):", - fence("", "/plugin marketplace add genclass-dev/GenClass-lib\n/plugin install genclass@genclass"), + fence("", "/plugin marketplace add genclass-dev/genclass-plugin\n/plugin install genclass@genclass"), "Or the server alone (`--scope project` writes `.mcp.json` for everyone on the project; `--scope user` for all your projects). `init` also writes the skill to `.claude/skills/genclass/SKILL.md` (`--no-skill` leaves it out):", fence("bash", `claude mcp add --scope project ${NAME} -- npx -y ${pinned(v)}\n# or: npx -y ${pinned(v)} init --hook (.mcp.json and the skill, plus the prompt hook in .claude/settings.json)`), - "Or just the skill (no server; `skills/genclass/SKILL.md` at the repository root, for skill directories): `npx skills add genclass-dev/GenClass-lib`", + "Or just the skill (no server): `npx skills add genclass-dev/genclass-plugin`. (`genclass-dev/genclass-plugin` is this plugin at a repository root, assembled from this package; the marketplace in `genclass-dev/GenClass-lib` lists the same plugin.)", ].join("\n\n"), }, { @@ -126,6 +126,9 @@ export const CLIENTS: ClientInstall[] = [ match: /codex/i, block: (v) => [ + "As a plugin (the server and the `genclass` skill):", + fence("bash", "codex plugin marketplace add genclass-dev/genclass-plugin\ncodex plugin add genclass@genclass"), + "Or the server alone:", fence("bash", `codex mcp add ${NAME} -- npx -y ${pinned(v)}`), "Or `~/.codex/config.toml` (`.codex/config.toml` in a trusted project). The first start downloads the package, so allow more than the default 10 seconds:", fence("toml", `[mcp_servers.${NAME}]\ncommand = "npx"\nargs = ["-y", "${pinned(v)}"]\nstartup_timeout_sec = 30`), @@ -137,6 +140,9 @@ export const CLIENTS: ClientInstall[] = [ match: /gemini/i, block: (v) => [ + "As an extension (the server and the `genclass` skill):", + fence("bash", "gemini extensions install https://github.com/genclass-dev/genclass-plugin"), + "Or the server alone:", fence("bash", `gemini mcp add ${NAME} npx -- -y ${pinned(v)} # -s user: for all projects`), "Or `.gemini/settings.json` in the project (`~/.gemini/settings.json` for all projects):", fence("json", mcpServers(v)), diff --git a/packages/mcp/test/manifests.test.ts b/packages/mcp/test/manifests.test.ts index fa047189..123b4dcc 100644 --- a/packages/mcp/test/manifests.test.ts +++ b/packages/mcp/test/manifests.test.ts @@ -5,7 +5,9 @@ // keeps the repo-root copy of the skill (skills/genclass/SKILL.md, what skill directories such as skills.sh read) // byte-identical to the plugin's. The second describe checks the skill and the server's instructions: the // instructions carry the triggers, the skill the procedure (docs/runtime/AGENT.md section 4.2). -import { readFileSync, writeFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; +import { lstatSync, mkdtempSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { CLIENTS, hookCommand, installMarkdown, pinned, serverConfig } from "../src/clients.js"; @@ -176,3 +178,64 @@ describe("the genclass skill and the server's instructions (AGENT.md section 4.2 expect(INSTRUCTIONS).not.toContain("\u2014"); }); }); + +describe("the Codex marketplace at the repository root (.agents/plugins/marketplace.json)", () => { + it("points at the plugin folder with Codex's required policy fields", () => { + // developers.openai.com/plugins/build/plugins: source.path starts with ./ and stays inside the root; policy.installation, + // policy.authentication (ON_INSTALL or ON_USE) and category are required on each entry + const m = JSON.parse(read(".agents/plugins/marketplace.json")); + expect(m.name).toBe("genclass"); + expect(m.plugins).toHaveLength(1); + expect(m.plugins[0]).toMatchObject({ name: "genclass", source: { source: "local", path: "./packages/mcp/plugin" }, policy: { installation: "AVAILABLE", authentication: "ON_USE" } }); + expect(typeof m.plugins[0].category).toBe("string"); + }); +}); + +/** Every file under `dir`, as paths relative to it (symlinks are listed, not followed). */ +function walk(dir: string, rel = ""): string[] { + return readdirSync(join(dir, rel)).flatMap((n) => { + const r = rel ? `${rel}/${n}` : n; + return lstatSync(join(dir, r)).isDirectory() ? walk(dir, r) : [r]; + }); +} + +describe("the plugin's own repository (packages/mcp/scripts/plugin-repo.mjs assembles genclass-dev/genclass-plugin)", () => { + const out = mkdtempSync(join(tmpdir(), "genclass-plugin-repo-")); + execFileSync(process.execPath, [join(pkgDir, "scripts", "plugin-repo.mjs"), out], { stdio: "pipe" }); + const at = (f: string) => readFileSync(join(out, f), "utf8"); + + it("is the plugin folder at the root, byte for byte, plus the licence and the Gemini manifest", () => { + const plugin = join(pkgDir, "plugin"); + for (const f of walk(plugin)) expect(readFileSync(join(out, f)), f).toEqual(readFileSync(join(plugin, f))); + expect(at("LICENSE")).toBe(read("LICENSE")); + expect(at("gemini-extension.json")).toBe(read("gemini-extension.json")); + }); + + it("its marketplace files point at the root and carry package.json's version", () => { + const claude = JSON.parse(at(".claude-plugin/marketplace.json")); + expect(claude.name).toBe("genclass"); + expect(claude.plugins[0]).toMatchObject({ name: "genclass", source: "./", version: V }); + const codex = JSON.parse(at(".agents/plugins/marketplace.json")); + expect(codex.plugins[0]).toMatchObject({ name: "genclass", source: { source: "local", path: "./" }, policy: { installation: "AVAILABLE", authentication: "ON_USE" } }); + const cursor = JSON.parse(at(".cursor-plugin/marketplace.json")); + expect(cursor.plugins[0]).toMatchObject({ name: "genclass", source: "./", version: V }); + }); + + it("has nothing Anthropic's directory blocks or holds: no symlinks, no system files, every non-image file under 256 KiB, 512 files or fewer", () => { + // claude.com/docs/plugins/pre-submission-checklist + const files = walk(out); + expect(files.length).toBeLessThanOrEqual(512); + for (const f of files) { + expect(lstatSync(join(out, f)).isSymbolicLink(), `${f} is a symlink`).toBe(false); + expect(/(^|\/)(\.DS_Store|Thumbs\.db|desktop\.ini|__MACOSX)/.test(f), f).toBe(false); + if (!/\.(png|jpe?g|gif|webp|svg|woff2?|ttf|otf)$/i.test(f)) expect(statSync(join(out, f)).size, `${f} over 256 KiB`).toBeLessThanOrEqual(256 * 1024); + } + }); + + it("its workflow re-runs this script from GenClass-lib and touches nothing by hand", () => { + const y = at(".github/workflows/sync.yml"); + expect(y).toContain("repository: genclass-dev/GenClass-lib"); + expect(y).toContain("node lib/packages/mcp/scripts/plugin-repo.mjs out"); + expect(y).toContain("--exclude .git --exclude .github"); + }); +}); diff --git a/skills/genclass/SKILL.md b/skills/genclass/SKILL.md index c1b6d7ef..46ef5544 100644 --- a/skills/genclass/SKILL.md +++ b/skills/genclass/SKILL.md @@ -1,5 +1,6 @@ --- name: genclass +license: Apache-2.0 description: Find, reproduce and prove fixes for web-app bugs that depend on timing or layout with the GenClass MCP tools. Use when the user says something happens "sometimes", "randomly", "only in production" or "on slow connections", when a value "flips back", and for stale or old results, lost edits, double submits or a page that scrolls sideways on a phone; also after you change code that fetches, submits, autosaves, polls, debounces, paginates or listens to a socket, and before you call a page or UI change done. ---