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) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 17:00:49 +00:00
parent 4caa5f91ff
commit 875cae0843

View File

@@ -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[] {