|
| 1 | +#+TITLE: 读:ox-skills——用 Org-mode 管理 Claude Code 的 Skill 定义 |
| 2 | +#+AUTHOR: lujun9972,Claude Code |
| 3 | +#+TAGS: Emacs, Org-mode, Claude Code, AI |
| 4 | +#+DATE: [2026-05-30 Fri] |
| 5 | +#+LANGUAGE: zh-CN |
| 6 | +#+OPTIONS: H:6 num:nil toc:t \n:nil ::t |:t ^:nil -:nil f:t *:t <:nil |
| 7 | +#+DESCRIPTION: ox-skills 是一个 Org exporter backend,把 Org 文件导出为带 YAML frontmatter 的 SKILL.md 文件。它让你用 Org-mode 编写 AI coding assistant 的 skill 定义,通过 org-export 一键生成目标格式,还支持动态上下文注入和批量管理。本文从实际操作出发,展示完整的编写→导出→使用流程。 |
| 8 | + |
| 9 | +[[https://github.com/gicrisf/ox-skills][ox-skills]] 是一个 Org exporter backend,做一件事,把 Org 文件导出为带 YAML frontmatter 的 SKILL.md 文件。 |
| 10 | + |
| 11 | +如果你用 Claude Code 或 OpenCode 这类 AI coding assistant,迟早会写自己的 skill(一种用 Markdown + YAML frontmatter 定义的提示词模板)。手写 YAML frontmatter 很繁琐,skill 的内容(指令、代码示例、检查清单)说到底就是结构化文本,用 Org-mode 管理顺手得多。ox-skills 把这个流程打通了,在 Org 里写 skill,一键导出成 AI 工具需要的格式。 |
| 12 | + |
| 13 | +* 一个 skill 从 Org 到 SKILL.md |
| 14 | + |
| 15 | +假设我们要写一个审查当前未提交改动的 skill。用 ox-skills 的方式,Org 文件长这样, |
| 16 | + |
| 17 | +#+begin_src org |
| 18 | + ,#+SKILL_NAME: git-review |
| 19 | + ,#+SKILL_DESCRIPTION: Review current uncommitted changes and suggest improvements |
| 20 | + ,#+SKILL_ALLOWED_TOOLS: Bash Read |
| 21 | + ,#+SKILL_USER_INVOCABLE: true |
| 22 | + ,#+SKILL_BASE_DIR: ~/.claude/skills |
| 23 | + |
| 24 | + Review the current uncommitted changes for: |
| 25 | + |
| 26 | + 1. *Bugs*: Logic errors, edge cases, missing error handling |
| 27 | + 2. *Style*: Naming, function length, code organization |
| 28 | + 3. *Security*: Input validation, injection risks |
| 29 | + |
| 30 | + Be constructive. Prioritize findings by severity. |
| 31 | + |
| 32 | + ,* Current changes |
| 33 | + |
| 34 | + ,#+begin_src sh :inject yes |
| 35 | + git diff HEAD |
| 36 | + ,#+end_src |
| 37 | +#+end_src |
| 38 | + |
| 39 | +按 =C-c C-e s s= (或者 =M-x org-export-dispatch= 选 =s s= )导出后,ox-skills 在 =~/.claude/skills/git-review/SKILL.md= 生成这样的文件, |
| 40 | + |
| 41 | +#+begin_src yaml |
| 42 | +--- |
| 43 | +name: git-review |
| 44 | +description: Review current uncommitted changes and suggest improvements |
| 45 | +allowed-tools: [Bash, Read] |
| 46 | +user-invocable: true |
| 47 | +--- |
| 48 | + |
| 49 | +Review the current uncommitted changes for: |
| 50 | + |
| 51 | +1. **Bugs**: Logic errors, edge cases, missing error handling |
| 52 | +2. **Style**: Naming, function length, code organization |
| 53 | +3. **Security**: Input validation, injection risks |
| 54 | + |
| 55 | +Be constructive. Prioritize findings by severity. |
| 56 | + |
| 57 | +## Current changes |
| 58 | + |
| 59 | +```! |
| 60 | +git diff HEAD |
| 61 | +``` |
| 62 | +#+end_src |
| 63 | + |
| 64 | +对比源文件和产物,能看出 ox-skills 做了什么。 =#+SKILL_*= 关键字变成 YAML frontmatter 里的对应字段,Org 的正文变成 Markdown 正文, =*Current changes*= 标题变成 =## Current changes= ,加了 =:inject yes= 的 src block 变成了带叹号的代码围栏(区别于普通的代码围栏)。 |
| 65 | + |
| 66 | +带叹号的代码围栏是关键差异。普通代码块是静态内容,带叹号的围栏会被 Claude Code / OpenCode 在调用 skill 时实际执行,把运行结果注入到上下文里。你调用 =git-review= skill 的时候,AI 工具先跑 =git diff HEAD= ,拿到当前改动,再基于这个结果做审查。手写 SKILL.md 也可以直接写带叹号的围栏,但 ox-skills 让你用 Org 语法( =:inject yes= )来表达,跟其他 src block 的写法一致。 |
| 67 | + |
| 68 | +*关键词对照。 * ox-skills 支持的 =#+SKILL_*= 关键字和对应的 YAML 字段如下, |
| 69 | + |
| 70 | +| Org 关键字 | YAML 字段 | 类型 | |
| 71 | +|-----------+----------+------| |
| 72 | +| =#+SKILL_NAME= | =name= | 字符串 | |
| 73 | +| =#+SKILL_DESCRIPTION= | =description= | 字符串 | |
| 74 | +| =#+SKILL_WHEN_TO_USE= | =when_to_use= | 字符串 | |
| 75 | +| =#+SKILL_ALLOWED_TOOLS= | =allowed-tools= | 列表(空格分隔) | |
| 76 | +| =#+SKILL_USER_INVOCABLE= | =user-invocable= | 布尔( =true=/=false= ) | |
| 77 | +| =#+SKILL_MODEL= | =model= | 字符串 | |
| 78 | +| =#+SKILL_EFFORT= | =effort= | 字符串 | |
| 79 | + |
| 80 | +上表是常用字段,完整列表(含 =SKILL_ARGUMENTS= 、 =SKILL_CONTEXT= 、 =SKILL_AGENT= 等)见 [[https://github.com/gicrisf/ox-skills][项目 README]] 。另外示例里的 =#+SKILL_BASE_DIR= 控制输出文件写到哪个目录,它不会被导出到 YAML 里。 |
| 81 | + |
| 82 | +列表类型的值用空格分隔,多个词用引号括起来。布尔类型接受 =true=/=yes= 或 =false=/=no= 。 |
| 83 | + |
| 84 | +* 批量管理,一个文件管多个 skill |
| 85 | + |
| 86 | +用着用着你会积累十几个甚至几十个 skill。ox-skills 支持把多个 skill 放在同一个 Org 文件里,给每个标题加属性抽屉(property drawer)设置元数据,一次性全部导出。 |
| 87 | + |
| 88 | +注意命名变化:文件级用 =#+SKILL_NAME= ,子树属性用 =EXPORT_SKILL_NAME= (多了 =EXPORT_= 前缀)。两种写法效果一样,只是位置不同, |
| 89 | + |
| 90 | +#+begin_src org |
| 91 | + ,#+SKILL_BASE_DIR: ~/.claude/skills |
| 92 | + |
| 93 | + ,* Review skill |
| 94 | + :PROPERTIES: |
| 95 | + :EXPORT_SKILL_NAME: git-review |
| 96 | + :EXPORT_SKILL_DESCRIPTION: Review current uncommitted changes |
| 97 | + :EXPORT_SKILL_ALLOWED_TOOLS: Bash Read |
| 98 | + :END: |
| 99 | + |
| 100 | + Review instructions here... |
| 101 | + |
| 102 | + ,* Deploy skill |
| 103 | + :PROPERTIES: |
| 104 | + :EXPORT_SKILL_NAME: deploy |
| 105 | + :EXPORT_SKILL_DESCRIPTION: Deploy to production |
| 106 | + :EXPORT_SKILL_ALLOWED_TOOLS: Bash |
| 107 | + :END: |
| 108 | + |
| 109 | + Deploy instructions here... |
| 110 | +#+end_src |
| 111 | + |
| 112 | +按 =C-c C-e s w= (WIM 导出,"What I Mean",导出当前子树或文件),ox-skills 会遍历当前范围内的每个带 =EXPORT_SKILL_NAME= 属性的子树,各导出一个 SKILL.md 。上面的例子会在 =~/.claude/skills/git-review/SKILL.md= 和 =~/.claude/skills/deploy/SKILL.md= 各生成一个文件。 |
| 113 | + |
| 114 | +如果 skill 需要按目录组织(比如把工程类 skill 放在 =engineering/= 下),用 =EXPORT_SKILL_SUBDIR= 属性, |
| 115 | + |
| 116 | +#+begin_src org |
| 117 | + ,* Engineering |
| 118 | + :PROPERTIES: |
| 119 | + :EXPORT_SKILL_SUBDIR: engineering |
| 120 | + :END: |
| 121 | + |
| 122 | + ,** Code Review |
| 123 | + :PROPERTIES: |
| 124 | + :EXPORT_SKILL_NAME: code-review |
| 125 | + :EXPORT_SKILL_DESCRIPTION: Review code changes |
| 126 | + :END: |
| 127 | + ... |
| 128 | +#+end_src |
| 129 | + |
| 130 | +这样导出路径变成 =~/.claude/skills/engineering/code-review/SKILL.md= 。子目录层级可以嵌套,ox-skills 会沿着上层标题的 =EXPORT_SKILL_SUBDIR= 逐层拼接路径。没有 =EXPORT_SKILL_NAME= 的标题只贡献路径,本身不会被导出。 |
| 131 | + |
| 132 | +* 局限:只管 SKILL.md,不管 skill 目录里的其他文件 |
| 133 | + |
| 134 | +ox-skills 只导出 SKILL.md 这一个文件。如果你的 skill 依赖辅助脚本、参考文档(比如 =scripts/= 和 =references/= 目录下的文件),这些不会自动管理。你需要自己维护 skill 目录下的完整结构。 |
| 135 | + |
| 136 | +变通办法是用 org-babel 的 =:tangle= 。在 skills.org 里把脚本作为 src block 放在对应 skill 的子树下,用 =:tangle= 指定输出路径: |
| 137 | + |
| 138 | +#+begin_src org |
| 139 | +,* url2blog |
| 140 | +:PROPERTIES: |
| 141 | +:EXPORT_SKILL_NAME: url2blog |
| 142 | +:EXPORT_SKILL_DESCRIPTION: 从 URL 读取内容生成 Org-mode 博文 |
| 143 | +:END: |
| 144 | + |
| 145 | +skill 正文... |
| 146 | + |
| 147 | +,*** lint_org.el |
| 148 | +:PROPERTIES: |
| 149 | +:HEADER-ARGS: :tangle ~/.claude/skills/url2blog/scripts/lint_org.el |
| 150 | +:END: |
| 151 | + |
| 152 | +,#+begin_src emacs-lisp |
| 153 | +;; lint 脚本内容 |
| 154 | +,#+end_src |
| 155 | +#+end_src |
| 156 | + |
| 157 | +这样工作流变成两步, =C-c C-e s w= 导出所有 SKILL.md , =C-c C-v t= ( =M-x org-babel-tangle= )提取脚本到 skill 目录。两个 Org 原生机制拼起来,勉强算完整。缺点是 skills.org 会变得很长,所有脚本内容都堆在一个文件里。 |
| 158 | + |
| 159 | +* 安装 |
| 160 | + |
| 161 | +四种方式选一个, |
| 162 | + |
| 163 | +#+begin_src emacs-lisp |
| 164 | +;; Straight |
| 165 | +(use-package ox-skills |
| 166 | + :straight (:host github :repo "gicrisf/ox-skills")) |
| 167 | + |
| 168 | +;; Emacs 29+ 内置 :vc |
| 169 | +(use-package ox-skills |
| 170 | + :vc (:url "https://github.com/gicrisf/ox-skills" |
| 171 | + :rev :newest |
| 172 | + :branch "main")) |
| 173 | + |
| 174 | +;; Doom Emacs |
| 175 | +;; packages.el: |
| 176 | +(package! ox-skills :recipe (:host github :repo "gicrisf/ox-skills")) |
| 177 | +;; config.el: |
| 178 | +(use-package! ox-skills) |
| 179 | + |
| 180 | +;; Spacemacs |
| 181 | +;; 在 dotspacemacs-configuration-layers 或 dotspacemacs-additional-packages 里加: |
| 182 | +(ox-skills :location (recipe :fetcher github :repo "gicrisf/ox-skills")) |
| 183 | +;; 然后在 dotspacemacs/user-config 里: |
| 184 | +(use-package ox-skills) |
| 185 | +#+end_src |
| 186 | + |
| 187 | +安装后,Org 导出菜单里会多一个 =s= 键( =C-c C-e s= ),下级菜单分别是 =s= 导出当前文件到一个 SKILL.md , =S= 导出到临时缓冲区(预览用), =w= 导出当前子树或文件, =a= 导出所有子树或文件。 |
| 188 | + |
| 189 | +* 小结 |
| 190 | + |
| 191 | +ox-skills 解决的问题不大但很实在,让 skill 定义回到 Org 生态。你用 Org 管理笔记、知识库、任务,现在 skill 也在一起了,不用另外手写 YAML frontmatter,不用单独维护 Markdown 模板。 如果你同时是 Org-mode 重度用户和 Claude Code 使用者,值得一试。 |
0 commit comments