mirror of
https://github.com/addyosmani/agent-skills.git
synced 2026-10-26 15:05:04 +00:00
Merge #676: read top-level command descriptions with the shared parser
This commit is contained in:
2 files changed
+36
-16
No files matched your search
@@ -214,3 +214,33 @@ test('the TOML directories are not put through the YAML rules', () => {
|
|||||||
assert.equal(result.status, 0, result.stdout);
|
assert.equal(result.status, 0, result.stdout);
|
||||||
assert.doesNotMatch(result.stdout, /\.gemini.*unquoted value/);
|
assert.doesNotMatch(result.stdout, /\.gemini.*unquoted value/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('nested metadata descriptions cannot hide command description drift', () => {
|
||||||
|
const root = makeSandbox();
|
||||||
|
writeClaudeCommand(root, 'review', 'metadata:\n description: Review a change\ndescription: Inspect a change');
|
||||||
|
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||||
|
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||||
|
const result = run(root);
|
||||||
|
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||||
|
assert.match(result.stdout, /Inspect a change/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('matching top-level descriptions pass despite different nested metadata', () => {
|
||||||
|
const root = makeSandbox();
|
||||||
|
writeClaudeCommand(root, 'review', 'metadata:\n description: Internal metadata\ndescription: Review a change');
|
||||||
|
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||||
|
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||||
|
const result = run(root);
|
||||||
|
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a nested description does not supply a missing command description', () => {
|
||||||
|
const root = makeSandbox();
|
||||||
|
writeClaudeCommand(root, 'review', 'metadata:\n description: Review a change');
|
||||||
|
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||||
|
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||||
|
const result = run(root);
|
||||||
|
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||||
|
assert.match(result.stdout, /missing or malformed description/);
|
||||||
|
});
|
||||||
@@ -25,12 +25,11 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
// The same frontmatter-validity rules validate-skills applies to SKILL.md.
|
// The same frontmatter-validity rules validate-skills applies to SKILL.md.
|
||||||
// `descriptionFromMd` below splits each line on its first colon, exactly as the
|
// Description extraction shares the top-level frontmatter reader with skills.
|
||||||
// skill reader used to, so a command whose frontmatter is not valid YAML passes
|
// YAML validity is checked separately because Claude Code parses frontmatter
|
||||||
// every check here — and Claude Code parses that frontmatter when the command is
|
// when the command is loaded. The #494 thread verified all 25 SKILL.md files
|
||||||
// loaded. The #494 thread verified all 25 SKILL.md files AND the command files by
|
// and the command files by hand; this makes the second half a check too.
|
||||||
// hand; this makes the second half a check too.
|
const { frontmatterYamlErrors, parseFrontmatter } = require(path.join(__dirname, 'lib', 'skill-lint.js'));
|
||||||
const { frontmatterYamlErrors } = require(path.join(__dirname, 'lib', 'skill-lint.js'));
|
|
||||||
|
|
||||||
// ─── Config ───────────────────────────────────────────────────────────────────
|
// ─── Config ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -55,16 +54,7 @@ const NAME_MAP_REVERSE = Object.fromEntries(
|
|||||||
|
|
||||||
function descriptionFromMd(filePath) {
|
function descriptionFromMd(filePath) {
|
||||||
const content = fs.readFileSync(filePath, 'utf8');
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
const match = content.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n/);
|
return parseFrontmatter(content)?.description ?? null;
|
||||||
if (!match) return null;
|
|
||||||
for (const line of match[1].split(/\r?\n/)) {
|
|
||||||
const colonIdx = line.indexOf(':');
|
|
||||||
if (colonIdx === -1) continue;
|
|
||||||
if (line.slice(0, colonIdx).trim() === 'description') {
|
|
||||||
return line.slice(colonIdx + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function descriptionFromToml(filePath) {
|
function descriptionFromToml(filePath) {
|
||||||
|
|||||||
Reference in new issue
Block a user