Skip to content

Derive extension source from the tool's install manifest, not the enclosing .git #89

Description

@SherlockSalvatore

Derive extension source from the tool's install manifest, not the enclosing .git

Summary

HarnessKit infers an extension's source by walking up to the nearest enclosing .git (scanner::detect_source). Whenever the files live inside the user's own dotfiles repo (e.g. ~/.claude kept under git), everything beneath it is mis-attributed to that dotfiles remote, regardless of where it actually came from. This is one root cause with several faces:

Face Mechanism Status
Symlinked skill (e.g. ~/.claude/skills/tdd~/.agents/skills/tdd) forks into a dotfiles-repo group detect_source walks the symlink's textual parents to ~/.claude/.git Fixed in #88 (canonicalize + self-heal)
install_meta itself stamped git+dotfiles by the source backfill grouping then prefers the polluted install_meta.url Partly addressed (#76 frontend, #88 skill heal)
Plugins cached under ~/.claude/plugins/cache/<marketplace>/<plugin>/ mis-attributed to the dotfiles repo real directories inside ~/.claude/.git; no symlink to resolve Not fixed
Shared skills root (~/.agents) kept under git, or a marketplace skill copied as a real dir into ~/.claude same enclosing-.git inference Not fixed

#76 and #88 patch individual faces at the symptom level (frontend grouping; the symlink subset). They cannot fix plugins (no symlink) or the ~/.agents-under-git case, because the source is inferred rather than read from the authoritative record.

Reproduce

  1. Keep ~/.claude under a personal dotfiles git repo (common backup setup).
  2. Install Claude plugins from any marketplace (they cache under ~/.claude/plugins/cache/<marketplace>/...).
  3. Open Extensions, group by source.

Expected: each plugin attributed to its marketplace's upstream repo (recorded in ~/.claude/plugins/known_marketplaces.json).
Actual: every plugin attributed to the user's dotfiles repo, collapsing N unrelated marketplaces into one bogus group.

The analogous skill case: with ~/.agents under git, every CLI-installed skill shows the ~/.agents remote instead of its real source from ~/.agents/.skill-lock.json.

Root fix

Tool-managed extensions should take their source from the tool's own install manifest, falling back to detect_source only for genuinely git-cloned extensions with no manifest entry:

  • Skills<root>/.skill-lock.json (the skills CLI lockfile), keyed by skill name: source (owner/repo) + sourceUrl.
  • Plugins~/.claude/plugins/installed_plugins.json (<plugin>@<marketplace>) + ~/.claude/plugins/known_marketplaces.json (marketplace → owner/repo).

This subsumes the symptom-level patches: with the authoritative source in hand there is no polluted source.url/install_meta to outrank, and the symlink canonicalize/heal in #88 become defensive rather than load-bearing.

Scope of the proposed PR

  • scanner::scan_skill_dir: after detect_source, override with the lockfile entry when present (cached per lockfile, one read per scanned dir).
  • PluginEntry: new source_url field; Claude's adapter fills it from known_marketplaces.json; scanner::scan_plugins prefers it over .git detection. Other adapters leave it None (unchanged behavior).
  • Regression tests: lockfile beats a populated enclosing repo (and a sibling skill absent from the lock still falls back); a plugin is attributed to its marketplace repo.

Deferred / follow-up

  • Codex and Copilot also parse marketplace-based plugins; they can adopt the same source_url resolution later (kept None here to stay surgical).
  • Once manifest-based resolution lands, the store's git-source backfill (which stamps install_meta from the inferred enclosing .git) can be narrowed or retired.

Depends on #88 (built on its canonicalize in scan_skill_dir). PR incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions