← Files ForgeMindARCHIVED FILE

src/frontmatter.mjs

1.79 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

import { ForgeMindError } from './errors.mjs';

export function parseSkillFrontmatter(content, source = 'SKILL.md') {
  const normalized = content.replace(/^\uFEFF/, '').replaceAll('\r\n', '\n');
  const match = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
  if (!match) {
    throw invalid(source, 'Missing YAML frontmatter block');
  }

  const result = {};
  for (const [index, line] of match[1].split('\n').entries()) {
    if (!line.trim() || line.trimStart().startsWith('#')) continue;
    const pair = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):(?:\s+(.*)|\s*)$/);
    if (!pair) throw invalid(source, `Invalid frontmatter line ${index + 2}`);
    const [, key, raw = ''] = pair;
    if (Object.hasOwn(result, key)) throw invalid(source, `Duplicate frontmatter key: ${key}`);
    result[key] = parseScalar(raw, source, key);
  }

  for (const required of ['name', 'description']) {
    if (typeof result[required] !== 'string' || !result[required].trim()) {
      throw invalid(source, `Missing required frontmatter field: ${required}`);
    }
  }
  return result;
}

function parseScalar(raw, source, key) {
  const value = raw.trim();
  if (value.startsWith('"')) {
    try {
      const parsed = JSON.parse(value);
      if (typeof parsed !== 'string') throw new Error('not a string');
      return parsed;
    } catch {
      throw invalid(source, `Invalid quoted value for ${key}`);
    }
  }
  if (value.startsWith("'") && value.endsWith("'")) {
    return value.slice(1, -1).replaceAll("''", "'");
  }
  if (/:\s/.test(value) || /\s#/.test(value)) {
    throw invalid(source, `YAML-sensitive value for ${key} must be quoted`);
  }
  return value;
}

function invalid(source, message) {
  return new ForgeMindError('FM_FRONTMATTER_INVALID', `${source}: ${message}`);
}

SHA-256: b2e15555b76df2789f88d69b515a19fdc75b604d3f0a3f3eb4575caf75b0f301