mcpField
D
docs-validate-code-samples

docs-validate-code-samples

github.com/elastic测试与质量72145 次安装2 天前更新
代码仓库

关于 docs-validate-code-samples

用于按 docs-builder 规则检查 Elastic Markdown 文档代码块的技能,只报告问题,不修改源文件。

主要功能

  1. 01支持文件、目录和 glob 目标
  2. 02使用附带 AWK 脚本提取代码围栏
  3. 03检查 Console 请求与 JSON
  4. 04检查 Painless 代码
  5. 05按严重级别生成报告

适用场景

  1. 01发布 Elastic 文档前检查代码示例
  2. 02查找格式错误的 Console 请求或 JSON 响应
  3. 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.json
scripts/
check_console.pl
extract_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-responseconsole-result文档中规定的响应标识符是 console-result。如果不确定,请先对照 docs-builder 代码语法页面核实,再进行标记。
language实际标识符从模板中遗留的占位符。
检查 B — 变量替换与 subs=true

此检查仅检查代码块正文。正文、标题或链接目标中的 {{var}} 不在检查范围内。只有 docs-builder 会替换的变量才计入。许多代码块中的 {{...}} 是字面模板语法(Mustache、Go 模板、工作流表达式、Painless 或 ingest 模板)。将这些标记出来是错误的。

  1. 从目标文件向上查找 docset.yml,并解析其中的 subs: 键。将文件所在目录计为第 1 层。遇到 git 根目录(存在 .git/)或向上查找达到 10 层时停止,以先发生者为准(生成的参考页面可能位于 docset.yml 下方很深的位置)。
  2. 使用 \{\{\{?\s*([^{}|]+?)\s*(\|[^{}]*)?\}?\}\} 从每个代码块中提取变量名。此操作会去掉变换过滤器,例如 {{version.stack|M.M}},并处理 {{{var}}}。
  3. B-1(中): 仅当至少有一个提取出的名称是 subs: 中的键时,才标记没有 subs=true 的代码块。报告匹配的变量名。
  4. B-2(低): 标记含有 subs=true 但完全没有 {{ 的代码块。
  5. 如果 {{...}} 中的名称都不在 subs: 中,则不要标记该代码块,例如 {{steps.create_case.output.case.id}}、{{event.alerts}}、{{foreach.item}}、{{#toJson}}、{{ctx.payload.hits.total}} 或 {{.Config.Image}}。
  6. 如果未找到 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 文件。记录无法读取的文件并继续。

评论

支持 Markdown