Compare commits

..

1 Commits

Author SHA1 Message Date
Dickson Tsai
82d041227f Fix YAML frontmatter parsing errors in agent description fields
Use YAML block scalars (|) for multi-line description fields that contain
<example> blocks with colons and special characters. Without block scalars,
the YAML parser fails because it interprets lines like 'user:' and
'assistant:' as new key-value pairs.

Affected files:
- plugins/plugin-dev/agents/agent-creator.md
- plugins/plugin-dev/agents/skill-reviewer.md
- plugins/plugin-dev/agents/plugin-validator.md
- plugins/pr-review-toolkit/agents/code-simplifier.md
2026-02-04 15:59:39 -08:00
6 changed files with 116 additions and 418 deletions

View File

@@ -1,273 +0,0 @@
#!/usr/bin/env bun
/**
* Validates YAML frontmatter in agent, skill, and command .md files.
*
* Usage:
* bun validate-frontmatter.ts # scan current directory
* bun validate-frontmatter.ts /path/to/dir # scan specific directory
* bun validate-frontmatter.ts file1.md file2.md # validate specific files
*/
import { parse as parseYaml } from "yaml";
import { readdir, readFile } from "fs/promises";
import { basename, join, relative, resolve } from "path";
// Characters that require quoting in YAML values when unquoted:
// {} [] flow indicators, * anchor/alias, & anchor, # comment,
// ! tag, | > block scalars, % directive, @ ` reserved
const YAML_SPECIAL_CHARS = /[{}[\]*&#!|>%@`]/;
const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)---\s*\n?/;
/**
* Pre-process frontmatter text to quote values containing special YAML
* characters. This allows glob patterns like **\/*.{ts,tsx} to parse.
*/
function quoteSpecialValues(text: string): string {
const lines = text.split("\n");
const result: string[] = [];
for (const line of lines) {
const match = line.match(/^([a-zA-Z_-]+):\s+(.+)$/);
if (match) {
const [, key, value] = match;
if (!key || !value) {
result.push(line);
continue;
}
// Skip already-quoted values
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
result.push(line);
continue;
}
if (YAML_SPECIAL_CHARS.test(value)) {
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
result.push(`${key}: "${escaped}"`);
continue;
}
}
result.push(line);
}
return result.join("\n");
}
interface ParseResult {
frontmatter: Record<string, unknown>;
content: string;
error?: string;
}
function parseFrontmatter(markdown: string): ParseResult {
const match = markdown.match(FRONTMATTER_REGEX);
if (!match) {
return {
frontmatter: {},
content: markdown,
error: "No frontmatter found",
};
}
const frontmatterText = quoteSpecialValues(match[1] || "");
const content = markdown.slice(match[0].length);
try {
const parsed = parseYaml(frontmatterText);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return { frontmatter: parsed as Record<string, unknown>, content };
}
return {
frontmatter: {},
content,
error: `YAML parsed but result is not an object (got ${typeof parsed}${Array.isArray(parsed) ? " array" : ""})`,
};
} catch (err) {
return {
frontmatter: {},
content,
error: `YAML parse failed: ${err instanceof Error ? err.message : err}`,
};
}
}
// --- Validation ---
type FileType = "agent" | "skill" | "command";
interface ValidationIssue {
level: "error" | "warning";
message: string;
}
function validateAgent(
frontmatter: Record<string, unknown>
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
if (!frontmatter["name"] || typeof frontmatter["name"] !== "string") {
issues.push({ level: "error", message: 'Missing required "name" field' });
}
if (
!frontmatter["description"] ||
typeof frontmatter["description"] !== "string"
) {
issues.push({
level: "error",
message: 'Missing required "description" field',
});
}
return issues;
}
function validateSkill(
frontmatter: Record<string, unknown>
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
if (!frontmatter["description"] && !frontmatter["when_to_use"]) {
issues.push({
level: "error",
message: 'Missing required "description" field',
});
}
return issues;
}
function validateCommand(
frontmatter: Record<string, unknown>
): ValidationIssue[] {
const issues: ValidationIssue[] = [];
if (
!frontmatter["description"] ||
typeof frontmatter["description"] !== "string"
) {
issues.push({
level: "error",
message: 'Missing required "description" field',
});
}
return issues;
}
// --- File type detection ---
function detectFileType(filePath: string): FileType | null {
if (filePath.includes("/agents/")) return "agent";
if (filePath.includes("/skills/") && basename(filePath) === "SKILL.md")
return "skill";
if (filePath.includes("/commands/")) return "command";
return null;
}
// --- File discovery ---
async function findMdFiles(
baseDir: string
): Promise<{ path: string; type: FileType }[]> {
const results: { path: string; type: FileType }[] = [];
async function walk(dir: string) {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.name.endsWith(".md")) {
const type = detectFileType(fullPath);
if (type) {
results.push({ path: fullPath, type });
}
}
}
}
await walk(baseDir);
return results;
}
// --- Main ---
async function main() {
const args = process.argv.slice(2);
let files: { path: string; type: FileType }[];
let baseDir: string;
if (args.length > 0 && args.every((a) => a.endsWith(".md"))) {
baseDir = process.cwd();
files = [];
for (const arg of args) {
const fullPath = resolve(arg);
const type = detectFileType(fullPath);
if (type) {
files.push({ path: fullPath, type });
}
}
} else {
baseDir = args[0] || process.cwd();
files = await findMdFiles(baseDir);
}
let totalErrors = 0;
let totalWarnings = 0;
console.log(`Validating ${files.length} frontmatter files...\n`);
for (const { path: filePath, type } of files) {
const rel = relative(baseDir, filePath);
const content = await readFile(filePath, "utf-8");
const result = parseFrontmatter(content);
const issues: ValidationIssue[] = [];
if (result.error) {
issues.push({ level: "error", message: result.error });
}
if (!result.error) {
switch (type) {
case "agent":
issues.push(...validateAgent(result.frontmatter));
break;
case "skill":
issues.push(...validateSkill(result.frontmatter));
break;
case "command":
issues.push(...validateCommand(result.frontmatter));
break;
}
}
if (issues.length > 0) {
console.log(`${rel} (${type})`);
for (const issue of issues) {
const prefix = issue.level === "error" ? " ERROR" : " WARN ";
console.log(`${prefix}: ${issue.message}`);
if (issue.level === "error") totalErrors++;
else totalWarnings++;
}
console.log();
}
}
console.log("---");
console.log(
`Validated ${files.length} files: ${totalErrors} errors, ${totalWarnings} warnings`
);
if (totalErrors > 0) {
process.exit(1);
}
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(2);
});

View File

@@ -1,34 +0,0 @@
name: Validate Frontmatter
on:
pull_request:
paths:
- '**/agents/*.md'
- '**/skills/*/SKILL.md'
- '**/commands/*.md'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: cd .github/scripts && bun install yaml
- name: Get changed frontmatter files
id: changed
run: |
FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '(agents/.*\.md|skills/.*/SKILL\.md|commands/.*\.md)$' || true)
echo "files<<EOF" >> "$GITHUB_OUTPUT"
echo "$FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: Validate frontmatter
if: steps.changed.outputs.files != ''
run: |
echo "${{ steps.changed.outputs.files }}" | xargs bun .github/scripts/validate-frontmatter.ts

View File

@@ -1,34 +1,34 @@
--- ---
name: agent-creator name: agent-creator
description: Use this agent when the user asks to "create an agent", "generate an agent", "build a new agent", "make me an agent that...", or describes agent functionality they need. Trigger when user wants to create autonomous agents for plugins. Examples: description: |
Use this agent when the user asks to "create an agent", "generate an agent", "build a new agent", "make me an agent that...", or describes agent functionality they need. Trigger when user wants to create autonomous agents for plugins. Examples:
<example> <example>
Context: User wants to create a code review agent Context: User wants to create a code review agent
user: "Create an agent that reviews code for quality issues" user: "Create an agent that reviews code for quality issues"
assistant: "I'll use the agent-creator agent to generate the agent configuration." assistant: "I'll use the agent-creator agent to generate the agent configuration."
<commentary> <commentary>
User requesting new agent creation, trigger agent-creator to generate it. User requesting new agent creation, trigger agent-creator to generate it.
</commentary> </commentary>
</example> </example>
<example> <example>
Context: User describes needed functionality Context: User describes needed functionality
user: "I need an agent that generates unit tests for my code" user: "I need an agent that generates unit tests for my code"
assistant: "I'll use the agent-creator agent to create a test generation agent." assistant: "I'll use the agent-creator agent to create a test generation agent."
<commentary> <commentary>
User describes agent need, trigger agent-creator to build it. User describes agent need, trigger agent-creator to build it.
</commentary> </commentary>
</example> </example>
<example>
Context: User wants to add agent to plugin
user: "Add an agent to my plugin that validates configurations"
assistant: "I'll use the agent-creator agent to generate a configuration validator agent."
<commentary>
Plugin development with agent addition, trigger agent-creator.
</commentary>
</example>
<example>
Context: User wants to add agent to plugin
user: "Add an agent to my plugin that validates configurations"
assistant: "I'll use the agent-creator agent to generate a configuration validator agent."
<commentary>
Plugin development with agent addition, trigger agent-creator.
</commentary>
</example>
model: sonnet model: sonnet
color: magenta color: magenta
tools: ["Write", "Read"] tools: ["Write", "Read"]

View File

@@ -1,36 +1,36 @@
--- ---
name: plugin-validator name: plugin-validator
description: Use this agent when the user asks to "validate my plugin", "check plugin structure", "verify plugin is correct", "validate plugin.json", "check plugin files", or mentions plugin validation. Also trigger proactively after user creates or modifies plugin components. Examples: description: |
Use this agent when the user asks to "validate my plugin", "check plugin structure", "verify plugin is correct", "validate plugin.json", "check plugin files", or mentions plugin validation. Also trigger proactively after user creates or modifies plugin components. Examples:
<example> <example>
Context: User finished creating a new plugin Context: User finished creating a new plugin
user: "I've created my first plugin with commands and hooks" user: "I've created my first plugin with commands and hooks"
assistant: "Great! Let me validate the plugin structure." assistant: "Great! Let me validate the plugin structure."
<commentary> <commentary>
Plugin created, proactively validate to catch issues early. Plugin created, proactively validate to catch issues early.
</commentary> </commentary>
assistant: "I'll use the plugin-validator agent to check the plugin." assistant: "I'll use the plugin-validator agent to check the plugin."
</example> </example>
<example> <example>
Context: User explicitly requests validation Context: User explicitly requests validation
user: "Validate my plugin before I publish it" user: "Validate my plugin before I publish it"
assistant: "I'll use the plugin-validator agent to perform comprehensive validation." assistant: "I'll use the plugin-validator agent to perform comprehensive validation."
<commentary> <commentary>
Explicit validation request triggers the agent. Explicit validation request triggers the agent.
</commentary> </commentary>
</example> </example>
<example>
Context: User modified plugin.json
user: "I've updated the plugin manifest"
assistant: "Let me validate the changes."
<commentary>
Manifest modified, validate to ensure correctness.
</commentary>
assistant: "I'll use the plugin-validator agent to check the manifest."
</example>
<example>
Context: User modified plugin.json
user: "I've updated the plugin manifest"
assistant: "Let me validate the changes."
<commentary>
Manifest modified, validate to ensure correctness.
</commentary>
assistant: "I'll use the plugin-validator agent to check the manifest."
</example>
model: inherit model: inherit
color: yellow color: yellow
tools: ["Read", "Grep", "Glob", "Bash"] tools: ["Read", "Grep", "Glob", "Bash"]

View File

@@ -1,35 +1,35 @@
--- ---
name: skill-reviewer name: skill-reviewer
description: Use this agent when the user has created or modified a skill and needs quality review, asks to "review my skill", "check skill quality", "improve skill description", or wants to ensure skill follows best practices. Trigger proactively after skill creation. Examples: description: |
Use this agent when the user has created or modified a skill and needs quality review, asks to "review my skill", "check skill quality", "improve skill description", or wants to ensure skill follows best practices. Trigger proactively after skill creation. Examples:
<example> <example>
Context: User just created a new skill Context: User just created a new skill
user: "I've created a PDF processing skill" user: "I've created a PDF processing skill"
assistant: "Great! Let me review the skill quality." assistant: "Great! Let me review the skill quality."
<commentary> <commentary>
Skill created, proactively trigger skill-reviewer to ensure it follows best practices. Skill created, proactively trigger skill-reviewer to ensure it follows best practices.
</commentary> </commentary>
assistant: "I'll use the skill-reviewer agent to review the skill." assistant: "I'll use the skill-reviewer agent to review the skill."
</example> </example>
<example> <example>
Context: User requests skill review Context: User requests skill review
user: "Review my skill and tell me how to improve it" user: "Review my skill and tell me how to improve it"
assistant: "I'll use the skill-reviewer agent to analyze the skill quality." assistant: "I'll use the skill-reviewer agent to analyze the skill quality."
<commentary> <commentary>
Explicit skill review request triggers the agent. Explicit skill review request triggers the agent.
</commentary> </commentary>
</example> </example>
<example>
Context: User modified skill description
user: "I updated the skill description, does it look good?"
assistant: "I'll use the skill-reviewer agent to review the changes."
<commentary>
Skill description modified, review for triggering effectiveness.
</commentary>
</example>
<example>
Context: User modified skill description
user: "I updated the skill description, does it look good?"
assistant: "I'll use the skill-reviewer agent to review the changes."
<commentary>
Skill description modified, review for triggering effectiveness.
</commentary>
</example>
model: inherit model: inherit
color: cyan color: cyan
tools: ["Read", "Grep", "Glob"] tools: ["Read", "Grep", "Glob"]

View File

@@ -1,37 +1,42 @@
--- ---
name: code-simplifier name: code-simplifier
description: Use this agent when code has been written or modified and needs to be simplified for clarity, consistency, and maintainability while preserving all functionality. This agent should be triggered automatically after completing a coding task or writing a logical chunk of code. It simplifies code by following project best practices while retaining all functionality. The agent focuses only on recently modified code unless instructed otherwise.\n\nExamples:\n\n<example> description: |
Context: The assistant has just implemented a new feature that adds user authentication to an API endpoint. Use this agent when code has been written or modified and needs to be simplified for clarity, consistency, and maintainability while preserving all functionality. This agent should be triggered automatically after completing a coding task or writing a logical chunk of code. It simplifies code by following project best practices while retaining all functionality. The agent focuses only on recently modified code unless instructed otherwise.
user: "Please add authentication to the /api/users endpoint"
assistant: "I've implemented the authentication for the /api/users endpoint. Here's the code:"
<function call omitted for brevity>
<commentary>
Since a logical chunk of code has been written (authentication feature), use the Task tool to launch the code-simplifier agent to improve the code's clarity and maintainability while preserving functionality.
</commentary>
assistant: "Now let me use the code-simplifier agent to refine this implementation for better clarity and maintainability"
</example>
<example> Examples:
Context: The assistant has just fixed a bug by adding several conditional checks.
user: "Fix the null pointer exception in the data processor"
assistant: "I've added the necessary null checks to prevent the exception:"
<function call omitted for brevity>
<commentary>
After modifying code to fix a bug, use the code-simplifier agent to ensure the fix follows best practices and maintains code quality.
</commentary>
assistant: "Let me refine this bug fix using the code-simplifier agent to ensure it follows our best practices"
</example>
<example> <example>
Context: The assistant has just refactored a function to improve performance. Context: The assistant has just implemented a new feature that adds user authentication to an API endpoint.
user: "Optimize the data sorting algorithm for better performance" user: "Please add authentication to the /api/users endpoint"
assistant: "I've optimized the sorting algorithm. Here's the updated implementation:" assistant: "I've implemented the authentication for the /api/users endpoint. Here's the code:"
<function call omitted for brevity> <function call omitted for brevity>
<commentary> <commentary>
After completing a performance optimization task, use the code-simplifier agent to ensure the optimized code is also clear and maintainable. Since a logical chunk of code has been written (authentication feature), use the Task tool to launch the code-simplifier agent to improve the code's clarity and maintainability while preserving functionality.
</commentary> </commentary>
assistant: "Now I'll use the code-simplifier agent to ensure the optimized code is also clear and follows our coding standards" assistant: "Now let me use the code-simplifier agent to refine this implementation for better clarity and maintainability"
</example> </example>
<example>
Context: The assistant has just fixed a bug by adding several conditional checks.
user: "Fix the null pointer exception in the data processor"
assistant: "I've added the necessary null checks to prevent the exception:"
<function call omitted for brevity>
<commentary>
After modifying code to fix a bug, use the code-simplifier agent to ensure the fix follows best practices and maintains code quality.
</commentary>
assistant: "Let me refine this bug fix using the code-simplifier agent to ensure it follows our best practices"
</example>
<example>
Context: The assistant has just refactored a function to improve performance.
user: "Optimize the data sorting algorithm for better performance"
assistant: "I've optimized the sorting algorithm. Here's the updated implementation:"
<function call omitted for brevity>
<commentary>
After completing a performance optimization task, use the code-simplifier agent to ensure the optimized code is also clear and maintainable.
</commentary>
assistant: "Now I'll use the code-simplifier agent to ensure the optimized code is also clear and follows our coding standards"
</example>
model: opus model: opus
--- ---