Skip to content

fix: resolve Cursor marketplace monorepo plugins - #164

Merged
Minitour merged 2 commits into
developfrom
fix/cursor-marketplace-plugin-subpath
Aug 3, 2026
Merged

fix: resolve Cursor marketplace monorepo plugins#164
Minitour merged 2 commits into
developfrom
fix/cursor-marketplace-plugin-subpath

Conversation

@Minitour

@Minitour Minitour commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

Cursor marketplace listings often point at a multi-plugin git repo (gitUrl) with the actual plugin under gitPath. capa previously installed owner/repo at the root, hit only a marketplace catalog (no plugin.json), and failed to expand the plugin.

Changes

  • cursor-marketplace adapter: include gitPath as owner/repo::path and optional gitRef in the install snippet
  • resolvePlugins: if the repo root has no plugin manifest and the entry has an id, search the tree for a nested plugin matching that id (helps already-authored entries without ::/@)
  • Reject gitPath traversal (..) from marketplace data
  • Unit tests for pluginDef and monorepo id fallback

Test plan

  • bun test registries/cursor-marketplace/adapter.test.ts src/cli/commands/__tests__/plugin-monorepo-id-fallback.test.ts
  • Add a Cursor marketplace monorepo plugin from the UI; confirm skills/servers expand
  • Re-add / reconfigure an existing root-only monorepo entry that has a matching id

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Cursor marketplace monorepo plugin installs via gitPath and id fallback

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Include marketplace gitPath/gitRef in install snippet using capa repo::subpath + ref pinning
• Fall back to locating nested plugin by entry id when repo root lacks a manifest
• Add traversal hardening for marketplace paths and unit tests for both behaviors
Diagram

graph TD
  A{{"Cursor Marketplace API"}} --> B["cursor-marketplace adapter"] --> C["Install snippet: repo::path + ref"] --> D["CLI resolvePlugins"] --> E[("Repo snapshot")]
  E --> F["detectAndParseManifest"] --> G[("plugin.json")]
  F -. "no manifest & has id" .-> H["findPluginInDirectory (by id)"] --> G

  subgraph Legend
    direction LR
    _ext{{"External"}} ~~~ _mod["Module"] ~~~ _fs[("Filesystem")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Rely solely on gitPath in marketplace snippets (no id-based fallback)
  • ➕ Keeps resolvePlugins logic simpler and more deterministic
  • ➕ Avoids potentially expensive directory walks on large repos
  • ➖ Doesn't help already-authored capabilities entries that only specify owner/repo and id
  • ➖ Requires marketplace adapter consumers to always re-add/update entries to get ::subpath pinning
2. Parse Cursor .cursor-plugin/marketplace.json to map plugin id → source dir
  • ➕ More accurate than heuristically searching by directory/manifest name
  • ➕ Could support multiple plugins sharing similar names without ambiguity
  • ➖ Couples resolver to Cursor-specific catalog format and location
  • ➖ Still needs a fallback when the catalog is missing or inconsistent

Recommendation: Current approach is a good compatibility-first fix: pin gitPath/gitRef when available (best-case deterministic), and only run the id-based search when the root has no manifest and the entry provides an id (limits unintended matches and avoids extra work in normal cases). If false-positives appear in the wild, consider upgrading the fallback to consult .cursor-plugin/marketplace.json when present before doing a generic tree search.

Files changed (4) +226 / -10

Bug fix (2) +77 / -10
adapter.tsSupport monorepo gitPath + gitRef in Cursor marketplace install defs +52/-7

Support monorepo gitPath + gitRef in Cursor marketplace install defs

• Extends marketplace plugin metadata with gitPath/gitRef and exports a richer pluginDef that builds repo::subpath coordinates and optional ref pinning. Normalizes path separators, trims slashes, and rejects unsafe segments (., ..) to prevent traversal from marketplace-provided paths.

registries/cursor-marketplace/adapter.ts

plugin-install.tsFall back to nested plugin discovery by entry id when root has no manifest +25/-3

Fall back to nested plugin discovery by entry id when root has no manifest

• Updates resolvePlugins so that when no subpath/search is provided and the repo root lacks a manifest, it will search the snapshot for a nested plugin matching pluginRef.id. Improves the missing-manifest error message with a monorepo pinning tip.

src/cli/commands/plugin-install.ts

Tests (2) +149 / -0
adapter.test.tsAdd unit tests for gitPath/ref pinning and traversal rejection +40/-0

Add unit tests for gitPath/ref pinning and traversal rejection

• Introduces tests covering root repo mapping, monorepo ::subpath pinning with optional ref, path separator normalization, traversal rejection, and GitLab URL support.

registries/cursor-marketplace/adapter.test.ts

plugin-monorepo-id-fallback.test.tsAdd resolvePlugins test for monorepo id-based nested manifest lookup +109/-0

Add resolvePlugins test for monorepo id-based nested manifest lookup

• Adds an integration-style unit test that constructs a snapshot with a root marketplace catalog but a nested .cursor-plugin/plugin.json, then verifies resolvePlugins finds and expands the nested plugin using only the entry id.

src/cli/commands/tests/plugin-monorepo-id-fallback.test.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 3, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Unbounded snapshot tree scan ✓ Resolved 🐞 Bug ➹ Performance
Description
resolvePlugins now falls back to searching the entire snapshot tree via findPluginInDirectory when
the repo root has no manifest and no subpath is pinned, which introduces an unbounded recursive
directory walk on this path and can add significant overhead on large repos.
Code

src/cli/commands/plugin-install.ts[R347-350]

+      if (!manifest && !subpath && pluginRef.id) {
+        const located = findPluginInDirectory(
+          snapshot.snapshotDir,
+          pluginRef.id,
Evidence
The new fallback in resolvePlugins calls findPluginInDirectory, which discovers plugin entries by
recursively descending directories with readdirSync, making this path O(snapshot tree size) when
triggered.

src/cli/commands/plugin-install.ts[309-369]
src/shared/plugin-manifest/detect.ts[358-374]
src/shared/plugin-manifest/detect.ts[387-402]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`resolvePlugins()` now calls `findPluginInDirectory(snapshotDir, pluginRef.id, ...)` as a fallback when the root has no manifest and no subpath is provided. `findPluginInDirectory()` performs a full recursive discovery (`discoverPluginEntries()`), so this path can trigger an expensive, unbounded filesystem walk.
### Issue Context
This behavior is intended to help Cursor marketplace monorepos, but it currently applies broadly whenever `pluginRef.id` is set and root manifest detection fails.
### Fix Focus Areas
- src/cli/commands/plugin-install.ts[344-360]
- src/shared/plugin-manifest/detect.ts[358-374]
- src/shared/plugin-manifest/detect.ts[387-402]
### Suggested remediation
- Gate the fallback to Cursor-monorepo signals (e.g., only attempt the scan when `providers` includes `cursor` AND the snapshot root contains `.cursor-plugin/marketplace.json`).
- Or add a bounded search strategy (limit depth / limit visited dirs) and/or cache discovery results per snapshot so repeated calls don’t re-walk the tree.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. BOM in test files ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Two newly added test files include a UTF-8 BOM (U+FEFF) at the start of the file, adding invisible
bytes that create noisy diffs and can conflict with repo formatting/encoding conventions.
Code

registries/cursor-marketplace/adapter.test.ts[R1-2]

+import { describe, expect, it } from 'bun:test';
+import { pluginDef } from './adapter.ts';
Evidence
Both newly added test files begin with a BOM character (shown as an invisible prefix before
import).

registries/cursor-marketplace/adapter.test.ts[1-2]
src/cli/commands/tests/plugin-monorepo-id-fallback.test.ts[1-2]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Two new test files were committed with a UTF-8 BOM (U+FEFF) prefixing the first `import`. This adds invisible bytes and can cause inconsistent diffs / encoding policy violations.
### Issue Context
The BOM is visible in the diff as an invisible character before `import` on line 1 of each file.
### Fix Focus Areas
- registries/cursor-marketplace/adapter.test.ts[1-2]
- src/cli/commands/__tests__/plugin-monorepo-id-fallback.test.ts[1-2]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread registries/cursor-marketplace/adapter.test.ts Outdated
Comment thread src/cli/commands/plugin-install.ts
Minitour and others added 2 commits August 3, 2026 20:42
Install snippets now pin ::subpath (and optional gitRef) from the marketplace API, and resolvePlugins falls back to finding a nested plugin by id when the repo root only has a catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the full-tree findPluginInDirectory fallback with a direct-child and marketplace-catalog resolve, and fix the adapter test import for tsc.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Minitour
Minitour force-pushed the fix/cursor-marketplace-plugin-subpath branch from 21b9263 to 24831dd Compare August 3, 2026 17:42
@Minitour
Minitour merged commit e6ffc2e into develop Aug 3, 2026
7 checks passed
@Minitour
Minitour deleted the fix/cursor-marketplace-plugin-subpath branch August 3, 2026 17:46
@Minitour Minitour mentioned this pull request Aug 4, 2026
8 tasks
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.

1 participant