* feat(#1243): consume Claude plugin-provided skills via native Skill-tool directive + grant Skill to agent_skills-consumer agents - Relax global skill name validation to accept namespaced form `^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)*$` - Namespaced names (containing colon) on claude runtime emit a Skill-tool load directive instead of a @-include line - Namespaced names on non-claude runtimes are skipped with a warning - Bare unresolved names retain existing warn-and-skip behavior (no promotion to directive) - Grant `Skill` tool to all 22 agent_skills consumer agents; 5 generated agents updated via research-profiles.cjs + regen, 17 hand-authored agents edited directly - Add 16 TDD tests in describe('bug #1243') covering happy/mixed/precedence/negative/cross-runtime/regression/grant cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(#1243): document plugin-provided skills in agent_skills Update the Agent Skills Injection reference in CONFIGURATION.md with the three entry forms (project-relative, global:<name>, global:<plugin>:<skill>), the Claude-only runtime behaviour of the namespaced form and the warn-skip on other runtimes, the plugin pre-install prerequisite, and the consumer-agent Skill tool grant. Add docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md with a step-by-step guide for installing the plugin, locating the namespaced skill name, wiring it into agent_skills, and verifying injection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#1243): align agent_skills docs with emitted block format + mixed-block regression test (code-review) - Replace two-section mixed-block example (bogus "Load these plugin-provided skills using the Skill tool:" header) with the actual single-section inline format in CONFIGURATION.md and docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md - Fix quoted warning text in how-to doc to exactly match the emitted string: [agent-skills] WARNING: Plugin-namespaced skill "global:<name>" requires a Skill-tool-capable runtime (claude) — skipping on runtime "<runtime>" - Replace phantom agent slugs (gsd-checker, gsd-researcher, gsd-advisor, gsd-synthesizer) in CONFIGURATION.md Supported Agent Types with real agents/gsd-*.md examples (gsd-plan-checker, gsd-phase-researcher, gsd-code-reviewer, gsd-ui-auditor, gsd-research-synthesizer) - Add byte-identical mixed-block regression test: one path-resolvable global skill + one plugin-namespaced skill on claude runtime → asserts r.ir.block === single-section interleaved block, no secondary header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(#1243): regenerate agent-size baseline for the Skill-tool grant The 22 agent_skills-consumer agents each grew +7 bytes from adding `Skill` to their tools list; refresh the committed per-agent size baseline (#1074 guard). * chore(#1243): add Added changeset fragment * fix(#1243): traceable allow-test-rule ref + separator-agnostic byte-identical tests (CI) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
35
src/init.cts
35
src/init.cts
@@ -1882,7 +1882,9 @@ function buildAgentSkillsBlock(
|
||||
// occurs when the caller has actually set trusted_global_roots.
|
||||
const trustedGlobalRoots = loadTrustedGlobalRoots(config);
|
||||
|
||||
const validPaths: { ref: string; display: string }[] = [];
|
||||
// Each entry is either a filesystem include ({ kind: 'include', ref, display }) or a
|
||||
// Skill-tool directive ({ kind: 'directive', name }) for plugin-provided namespaced skills.
|
||||
const validEntries: Array<{ kind: 'include'; ref: string; display: string } | { kind: 'directive'; name: string }> = [];
|
||||
for (const skillPath of skillPaths) {
|
||||
if (typeof skillPath !== 'string') continue;
|
||||
|
||||
@@ -1894,12 +1896,28 @@ function buildAgentSkillsBlock(
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(skillName)) {
|
||||
// Accept: one or more [A-Za-z0-9_-]+ segments joined by single colons.
|
||||
// Rejects: empty segments (::), leading/trailing colon, dots, slashes, backslashes.
|
||||
if (!/^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)*$/.test(skillName)) {
|
||||
process.stderr.write(
|
||||
`[agent-skills] WARNING: Invalid global skill name "${skillName}" — skipping\n`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const isNamespaced = skillName.includes(':');
|
||||
if (isNamespaced) {
|
||||
// Plugin-provided namespaced skill: no filesystem path exists locally.
|
||||
if (runtime === 'claude') {
|
||||
// Emit a natural-language Skill-tool directive (not a @-include).
|
||||
validEntries.push({ kind: 'directive', name: skillName });
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`[agent-skills] WARNING: Plugin-namespaced skill "global:${skillName}" requires a Skill-tool-capable runtime (claude) — skipping on runtime "${runtime}"\n`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Non-namespaced bare name: attempt filesystem resolution as before.
|
||||
if (globalSkillsBase === null) {
|
||||
process.stderr.write(
|
||||
`[agent-skills] WARNING: Runtime "${runtime}" does not use a skills directory — "global:${skillName}" is not supported on this runtime\n`,
|
||||
@@ -1929,7 +1947,7 @@ function buildAgentSkillsBlock(
|
||||
}
|
||||
process.stderr.write(`[agent-skills] NOTE: Global skill "${skillName}" accepted via trusted_global_roots (resolves outside the default skills dir)\n`);
|
||||
}
|
||||
validPaths.push({ ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
||||
validEntries.push({ kind: 'include', ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1949,12 +1967,17 @@ function buildAgentSkillsBlock(
|
||||
continue;
|
||||
}
|
||||
|
||||
validPaths.push({ ref: `${skillPath}/SKILL.md`, display: skillPath });
|
||||
validEntries.push({ kind: 'include', ref: `${skillPath}/SKILL.md`, display: skillPath });
|
||||
}
|
||||
|
||||
if (validPaths.length === 0) return '';
|
||||
if (validEntries.length === 0) return '';
|
||||
|
||||
const lines = validPaths.map((p) => `- @${p.ref}`).join('\n');
|
||||
const lines = validEntries.map((entry) => {
|
||||
if (entry.kind === 'directive') {
|
||||
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
||||
}
|
||||
return `- @${entry.ref}`;
|
||||
}).join('\n');
|
||||
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user