Conversation
PR Summary by QodoInstall skills and plugins from any Git host
AI Description
Diagram
High-Level Assessment
Files changed (26)
|
Code Review by Qodo
1. Git installs can use the wrong repository
|
| return [u.host, ...path.split("/")] | ||
| .filter((s) => s && s !== "." && s !== "..") | ||
| .map((s) => s.replace(/[<>:"|?*\\]/g, "_")) | ||
| .join("/"); |
There was a problem hiding this comment.
1. Git installs can use the wrong repository 🐞 Bug ≡ Correctness
gitRepoKey() omits the URL scheme and normalizes distinct URL components into the same cache key. When two clone URLs share that key, ensureMirrorClone() keeps the first URL's existing mirror, so a later skill or plugin install can receive content from the first repository.
Agent Prompt
## Issue description
Distinct clone URLs can share a generic git cache key, causing installs to use an existing mirror for another URL.
## Fix Focus Areas
- src/shared/git-url.ts[54-65]
- src/shared/cache/mirror.ts[137-160]
## Recommended Fix
Include all source-distinguishing URL components in a safe cache identity and verify that an existing mirror's origin matches the requested clone URL before reusing it.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| ref, | ||
| pinnedSha, | ||
| noCache, | ||
| repoUrl, |
There was a problem hiding this comment.
2. Changed plugin urls keep old content 🐞 Bug ≡ Correctness
resolvePlugins() passes a prior plugin lock's SHA to snapshot resolution without checking the lock entry's url against the current repoUrl. If a git plugin's clone URL changes but retains the same normalized cache key, the pinned-snapshot fast path can install the old content and then record the new URL in the lock.
Agent Prompt
## Issue description
A git plugin can reuse a lock pin created for a different clone URL with the same cache key.
## Fix Focus Areas
- src/cli/commands/plugin-install.ts[393-424]
- src/shared/lockfile.ts[382-415]
- src/shared/lockfile.ts[423-449]
## Recommended Fix
Require the previous git lock entry's URL to match the current clone URL in both plugin lock lookup paths before passing its resolved SHA to snapshot resolution.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| // github/gitlab use `repo`; git uses `url` (validatePluginDef checks which). | ||
| repo: z.string().optional(), | ||
| url: z.string().optional(), |
There was a problem hiding this comment.
5. Plugins with no repository disappear 🐞 Bug ☼ Reliability
pluginDefSchema now makes both def.repo and def.url optional, allowing a plugin with neither source through capabilities-file validation. When that entry reaches resolvePlugins(), pluginSourceOf() returns no source and the loop silently continues before validatePluginDef() can report the missing field.
Agent Prompt
## Issue description
Plugin definitions with no repository or clone URL now validate but are silently skipped during installation.
## Fix Focus Areas
- src/shared/capabilities.ts[217-248]
- src/cli/commands/plugin-install.ts[360-387]
## Recommended Fix
Validate the required source field according to plugin type in the schema, and let source-less entries reach the plugin validator so an actionable warning is emitted.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| lockBuilder.upsertSkill({ | ||
| id: skill.id, | ||
| source: 'git', | ||
| repo: repoPath, | ||
| url, |
There was a problem hiding this comment.
8. Failed git skills retain lock pins 🐞 Bug ☼ Reliability
The git branch calls lockBuilder.upsertSkill() before checking for SKILL.md or copying the skill. If either later step fails, the install task records the failure but lockfile writing retains the new git skill's ID and saves its pin, which a subsequent install can reuse.
Agent Prompt
## Issue description
A failed generic-git skill install can persist a lock pin for content that was never installed.
## Fix Focus Areas
- src/cli/commands/install-tasks/helpers/install-one-skill.ts[265-290]
- src/cli/commands/install-tasks/install-skills.ts[38-65]
- src/cli/commands/install-tasks/write-lockfile.ts[15-40]
## Recommended Fix
Stage the git skill lock entry until its path validation and installation succeed, or remove that entry when installation fails; avoid pruning based solely on declared IDs.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| skillMarkdown = await response.text(); | ||
| // A web page is never a SKILL.md: usually a repository page or a clone URL | ||
| // missing its .git suffix. Installing it would hand the agent HTML. | ||
| if (/^s*<(!doctype|html)[s>]/i.test(skillMarkdown)) { |
There was a problem hiding this comment.
3. Remote skills that return a web page still install 🐞 Bug ≡ Correctness
installOneSkill() uses /^s*<(!doctype|html)[s>]/i, which matches literal s characters instead of whitespace. Standard <!DOCTYPE html> and <html lang="en"> responses, including ones with leading whitespace, bypass the check and continue through installation as SKILL.md while the install reports success.
Agent Prompt
## Issue description
The remote-skill HTML check uses literal `s` characters where whitespace matching is needed, allowing standard HTML responses to proceed through skill installation.
## Fix Focus Areas
- src/cli/commands/install-tasks/helpers/install-one-skill.ts[312-320]
## Recommended Fix
Change the expression to `/^\s*<(!doctype|html)[\s>]/i`, or use `skillMarkdown.trimStart().slice(0,200).toLowerCase()` with `startsWith('<!doctype') || startsWith('<html')`, as in `src/server/skill-content.ts`. Add tests for ordinary HTML responses with and without leading whitespace, including `<!DOCTYPE html>\n<html lang="en">`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (errorMessage.includes('not found') || errorMessage.includes('does not appear to be a git repository')) { | ||
| return new Error(`Repository not found: ${repoUrl}\nCheck the URL, or sign in with git if it is private.`); | ||
| } |
There was a problem hiding this comment.
9. A missing tag is reported as a missing repo 🐞 Bug ◔ Observability
explainGenericGitError treats any message that contains 'not found' as a missing repository. The cache's own ref-resolution errors (Tag/branch "v2" not found, Commit … not found) contain that phrase, so a bad def.version or def.ref becomes 'Repository not found … sign in with git', and the real cause is dropped.
Agent Prompt
## Issue description
The generic git error mapper rewrites 'Tag/branch … not found' and 'Commit … not found' as 'Repository not found'.
## Fix Focus Areas
- src/cli/commands/install-tasks/helpers/git.ts[39-70]
## Recommended Fix
Before the 'not found' check, return the original error when the message starts with 'Tag/branch', 'Commit ' or 'Pinned commit'. Otherwise match only git transport text such as `repository '` + `' not found` or 'does not appear to be a git repository'.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
A clone URL ending in .git on a host other than github.com or gitlab.com used to fall through to the raw-URL skill type, which saved the host's HTML page as SKILL.md and reported success. Skills and plugins get a `git` type with `def.url` (the clone URL). It reuses the existing mirror cache, keyed by host and path, so pinning by tag or commit, "latest version tag" and the lockfile work as for GitHub. Credentials come from the user's git credential helper. `capa add` recognizes `https://host/path/repo.git[::path][:version|#sha]`, rules and hooks refuse such URLs until they support them, and a remote skill that returns a web page now fails instead of installing HTML. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2dbead7 to
c727cd0
Compare
Summary
capa addonly understood github.com and gitlab.com. A clone URL on any other host (Gitea, Forgejo, self-hosted servers, internal registries) fell through to the raw-URL skill type, so capa downloaded the host's HTML page, saved it asSKILL.mdand reported success. This adds agitsource type so any git host works for skills and plugins.What changed
type: gitfor skills (def: { url, path?, version?, ref? }) and plugins (def: { url, subpath?, version?, ref? }), in the schema, types and lockfile (source: gitplusurl).CachePlatformgainsgit, keyed by<host>/<path>(file://URLs by name plus a hash to stay under Windows path limits), with the clone URL threaded throughgetRepoSnapshot. Pinning by tag or commit, "newest vX.Y.Z tag" and lock reuse behave exactly as for GitHub.capa addrecognizeshttps://host/path/repo.git[::path][:version|#sha]for skills and--plugin. GitHub and GitLab URLs still parse as before.SKILL.mdat the repo root by default. When it's missing, the error lists the skills found in the repo.def.repo, and the duplicate check incapa add --plugincompareddef.repoonly..gitURLs with a clear message until they support them. Aremoteskill whose response is an HTML page now fails instead of installing it.capabilities-managercommand and schema references.Not included (follow-ups): auth integration for generic hosts (
capa auth, env tokens), rules/hooks/instructions from git hosts, registry adapters, web UI source badges.Screenshots / logs
Missing or private repo:
Test plan
capa addskill/plugin parsing and plugin validation (src/shared/__tests__/git-url.test.ts)file://: root skill at the newest tag with the URL in the lock, nesteddef.path, and the "skills found" error (install-one-skill-git.test.ts)capa add+capa installof a skill from a live non-GitHub HTTPS git host (pinned to itsv0.1.0tag), a plugin from a bare repo (its skill installed), and the error for a missing repomainlocallybunx tsc --noEmit,bunx tsc --noEmit -p web-ui/tsconfig.json,biome linton changed filesChecklist
bunx tsc --noEmitpasses🤖 Generated with Claude Code