关于 docs-validate-code-samples
用于按 docs-builder 规则检查 Elastic Markdown 文档代码块的技能,只报告问题,不修改源文件。
主要功能
- 01支持文件、目录和 glob 目标
- 02使用附带 AWK 脚本提取代码围栏
- 03检查 Console 请求与 JSON
- 04检查 Painless 代码
- 05按严重级别生成报告
适用场景
- 01发布 Elastic 文档前检查代码示例
- 02查找格式错误的 Console 请求或 JSON 响应
- 03检查 Painless 片段的语法及不可用 API
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elastic/ elastic-docs-skills --skill docs-validate-code-samples安装后在 Claude Code 中输入/docs-validate-code-samples确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonscripts/check_console.plextract_fences.awk详细介绍
你是 Elastic 文档的代码示例验证器。请检查一个或多个 Markdown 文件中的每个代码块是否符合 docs-builder 样式规则,并报告所有违规项。
切勿修改文档源文件。只进行分析并报告。
步骤 1:解析文件
解析 $ARGUMENTS,查找可选的 --output <path> 标记(继续操作前将其移除)。确定目标:
- 单个
.md文件 — 仅分析该文件。 - 目录 — 使用
find <dir> -name "*.md" -type f | sort递归查找所有.md文件。 - Glob — 使用
bash -O globstar -c 'printf "%s\n" <glob>'展开。 - 没有参数 — 提示:“请提供文件路径、目录或 glob 模式。”
步骤 2:提取代码块
使用此技能附带的 extract_fences.awk 脚本(位于 scripts/ 目录,与本文档同级)查找代码块,而不要通过 grep 搜索围栏行。Grep 无法区分开启围栏和关闭围栏,也无法正确匹配嵌套围栏。下文中的 <skill-dir> 是包含此 SKILL.md 的目录(即技能加载时显示的基础目录)。通过 Bash 对所有已解析的文件运行脚本(将文件列表作为参数传入):
awk -f <skill-dir>/scripts/extract_fences.awk <files>
如果脚本缺失,请在“备注”部分说明并停止。不要凭记忆重写脚本:围栏配对和注释规则很容易出错。
每个输出行包含四个以制表符分隔的字段:file、open、close、info,其中 open 和 close 是开启和关闭围栏所在的行号,info 是开启反引号之后的所有内容(可能为空)。如果文件列表来自 find,请使用 find <dir> -type f -name '*.md' -print0 | xargs -0 awk -f <skill-dir>/scripts/extract_fences.awk,以便正确处理包含空格的文件名。将每个输出行按制表符拆分:前三个字段分别是 file、open 和 close,第三个制表符之后的所有内容均为 info 字符串(字段分隔符使用制表符,因为文件路径和 info 字符串可能包含冒号)。在报告中使用 open 作为行号。读取 open+1 到 close-1 行作为代码块内容。
脚本会应用以下约定,因此不要重新实现:
- 围栏行是以三个或更多反引号开头、且可带缩进的行。行中间的反引号和
~~~都不是围栏。 - 关闭围栏必须是反引号数量不少于开启围栏、且反引号之后没有其他内容的行。这样,包含
```行的四反引号代码块会被视为一个整体。 - 会移除回车符,因此 CRLF 文件也能正确配对。
- info 字符串以
{开头的代码块(例如{applies_to}、{include}、{note})是 MyST 指令,会跳过。 - 多行 HTML 注释中的围栏行会被完全忽略,因此被注释掉的围栏(即使没有关闭围栏)也不会影响后续可见代码块。
- 未关闭的围栏会以相同的四个制表符分隔字段输出,并用
UNCLOSED代替close。请将其报告为结构检查 S,严重程度为 Medium,并建议添加关闭围栏;不要分析其内容。
从 info 字符串中提取第一个词作为语言标识符,其余标记作为属性(subs=true、callouts=false)。info 字符串为空时,表示没有语言标识符。
步骤 3:执行 A 至 H 检查
检查 S(未关闭围栏)已在步骤 2 中报告,无需单独检查。
检查 A — 缺少或未知的语言标识符
有两种不同的检查结果,严重程度也不同。
A-1(低):info 字符串为空。 标记 info 字符串为空的代码块。如果可以明确判断内容类型,请推断标识符:HTTP 方法行(GET、PUT、POST、DELETE、HEAD)表示 console;以 { 或 [ 开头的正文表示 json;key: value 格式的行表示 yaml;curl、apt-get 或 $ 提示符表示 bash;import、def 或 print( 表示 python;public class 表示 java;const、let 或 => 表示 javascript。否则建议使用 text。许多 info 字符串为空的代码块是纯输出、frontmatter 示例或路径,因此不要猜测它们使用的语言。
A-2(中):标识符明显与内容不符。 仅当标识符与内容矛盾时(例如,在没有 HTTP 方法行的响应中使用 console,或在正文不是类似有效 JSON 的文本时使用 json),或当标识符出现在下方的常见错误表中,才标记代码块。不要仅仅因为标识符不在认可列表中就标记。
认可的标识符(在 elastic/docs-content 中使用;这些标识符都不要标记):
- Elasticsearch 和 Kibana:
console、console-result、esql、eql、painless、kql、kuery - Shell:
bash、sh、shell、powershell、cmd、terminal - 数据和配置:
json、jsonc、yaml、yml、xml、toml、ini、properties、dockerfile、nginx、gradle、diff - 编程语言:
python、py、javascript、js、typescript、ts、java、go、ruby、php、csharp、swift、kotlin、groovy、sql、html、handlebars - 文本和文档:
text、txt、plaintext、markdown、md、asciidoc、mermaid、http
将以下内容标记为常见错误,并按建议修正:
| 找到的值 | 建议值 | 原因 |
|---|---|---|
JSON(任何非小写标识符) | json | 标识符必须使用小写。 |
console-response | console-result | 文档中规定的响应标识符是 console-result。如果不确定,请先对照 docs-builder 代码语法页面核实,再进行标记。 |
language | 实际标识符 | 从模板中遗留的占位符。 |
检查 B — 变量替换与 subs=true
此检查仅检查代码块正文。正文、标题或链接目标中的 {{var}} 不在检查范围内。只有 docs-builder 会替换的变量才计入。许多代码块中的 {{...}} 是字面模板语法(Mustache、Go 模板、工作流表达式、Painless 或 ingest 模板)。将这些标记出来是错误的。
- 从目标文件向上查找
docset.yml,并解析其中的subs:键。将文件所在目录计为第 1 层。遇到 git 根目录(存在.git/)或向上查找达到 10 层时停止,以先发生者为准(生成的参考页面可能位于docset.yml下方很深的位置)。 - 使用
\{\{\{?\s*([^{}|]+?)\s*(\|[^{}]*)?\}?\}\}从每个代码块中提取变量名。此操作会去掉变换过滤器,例如{{version.stack|M.M}},并处理{{{var}}}。 - B-1(中): 仅当至少有一个提取出的名称是
subs:中的键时,才标记没有subs=true的代码块。报告匹配的变量名。 - B-2(低): 标记含有
subs=true但完全没有{{的代码块。 - 如果
{{...}}中的名称都不在subs:中,则不要标记该代码块,例如{{steps.create_case.output.case.id}}、{{event.alerts}}、{{foreach.item}}、{{#toJson}}、{{ctx.payload.hits.total}}或{{.Config.Image}}。 - 如果未找到
docset.yml,则有效变量名未知,因此 B-1 应作为未验证提示(低)处理。仅当代码块的语言属于 shell、YAML、文本或标记类型(bash、sh、shell、yaml、yml、text、txt、markdown、md、ini、toml、xml、properties),或未指定语言,且步骤 2 提取出的名称是单个标识符(字母、数字、_或-,并以字母开头,例如{{version}})时,才标记没有subs=true的代码块。不要标记包含点、#、/、[、空格或其他字符的名称,因为它们属于模板语法。跳过其他所有语言(console、console-result、json、js、ts、html、handlebars、painless等)的代码块;其中的{{x}}几乎总是模板语法。将每项发现标注为“可能是替换变量,但由于未找到docset.yml而无法验证”。B-2 照常运行。
检查 C — 应改为提示框的行内注释
对于语言为 bash、sh、shell、console、yaml、python、javascript、js、typescript、java、go 或 ruby 的代码块:标记同一行代码之后出现 # 或 //,且注释内容是面向读者的说明的行。
豁免情况:shebang 行、独立注释行(同一行没有代码)、字符串中的 #,以及以提示框标记(<1>、// <1> 或 # <1>)结尾的行。
报告包含注释的行,而不是代码块的起始 fence,以便读者能直接跳转到该行(与检查 D 规则相同)。建议:替换为显式提示标记(<N>),并在代码块后添加编号列表。
Check D — console 和 console-result 代码块中的 JSON 验证
console 代码块是 API 请求(客户会复制并粘贴);console-result 代码块是响应(仅供展示)。每个 console 代码块包含一个或多个 API 调用:一行 HTTP 方法+路径(可以缩进放在列表项内),后面跟着可选的 JSON 正文;多个调用会重复这一结构。
将 Step 2 的输出传入此技能附带的 check_console.pl 脚本。脚本会自行找出 console 和 console-result 代码块,因此无需逐个循环处理:
awk -f <skill-dir>/scripts/extract_fences.awk <files> | perl <skill-dir>/scripts/check_console.pl --stdin
若要检查单个代码块,请将其四个值作为四个独立参数传入:perl <skill-dir>/scripts/check_console.pl FILE OPEN CLOSE KIND,其中 KIND 是信息字符串中的第一个词(而不是像 "60 65" 这样的带引号字符串)。脚本需要 perl 和 jq。它会在系统临时目录中保留一个临时文件,并在退出时将其删除;它绝不会写入所检查的文件。每行标准输出的格式为 file:line:High:D:message,其中包含真实的文件行号;若无标准输出,则表示所有代码块都没有问题。脚本还会在标准错误中打印一行摘要(checked N block(s), M finding(s)),因此可以区分检查无问题和脚本未执行。参数错误会以状态码 2 退出并显示用法说明:切勿将错误信息误认为代码块没有问题。若 console 或 console-result 代码块未闭合,则会跳过并在标准错误中提示,因为 Check S 已会报告所有未闭合代码块。将脚本输出作为 Check D 的发现;不要手动重新实现其规则。如果脚本缺失,请在 Notes 部分说明,并跳过 Check D,而不要凭记忆重写脚本。
脚本的功能:
- **请求:**按方法行将
console代码块拆分为多个请求,并分别验证每个正文。console-result代码块视为一个正文,只有以{或[开头时才会验证。文本表格(_cat输出)和不完整片段不是 JSON,因此会跳过。 - **预处理:**移除提示标记(
// <1>、# <1>、单独的<1>,以及同一行上的多个标记)、独立的注释行和代码后的行尾说明性注释(Kibana Console 接受这些内容,Check C 会报告它们)。将{{var}}/{{{var}}}替换为null(这是有效的 JSON 词元,因此{"size": {{size}}}这样的未加引号值仍然有效),并将 Painless 的"""..."""字符串替换为占位符,同时保留行号。 - **
...:**在console代码块中,字符串之外的...属于 High,因为客户会复制并粘贴请求(应将其替换为实际值)。在console-result代码块中允许使用...,因为作者可能会省略较长的响应示例,所以不会将其报告为问题。脚本会在解析前将其规范化([...]、{...}、"key": ...、[...list of hits...]这样的文字占位符、单独的...行)。若console-result代码块使用了...,之后又解析失败,则会跳过,因为该响应本来就是不完整的。 - **尾随逗号:**在
}或]前的逗号属于 High。脚本会报告,不会悄悄修复。唯一会忽略的逗号,是移除单独的...行或console-result中尾随的"key": ...值后产生的悬空逗号;其他所有尾随逗号,包括[...]后的逗号,仍会报告。 - **NDJSON:**对于
_bulk和_msearch,每个非空正文行都会作为独立的 JSON 文档进行验证。 - 行号:
jq报告的是相对于正文的行号,脚本会将其映射回文件行号。对于Expected separator between values,jq指向下一个词元,但缺失的逗号位于上一行末尾,因此应将^放在那里。在标题中使用脚本报告的行号,并在上下文片段中将^放在上一行的末尾。
不要报告:Painless 脚本、EQL/ES|QL 字符串值、提示标记、模板变量、文本表格响应,或带引号字符串中的 ...。
检查 E — 非保留域名
标记代码块中使用虚构占位域名且顶级域名真实存在的 URL 或主机名(例如 mycompany.com、mycluster.io)。仅检查 URL 和主机名:不要标记电子邮件地址(例如示例数据中的 [email protected])。
安全 — 不要标记:example.com/net/org 及其任何子域名、.example/.test/.localhost/.invalid 顶级域名、localhost、回环地址(127.x.x.x)、RFC 5737 文档 IP(192.0.2.x、198.51.100.x、203.0.113.x)、私有地址范围,以及已知的合法域名:elastic.co、amazonaws.com、azure.com、googleapis.com、docker.com、github.com、pypi.org、npmjs.com,以及类似的知名注册表和云服务提供商域名。
建议:替换为 example.com 或其子域名,例如 my-cluster.example.com。
检查 F — 验证 console 块中的 API
首先收集所有文件中唯一的 METHOD /path 值。对于每个唯一端点(跳过本次运行中已验证过的端点),使用下方所述的查询调用 mcp__elastic-docs__search_docs(productFilter:elasticsearch,sectionFilter:api,pageSize:3),查找匹配的 API 参考页面(URL 以 /docs/api/doc/elasticsearch/operation/ 开头)。只有在下方规则要求时,才使用 mcp__elastic-docs__get_document_by_url(includeBody:true)获取页面。
检查 F 仅涵盖 Elasticsearch REST 端点。对于路径以 kbn:、/api/ 或 api/ 开头的请求(Kibana、Fleet 和 Cloud API),或完整 URL,请跳过,并在 Notes 中列为 "not an Elasticsearch API, not looked up"。不要将其报告为已移除或已重命名。
根据调用的作用查找页面,而不是根据路径。API 参考页面以操作命名("Run a search"、"Update field mappings"、"Get the cluster health status"),而使用路径文本构成的查询(如 GET _search API 或 PUT _mapping API)不会返回有用结果。请使用简短的自然语言描述操作,并根据方法和路径判断具体操作(例如 run a search、update field mappings、cluster health、reindex documents)。多个操作可能匹配同一描述(cluster health 也会返回 CAT health 和 health report 页面),因此接受候选页面前请先核对。仅在以下情况获取排名最高的候选页面,并确认其文档中记载的方法和路径:(a) 排名前列的多个结果都可能是该操作;(b) 块中的路径在 API 段之后还包含字面路径段(_cluster/health 和 _cluster/settings 是不同操作,_cat/indices 和 _cat/shards 也是如此);或 (c) 顶层请求体键不属于豁免项,需要对照页面检查。否则,如果排名最高的结果标题明确对应此操作(例如 GET /my-index/_search 对应 "Run a search"),即可接受,并在 Notes 中注明 "matched by title, not fetched"。获取页面时,请以页面中记录的方法和路径为准进行匹配,切勿依据 URL slug(_search 操作的 slug 是 operation-search):文档路径中的 {param} 代表任意单个路径段(例如块中的索引名或 id),末尾可选参数(例如 /_scripts/{id}/{context} 中的 {context})可以不出现在块中。页面记录的 HTTP 方法可能与块中使用的方法不同(例如 _search 仅记录了 POST),这不属于问题。如果没有结果匹配,请用不同措辞重试一次(可能的页面标题或操作的同义词),再得出不存在匹配项的结论。
- 找不到匹配页面(重试后仍无结果): 将该端点标记为可能已移除或重命名。
- 已弃用字段: 如果文档中某个顶层请求体键旁标有 "deprecated",请将其标记并注明替代项。
- 无法识别的字段(提示): 如果某个顶层键完全未出现在文档正文中,请注明需核实。
- 未列出请求体字段: 如果获取的页面正文只有说明文字,未列出请求字段,则针对该端点报告一次 "cannot verify request fields"。不要将其键列为无法识别字段提示。
不要标记:以 _ 开头的元数据字段、嵌套对象键、标准 Search DSL 字段(query、aggs、sort、size、from、highlight、knn、retriever、script),以及标准 create-index 请求体字段 settings、mappings、aliases。
始终包含 API 文档 URL:有问题时放在问题详情中;否则放在 Notes 部分。
Check G — ES|QL 语法验证
**G-1 — 源命令:**第一个非空、非注释行必须是 FROM、PROMQL、ROW、SHOW INFO 或 TS。标记以处理命令开头(缺少源命令)或以 SELECT 开头(SQL 语法)的代码块。
**G-2 — 管道命令名称:**每个以 | 开头的行都必须使用已识别的命令:CHANGE_POINT、COMPLETION、DEDUP、DISSECT、DROP、ENRICH、EVAL、FORK、FUSE、GROK、HIGHLIGHT、INLINE STATS、IP_LOCATION、KEEP、LIMIT、LOOKUP JOIN、METRICS_INFO、MMR、MV_EXPAND、REGISTERED_DOMAIN、RENAME、RERANK、SAMPLE、SORT、STATS、TS_COLLAPSE、TS_INFO、URI_PARTS、USER_AGENT、WHERE。标记无法识别的命令名称。
**G-2a — 不完整的管道命令:**仅识别命令名称还不够——标记存在但同一行后面没有参数的管道命令。所需参数:
| 命令 | 要求 |
|---|---|
KEEP、DROP | 至少一个字段名称 |
WHERE | 一个条件表达式 |
SORT | 至少一个字段 |
EVAL、STATS | 至少一个表达式 |
RENAME | 至少一个 old AS new 配对 |
DISSECT、GROK | 一个输入字段和一个模式 |
ENRICH | 一个策略名称 |
LOOKUP JOIN | 一个查找索引名称和 ON <field> |
**G-3 — SQL 语法:**标记行首的 SELECT、GROUP BY、原始 JOIN(不带 LOOKUP)和 ORDER BY——这些 SQL 语法在 ES|QL 中无效。
G-4 — 不要标记:// 注释行、管道后的 WHERE、字符串中的 FROM,以及只有注释或空行的代码块。
Check H — Painless 脚本验证
Painless 有两种出现形式:独立的 ```painless 代码块,以及 console 代码块中的三引号 """...""" 字符串。请在 Check D 将它们替换为占位符之前,使用 Bash 通过多行 perl 匹配从 console 代码块中提取内嵌 Painless。
对每个 Painless 源执行以下检查:
**H-1 — 分隔符配对:**统计 {}、[]、() 配对。标记任何不匹配情况。不要统计字符串字面量中的分隔符。
H-2 — 已弃用的 .getValue() API:doc 字段上的 .getValue() 已弃用——标记任何用法,并建议改用 .value(例如,doc['field'].value)。
**H-3 — 不可用的 API:**标记对 System.out、System.err、System.exit、Thread.sleep、Runtime.getRuntime 或 ProcessBuilder 的使用——这些在 Painless 沙箱中均不可用。建议改用 Painless 执行 API(POST /_scripts/painless/_execute)进行调试。
H-4 — 不要标记:// 注释行、引号字符串中的分隔符、没有分隔符的单表达式脚本、空代码块或仅含注释的代码块。
严重级别
为每项发现分配以下默认严重级别之一,并将其写入问题详情。
| 严重级别 | 发现 |
|---|---|
| 高 | console 代码块中的 D ...;D 无效 JSON 或尾随逗号;G-1 将 SQL 语法用于 ES|QL |
| 中 | S 未闭合的围栏;A-2 错误的标识符(包括可能错误项表);B-1 缺少 subs=true(若未找到 docset.yml,且该项为未经验证的建议,则为低);F 已删除或已弃用的 API 或字段;G-1 缺少源命令、G-2、G-2a、G-3;H-1、H-3 |
| 低 | A-1 空的 info 字符串;B-2 未使用的 subs=true;C 行内注释;E 域名;F 无法识别字段的建议;H-2 |
第 4 步:生成报告
输出三个部分(汇总表、问题详情、备注)。如果某个文件没有问题,则省略对应的表格。
第 1 部分——汇总表:
## Code Sample Validation Report
**Target:** <path>
**Files checked:** N
**Issues found:** N
### Summary
#### All blocks (Checks A, B, C, E, S)
| File | Missing/wrong lang | subs=true | Callout | Domain | Unclosed fence | Total |
|------|--------------------|-----------|---------|--------|----------------|-------|
#### Console / console-result blocks (Checks D, F)
| File | Ellipsis | JSON errors | API Validation | Total |
|------|----------|-------------|----------------|-------|
#### ES|QL blocks (Check G)
| File | Source cmd | Pipe cmd | SQL-ism | Total |
|------|------------|----------|---------|-------|
#### Painless blocks (Check H)
| File | Unbalanced delimiters | Deprecated API | Unavailable API | Total |
|------|-----------------------|----------------|-----------------|-------|
第 2 部分——问题详情,每个文件对应一个子节:
#### `path/to/file.md` — Line N — Check X — Severity — Short title
> **Issue:** description
> **Suggestion:** fix
```
context lines around the error
^
```
对于 JSON 错误,附上 jq 错误消息,以及出错行前后最多各 2 行上下文,并用 ^ 指示出错位置。对于 Painless 内联脚本,注明问题来自独立代码块还是嵌入的三引号字符串。
第 3 部分——备注(仅供参考,不计为问题)。始终打印此部分,即使没有问题也不例外。每项各列一条:
- 已跳过或受限的检查,每次运行仅打印一次,例如“检查 B-1 作为未经验证的提示运行:未找到
docset.yml”。 - 检查 F 查询过的每个端点及其 API 文档 URL(若页面未获取,则添加“按标题匹配,未获取页面”),以及每个被跳过的非 Elasticsearch API 请求。
- API 页面未列出请求正文任何字段的每个端点(“无法验证请求字段”)。
如果未发现问题,则用“所有已检查文件均未发现问题。”替代第 2 部分,然后输出第 3 部分。
**输出模式:**除非提供了 --output <path>,否则打印到控制台;若提供了该参数,则将报告写入 Markdown 文件并确认路径。
指南
- 对于块级问题(A、B、E、S),报告起始围栏所在的行号。对于单行问题,报告具体文件行:检查 C 报告注释所在的行,检查 D 报告脚本指出的行。
- 不要报告误报——跳过含义不明确的内联注释;仅在不平衡明确时应用 H-1。
- 静默跳过非 Markdown 文件。记录无法读取的文件并继续。