From de65426d64ec5ba7994c90762f7a03057ad729b6 Mon Sep 17 00:00:00 2001 From: hjjjj <1311711287@qq.com> Date: Mon, 13 Apr 2026 13:55:36 +0800 Subject: [PATCH] feat(server): enhance frontmatter parsing for skill descriptions - Integrated a new utility function `parseFrontmatterScalarKey` to streamline the extraction of the description from skill files. - Improved the logic for handling frontmatter, ensuring more robust parsing and trimming of description values. --- lib/frontmatter-scalar.js | 77 +++++++++++++++++++++++++++++++++++++++ server.js | 11 +++--- 2 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 lib/frontmatter-scalar.js diff --git a/lib/frontmatter-scalar.js b/lib/frontmatter-scalar.js new file mode 100644 index 0000000..2c0bd9d --- /dev/null +++ b/lib/frontmatter-scalar.js @@ -0,0 +1,77 @@ +/** + * 从 frontmatter 文本里读一个顶层标量(整块 metadata 已由调用方从 --- … --- 切好)。 + * 多行块(description: > / |):从下一行起收到「下一个顶层键」或 metadata 结束为止。 + */ + +function escapeRegExp(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +/** 下一行若是顶层键 `foo:`(行首无缩进),则当前块结束。 */ +function isTopLevelKeyLine(line) { + const t = line.trimStart() + if (!t || t.startsWith('#')) return false + return /^[A-Za-z_][\w-]*\s*:\s/.test(t) +} + +function minIndentOfNonEmpty(lines) { + let min = Infinity + for (const l of lines) { + if (!l.trim()) continue + const n = l.match(/^(\s*)/)[1].length + if (n < min) min = n + } + return min === Infinity ? 0 : min +} + +function dedent(lines) { + const min = minIndentOfNonEmpty(lines) + return lines.map((l) => (l.length === 0 ? '' : l.slice(min))) +} + +function parseFrontmatterScalarKey(fmBlock, key) { + if (!fmBlock || !key) return null + const lines = fmBlock.split(/\r?\n/) + const keyRe = new RegExp(`^${escapeRegExp(key)}:\\s*(.*)$`) + + for (let i = 0; i < lines.length; i++) { + const m = lines[i].match(keyRe) + if (!m) continue + + let after = m[1].replace(/\s+$/, '') + const blockHead = after.match(/^([>|])([-+]?)\s*(.*)$/) + + if (blockHead) { + const mode = blockHead[1] + const sameLine = (blockHead[3] || '').trimEnd() + const raw = [] + if (sameLine) raw.push(sameLine) + + for (let j = i + 1; j < lines.length; j++) { + const line = lines[j] + if (line.length > 0 && !/^\s/.test(line) && isTopLevelKeyLine(line)) break + raw.push(line) + } + + const body = dedent(raw) + if (mode === '>') { + return body + .map((l) => l.trim()) + .filter(Boolean) + .join(' ') + .trim() + } + return body.join('\n').trim() + } + + const t = after.trim() + if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) { + return t.slice(1, -1) + } + if (t) return t.replace(/^["']|["']$/g, '') + return '' + } + return null +} + +module.exports = { parseFrontmatterScalarKey } diff --git a/server.js b/server.js index 280a430..41632cf 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,7 @@ const express = require('express') const cors = require('cors') const path = require('path') const { MongoClient, ObjectId } = require('mongodb') +const { parseFrontmatterScalarKey } = require('./lib/frontmatter-scalar') const { createAuthRoutes } = require('./routes/auth') const app = express() @@ -196,16 +197,16 @@ function canUserAccessTaggedResource(user, resourceTags) { function extractDescription(files) { const skillFile = files.find(f => f.path === 'SKILL.md' || f.path.endsWith('SKILL.md')) if (!skillFile) return '' - + const content = skillFile.content const fmMatch = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/) if (fmMatch) { - const descMatch = fmMatch[1].match(/^description:\s*(.+)$/m) - if (descMatch) { - return descMatch[1].trim().replace(/^["']|["']$/g, '') + const fromFm = parseFrontmatterScalarKey(fmMatch[1], 'description') + if (fromFm != null && String(fromFm).trim() !== '') { + return String(fromFm).trim() } } - + const lines = content.split('\n') let inFrontmatter = false for (const line of lines) {