Skip to content

fix(cursor): conform local plugin artifacts to real host - #131

Merged
ScriptedAlchemy merged 4 commits into
mainfrom
fix/cursor-real-host-conformance
Sep 1, 2026
Merged

fix(cursor): conform local plugin artifacts to real host#131
ScriptedAlchemy merged 4 commits into
mainfrom
fix/cursor-real-host-conformance

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • target Cursor's full Cursor Plugin contract exclusively: .cursor-plugin/plugin.json, explicit component pointers, root mcp.json, plugin-scoped hooks, and ${CURSOR_PLUGIN_ROOT}; reject portable Agent Plugin ${PLUGIN_ROOT} / ${PLUGIN_DATA} tokens instead of emitting a hybrid
  • infer every custom uppercase ${VAR} used by emitted mcp.json into the manifest's top-level variables JSON schema while exempting Cursor's built-in root token and supported legacy alias
  • replace symlink install guidance with a physical copy into ~/.cursor/plugins/local/<name> and cite Cursor staff's cross-platform realpath-containment confirmation
  • validate against Cursor's official draft-07 manifest schema pinned at cursor/plugins@070189284e702e8a4d2e3cc8913994b204c5337a, retaining strict MCP/hooks schemas and immutable provenance
  • ship a patch changeset and advance the Cursor adapter revision so stale artifacts are detectable

Contract decision

The adapter now targets the Cursor Plugin format, not the portable Agent Plugin format. This matches the adapter's hook/full-component responsibilities and the known-loading TraceDecay install. The two formats are kept separate at both layout and token-validation boundaries.

Cursor's schema/tooling can drift independently (see cursor/plugins#281), so the manifest schema is commit-pinned rather than fetched from main during builds.

Observed vs emitted evidence

Read-only observation on 2026-08-31 against Cursor server build 9746bf00534f29fc29f1deb9ddfb5448f7905eb0 and the active physical install at ~/.cursor/plugins/local/tracedecay:

Claim Real host / official observation Previous emission Verdict / change
Plugin contract Cursor Plugin uses .cursor-plugin/plugin.json and supports rules/agents/skills/commands/hooks/MCP/variables; portable Agent Plugin uses root plugin.json, skills/MCP only, and different tokens Cursor target carried the full component responsibilities but lacked an explicit contract pin Target is now pinned as Cursor Plugin; portable root/data tokens are rejected
Manifest location Known-loading TraceDecay uses .cursor-plugin/plugin.json; installed loader candidates include .cursor-plugin/plugin.json Standalone and unified targets already emitted .cursor-plugin/plugin.json Original “hidden manifest is unread” claim was not reproduced for the chosen contract; preserve the confirmed hidden manifest
Component discovery Explicit manifest component fields replace defaults Adapter emits explicit skills, hooks, and mcpServers pointers for surfaces that exist Retained consistently; tests resolve every emitted path safely inside the artifact
MCP location TraceDecay uses root mcp.json; manifest points to it Standalone used root mcp.json, unified used .cursor-plugin/mcp.json Verified unified-layout mismatch; unified now emits root mcp.json and points to ./mcp.json
Hook document collision Plugin hooks are selected by a manifest pointer; TraceDecay uses hooks/hooks.json. Project/user hooks are separate .cursor/hooks.json locations Unified bundle already selected hooks/hooks-cursor.json to avoid its Claude/Codex hooks/hooks.json Preserve plugin-scoped explicit Cursor pointer and regression coverage; never emit project/user hook paths
Root tokens Installed loader substitutes ${CURSOR_PLUGIN_ROOT} for local MCP and hook commands; ${CLAUDE_PLUGIN_ROOT} is a legacy compatibility alias Generated wrappers and MCP anchors use ${CURSOR_PLUGIN_ROOT} Retain Cursor token; reject portable ${PLUGIN_ROOT} / ${PLUGIN_DATA} so no hybrid can be emitted
MCP variables Official schema revision 0701892 adds top-level variables; custom MCP placeholders must be declared Custom ${VAR} placeholders could be emitted without manifest declaration Infer deterministic string properties for every custom uppercase placeholder and validate the resulting manifest with Ajv
Local symlink install Cursor staff confirms external symlink targets are rejected by security validation on every platform Generated AGENTS.md recommended “clone (or symlink)” Verified mismatch; docs require a physical copy and link the staff confirmation
Manifest version Official schema supports version; TraceDecay and generated artifacts include it Adapter already emitted configured version Retained and asserted in fixture + real-shape build evidence

Schema provenance

Document Vendored source Strength
plugin.schema.json cursor/plugins@070189284e702e8a4d2e3cc8913994b204c5337a:schemas/plugin.schema.json Official Cursor Plugin draft-07 schema; first pinned revision with variables, plus minClientVersions
mcp.schema.json ScriptedAlchemy/tracedecay@30e04b34d4e236d5f00fccf00eea7552dafde5a3 Strict schema derived from Cursor MCP docs; Cursor publishes no standalone machine-readable schema
hooks.schema.json Same TraceDecay revision Strict schema derived from Cursor hooks docs and cross-checked with official plugins

The capability table and schema provenance record the selected contract, observation date, server build, source revisions, and SHA-256 pins.

Read-only real-shape comparison

A temporary hooks-and-scripts Cursor build (with hook + MCP surfaces) was compared without installing it:

  • generated: .cursor-plugin/plugin.json, root mcp.json, plugin-scoped hooks/hooks.json
  • known-loading TraceDecay: the same three path classes
  • normalized manifest pointers matched byte-for-byte: hooks/hooks.json and mcp.json
  • both hook documents use the exact top-level key set { hooks, version } and version 1
  • generated manifest retained version 1.0.0
  • MCP/hooks schemas match their pinned TraceDecay sources byte-for-byte; the manifest validates with Ajv against the pinned official Cursor schema

Test plan

  • pnpm exec rstest run packages/agent-bundle/tests/cursor-adapter.test.ts packages/agent-bundle/tests/adapter-metadata.test.ts --config rstest.unit.config.ts (16 passed)
  • AGENT_BUNDLE_INTEGRATION_MAX_WORKERS=1 pnpm exec rstest run packages/agent-bundle/tests/plugin-bundle.test.ts --config rstest.integration.config.ts (5 passed)
  • pnpm --filter agent-bundle build
  • targeted rslint on all changed TypeScript files (0 findings)
  • no local-CI matrix run, per owner policy; hosted GitHub checks are the merge bar

Fixes #126
Fixes #127

Emit the root layout Cursor local installs consume and replace the rejected symlink guidance with a physical-copy contract backed by pinned host evidence.
Preserve the confirmed hidden manifest layout while moving MCP to the local-plugin root and validating emitted documents against byte-vendored real-host schemas.
Record both the local evidence checkout and repository-relative source identities so future schema refreshes remain reproducible.
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ce10ebb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T05:16:45.436291Z 1758221 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17582216a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@131
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@131
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/rsc-runtime@131

commit: ce10ebb

Pin the variables-capable official schema and reject portable-contract tokens so generated artifacts cannot silently become hybrids.
@ScriptedAlchemy
ScriptedAlchemy merged commit 3b1c423 into main Sep 1, 2026
9 of 10 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fix/cursor-real-host-conformance branch September 1, 2026 05:41
ScriptedAlchemy added a commit that referenced this pull request Sep 1, 2026
Cursor's 64-character plugin-name bound was only enforced by the unified
`plugin` planner, so the standalone `cursor` target accepted and emitted a
manifest for an over-long name. The pinned official schema
(cursor/plugins@0701892) constrains the name's charset but carries no
maxLength, so both planners now assert the bound through one shared message.

The capability-state exhaustive `default` branches returned the capability
object itself, so an untyped adapter's misspelled state read as truthy support
and could enable hooks or MCP. They now raise a typed CapabilityStateError,
and the registry rejects a malformed declaration at registration so a bad
state never reaches `supports()` or capability intersection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant