diff --git a/pkg/skills/github_registry.go b/pkg/skills/github_registry.go index 59f7d4fd7..25beca5d7 100644 --- a/pkg/skills/github_registry.go +++ b/pkg/skills/github_registry.go @@ -73,6 +73,14 @@ func (r *GitHubRegistry) ResolveInstallDirName(target string) (string, error) { return githubInstallDirNameWithBaseURL(target, r.webBase) } +func (r *GitHubRegistry) NormalizeInstallTarget(target string) string { + normalized, err := canonicalGitHubRegistrySlugWithBaseURL(target, r.webBase) + if err != nil { + return target + } + return normalized +} + func (r *GitHubRegistry) SkillURL(target, version string) string { defaultRef := strings.TrimSpace(version) ref, err := parseGitHubRefWithBaseURL(target, r.webBase, defaultRef) diff --git a/pkg/skills/registry.go b/pkg/skills/registry.go index 871c10226..6c8e28a4e 100644 --- a/pkg/skills/registry.go +++ b/pkg/skills/registry.go @@ -73,6 +73,27 @@ type SkillRegistry interface { DownloadAndInstall(ctx context.Context, slug, version, targetDir string) (*InstallResult, error) } +// InstallTargetNormalizer is implemented by registries that can canonicalize +// user-provided install targets into a stable slug for origin metadata. +type InstallTargetNormalizer interface { + NormalizeInstallTarget(target string) string +} + +func NormalizeInstallTargetForRegistryInstance(registry SkillRegistry, target string) string { + if registry == nil || target == "" { + return target + } + normalizer, ok := registry.(InstallTargetNormalizer) + if !ok { + return target + } + normalized := normalizer.NormalizeInstallTarget(target) + if normalized == "" { + return target + } + return normalized +} + // RegistryConfig holds configuration for all skill registries. // This is the input to NewRegistryManagerFromConfig. type RegistryConfig struct { diff --git a/pkg/tools/skills_install.go b/pkg/tools/skills_install.go index e73692fc2..c7db02eb3 100644 --- a/pkg/tools/skills_install.go +++ b/pkg/tools/skills_install.go @@ -198,18 +198,19 @@ type originMeta struct { } func writeOriginMeta(targetDir string, registry skills.SkillRegistry, slug, version string) error { + normalizedSlug := skills.NormalizeInstallTargetForRegistryInstance(registry, slug) registryName := "" registryURL := "" if registry != nil { registryName = registry.Name() - registryURL = registry.SkillURL(slug, version) + registryURL = registry.SkillURL(normalizedSlug, version) } meta := originMeta{ Version: 1, OriginKind: "third_party", Registry: registryName, - Slug: slug, + Slug: normalizedSlug, RegistryURL: registryURL, InstalledVersion: version, InstalledAt: time.Now().UnixMilli(), diff --git a/pkg/tools/skills_install_test.go b/pkg/tools/skills_install_test.go index fdf42f11d..9ecf34ea3 100644 --- a/pkg/tools/skills_install_test.go +++ b/pkg/tools/skills_install_test.go @@ -67,6 +67,19 @@ func (m *mockGitHubInstallRegistry) DownloadAndInstall( return &skills.InstallResult{Version: "main"}, nil } +type stubGitHubInstallRegistry struct { + *skills.GitHubRegistry +} + +func (m *stubGitHubInstallRegistry) DownloadAndInstall( + context.Context, + string, + string, + string, +) (*skills.InstallResult, error) { + return &skills.InstallResult{Version: "main"}, nil +} + func TestInstallSkillToolName(t *testing.T) { tool := NewInstallSkillTool(skills.NewRegistryManager(), t.TempDir()) assert.Equal(t, "install_skill", tool.Name()) @@ -166,8 +179,12 @@ func TestInstallSkillToolMissingRegistry(t *testing.T) { } func TestInstallSkillToolAllowsGitHubURLSlug(t *testing.T) { + registry := skills.GitHubRegistryConfig{Enabled: true, BaseURL: "https://github.com"}.BuildRegistry() + githubRegistry, ok := registry.(*skills.GitHubRegistry) + require.True(t, ok) + registryMgr := skills.NewRegistryManager() - registryMgr.AddRegistry(&mockGitHubInstallRegistry{}) + registryMgr.AddRegistry(&stubGitHubInstallRegistry{GitHubRegistry: githubRegistry}) workspace := t.TempDir() tool := NewInstallSkillTool(registryMgr, workspace) @@ -187,7 +204,7 @@ func TestInstallSkillToolAllowsGitHubURLSlug(t *testing.T) { require.NoError(t, json.Unmarshal(data, &meta)) assert.Equal(t, "third_party", meta.OriginKind) assert.Equal(t, "github", meta.Registry) - assert.Equal(t, slug, meta.Slug) + assert.Equal(t, "synthetic-lab/octofriend/.agents/skills/pr-review", meta.Slug) assert.Equal(t, slug, meta.RegistryURL) assert.Equal(t, "main", meta.InstalledVersion) assert.NotZero(t, meta.InstalledAt)