Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions scripts/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,19 @@ export function parseFrontmatter(content) {
if (colonIndex > 0) {
const key = trimmed.slice(0, colonIndex).trim();
const value = trimmed.slice(colonIndex + 1).trim();
const isQuoted = /^(".*"|'.*')$/.test(value);
const unquotedValue = isQuoted ? value.slice(1, -1) : value;
const shouldCoerceBoolean =
key === 'user-invocable' || key === 'user-invokable' || !isQuoted;

if (value) {
// Strip YAML quotes
const unquoted = (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))
? value.slice(1, -1)
: value;
frontmatter[key] = unquoted === 'true' ? true : unquoted === 'false' ? false : unquoted;
frontmatter[key] = shouldCoerceBoolean
? unquotedValue === 'true'
? true
: unquotedValue === 'false'
? false
: unquotedValue
: unquotedValue;
currentKey = key;
currentArray = null;
} else {
Expand Down
34 changes: 32 additions & 2 deletions tests/lib/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Body.`;
expect(result.frontmatter['user-invocable']).toBe(true);
});

test('should parse user-invocable as string true (code behavior)', () => {
test('should parse quoted user-invocable boolean as true', () => {
const content = `---
name: test-skill
user-invocable: 'true'
Expand All @@ -99,10 +99,21 @@ user-invocable: 'true'
Body.`;

const result = parseFrontmatter(content);
// parseFrontmatter strips YAML quotes, so 'true' becomes boolean true
expect(result.frontmatter['user-invocable']).toBe(true);
});

test('should keep quoted non-user-invocable booleans as plain strings', () => {
const content = `---
name: test-skill
description: 'true'
---

Body.`;

const result = parseFrontmatter(content);
expect(result.frontmatter.description).toBe('true');
});

test('should parse allowed-tools field', () => {
const content = `---
name: test-skill
Expand Down Expand Up @@ -358,6 +369,25 @@ description: Run technical quality checks
user-invocable: true
---

Audit the code.`;

const skillDir = path.join(testRootDir, 'source/skills/audit');
ensureDir(skillDir);
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillContent);

const { skills } = readSourceFiles(testRootDir);

expect(skills).toHaveLength(1);
expect(skills[0].userInvocable).toBe(true);
});

test('should read skill with quoted user-invocable flag', () => {
const skillContent = `---
name: audit
description: Run technical quality checks
user-invocable: 'true'
---

Audit the code.`;

const skillDir = path.join(testRootDir, 'source/skills/audit');
Expand Down
Loading