Merge pull request #13235 from cli/sammorrowdrums/fix-skill-install-discovery
Make skill discovery less strict: support nested `skills/` directories
This commit is contained in:
commit
a67f4f7303
4 changed files with 313 additions and 17 deletions
|
|
@ -107,7 +107,9 @@ func NewCmdInstall(f *cmdutil.Factory, telemetry ghtelemetry.CommandRecorder, ru
|
|||
tracking metadata injected into frontmatter.
|
||||
|
||||
Skills are discovered automatically using the %[1]sskills/*/SKILL.md%[1]s convention
|
||||
defined by the Agent Skills specification. For more information on the specification,
|
||||
defined by the Agent Skills specification, including when the %[1]sskills/%[1]s
|
||||
directory is nested under a prefix (e.g. %[1]sterraform/code-generation/skills/...%[1]s).
|
||||
For more information on the specification,
|
||||
see: https://agentskills.io/specification
|
||||
|
||||
The skill argument can be a name, a namespaced name (%[1]sauthor/skill%[1]s),
|
||||
|
|
@ -504,6 +506,9 @@ func isSkillPath(name string) bool {
|
|||
if strings.HasPrefix(name, "skills/") || strings.HasPrefix(name, "plugins/") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(name, "/skills/") || strings.Contains(name, "/plugins/") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -231,7 +232,7 @@ func stubSkillByPath(reg *httpmock.Registry, owner, repo, sha, skillPath, skillN
|
|||
parentPath = skillPath[:idx]
|
||||
}
|
||||
reg.Register(
|
||||
httpmock.REST("GET", fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, parentPath)),
|
||||
httpmock.REST("GET", fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, url.PathEscape(parentPath))),
|
||||
httpmock.StringResponse(fmt.Sprintf(`[{"name": %q, "path": %q, "sha": %q, "type": "dir"}]`, skillName, skillPath, treeSHA)),
|
||||
)
|
||||
}
|
||||
|
|
@ -759,6 +760,34 @@ func TestInstallRun(t *testing.T) {
|
|||
},
|
||||
wantStdout: "Installed git-commit",
|
||||
},
|
||||
{
|
||||
name: "remote install by nested skill path skips full discovery",
|
||||
isTTY: true,
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
stubResolveVersion(reg, "monalisa", "skills-repo", "v1.0.0", "abc123")
|
||||
stubSkillByPath(reg, "monalisa", "skills-repo", "abc123",
|
||||
"terraform/code-generation/skills/terraform-style-guide", "terraform-style-guide", "treeSHA")
|
||||
// DiscoverSkillByPath: tree + blob (for fetchDescription)
|
||||
stubInstallFiles(reg, "monalisa", "skills-repo", "treeSHA", "blobSHA", gitCommitContent)
|
||||
// installer.Install: tree + blob (again, for writing files)
|
||||
stubInstallFiles(reg, "monalisa", "skills-repo", "treeSHA", "blobSHA", gitCommitContent)
|
||||
},
|
||||
opts: func(ios *iostreams.IOStreams, reg *httpmock.Registry) *InstallOptions {
|
||||
t.Helper()
|
||||
return &InstallOptions{
|
||||
IO: ios,
|
||||
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
|
||||
GitClient: &git.Client{RepoDir: t.TempDir()},
|
||||
SkillSource: "monalisa/skills-repo",
|
||||
SkillName: "terraform/code-generation/skills/terraform-style-guide",
|
||||
Agent: "github-copilot",
|
||||
Scope: "project",
|
||||
ScopeChanged: true,
|
||||
Dir: t.TempDir(),
|
||||
}
|
||||
},
|
||||
wantStdout: "Installed terraform-style-guide",
|
||||
},
|
||||
{
|
||||
name: "remote install with URL repo argument",
|
||||
isTTY: true,
|
||||
|
|
@ -2075,6 +2104,31 @@ func TestRunLocalInstall(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_isSkillPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{name: "empty string", path: "", want: false},
|
||||
{name: "plain skill name", path: "git-commit", want: false},
|
||||
{name: "SKILL.md at root", path: "SKILL.md", want: true},
|
||||
{name: "SKILL.md suffix", path: "skills/code-review/SKILL.md", want: true},
|
||||
{name: "starts with skills/", path: "skills/code-review", want: true},
|
||||
{name: "starts with plugins/", path: "plugins/hubot/skills/pr-summary", want: true},
|
||||
{name: "nested skills/ path", path: "terraform/code-generation/skills/terraform-style-guide", want: true},
|
||||
{name: "deeply nested skills/ path", path: "a/b/c/skills/my-skill", want: true},
|
||||
{name: "nested plugins/ path", path: "vendor/plugins/hubot/skills/pr-summary", want: true},
|
||||
{name: "name containing skills substring", path: "myskills", want: false},
|
||||
{name: "namespaced path", path: "skills/monalisa/issue-triage", want: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, isSkillPath(tt.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_printReviewHint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue