* fix(#2808): SKILL.md name uses hyphen form for Claude Code autocomplete skillFrontmatterName() was converting gsd-<cmd> to gsd:<cmd> (colon) so installed SKILL.md files had name: gsd:add-phase etc. Claude Code surfaces this name in autocomplete, showing the deprecated colon form to users even though the hyphen form is canonical everywhere else. Root cause: the colon form was needed because workflows called Skill(skill="gsd:<cmd>"). All 4 remaining colon-form Skill() calls in autonomous.md and execute-phase.md are updated to hyphen form. skillFrontmatterName() now returns the hyphen dir name unchanged. Updated 4 existing tests that asserted colon form. Regression test: tests/bug-2808-skill-hyphen-name.test.cjs * fix(#2808): address CodeRabbit — bash/text fences, structured test assertions, fail-loud on errors
This commit is contained in:
@@ -1123,21 +1123,21 @@ function convertClaudeCommandToCopilotSkill(content, skillName, isGlobal = false
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a skill directory name (gsd-<cmd>) to the frontmatter `name:` used
|
* Map a skill directory name (gsd-<cmd>) to the frontmatter `name:` used
|
||||||
* by Claude Code as the skill identity. Workflows emit `Skill(skill="gsd:<cmd>")`
|
* by Claude Code as the skill identity. Emits the hyphen form (gsd-<cmd>)
|
||||||
* (colon form) and Claude Code resolves skills by frontmatter `name:`, not
|
* so Claude Code autocomplete shows the canonical invocation form, not the
|
||||||
* directory name — so emit colon form here. Directory stays hyphenated for
|
* deprecated colon form. See #2808.
|
||||||
* Windows path safety. See #2643.
|
*
|
||||||
|
* Historical note: this previously returned `gsd:<cmd>` (colon) because
|
||||||
|
* workflows called Skill(skill="gsd:<cmd>"). Those calls have been updated
|
||||||
|
* to use hyphen form (#2808) so the colon rewrite is no longer needed.
|
||||||
*
|
*
|
||||||
* Codex must NOT use this helper: its adapter invokes skills as `$gsd-<cmd>`
|
* Codex must NOT use this helper: its adapter invokes skills as `$gsd-<cmd>`
|
||||||
* (shell-var syntax) and a colon would terminate the variable name. Codex
|
* (shell-var syntax) — hyphen form is already correct there.
|
||||||
* keeps the hyphen form via `yamlQuote(skillName)` directly.
|
|
||||||
*/
|
*/
|
||||||
function skillFrontmatterName(skillDirName) {
|
function skillFrontmatterName(skillDirName) {
|
||||||
if (typeof skillDirName !== 'string') return skillDirName;
|
if (typeof skillDirName !== 'string') return skillDirName;
|
||||||
// Idempotent on already-colon form.
|
// Return the hyphen form as-is (gsd-<cmd>) — canonical since #2808.
|
||||||
if (skillDirName.includes(':')) return skillDirName;
|
return skillDirName;
|
||||||
// Only rewrite the first hyphen after the `gsd` prefix.
|
|
||||||
return skillDirName.replace(/^gsd-/, 'gsd:');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ The discuss step in `--auto` mode MUST NOT loop. If CONTEXT.md already exists af
|
|||||||
**If `INTERACTIVE` is set:** Run the standard discuss-phase skill inline (asks interactive questions, waits for user answers). This preserves user input on all design decisions while keeping plan+execute out of the main context:
|
**If `INTERACTIVE` is set:** Run the standard discuss-phase skill inline (asks interactive questions, waits for user answers). This preserves user input on all design decisions while keeping plan+execute out of the main context:
|
||||||
|
|
||||||
```
|
```
|
||||||
Skill(skill="gsd:discuss-phase", args="${PHASE_NUM}")
|
Skill(skill="gsd-discuss-phase", args="${PHASE_NUM}")
|
||||||
```
|
```
|
||||||
|
|
||||||
**If `INTERACTIVE` is NOT set:** Execute the smart_discuss step for this phase (batch table proposals, auto-optimized).
|
**If `INTERACTIVE` is NOT set:** Execute the smart_discuss step for this phase (batch table proposals, auto-optimized).
|
||||||
@@ -367,12 +367,12 @@ CODE_REVIEW_ENABLED=$(gsd-sdk query config-get workflow.code_review 2>/dev/null
|
|||||||
If `"false"`: display "Code review skipped (workflow.code_review=false)" and proceed to 3d.
|
If `"false"`: display "Code review skipped (workflow.code_review=false)" and proceed to 3d.
|
||||||
|
|
||||||
```
|
```
|
||||||
Skill(skill="gsd:code-review", args="${PHASE_NUM}")
|
Skill(skill="gsd-code-review", args="${PHASE_NUM}")
|
||||||
```
|
```
|
||||||
|
|
||||||
Parse status from REVIEW.md frontmatter. If "clean" or "skipped": proceed to 3d. If findings found: auto-invoke:
|
Parse status from REVIEW.md frontmatter. If "clean" or "skipped": proceed to 3d. If findings found: auto-invoke:
|
||||||
```
|
```text
|
||||||
Skill(skill="gsd:code-review-fix", args="${PHASE_NUM} --auto")
|
Skill(skill="gsd-code-review-fix", args="${PHASE_NUM} --auto")
|
||||||
```
|
```
|
||||||
|
|
||||||
**Error handling:** If either Skill fails, catch the error, display as non-blocking, and proceed to 3d.
|
**Error handling:** If either Skill fails, catch the error, display as non-blocking, and proceed to 3d.
|
||||||
|
|||||||
@@ -1095,7 +1095,7 @@ If `CODE_REVIEW_ENABLED` is `"false"`: display "Code review skipped (workflow.co
|
|||||||
|
|
||||||
**Invoke review:**
|
**Invoke review:**
|
||||||
```
|
```
|
||||||
Skill(skill="gsd:code-review", args="${PHASE_NUMBER}")
|
Skill(skill="gsd-code-review", args="${PHASE_NUMBER}")
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check results using deterministic path (not glob):**
|
**Check results using deterministic path (not glob):**
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
process.env.GSD_TEST_MODE = '1';
|
process.env.GSD_TEST_MODE = '1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bug #2643: workflows emit Skill(skill="gsd:<cmd>") but flat-skills install
|
* Bug #2643 / #2808: skill frontmatter name parity.
|
||||||
* registers `gsd-<cmd>` as the frontmatter `name:`. Claude Code uses the
|
|
||||||
* frontmatter name (not dir name) as the skill identity — so the emitted
|
|
||||||
* `name:` must match the colon form used by workflow Skill() calls.
|
|
||||||
*
|
*
|
||||||
* The directory name stays hyphenated for Windows path safety.
|
* Original (#2643): workflows emitted Skill(skill="gsd:<cmd>") and the
|
||||||
|
* installer registered colon form in SKILL.md name: to match.
|
||||||
|
*
|
||||||
|
* Updated (#2808): workflows now use Skill(skill="gsd-<cmd>") (hyphen),
|
||||||
|
* and the installer emits name: gsd-<cmd> (hyphen). Claude Code autocomplete
|
||||||
|
* now shows the canonical hyphen form instead of the deprecated colon form.
|
||||||
|
* The directory name (gsd-<cmd>) is unchanged.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, describe } = require('node:test');
|
const { test, describe } = require('node:test');
|
||||||
@@ -37,7 +40,15 @@ function collectFiles(dir, results) {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractSkillNames(content) {
|
function extractSkillNamesHyphen(content) {
|
||||||
|
const names = new Set();
|
||||||
|
const rx = /Skill\(skill=['"]gsd-([a-z0-9-]+)['"]/gi;
|
||||||
|
let m;
|
||||||
|
while ((m = rx.exec(content)) !== null) names.add('gsd-' + m[1]);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractSkillNamesColon(content) {
|
||||||
const names = new Set();
|
const names = new Set();
|
||||||
const rx = /Skill\(skill=['"]gsd:([a-z0-9-]+)['"]/gi;
|
const rx = /Skill\(skill=['"]gsd:([a-z0-9-]+)['"]/gi;
|
||||||
let m;
|
let m;
|
||||||
@@ -45,28 +56,58 @@ function extractSkillNames(content) {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('skill frontmatter name parity (#2643)', () => {
|
describe('skill frontmatter name parity (#2643 / #2808)', () => {
|
||||||
test('skillFrontmatterName helper emits colon form', () => {
|
test('skillFrontmatterName helper emits hyphen form (#2808)', () => {
|
||||||
assert.strictEqual(typeof skillFrontmatterName, 'function');
|
assert.strictEqual(typeof skillFrontmatterName, 'function');
|
||||||
assert.strictEqual(skillFrontmatterName('gsd-execute-phase'), 'gsd:execute-phase');
|
assert.strictEqual(skillFrontmatterName('gsd-execute-phase'), 'gsd-execute-phase');
|
||||||
assert.strictEqual(skillFrontmatterName('gsd-plan-phase'), 'gsd:plan-phase');
|
assert.strictEqual(skillFrontmatterName('gsd-plan-phase'), 'gsd-plan-phase');
|
||||||
assert.strictEqual(skillFrontmatterName('gsd:next'), 'gsd:next');
|
assert.strictEqual(skillFrontmatterName('gsd-next'), 'gsd-next');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('convertClaudeCommandToClaudeSkill emits name: gsd:<cmd>', () => {
|
test('convertClaudeCommandToClaudeSkill emits name: gsd-<cmd> (hyphen)', () => {
|
||||||
const input = '---\nname: old\ndescription: test\n---\n\nBody.';
|
const input = '---\nname: old\ndescription: test\n---\n\nBody.';
|
||||||
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-execute-phase');
|
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-execute-phase');
|
||||||
assert.match(result, /^---\nname: gsd:execute-phase\n/);
|
// Parse the frontmatter block structurally: extract the name: field value.
|
||||||
|
const frontmatterMatch = result.match(/^---\n([\s\S]*?)\n---/);
|
||||||
|
assert.ok(frontmatterMatch, 'output must have a frontmatter block delimited by ---');
|
||||||
|
const frontmatterLines = frontmatterMatch[1].split('\n');
|
||||||
|
const nameEntry = frontmatterLines.find((l) => l.startsWith('name:'));
|
||||||
|
assert.ok(nameEntry, 'frontmatter must contain a name: field');
|
||||||
|
const nameValue = nameEntry.replace(/^name:\s*/, '').trim();
|
||||||
|
assert.strictEqual(
|
||||||
|
nameValue,
|
||||||
|
'gsd-execute-phase',
|
||||||
|
`frontmatter name: must be 'gsd-execute-phase' (hyphen form), got '${nameValue}'`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('every workflow Skill(skill="gsd:<cmd>") resolves to an emitted skill name', () => {
|
test('no workflow uses deprecated Skill(skill="gsd:<cmd>") colon form', () => {
|
||||||
|
const workflowFiles = collectFiles(WORKFLOWS_DIR);
|
||||||
|
const colonRefs = [];
|
||||||
|
for (const f of workflowFiles) {
|
||||||
|
const src = fs.readFileSync(f, 'utf-8');
|
||||||
|
for (const n of extractSkillNamesColon(src)) {
|
||||||
|
colonRefs.push(path.basename(f) + ': ' + n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
colonRefs,
|
||||||
|
[],
|
||||||
|
'deprecated colon-form Skill() calls found (update to hyphen): ' + colonRefs.join(', ')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('every workflow Skill(skill="gsd-<cmd>") resolves to an emitted skill name', () => {
|
||||||
const workflowFiles = collectFiles(WORKFLOWS_DIR);
|
const workflowFiles = collectFiles(WORKFLOWS_DIR);
|
||||||
const referenced = new Set();
|
const referenced = new Set();
|
||||||
for (const f of workflowFiles) {
|
for (const f of workflowFiles) {
|
||||||
const src = fs.readFileSync(f, 'utf-8');
|
const src = fs.readFileSync(f, 'utf-8');
|
||||||
for (const n of extractSkillNames(src)) referenced.add(n);
|
for (const n of extractSkillNamesHyphen(src)) referenced.add(n);
|
||||||
}
|
}
|
||||||
assert.ok(referenced.size > 0, 'expected at least one Skill(skill="gsd:<cmd>") reference');
|
assert.ok(
|
||||||
|
referenced.size > 0,
|
||||||
|
`expected at least one Skill(skill="gsd-<cmd>") reference in workflows under ${WORKFLOWS_DIR}`
|
||||||
|
);
|
||||||
|
|
||||||
const emitted = new Set();
|
const emitted = new Set();
|
||||||
const cmdFiles = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
const cmdFiles = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
||||||
|
|||||||
116
tests/bug-2808-skill-hyphen-name.test.cjs
Normal file
116
tests/bug-2808-skill-hyphen-name.test.cjs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Regression test for bug #2808
|
||||||
|
*
|
||||||
|
* All 85 GSD SKILL.md files declared `name: gsd:<cmd>` (colon), the deprecated
|
||||||
|
* form. Claude Code surfaces the `name:` frontmatter field in autocomplete, so
|
||||||
|
* users saw `/gsd:add-phase` suggestions instead of the canonical `/gsd-add-phase`.
|
||||||
|
*
|
||||||
|
* Root cause: skillFrontmatterName() in bin/install.js converted hyphenated
|
||||||
|
* skill dir names to colon form (gsd-add-phase → gsd:add-phase) because
|
||||||
|
* workflows called Skill(skill="gsd:<cmd>"). That was the original fix for
|
||||||
|
* #2643. Since then, workflows have been updated to use hyphen form (#2808).
|
||||||
|
*
|
||||||
|
* Fix: skillFrontmatterName() now returns the hyphen form unchanged.
|
||||||
|
* Four workflow Skill() colon calls updated to hyphen.
|
||||||
|
*
|
||||||
|
* This test verifies:
|
||||||
|
* 1. skillFrontmatterName returns hyphen form (not colon).
|
||||||
|
* 2. Installed SKILL.md would emit name: gsd-<cmd> (not gsd:<cmd>).
|
||||||
|
* 3. No workflow contains a Skill(skill="gsd:<cmd>") colon call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
process.env.GSD_TEST_MODE = '1';
|
||||||
|
|
||||||
|
const { describe, test } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const { convertClaudeCommandToClaudeSkill, skillFrontmatterName } =
|
||||||
|
require(path.join(ROOT, 'bin', 'install.js'));
|
||||||
|
|
||||||
|
const WORKFLOWS_DIR = path.join(ROOT, 'get-shit-done', 'workflows');
|
||||||
|
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
|
||||||
|
|
||||||
|
function walkMd(dir) {
|
||||||
|
const files = [];
|
||||||
|
try {
|
||||||
|
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = path.join(dir, e.name);
|
||||||
|
if (e.isDirectory()) files.push(...walkMd(full));
|
||||||
|
else if (e.name.endsWith('.md')) files.push(full);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
assert.fail(`failed to read markdown files from ${dir}: ${err.message}`);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('bug-2808: SKILL.md name: uses hyphen form', () => {
|
||||||
|
test('skillFrontmatterName returns hyphen form (not colon)', () => {
|
||||||
|
assert.strictEqual(skillFrontmatterName('gsd-add-phase'), 'gsd-add-phase');
|
||||||
|
assert.strictEqual(skillFrontmatterName('gsd-plan-phase'), 'gsd-plan-phase');
|
||||||
|
assert.strictEqual(skillFrontmatterName('gsd-autonomous'), 'gsd-autonomous');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generated SKILL.md contains name: gsd-<cmd> (not gsd:<cmd>)', () => {
|
||||||
|
const cmdFiles = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
||||||
|
assert.ok(cmdFiles.length > 0, 'expected GSD command files');
|
||||||
|
|
||||||
|
for (const cmd of cmdFiles) {
|
||||||
|
const base = cmd.replace(/\.md$/, '');
|
||||||
|
const skillDirName = 'gsd-' + base;
|
||||||
|
const src = fs.readFileSync(path.join(COMMANDS_DIR, cmd), 'utf-8');
|
||||||
|
const skillContent = convertClaudeCommandToClaudeSkill(src, skillDirName);
|
||||||
|
|
||||||
|
// Parse frontmatter structurally: extract name: line from the --- block.
|
||||||
|
const fmMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
||||||
|
assert.ok(fmMatch, `${cmd}: generated skill content must have a frontmatter block`);
|
||||||
|
const fmLines = fmMatch[1].split('\n');
|
||||||
|
const nameEntry = fmLines.find((l) => l.startsWith('name:'));
|
||||||
|
assert.ok(nameEntry, `${cmd}: generated SKILL.md is missing required name: field`);
|
||||||
|
|
||||||
|
const name = nameEntry.replace(/^name:\s*/, '').trim();
|
||||||
|
assert.ok(
|
||||||
|
!name.includes(':'),
|
||||||
|
`${cmd}: SKILL.md name should be hyphen form, got "${name}"`
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
name.startsWith('gsd-'),
|
||||||
|
`${cmd}: SKILL.md name should start with gsd-, got "${name}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no workflow contains Skill(skill="gsd:<cmd>") colon form', () => {
|
||||||
|
const workflowFiles = walkMd(WORKFLOWS_DIR);
|
||||||
|
assert.ok(
|
||||||
|
workflowFiles.length > 0,
|
||||||
|
`expected workflow markdown files under ${WORKFLOWS_DIR}`
|
||||||
|
);
|
||||||
|
const colonCalls = [];
|
||||||
|
for (const f of workflowFiles) {
|
||||||
|
const src = fs.readFileSync(f, 'utf-8');
|
||||||
|
// Strip HTML comments to avoid matching commented-out examples.
|
||||||
|
const stripped = src.replace(/<!--[\s\S]*?-->/g, '');
|
||||||
|
// Scan each line for Skill() calls using the colon form.
|
||||||
|
// Parsing line-by-line is more precise than a multi-line regex
|
||||||
|
// and avoids false positives from incidental matches in prose.
|
||||||
|
for (const line of stripped.split('\n')) {
|
||||||
|
const colonCallRe = /Skill\(skill=['"]gsd:([a-z0-9-]+)['"]/gi;
|
||||||
|
let m;
|
||||||
|
while ((m = colonCallRe.exec(line)) !== null) {
|
||||||
|
colonCalls.push(`${path.basename(f)}: Skill(skill="gsd:${m[1]}")`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
colonCalls,
|
||||||
|
[],
|
||||||
|
'deprecated colon-form Skill() calls found — update to gsd-<cmd>: ' + colonCalls.join(', ')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -69,7 +69,7 @@ describe('convertClaudeCommandToClaudeSkill', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('emits colon-form name (gsd:<cmd>) from hyphen-form dir (#2643)', () => {
|
test('emits hyphen-form name (gsd-<cmd>) from hyphen-form dir (#2808)', () => {
|
||||||
const input = [
|
const input = [
|
||||||
'---',
|
'---',
|
||||||
'name: gsd:next',
|
'name: gsd:next',
|
||||||
@@ -80,9 +80,9 @@ describe('convertClaudeCommandToClaudeSkill', () => {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
// Directory name is gsd-next (hyphen, Windows-safe), frontmatter name is
|
// Directory name is gsd-next (hyphen, Windows-safe), frontmatter name is
|
||||||
// gsd:next (colon) so Claude Code resolves `/gsd:next` against the skill.
|
// gsd-next (hyphen, #2808) so Claude Code autocomplete shows canonical form.
|
||||||
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
|
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
|
||||||
assert.ok(result.includes('name: gsd:next'), 'frontmatter name uses colon form');
|
assert.ok(result.includes('name: gsd-next'), 'frontmatter name uses hyphen form (#2808)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('preserves body content unchanged', () => {
|
test('preserves body content unchanged', () => {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe('Qwen Code: convertClaudeCommandToClaudeSkill', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('emits colon-form name (gsd:<cmd>) from hyphen-form dir (#2643)', () => {
|
test('emits hyphen-form name (gsd-<cmd>) from hyphen-form dir (#2808)', () => {
|
||||||
const input = [
|
const input = [
|
||||||
'---',
|
'---',
|
||||||
'name: gsd:next',
|
'name: gsd:next',
|
||||||
@@ -77,9 +77,9 @@ describe('Qwen Code: convertClaudeCommandToClaudeSkill', () => {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
// Directory name is gsd-next (hyphen, Windows-safe), frontmatter name is
|
// Directory name is gsd-next (hyphen, Windows-safe), frontmatter name is
|
||||||
// gsd:next (colon) so Claude Code resolves `/gsd:next` against the skill.
|
// gsd-next (hyphen, #2808 — canonical invocation form for Claude Code autocomplete).
|
||||||
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
|
const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
|
||||||
assert.ok(result.includes('name: gsd:next'), 'frontmatter name uses colon form');
|
assert.ok(result.includes('name: gsd-next'), 'frontmatter name uses hyphen form (#2808)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('preserves body content unchanged', () => {
|
test('preserves body content unchanged', () => {
|
||||||
@@ -154,7 +154,7 @@ describe('Qwen Code: copyCommandsAsClaudeSkills', () => {
|
|||||||
|
|
||||||
// Verify content
|
// Verify content
|
||||||
const content = fs.readFileSync(skillPath, 'utf8');
|
const content = fs.readFileSync(skillPath, 'utf8');
|
||||||
assert.ok(content.includes('name: gsd:quick'), 'frontmatter name uses colon form (#2643)');
|
assert.ok(content.includes('name: gsd-quick'), 'frontmatter name uses hyphen form (#2808)');
|
||||||
assert.ok(content.includes('description:'), 'description present');
|
assert.ok(content.includes('description:'), 'description present');
|
||||||
assert.ok(content.includes('allowed-tools:'), 'allowed-tools preserved');
|
assert.ok(content.includes('allowed-tools:'), 'allowed-tools preserved');
|
||||||
assert.ok(content.includes('<objective>'), 'body content preserved');
|
assert.ok(content.includes('<objective>'), 'body content preserved');
|
||||||
@@ -274,7 +274,7 @@ describe('Qwen Code: SKILL.md format validation', () => {
|
|||||||
assert.ok(fmMatch, 'has frontmatter block');
|
assert.ok(fmMatch, 'has frontmatter block');
|
||||||
|
|
||||||
const fmLines = fmMatch[1].split('\n');
|
const fmLines = fmMatch[1].split('\n');
|
||||||
const hasName = fmLines.some(l => l.startsWith('name: gsd:review'));
|
const hasName = fmLines.some(l => l.startsWith('name: gsd-review'));
|
||||||
const hasDesc = fmLines.some(l => l.startsWith('description:'));
|
const hasDesc = fmLines.some(l => l.startsWith('description:'));
|
||||||
const hasAgent = fmLines.some(l => l.startsWith('agent:'));
|
const hasAgent = fmLines.some(l => l.startsWith('agent:'));
|
||||||
const hasTools = fmLines.some(l => l.startsWith('allowed-tools:'));
|
const hasTools = fmLines.some(l => l.startsWith('allowed-tools:'));
|
||||||
|
|||||||
Reference in New Issue
Block a user