From 875cae0843d25c3384e53a33182b00ee302bfc6c Mon Sep 17 00:00:00 2001 From: indifferentketchup Date: Wed, 3 Jun 2026 17:00:49 +0000 Subject: [PATCH] fix(coder): parse YAML block-scalar descriptions in slash command discovery Most plugin/han SKILL.md and command files write `description:` as a folded block scalar (`>` / `|`) with the text on the following indented lines. The old single-line frontmatter reader captured the literal `>`, so the slash menu showed garbage/blank descriptions for nearly all of them. frontmatterField now collapses folded blocks (join with spaces) and preserves literal blocks. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/services/claude-command-discovery.ts | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/coder/src/services/claude-command-discovery.ts b/apps/coder/src/services/claude-command-discovery.ts index 3ac45ab..e94ba99 100644 --- a/apps/coder/src/services/claude-command-discovery.ts +++ b/apps/coder/src/services/claude-command-discovery.ts @@ -12,12 +12,48 @@ import { homedir } from 'node:os'; import { join } from 'node:path'; import type { AgentCommand } from './provider-types.js'; -/** Minimal frontmatter reader — single-line `key: value` between `---` fences. */ +/** + * Frontmatter reader between `---` fences. Handles single-line `key: value` + * AND YAML block scalars (`key: >` folded / `key: |` literal) whose value + * spans the following more-indented lines — the shape most plugin SKILL.md + * descriptions use (`description: >`). + */ function frontmatterField(content: string, field: string): string | undefined { const block = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!block?.[1]) return undefined; - const m = block[1].match(new RegExp(`^${field}:\\s*(.+)$`, 'm')); - return m?.[1]?.trim().replace(/^["']|["']$/g, '') || undefined; + const lines = block[1].split(/\r?\n/); + const keyRe = new RegExp(`^(\\s*)${field}:\\s*(.*)$`); + for (let i = 0; i < lines.length; i++) { + const m = lines[i]?.match(keyRe); + if (!m) continue; + const keyIndent = (m[1] ?? '').length; + const inline = (m[2] ?? '').trim(); + // Block scalar: `>` (folded) or `|` (literal), optional chomping `+`/`-`. + if (/^[>|][+-]?$/.test(inline)) { + const folded = inline[0] === '>'; + const body: string[] = []; + for (let j = i + 1; j < lines.length; j++) { + const line = lines[j] ?? ''; + if (line.trim() === '') { + body.push(''); + continue; + } + const indent = line.length - line.trimStart().length; + if (indent <= keyIndent) break; // dedent ends the block + body.push(line.slice(keyIndent + 1)); + } + const joined = folded + ? body + .map((l) => l.trim()) + .join(' ') + .replace(/\s+/g, ' ') + .trim() + : body.join('\n').replace(/\n+$/, ''); + return joined || undefined; + } + return inline.replace(/^["']|["']$/g, '').trim() || undefined; + } + return undefined; } function readCommandDir(dir: string): AgentCommand[] {