feat: add personal skills settings UI and docs (#25066)

> Mux updated this PR on behalf of Mike.

## Summary

- Add experimental personal skills API helpers and an Agents settings UI
for listing, creating, editing, deleting, and importing SKILL.md
content.
- Add docs, Storybook coverage, and unit tests for backend-compatible
SKILL.md parsing.
- Address review feedback by simplifying frontmatter scalar parsing,
clarifying the UI parser scope, defaulting personal skill queries to
`me`, and patching React Query caches after create, update, and delete.
- Merge latest `main` and resolve the Agents sidebar refactor conflicts.

## Validation

- pre-commit hook
- `go test ./codersdk/workspacesdk -run TestParseSkillFrontmatter
-count=1`
- `go test ./coderd/x/chatd/chattool -run 'Test' -count=1`
- `cd site && pnpm test --
src/pages/AgentsPage/utils/personalSkills.test.ts
src/api/queries/userSkills.test.ts src/utils/fileSize.test.ts
--runInBand`
- `cd site && pnpm lint:types`
- `cd site && pnpm lint:check`
This commit is contained in:
Michael Suchacz
2026-05-22 00:20:10 +02:00
committed by GitHub
parent 35a624bebd
commit 356bccddc2
18 changed files with 2138 additions and 14 deletions
+24 -7
View File
@@ -24,6 +24,29 @@ var markdownCommentRe = regexp.MustCompile(`<!--[\s\S]*?-->`)
// the frontmatter is missing a required name field.
var ErrFrontmatterNameRequired = xerrors.New("frontmatter missing required 'name' field")
func unquoteFrontmatterScalar(value string) string {
if len(value) < 2 {
return value
}
quote := value[0]
if quote != value[len(value)-1] {
return value
}
inner := value[1 : len(value)-1]
switch quote {
case '"':
// This parser supports a small SKILL.md scalar subset, not full
// YAML. Double quotes only unescape quoted text and Windows paths.
return strings.NewReplacer(`\"`, `"`, `\\`, `\`).Replace(inner)
case '\'':
return inner
default:
return value
}
}
// ParseSkillFrontmatter extracts name, description, and the
// remaining body from a skill meta file. The expected format is
// YAML-ish frontmatter delimited by "---" lines:
@@ -62,13 +85,7 @@ func ParseSkillFrontmatter(content string) (name, description, body string, err
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
// Strip surrounding quotes from YAML string values.
if len(value) >= 2 {
if (value[0] == '"' && value[len(value)-1] == '"') ||
(value[0] == '\'' && value[len(value)-1] == '\'') {
value = value[1 : len(value)-1]
}
}
value = unquoteFrontmatterScalar(value)
switch strings.ToLower(key) {
case "name":
name = value
+18
View File
@@ -32,6 +32,24 @@ func TestParseSkillFrontmatter(t *testing.T) {
require.Equal(t, "single-quoted", desc)
})
t.Run("EscapedDoubleQuotedValue", func(t *testing.T) {
t.Parallel()
_, desc, _, err := workspacesdk.ParseSkillFrontmatter(
"---\nname: escaped\ndescription: \"Review \\\"critical\\\" C:\\\\paths.\"\n---\nBody\n",
)
require.NoError(t, err)
require.Equal(t, "Review \"critical\" C:\\paths.", desc)
})
t.Run("PlainHashValue", func(t *testing.T) {
t.Parallel()
_, desc, _, err := workspacesdk.ParseSkillFrontmatter(
"---\nname: plain-hash\ndescription: Build # test\n---\nBody\n",
)
require.NoError(t, err)
require.Equal(t, "Build # test", desc)
})
t.Run("NoDescription", func(t *testing.T) {
t.Parallel()
name, desc, body, err := workspacesdk.ParseSkillFrontmatter(