mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 15:16:35 +03:00
fix: normalize quoted user-invocable frontmatter (#87)
* fix: normalize quoted user-invokable frontmatter Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: preserve quoted non-boolean frontmatter values Only normalize quoted booleans for the user-invocable frontmatter flag so other quoted fields like argument-hint and description continue to round-trip as plain strings. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+11
-5
@@ -101,13 +101,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 {
|
||||
|
||||
+32
-2
@@ -89,7 +89,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'
|
||||
@@ -98,10 +98,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
|
||||
@@ -393,6 +404,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');
|
||||
|
||||
Reference in New Issue
Block a user