Skip to content

[Bug]: SKILL.md frontmatter 手写解析器不支持 YAML 块标量与 allowed-tools 字段,编辑后静默丢字段 #183

Description

@mawag

[Bug]: SKILL.md frontmatter 手写解析器不支持 YAML 块标量与 allowed-tools 字段,编辑后静默丢字段

Area

Skill Store / Skills

Platform

Multiple platforms(桌面端 + CLI,解析逻辑在两侧各自复制了一份)

Version

0.5.9-beta.1(main 分支当前实现,未见相关修复记录)

What happened?

SKILL.md 的 YAML frontmatter 解析没有使用标准 YAML 解析库,而是两处几乎相同的
手写逐行解析器:

  • apps/desktop/src/main/services/skill-validator.tsparseSkillMd()
  • packages/core/src/cli/skill-cli-service.tsparseSkillMd()

这两处实现按“找每行第一个 : 做 key/value 切分”处理 frontmatter,不识别 YAML
块标量语法(||->>-),也没有在 SkillFrontmatter/ParsedSkillMd
接口和序列化函数 exportAsSkillMd() 中支持官方标准字段 allowed-tools

实测复现(对当前 skill-validator.tsparseSkillMd() 逻辑原样抽取执行):

输入 SKILL.md:

---
name: pdf-processing
description: |-
  Extracts text and tables from PDF files, fills PDF forms, and merges multiple PDFs.
  Use when working with PDF documents or when the user mentions PDFs, forms, or document extraction.
license: Apache-2.0
allowed-tools: Read, Bash
metadata:
  author: your-org
  version: "1.2"
---

# PDF Processing

Body content here.

解析输出(JSON.stringify(parseSkillMd(sample))):

{
  "frontmatter": {
    "name": "pdf-processing",
    "description": "|-",
    "license": "Apache-2.0",
    "metadata": {
      "author": "your-org",
      "version": "\"1.2\""
    }
  },
  "body": "# PDF Processing\n\nBody content here.",
  "raw": "... (原始内容保留) ..."
}

问题点:

  1. description 多行块标量(|-)内容整体丢失frontmatter.description
    变成了字面字符串 "|-",原本两行真实描述内容全部消失。description
    是 Skill 是否被模型自动触发的关键字段,丢失后等价于该 Skill 描述为空/错误。
  2. allowed-tools 字段完全没有出现在解析结果里SkillFrontmatter
    接口本身没有声明这个字段,逐行循环里也没有任何分支处理它,导致这一行被
    静默丢弃,没有任何报错或警告。
  3. (次要,随手复现时发现)metadata 嵌套字段的引号未被剥除
    metadata.version 结果是 "\"1.2\""(多余的转义引号残留),说明嵌套 map
    的取值逻辑复用了顶层逻辑但边界不完整。

更严重的是回写路径。apps/desktop/src/main/services/skill-repo-sync.ts
syncFrontmatterToRepo() 会在用户编辑 Skill 元数据(如 description、tags、
author 等触发 METADATA_KEYS 命中)后,调用 SkillInstaller.exportAsSkillMd()
重写整份 SKILL.md 文件:

const newContent = SkillInstaller.exportAsSkillMd({
  name: updatedSkill.name,
  description: updatedSkill.description ?? undefined,
  version: updatedSkill.version ?? undefined,
  author: updatedSkill.author ?? undefined,
  tags: updatedSkill.tags ?? [],
  instructions: body,
  compatibility: parsed?.frontmatter.compatibility ?? undefined,
  license: parsed?.frontmatter.license ?? undefined,
});

exportAsSkillMd()apps/desktop/src/main/services/skill-installer-export.ts
packages/core/src/cli/skill-cli-service.ts 中各有一份)的参数签名里同样
没有 allowedToolsmetadata。因此:

  • 只要用户在 UI 里编辑一次任意元数据字段(哪怕只是改个 tag),触发
    syncFrontmatterToRepo() 重写 SKILL.md,原文件中的 allowed-tools 就会在
    这次重写中被整体丢弃,无法恢复(除非该 Skill 有版本历史快照)。
  • 如果原始 description 是用 |-/> 写的多行块标量,编辑其他字段触发的
    重写会把它替换成 exportAsSkillMd() 重新生成的单行字符串,内容与用户原始
    撰写的多行描述不一致(取决于解析阶段是否已经丢失,通常是丢失后写回一个
    几乎空的值)。

What did you expect?

  1. description 无论写成单行、加引号,还是 YAML 块标量(|/|-/>/>-
    多行形式,解析后都应该得到与该字段语义一致的完整字符串内容,不丢失、不
    截断。
  2. allowed-tools 作为 Agent Skills 官方规范定义的标准可选字段(与
    name/description/license/compatibility/metadata 同属六个规范
    字段),应该被正确解析、在内存模型中保留,并在 exportAsSkillMd()
    重写文件时原样写回,不因为编辑其他元数据字段而被静默清空。
  3. 理想情况下 frontmatter 解析/序列化应改用标准 YAML 库(例如 js-yaml),
    替代当前两处重复的手写逐行解析器,从根本上避免块标量、嵌套 map、flow
    数组等 YAML 语法子集缺失的问题;同时应统一到 packages/core,消除
    apps/desktoppackages/core 中两份定义不完全一致的
    SkillFrontmatter/ParsedSkillMd/exportAsSkillMd

Steps to reproduce

  1. 准备一个 SKILL.md,其中 description 使用 YAML 块标量语法(description: |-
    后接多行缩进文本),并加入 allowed-tools: Read, Bash 字段(可参考上文
    "What happened?" 中的示例内容)。
  2. 通过“扫描本地 / Git 仓库导入 / My Skills 手动添加”等任意方式将该 Skill
    纳入 PromptHub(走 parseSkillMd() 解析路径)。
  3. 查看该 Skill 在 UI 中的 description 展示,或直接检查 DB 中
    skills.description 字段的值:应会发现描述内容为空、或仅为字面字符串
    |-,而不是原始撰写的多行描述。
  4. 检查解析后的内存对象/元数据模型(或直接在代码中断点 parseSkillMd()
    返回值):allowed-tools 字段不存在于结果中。
  5. 在 UI 中编辑该 Skill 的任意其它元数据字段(例如新增一个 tag)并保存,
    触发 syncFrontmatterToRepo() 重写。
  6. 重新打开磁盘上的 SKILL.md 文件:allowed-tools: Read, Bash 这一行已经
    从文件中消失。

Logs, screenshots, or extra context

  • 涉及文件:
    • apps/desktop/src/main/services/skill-validator.tsparseSkillMd
      SkillFrontmatter/ParsedSkillMd 接口定义)
    • packages/core/src/cli/skill-cli-service.ts(重复实现的
      parseSkillMd/exportAsSkillMd/ParsedSkillMd
    • apps/desktop/src/main/services/skill-installer-export.ts
      exportAsSkillMd,序列化时未转发 allowed-tools/metadata
    • apps/desktop/src/main/services/skill-repo-sync.ts
      syncFrontmatterToRepoMETADATA_KEYS,触发静默重写丢字段的路径)
  • 官方规范参考:Agent Skills open standard 定义 6 个字段:
    name(必需)、description(必需)、licensecompatibility
    metadataallowed-tools(均为可选)。规范允许 description 使用标准
    YAML scalar 语法(包含块标量),allowed-tools 支持逗号分隔字符串(如
    Read, Bash)及带参数的工具限定(如 Bash(git add *))。当前实现只覆盖
    裸单行标量这一种最简子集。
  • 本地复现脚本:直接从 skill-validator.tsparseSkillMd() 抽取原始逻辑
    在 Node 中运行得到上文 JSON 输出(详见 "What happened?" 章节,未修改任何
    项目源码)。
  • 关联分析:spec/changes/active/(如落地修复,建议开一个 change,例如
    skill-frontmatter-yaml-parser,覆盖:是否引入 js-yaml 依赖、合并
    apps/desktop/packages/core 两处重复实现到 packages/core、补齐
    allowed-tools/metadata 全链路(解析 -> DB/内存模型 -> 序列化)、
    迁移期已存在 Skill 的兼容性与回归测试)。

Before submitting

  • I searched existing issues and did not find an obvious duplicate.
  • I can still reproduce this on the latest available build for my channel
    (main 分支当前实现,复现逻辑对照最新源码抽取)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions