From 9b4017a95feb5e66a95f0dca6c5379bd2e5018a8 Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:08:57 -0700 Subject: [PATCH 1/6] fix(validate): reject unquoted frontmatter values containing ': ' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reworks #30 without the yaml dependency (which broke the bundled dist with a CJS interop error). The hand-rolled frontmatter parser now flags top-level unquoted values containing ': ' — invalid YAML that the opencode runtime parser silently drops — with the offending key, line number, and a quoting hint. Quoted values, block scalars, continuation lines, and list items are unaffected. Co-authored-by: Michael Ledin Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 +- plugin/dist/skill-creator.js | 21 +++++-- plugin/lib/validate.ts | 23 +++++++- plugin/test/validate.test.ts | 99 +++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 plugin/test/validate.test.ts diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index 0ed93ca..8fb2059 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "6e4c1da97ef75f263cacd5f8e706bd849c99792b84a7a505148fa234de660aaf", - "builtAt": "2026-06-23T08:01:15.963Z" + "sourceHash": "64e8c9dc7347521f3d3c0a3cb4f308cb12cd50ba0c9c1277faad723a6ef44e0d", + "builtAt": "2026-07-05T23:08:30.744Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index 12febec..e9a41f1 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -10746,7 +10746,7 @@ class JSONSchemaGenerator { if (val === undefined) { if (this.unrepresentable === "throw") { throw new Error("Literal `undefined` cannot be represented in JSON Schema"); - } else {} + } } else if (typeof val === "bigint") { if (this.unrepresentable === "throw") { throw new Error("BigInt literals cannot be represented in JSON Schema"); @@ -12352,6 +12352,12 @@ var ALLOWED_PROPERTIES = new Set([ "metadata", "compatibility" ]); +function isQuotedValue(value) { + return value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"); +} +function isBlockScalarMarker(value) { + return /^[|>][+-]?$/.test(value); +} function validateSkill(skillPath) { const skillMdPath = join(skillPath, "SKILL.md"); if (!existsSync(skillMdPath)) { @@ -12370,8 +12376,9 @@ function validateSkill(skillPath) { let currentKey = ""; let currentValue = ""; let inMultiline = false; - for (const line of frontmatterText.split(` -`)) { + const frontmatterLines = frontmatterText.split(` +`); + for (const [index, line] of frontmatterLines.entries()) { if (inMultiline) { if (line.startsWith(" ") || line.startsWith("\t")) { currentValue += " " + line.trim(); @@ -12385,7 +12392,13 @@ function validateSkill(skillPath) { if (kvMatch) { currentKey = kvMatch[1]; const value = kvMatch[2].trim(); - if ([">", "|", ">-", "|-"].includes(value)) { + if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && value.includes(": ")) { + return { + valid: false, + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` + }; + } + if (isBlockScalarMarker(value)) { currentValue = ""; inMultiline = true; } else if (currentKey === "metadata" && (value === "" || value === "{}")) { diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index afa9f1f..b085a97 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -23,6 +23,17 @@ const ALLOWED_PROPERTIES = new Set([ "compatibility", ]) +function isQuotedValue(value: string): boolean { + return ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) +} + +function isBlockScalarMarker(value: string): boolean { + return /^[|>][+-]?$/.test(value) +} + /** * Validate a skill directory. * @@ -56,7 +67,8 @@ export function validateSkill(skillPath: string): ValidationResult { let currentValue = "" let inMultiline = false - for (const line of frontmatterText.split("\n")) { + const frontmatterLines = frontmatterText.split("\n") + for (const [index, line] of frontmatterLines.entries()) { if (inMultiline) { if (line.startsWith(" ") || line.startsWith("\t")) { currentValue += " " + line.trim() @@ -72,7 +84,14 @@ export function validateSkill(skillPath: string): ValidationResult { currentKey = kvMatch[1] const value = kvMatch[2].trim() - if ([">", "|", ">-", "|-"].includes(value)) { + if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && value.includes(": ")) { + return { + valid: false, + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").`, + } + } + + if (isBlockScalarMarker(value)) { currentValue = "" inMultiline = true } else if ( diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts new file mode 100644 index 0000000..b13ebfc --- /dev/null +++ b/plugin/test/validate.test.ts @@ -0,0 +1,99 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs" +import { tmpdir } from "os" +import { join } from "path" + +import { expect, test } from "bun:test" + +import { validateSkill } from "../lib/validate" + +const withSkill = (frontmatter: string, fn: (skillPath: string) => T): T => { + const dir = mkdtempSync(join(tmpdir(), "skill-creator-validate-")) + try { + mkdirSync(dir, { recursive: true }) + writeFileSync(join(dir, "SKILL.md"), `---\n${frontmatter}\n---\n\n# Test Skill\n`) + return fn(dir) + } finally { + rmSync(dir, { recursive: true, force: true }) + } +} + +test("quoted description containing colon-space passes", () => { + withSkill( + `name: pdf-reader +description: "Use for PDF files: reading, extracting."`, + (skillPath) => { + expect(validateSkill(skillPath)).toEqual({ + valid: true, + message: "Skill is valid!", + }) + }, + ) +}) + +test("unquoted description without colon-space passes", () => { + withSkill( + `name: pdf-reader +description: Use for PDF files, reading and extracting.`, + (skillPath) => { + expect(validateSkill(skillPath)).toEqual({ + valid: true, + message: "Skill is valid!", + }) + }, + ) +}) + +test("bundled skill fixture passes validation", () => { + expect(validateSkill(join(import.meta.dir, "..", "skill"))).toEqual({ + valid: true, + message: "Skill is valid!", + }) +}) + +test("unquoted description with colon-space fails with quote hint", () => { + withSkill( + `name: pdf-reader +description: Use for PDF files: reading, extracting.`, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("description") + expect(result.message).toContain("line 2") + expect(result.message).toContain("Hint: quote the value") + expect(result.message).toContain("description: \"your text here\"") + expect(result.message).toContain("unquoted values containing ': ' are invalid YAML") + expect(result.message).toContain("runtime will drop this skill") + }, + ) +}) + +test("unquoted value with colon-space mid-value fails", () => { + withSkill( + `name: pdf-reader +description: Use when reading docs. +compatibility: Works with docs: markdown and PDF`, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("compatibility") + expect(result.message).toContain("line 3") + }, + ) +}) + +test("unquoted name with colon-space fails before name validation", () => { + withSkill( + `name: pdf: reader +description: Use when reading docs.`, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("name") + expect(result.message).toContain("line 1") + expect(result.message).toContain("Hint: quote the value") + }, + ) +}) From a5b69fff7ef9d290a3283b746d6a85d853770a7f Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:14:36 -0700 Subject: [PATCH 2/6] fix(pr-feedback): guard single-char quoted values, add block-scalar and single-quote tests Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 ++-- plugin/dist/skill-creator.js | 2 +- plugin/lib/validate.ts | 5 +++-- plugin/test/validate.test.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index 8fb2059..7a579ac 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "64e8c9dc7347521f3d3c0a3cb4f308cb12cd50ba0c9c1277faad723a6ef44e0d", - "builtAt": "2026-07-05T23:08:30.744Z" + "sourceHash": "720df910610651e9a5e1366d35e69b1676ebde2eaadb3a6428edd43112a878a0", + "builtAt": "2026-07-05T23:14:25.765Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index e9a41f1..a769fc7 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -12353,7 +12353,7 @@ var ALLOWED_PROPERTIES = new Set([ "compatibility" ]); function isQuotedValue(value) { - return value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"); + return value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")); } function isBlockScalarMarker(value) { return /^[|>][+-]?$/.test(value); diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index b085a97..a19ca3b 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -25,8 +25,9 @@ const ALLOWED_PROPERTIES = new Set([ function isQuotedValue(value: string): boolean { return ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) + value.length >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) ) } diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts index b13ebfc..e8c344f 100644 --- a/plugin/test/validate.test.ts +++ b/plugin/test/validate.test.ts @@ -68,6 +68,33 @@ description: Use for PDF files: reading, extracting.`, ) }) +test("single-quoted description containing colon-space passes", () => { + withSkill( + `name: pdf-reader +description: 'Use for PDF files: reading, extracting.'`, + (skillPath) => { + expect(validateSkill(skillPath)).toEqual({ + valid: true, + message: "Skill is valid!", + }) + }, + ) +}) + +test("block scalar content with colon-space passes", () => { + withSkill( + `name: pdf-reader +description: | + Use for PDF files: reading, extracting.`, + (skillPath) => { + expect(validateSkill(skillPath)).toEqual({ + valid: true, + message: "Skill is valid!", + }) + }, + ) +}) + test("unquoted value with colon-space mid-value fails", () => { withSkill( `name: pdf-reader From 3e39568a7166b65c08ab21abfc539aa6fe7fe26e Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:18:04 -0700 Subject: [PATCH 3/6] fix(pr-feedback): support block scalar indentation indicators in marker regex Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 ++-- plugin/dist/skill-creator.js | 2 +- plugin/lib/validate.ts | 3 ++- plugin/test/validate.test.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index 7a579ac..b0bf0c1 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "720df910610651e9a5e1366d35e69b1676ebde2eaadb3a6428edd43112a878a0", - "builtAt": "2026-07-05T23:14:25.765Z" + "sourceHash": "17ed10f1d559d55448d0af96c5fc2f4d70a3a2c67bdf5840d1b8b272da85a4a6", + "builtAt": "2026-07-05T23:17:49.191Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index a769fc7..a2f2ffb 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -12356,7 +12356,7 @@ function isQuotedValue(value) { return value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")); } function isBlockScalarMarker(value) { - return /^[|>][+-]?$/.test(value); + return /^[|>](?:[1-9][+-]?|[+-][1-9]?)?$/.test(value); } function validateSkill(skillPath) { const skillMdPath = join(skillPath, "SKILL.md"); diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index a19ca3b..b4d1008 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -32,7 +32,8 @@ function isQuotedValue(value: string): boolean { } function isBlockScalarMarker(value: string): boolean { - return /^[|>][+-]?$/.test(value) + // header allows indentation (1-9) and chomping (+/-) indicators in either order + return /^[|>](?:[1-9][+-]?|[+-][1-9]?)?$/.test(value) } /** diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts index e8c344f..86f042b 100644 --- a/plugin/test/validate.test.ts +++ b/plugin/test/validate.test.ts @@ -84,7 +84,7 @@ description: 'Use for PDF files: reading, extracting.'`, test("block scalar content with colon-space passes", () => { withSkill( `name: pdf-reader -description: | +description: |2- Use for PDF files: reading, extracting.`, (skillPath) => { expect(validateSkill(skillPath)).toEqual({ From 2e22bec30bee815f3fb0c9a36f7905a6b46f0696 Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:21:03 -0700 Subject: [PATCH 4/6] fix(pr-feedback): also reject unquoted values ending with ':' Verified against a strict YAML parser: 'description: Note:' is a parse error the runtime drops, same as mid-value ': '. Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 ++-- plugin/dist/skill-creator.js | 4 ++-- plugin/lib/validate.ts | 9 +++++++-- plugin/test/validate.test.ts | 32 +++++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index b0bf0c1..c52c202 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "17ed10f1d559d55448d0af96c5fc2f4d70a3a2c67bdf5840d1b8b272da85a4a6", - "builtAt": "2026-07-05T23:17:49.191Z" + "sourceHash": "3c1b635dd079afaa73cd244d2ed41c5ea7f546981bc35b7750c23c27d71144bd", + "builtAt": "2026-07-05T23:20:53.528Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index a2f2ffb..fbc3852 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -12392,10 +12392,10 @@ function validateSkill(skillPath) { if (kvMatch) { currentKey = kvMatch[1]; const value = kvMatch[2].trim(); - if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && value.includes(": ")) { + if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && (value.includes(": ") || value.endsWith(":"))) { return { valid: false, - message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` }; } if (isBlockScalarMarker(value)) { diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index b4d1008..78e8863 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -86,10 +86,15 @@ export function validateSkill(skillPath: string): ValidationResult { currentKey = kvMatch[1] const value = kvMatch[2].trim() - if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && value.includes(": ")) { + if ( + value && + !isQuotedValue(value) && + !isBlockScalarMarker(value) && + (value.includes(": ") || value.endsWith(":")) + ) { return { valid: false, - message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").`, + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").`, } } diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts index 86f042b..f76ffc2 100644 --- a/plugin/test/validate.test.ts +++ b/plugin/test/validate.test.ts @@ -62,7 +62,9 @@ description: Use for PDF files: reading, extracting.`, expect(result.message).toContain("line 2") expect(result.message).toContain("Hint: quote the value") expect(result.message).toContain("description: \"your text here\"") - expect(result.message).toContain("unquoted values containing ': ' are invalid YAML") + expect(result.message).toContain( + "unquoted values containing ': ' or ending with ':' are invalid YAML", + ) expect(result.message).toContain("runtime will drop this skill") }, ) @@ -110,6 +112,34 @@ compatibility: Works with docs: markdown and PDF`, ) }) +test("unquoted value ending with colon fails", () => { + withSkill( + `name: pdf-reader +description: Note:`, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("description") + expect(result.message).toContain("line 2") + expect(result.message).toContain("Hint: quote the value") + }, + ) +}) + +test("unquoted value ending with colon-space fails", () => { + withSkill( + `name: pdf-reader +description: Note: `, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("description") + }, + ) +}) + test("unquoted name with colon-space fails before name validation", () => { withSkill( `name: pdf: reader From 7638c4d868d40930d1a590158924d740153dcab2 Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:23:03 -0700 Subject: [PATCH 5/6] fix(pr-feedback): treat colon-tab as mapping separator; tighten test assertions Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 ++-- plugin/dist/skill-creator.js | 2 +- plugin/lib/validate.ts | 2 +- plugin/test/validate.test.ts | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index c52c202..ab83e14 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "3c1b635dd079afaa73cd244d2ed41c5ea7f546981bc35b7750c23c27d71144bd", - "builtAt": "2026-07-05T23:20:53.528Z" + "sourceHash": "71d880c57ec805e651487291b5c4d0a78730e47ac2a1d1e56a99b61096c4cafe", + "builtAt": "2026-07-05T23:23:01.829Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index fbc3852..11a1731 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -12392,7 +12392,7 @@ function validateSkill(skillPath) { if (kvMatch) { currentKey = kvMatch[1]; const value = kvMatch[2].trim(); - if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && (value.includes(": ") || value.endsWith(":"))) { + if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && (/:[ \t]/.test(value) || value.endsWith(":"))) { return { valid: false, message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index 78e8863..94f9233 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -90,7 +90,7 @@ export function validateSkill(skillPath: string): ValidationResult { value && !isQuotedValue(value) && !isBlockScalarMarker(value) && - (value.includes(": ") || value.endsWith(":")) + (/:[ \t]/.test(value) || value.endsWith(":")) ) { return { valid: false, diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts index f76ffc2..80ace55 100644 --- a/plugin/test/validate.test.ts +++ b/plugin/test/validate.test.ts @@ -136,6 +136,22 @@ description: Note: `, expect(result.valid).toBe(false) expect(result.message).toContain("description") + expect(result.message).toContain("line 2") + expect(result.message).toContain("Hint: quote the value") + }, + ) +}) + +test("unquoted value with colon-tab fails", () => { + withSkill( + `name: pdf-reader +description: Use for PDF files:\treading`, + (skillPath) => { + const result = validateSkill(skillPath) + + expect(result.valid).toBe(false) + expect(result.message).toContain("description") + expect(result.message).toContain("line 2") }, ) }) From 9c2c9696cbb31e4ff3f8d7f0aaf3278f7293d441 Mon Sep 17 00:00:00 2001 From: Anton Gulin Date: Sun, 5 Jul 2026 16:25:47 -0700 Subject: [PATCH 6/6] fix(pr-feedback): report file line numbers (not frontmatter-relative), document heuristic Co-Authored-By: Claude Fable 5 --- plugin/dist/build-manifest.json | 4 ++-- plugin/dist/skill-creator.js | 2 +- plugin/lib/validate.ts | 5 ++++- plugin/test/validate.test.ts | 14 +++++++------- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/plugin/dist/build-manifest.json b/plugin/dist/build-manifest.json index ab83e14..8a56679 100644 --- a/plugin/dist/build-manifest.json +++ b/plugin/dist/build-manifest.json @@ -1,6 +1,6 @@ { "entrypoint": "skill-creator.ts", "runtimeEntrypoint": "runtime-entry.ts", - "sourceHash": "71d880c57ec805e651487291b5c4d0a78730e47ac2a1d1e56a99b61096c4cafe", - "builtAt": "2026-07-05T23:23:01.829Z" + "sourceHash": "a95606cc6b846614df711940b5cae746a9ef7e2b27521082bd454fb2f8831ada", + "builtAt": "2026-07-05T23:25:34.085Z" } diff --git a/plugin/dist/skill-creator.js b/plugin/dist/skill-creator.js index 11a1731..416e5c3 100644 --- a/plugin/dist/skill-creator.js +++ b/plugin/dist/skill-creator.js @@ -12395,7 +12395,7 @@ function validateSkill(skillPath) { if (value && !isQuotedValue(value) && !isBlockScalarMarker(value) && (/:[ \t]/.test(value) || value.endsWith(":"))) { return { valid: false, - message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 2}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").` }; } if (isBlockScalarMarker(value)) { diff --git a/plugin/lib/validate.ts b/plugin/lib/validate.ts index 94f9233..c703919 100644 --- a/plugin/lib/validate.ts +++ b/plugin/lib/validate.ts @@ -23,6 +23,8 @@ const ALLOWED_PROPERTIES = new Set([ "compatibility", ]) +// Intentionally simple first/last-char check — this validator is a lint +// heuristic for common frontmatter mistakes, not a full YAML parser. function isQuotedValue(value: string): boolean { return ( value.length >= 2 && @@ -94,7 +96,8 @@ export function validateSkill(skillPath: string): ValidationResult { ) { return { valid: false, - message: `Invalid frontmatter value for '${currentKey}' on line ${index + 1}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").`, + // +2: frontmatter starts on file line 2, after the opening --- + message: `Invalid frontmatter value for '${currentKey}' on line ${index + 2}: unquoted values containing ': ' or ending with ':' are invalid YAML and the runtime will drop this skill. Hint: quote the value (e.g. ${currentKey}: "your text here").`, } } diff --git a/plugin/test/validate.test.ts b/plugin/test/validate.test.ts index 80ace55..74ef73f 100644 --- a/plugin/test/validate.test.ts +++ b/plugin/test/validate.test.ts @@ -59,7 +59,7 @@ description: Use for PDF files: reading, extracting.`, expect(result.valid).toBe(false) expect(result.message).toContain("description") - expect(result.message).toContain("line 2") + expect(result.message).toContain("line 3") expect(result.message).toContain("Hint: quote the value") expect(result.message).toContain("description: \"your text here\"") expect(result.message).toContain( @@ -107,7 +107,7 @@ compatibility: Works with docs: markdown and PDF`, expect(result.valid).toBe(false) expect(result.message).toContain("compatibility") - expect(result.message).toContain("line 3") + expect(result.message).toContain("line 4") }, ) }) @@ -121,13 +121,13 @@ description: Note:`, expect(result.valid).toBe(false) expect(result.message).toContain("description") - expect(result.message).toContain("line 2") + expect(result.message).toContain("line 3") expect(result.message).toContain("Hint: quote the value") }, ) }) -test("unquoted value ending with colon-space fails", () => { +test("unquoted value with trailing colon-space fails after trim (ends-with-colon path)", () => { withSkill( `name: pdf-reader description: Note: `, @@ -136,7 +136,7 @@ description: Note: `, expect(result.valid).toBe(false) expect(result.message).toContain("description") - expect(result.message).toContain("line 2") + expect(result.message).toContain("line 3") expect(result.message).toContain("Hint: quote the value") }, ) @@ -151,7 +151,7 @@ description: Use for PDF files:\treading`, expect(result.valid).toBe(false) expect(result.message).toContain("description") - expect(result.message).toContain("line 2") + expect(result.message).toContain("line 3") }, ) }) @@ -165,7 +165,7 @@ description: Use when reading docs.`, expect(result.valid).toBe(false) expect(result.message).toContain("name") - expect(result.message).toContain("line 1") + expect(result.message).toContain("line 2") expect(result.message).toContain("Hint: quote the value") }, )