Skip to content

Commit d7f89e4

Browse files
committed
fix(skills): cap slash-picker line and drop dangling worker pointers
1 parent 38669fe commit d7f89e4

4 files changed

Lines changed: 41 additions & 9 deletions

File tree

‎src/agent/skill-search.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const skillSearchDefinition: ToolDefinition = {
3939
export const workerSkillSearchDefinition: ToolDefinition = {
4040
name: "skill_search",
4141
description:
42-
"Prep lookup for a skill when attached skills are not enough; skip tiny one-file fixes. Directly callable; load via use_skill. See Guidelines: Tool choice.",
42+
"Prep lookup for a skill when attached skills are not enough; skip tiny one-file fixes. Directly callable; load via use_skill.",
4343
inputSchema: SKILL_SEARCH_INPUT_SCHEMA,
4444
};
4545

‎src/agent/use-skill.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const useSkillDefinition: ToolDefinition = {
3636
export const workerUseSkillDefinition: ToolDefinition = {
3737
name: "use_skill",
3838
description:
39-
"Load a skill by name; do not reload attached skills or bodies already in context. See Guidelines: Tool choice.",
39+
"Load a skill by name; do not reload attached skills or bodies already in context.",
4040
inputSchema: USE_SKILL_INPUT_SCHEMA,
4141
};
4242

‎src/plugins/skill-commands.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type CommandDefinition,
66
type CommandResult,
77
} from "../tui/commands/registry.js";
8+
import { firstLine } from "../extensions/skills.js";
89
import { COMMAND_NAME_PATTERN, splitFrontmatter } from "./frontmatter.js";
910

1011
// Slash is the operator action surface: `/<skill-name> [args]` sends the skill
@@ -72,8 +73,8 @@ export async function loadSkillCommands(
7273
}
7374
const description =
7475
typeof frontmatter.description === "string" &&
75-
frontmatter.description.trim().length > 0
76-
? frontmatter.description.trim()
76+
firstLine(frontmatter.description).length > 0
77+
? firstLine(frontmatter.description)
7778
: name;
7879

7980
const argumentHint = argumentHintFromFrontmatter(frontmatter);

‎tests/unit/skills.test.ts‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
skillSearchDefinition,
1414
workerSkillSearchDefinition,
1515
} from "../../src/agent/skill-search.js";
16+
import { loadSkillCommands } from "../../src/plugins/skill-commands.js";
1617
import {
1718
useSkillDefinition,
1819
workerUseSkillDefinition,
@@ -276,18 +277,27 @@ describe("path-like skill refs", () => {
276277
describe("description caps (CL-8854)", () => {
277278
const MAX_DESCRIPTION_CHARS = 160;
278279

279-
test("all five registration descriptions are one line within the cap", () => {
280-
const skillDescriptions = [
280+
test("primary descriptions keep the pointer; worker copies are pointer-free, all one line within the cap", () => {
281+
const primaryDescriptions = [
281282
skillSearchDefinition.description,
282-
workerSkillSearchDefinition.description,
283283
useSkillDefinition.description,
284-
workerUseSkillDefinition.description,
285284
];
286-
for (const description of skillDescriptions) {
285+
for (const description of primaryDescriptions) {
287286
expect(description).not.toContain("\n");
288287
expect(description.length).toBeLessThanOrEqual(MAX_DESCRIPTION_CHARS);
289288
expect(description.endsWith("See Guidelines: Tool choice.")).toBe(true);
290289
}
290+
// The lean worker contract ships no guidelines, so the worker copies must
291+
// not point at them.
292+
const workerDescriptions = [
293+
workerSkillSearchDefinition.description,
294+
workerUseSkillDefinition.description,
295+
];
296+
for (const description of workerDescriptions) {
297+
expect(description).not.toContain("\n");
298+
expect(description.length).toBeLessThanOrEqual(MAX_DESCRIPTION_CHARS);
299+
expect(description).not.toContain("See Guidelines:");
300+
}
291301
expect(spawnAgentToolDefinition.description).not.toContain("\n");
292302
expect(spawnAgentToolDefinition.description.length).toBeLessThanOrEqual(
293303
MAX_DESCRIPTION_CHARS,
@@ -316,4 +326,25 @@ describe("description caps (CL-8854)", () => {
316326
);
317327
expect(out).toBe("- scribe: write docs");
318328
});
329+
330+
test("a multi-line SKILL.md frontmatter description renders as a single slash-picker line", async () => {
331+
const plugin = await mkdtemp(join(tmpdir(), "skill-slash-multiline-"));
332+
try {
333+
await mkdir(join(plugin, "skills", "scribe"), { recursive: true });
334+
await writeFile(
335+
join(plugin, "skills", "scribe", "SKILL.md"),
336+
"---\nname: scribe\ndescription: |\n write docs\n second line\n---\nScribe body.\n",
337+
"utf8",
338+
);
339+
const cmds = defined(await loadSkillCommands(plugin), "skill commands");
340+
const scribe = defined(
341+
cmds.find((c) => c.name === "scribe"),
342+
"scribe command",
343+
);
344+
expect(scribe.description).toBe("write docs");
345+
expect(scribe.description).not.toContain("\n");
346+
} finally {
347+
await rm(plugin, { recursive: true, force: true });
348+
}
349+
});
319350
});

0 commit comments

Comments
 (0)