Dedupe and organize skills/sources (#8731)

This commit is contained in:
Jack Amadeo
2026-04-23 15:07:45 -04:00
committed by GitHub
parent 586fa2b3c8
commit 5dd9f08d57
28 changed files with 1245 additions and 786 deletions
+24 -6
View File
@@ -96,6 +96,7 @@ export function buildInitScript(options?: {
content: s.instructions ?? s.content ?? "",
directory: (s.path ?? ("/mock/.agents/skills/" + s.name + "/SKILL.md")).replace(/\\/SKILL\\.md$/, ""),
global: true,
supportingFiles: [],
});
function nowIso() {
@@ -195,26 +196,43 @@ export function buildInitScript(options?: {
content: message.params?.content ?? "",
directory: "/mock/.agents/skills/" + (message.params?.name ?? "new-skill"),
global: message.params?.global ?? true,
supportingFiles: [],
},
});
case "_goose/sources/update":
case "_goose/sources/update": {
const path = message.params?.path ?? "/mock/.agents/skills/updated-skill";
const nextName = message.params?.name;
const name =
typeof nextName === "string" && nextName.length > 0
? nextName
: String(path).split("/").filter(Boolean).at(-1) ?? "updated-skill";
const segments = String(path).split("/").filter(Boolean);
if (segments.length > 0) {
segments[segments.length - 1] = name;
}
const directory = \`/\${segments.join("/")}\`;
return jsonRpcResult(message.id, {
source: {
name: message.params?.name ?? "updated-skill",
name,
type: "skill",
description: message.params?.description ?? "",
content: message.params?.content ?? "",
directory: "/mock/.agents/skills/" + (message.params?.name ?? "updated-skill"),
global: message.params?.global ?? true,
directory,
global: true,
supportingFiles: [],
},
});
}
case "_goose/sources/delete":
return jsonRpcResult(message.id, {});
case "_goose/sources/export":
case "_goose/sources/export": {
const path = message.params?.path ?? "/mock/.agents/skills/skill";
const name = String(path).split("/").filter(Boolean).at(-1) ?? "skill";
return jsonRpcResult(message.id, {
json: "{}",
filename: (message.params?.name ?? "skill") + ".skill.json",
filename: name + ".skill.json",
});
}
case "_goose/sources/import":
return jsonRpcResult(message.id, { sources: SKILLS.map(skillToSourceEntry) });
default:
+33 -7
View File
@@ -95,17 +95,17 @@ test.describe("Skills view", () => {
await expect(nameInput).toHaveValue("my-skill-name");
});
test("shows kebab-case validation error for trailing hyphen", async ({
test("shows validation error for trailing hyphen", async ({
tauriMocked: page,
}) => {
await navigateToSkills(page);
await page.getByRole("button", { name: "New Skill" }).first().click();
const dialog = page.getByRole("dialog");
// Type something that ends with a hyphen (the auto-formatter will produce "test-")
await dialog.getByPlaceholder("my-skill-name").pressSequentially("test ");
// The regex /^[a-z0-9]+(-[a-z0-9]+)*$/ won't match "test-", so error shows
await expect(
dialog.getByText("Must be kebab-case (e.g. code-review)"),
dialog.getByText(
"Use 164 lowercase letters, numbers, or hyphens. Names cannot start or end with a hyphen.",
),
).toBeVisible();
});
@@ -145,19 +145,45 @@ test.describe("Skills view", () => {
await expect(menu.getByRole("menuitem", { name: "Delete" })).toBeVisible();
});
test("Edit opens edit dialog with pre-filled fields", async ({
test("Edit opens edit dialog with pre-filled editable fields", async ({
tauriMocked: page,
}) => {
await navigateToSkills(page);
await page.getByLabel("Options for code-review").click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
await expect(dialog.locator("h2", { hasText: "Edit Skill" })).toBeVisible();
// Name should be pre-filled and read-only
const nameInput = dialog.getByPlaceholder("my-skill-name");
const descriptionInput = dialog.getByPlaceholder(
"What it does and when to use it...",
);
const instructionsInput = dialog.getByPlaceholder(
"Markdown instructions the agent will follow...",
);
await expect(nameInput).toHaveValue("code-review");
await expect(nameInput).toHaveAttribute("readonly", "");
await expect(descriptionInput).toHaveValue(
"Reviews code for quality and best practices",
);
await expect(instructionsInput).toHaveValue(
"When asked to review code, analyze the diff and provide feedback on code quality, potential bugs, and best practices.",
);
await expect(
dialog.getByText(
"Path on disk: /mock/.agents/skills/code-review/SKILL.md",
),
).toBeVisible();
await nameInput.fill("renamed-skill");
await expect(nameInput).toHaveValue("renamed-skill");
await expect(
dialog.getByText(
"Path on disk: /mock/.agents/skills/renamed-skill/SKILL.md",
),
).toBeVisible();
});
test("Delete triggers confirmation dialog", async ({ tauriMocked: page }) => {