Skip to content

Fix plugin skill content loading before capa install - #165

Merged
Minitour merged 2 commits into
developfrom
fix/plugin-skill-content-before-install
Aug 3, 2026
Merged

Fix plugin skill content loading before capa install#165
Minitour merged 2 commits into
developfrom
fix/plugin-skill-content-before-install

Conversation

@Minitour

@Minitour Minitour commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

  • Plugin skills listed in the UI after add could not load SKILL.md until capa install, because content resolution only checked provider skill dirs.
  • Resolve plugin skill content (and descriptions) from the already-unpacked tree under ~/.capa/plugins/<projectId>/.
  • Add regression tests for unpack-dir and .capa-commands layouts.

Test plan

  • Add a new plugin via the UI without running capa install
  • Confirm plugin skills appear and opening one shows SKILL.md content
  • Confirm skill description frontmatter appears in the project skills list
  • Run bun test src/server/__tests__/skill-content-plugin.test.ts
  • After capa install, content still loads as before

Read SKILL.md from the unpacked plugin tree under ~/.capa/plugins so the UI can show content as soon as a plugin is added, without requiring install to materialize provider skill dirs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Load plugin SKILL.md from unpacked plugin dir before capa install

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Resolve plugin skill content/description from unpacked plugin trees before capa install.
• Plumb projectId into skill content resolution for API endpoints.
• Add regression tests for skills/ and .capa-commands/ plugin layouts.
Diagram

graph TD
  A["Project routes"] --> B["resolveSkillDescription()"] --> C["resolveSkillContent()"]
  D["MCP meta routes"] --> E["resolveSkillContentById()"] --> C
  C --> F["Unpacked plugin dir (~/.capa/plugins/projectId)"]
  C --> G["Provider skillsDir (installed)"]
  E --> H["Remote fetch (git cache/HTTP)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Persist a plugin skill index/manifest at unpack time
  • ➕ Avoids recursive scanning (findSkillsInDirectory) on large plugin trees
  • ➕ Lets the UI/API list skills/descriptions without touching the filesystem repeatedly
  • ➖ Requires additional write/update logic during plugin add/update flows
  • ➖ Need versioning and invalidation strategy when plugin contents change
2. Materialize provider skillsDirs immediately when a plugin is added
  • ➕ Keeps all reads in the existing installed-provider resolution path
  • ➕ No special-case plugin-unpack lookup needed in skill-content resolver
  • ➖ Essentially performs part of capa install implicitly (more side effects/latency)
  • ➖ Harder to reason about user intent and install lifecycle

Recommendation: The PR’s approach (read SKILL.md from the already-unpacked plugin tree, with safe path handling and a provider-dir fallback) is the best trade-off for correctness and minimal side effects. Consider a follow-up manifest/index only if plugin tree scanning becomes a measurable performance bottleneck.

Files changed (4) +210 / -2

Bug fix (3) +85 / -2
mcp-meta-routes.tsPass projectId into skill content lookup for MCP meta endpoint +1/-0

Pass projectId into skill content lookup for MCP meta endpoint

• Updates the skill content endpoint to provide '{ projectId }' when calling 'resolveSkillContentById', enabling plugin-unpack resolution before install.

src/server/mcp-meta-routes.ts

project-routes.tsResolve skill descriptions using projectId-aware content resolution +1/-0

Resolve skill descriptions using projectId-aware content resolution

• Threads '{ projectId }' into 'resolveSkillDescription' so project skill lists can use SKILL.md frontmatter from the unpacked plugin tree.

src/server/project-routes.ts

skill-content.tsResolve plugin SKILL.md from unpacked plugin trees prior to install +83/-2

Resolve plugin SKILL.md from unpacked plugin trees prior to install

• Adds 'SkillContentResolveOptions' and new plugin-unpack resolution that locates SKILL.md under '~/.capa/plugins/<projectId>/<pluginId>/' (including common layouts and a scan fallback). Wires options through 'resolveSkillContent', 'resolveSkillContentById', and 'resolveSkillDescription' so both content and frontmatter descriptions work before provider skill dirs are materialized.

src/server/skill-content.ts

Tests (1) +125 / -0
skill-content-plugin.test.tsAdd regression tests for plugin-unpack SKILL.md resolution +125/-0

Add regression tests for plugin-unpack SKILL.md resolution

• Introduces tests that build a temporary unpacked plugin directory tree and assert SKILL.md content and frontmatter description resolve without running install. Covers both 'skills/<id>/SKILL.md' and '.capa-commands/<id>/SKILL.md' layouts, plus the 'resolveSkillContentById' path.

src/server/tests/skill-content-plugin.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. Unpacked overrides installed ✓ Resolved 🐞 Bug ≡ Correctness
Description
resolveSkillContent returns SKILL.md from the unpacked plugin tree before checking
provider-installed skill dirs, so after capa install the API/UI may serve a different copy than
the installed one. This can cause inconsistencies with install-time transformations (e.g., character
sanitization) applied when copying skills into provider directories.
Code

src/server/skill-content.ts[R249-252]

+	// Plugin skills: read from the unpacked tree under ~/.capa/plugins/<projectId>/
+	// (populated when the plugin is added / effective caps are resolved — before install).
+	const fromPlugin = resolvePluginSkillContent(skill, options);
+	if (fromPlugin) return fromPlugin;
Evidence
The new early return from the unpacked plugin tree happens before the provider skillsDir lookup.
Plugin installation can copy skills into provider directories with transformations (e.g.,
security/sanitization), so serving the unpacked copy post-install can diverge from what was
installed.

src/server/skill-content.ts[249-260]
src/cli/commands/plugin-install.ts[200-249]

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

## Issue description
`resolveSkillContent()` checks the unpacked plugin tree first and returns it if found, even when an installed provider copy exists. This changes post-install behavior and can make the content endpoint/UI inconsistent with the installed skill tree.
## Issue Context
Installed skills may be copied with transformations (security/sanitization) into provider skill directories during plugin install. The unpacked plugin tree is a raw copy of the plugin source.
## Fix Focus Areas
- src/server/skill-content.ts[249-260]
### Suggested fix
- In `resolveSkillContent()`, try resolving the installed/provider copy first (current loop over `providers`), and only if not found, fall back to `resolvePluginSkillContent()`.
- Alternatively: only use `resolvePluginSkillContent()` when the provider-installed resolution fails (or when providers list is empty), to preserve current post-install behavior while still enabling pre-install reads.

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


2. Plugin scan per request ✓ Resolved 🐞 Bug ➹ Performance
Description
findPluginSkillMd falls back to a recursive findSkillsInDirectory(pluginRoot) scan, which can
traverse large plugin trees synchronously. Because project details compute descriptions for every
skill, this fallback can repeat and noticeably slow API responses when candidate paths don’t match
plugin layouts.
Code

src/server/skill-content.ts[R169-171]

+	const found = findSkillsInDirectory(pluginRoot);
+	const fromScan = found.get(skillId);
+	if (fromScan) return fromScan;
Evidence
The new plugin resolution code triggers a recursive directory walk fallback, and the project details
route calls description resolution for every skill, which can cause repeated scans when direct
candidates miss.

src/server/skill-content.ts[152-171]
src/cli/commands/install-tasks/helpers/skill-discovery.ts[7-58]
src/server/project-routes.ts[139-146]

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

## Issue description
When candidate paths don’t match, plugin skill resolution recursively scans the entire unpacked plugin tree via `findSkillsInDirectory(pluginRoot)`. This is synchronous I/O and can be repeated across multiple skills and requests, increasing latency.
## Issue Context
`handleGetProject()` calls `resolveSkillDescription()` for each skill, and `resolveSkillDescription()` can call `resolveSkillContent()` which may trigger the recursive scan.
## Fix Focus Areas
- src/server/skill-content.ts[152-174]
- src/server/project-routes.ts[139-146]
### Suggested fix
- Add a small in-memory cache keyed by `pluginRoot` (e.g., `Map<string, Map<string,string>>`) to store the result of `findSkillsInDirectory(pluginRoot)` so multiple skills don’t re-scan.
- Optionally invalidate the cache using a cheap mtime/exists check on `pluginRoot`.
- Consider narrowing the scan scope (e.g., scan `pluginRoot/skills` first) before scanning the whole plugin tree.

ⓘ 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 src/server/skill-content.ts Outdated
Comment thread src/server/skill-content.ts Outdated
Prefer provider-installed skill copies over the unpacked plugin tree so post-install sanitized content wins, and cache plugin skill directory scans by mtime to avoid repeated walks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Minitour
Minitour merged commit 485b72d into develop Aug 3, 2026
7 checks passed
@Minitour
Minitour deleted the fix/plugin-skill-content-before-install branch August 3, 2026 18:07
@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