关闭

Tryaivo™技术

文档站跑 agent-docs-spec 审计,27 页 llms.txt 全通

2026-11-10 03:20:08 浏览:

文档站跑 agent-docs-spec 审计,27 页 llms.txt 全通

一句话结论:一个 Jekyll 文档站用 agent-docs-spec 的 afdocs 检查器做基线审计,8 过 6 失 9 跳过,改 3 处后 6 项失败里有 4 项关闭。

背景:页面在浏览器里完好无损,代理拿到的是残页

这是 Deepti Nalavade 的技术写作作品集:Jekyll 生成、Just the Docs 主题、托管在 GitHub Pages 上,内容是整套文档样本——用户指南、手写 REST API 参考、由 OpenAPI 规范生成的参考文档,外加 PRD、备忘录、会议纪要、发布说明这类跨职能写作。对人是合格的:导航清楚、侧边栏正常、代码块可读、示例响应完整。

问题在于代理读文档的方式和人不一样。作者列了三条差异,每一条都不报错:

  1. 多数代理只取原始 HTTP 响应、不执行 JavaScript,客户端渲染出来的内容对它们等于不存在。

  2. 抓取管线在固定字符数处静默截断(Claude Code 大约是 100,000 字符),而代理并不知道自己拿到的是半页。

  3. 代理没有"自己发现" /llms.txt 这类索引的能力,除非页面上或者索引里有东西明确告诉它存在。

三条差异的共同点是:不报错、不断链、正常 QA 走一遍也看不出来。所以整件事的第一步不是改,而是先量。

基线:先让检查器给分,再决定改什么

作者用的不是自拟标准,而是 agent-ecosystem/agent-docs-spec 这份规范的配套命令行工具 afdocs,在改动任何代码之前先对着线上部署的站点跑了一遍:

npx afdocs check https://deeptin1.github.io/portfolio/

结果:8 passed / 6 failed / 9 skipped,共 23 项检查。

已经通过、不需要动的四类:每页都是服务端渲染(没有客户端渲染的隐患)、没有死链或重定向链、缓存头干净、正文都落在每页前 10% 之内(没有被样板内容埋掉)。

失败面收得很窄,只有三处:站点上根本没有 llms.txt;页面上没有任何面向代理的发现指令;有一页超过了截断线。其中"没有 llms.txt"是源头——依赖它的 5 项检查因为没有文件可查,只能判失败或直接跳过。

落地清单一:根目录 llms.txt 按提案骨架写,覆盖 27 个真实页面

新文件只用了 llmstxt.org 提案的四类元素:H1 站名、blockquote 摘要、H2 分节、markdown 链接列表。没有自定义元数据字段,没有 JSON 头,没有版本声明。

怎么保证"覆盖全站"?作者的做法是把文件里的链接和构建产物的 sitemap 做 diff——两边差一条就不算完成。覆盖结果是 27 个真实内容页。

下面是线上抓到的文件原文前 22 行(2026-11-10 抓取,未改动任何字段):

## Deepti Nalavade—Technical Writing Portfolio

> A technical writing portfolio showcasing developer documentation, product and cross-functional writing, content strategy work, and open-source documentation contributions. Built with Jekyll and the Just the Docs theme, hosted on GitHub Pages.

## About

- [Overview](https://deeptin1.github.io/portfolio/): Bio, background, and a table of contents for every section of this portfolio.

## Developer documentation

Samples demonstrating developer-focused technical writing: a getting-started guide, a full REST API reference, and an OpenAPI/Swagger-generated reference, for the same third-party ExerciseDB API.

- [Developer documentation](https://deeptin1.github.io/portfolio/docs/developer-documentation/): Section landing page.
- [User guide](https://deeptin1.github.io/portfolio/docs/user-guide/): Getting-started guide for the ExerciseDB API—prerequisites, quickstart, and procedural how-tos.

结构上可以注意两点:H2 下面先给一句分节说明再列链接;链接用绝对 URL 而不是相对路径,代理不必推断域名。

落地清单二:一条全站生效的视觉隐藏发现指令

有了索引还不够。代理抓的是渲染后的 HTML,页面上没有任何东西提到 /llms.txt,它就不知道有这个文件。作者在主内容区最顶部加了一个视觉隐藏元素指向它,并且只在共享布局里实现一次——这样全站页面自动覆盖,不用逐个页面改模板。

以下是按作者描述复现的形态示意(不是原文代码):

<!-- _layouts/default.html,置于 <main> 内的第一个元素 -->
<p class="agent-hint" aria-hidden="false">
  Documentation index for agents: <a href="/portfolio/llms.txt">/llms.txt</a>
</p>
.agent-hint { position: absolute; left: -9999px; height: 1px; overflow: hidden; }

要点在"位置"而不是"样式":检查器判断的是指令距离正文起点的比例,所以它必须出现在 <main> 的最前面。这一点在下面的踩坑里还会回头咬一次。

落地清单三:只砍那一页,不动其他 27 页

基线审计点名超限的只有 1 页(共 28 页):API 文档参考页,42K 原始 HTML,转成 markdown 后 173K,直接跨过 100K 截断线。

作者去查体积来源,发现高度集中在一处:一个示例 API 响应里塞了 5 个完整 JSON 对象,每个对象重复同一套 schema,还带一段冗长的 instructions 数组。光这一块就占整页原始源码的 34%。

处理方式不是删字段,而是删重复:5 个对象形状完全相同,展示 5 个并不比展示 1 个多提供任何信息,只是重复。于是砍到 1 个代表性对象,加一句说明"真实响应里还有更多同形状条目";另外把两处参考表格里、与前文 JSON 完全重复的数组单元一并压缩。改动后,同一页变成 31K HTML → 10K markdown。

结果:六项检查的前后读数

每一项都在本地起服务、用同一个 afdocs 逐项复验,作者给出的前后对比如下:

检查项改动前改动后
llms-txt-existsFail——没有 llms.txtPass
llms-txt-validSkipped(依赖 exists)Pass——H1/blockquote/分节结构正确
llms-txt-sizeSkippedPass——5,337 字符,远低于 50,000 阈值
llms-txt-links-resolveSkippedPass——27 条同源链接全部可达
llms-txt-directive-htmlFail——找不到指令Pass——所有测试页均命中
page-size-htmlFail——28 页中 1 页超 100K(42K HTML → 173K markdown)Pass——同页 31K HTML → 10K markdown

这里有一个口径需要说清:作者复盘时写的是"关闭 6 项失败中的 4 项",而按检查器给出的前后读数,六项最终全部为 Pass。差异在于 llms-txt-valid、llms-txt-size、llms-txt-links-resolve 三项在改动前是因依赖缺失而跳过,不是失败。把 skipped 也算进失败分母,就得到"6 项失败"这个说法。写复盘时最好把 skipped 和 failed 分开报,否则读者会以为有三项曾经真的红过。

本站独立复核:2026-11-10 的线上读数

作者的数字是自述,所以我们把文件本身也核了一遍。2026-11-10 直连抓取 https://deeptin1.github.io/portfolio/llms.txt:

复核项实测值
HTTP 状态 / Content-Type200 / text/plain; charset=utf-8
体积5,727 字节 / 5,715 字符 / 54 行
结构H1 × 1、blockquote × 1、H2 × 6
链接共 28 条,同源去重后 27 条,逐条请求 27/27 返回 200,死链 0
sitemap.xml30 条 <loc>(含 404 页),与作者"27 页"口径吻合

字符数对不上(作者 5,337,本机 5,715)不是笔误,也不是文件改了:作者写完这篇案例后又把案例页本身加进了索引,而 5,337 是案例发布之前的读数。差量来自新增条目,属于正常演进——但这也说明一件事:llms.txt 的体量是动态的,任何写进文章的字符数都会随时间过期,必须标查询日期。

复盘:3 条可以直接抄的配置动作

  1. 先跑检查器,再动手。 一条 npx afdocs check <线上URL> 就能把"我觉得还行"变成 23 项可分派的清单,比逐页目测快一个量级,也避免改到本来没坏的地方。

  2. 索引用 sitemap 做 diff 验收。 llms.txt 覆盖不全是最常见的半成品状态:看起来有文件,实际漏页。把文件链接提取出来和构建产物 sitemap 对差集,比凭印象说"都加了"可靠。

  3. 发现指令写在共享布局,且必须是 <main> 的第一个元素。 一次实现全站生效;位置决定它是否被判为"埋在正文深处"。

复盘:2 条踩坑红线

  • 404 页仍然被判"指令埋得过深"。 线上复跑后,404 页的指令位置被判在 50% 之后。原因是那页几乎没有正文,<main> 之前的侧边栏和图标标记占了它总字节数的大部分,比例算法天然吃亏。作者选择不改:单页、低流量,而且代理走到 404 页本身就已经找不到内容了。结论是这类比例型检查对小页面必然失真,别为了分数给 404 页硬塞内容。

  • 静态托管做不了内容协商,就别装做得了。 afdocs 里有两项检查是刻意不做的:markdown-url-support(.md 变体)和 content-negotiation(Accept: text/markdown)。GitHub Pages 是纯静态托管,服务端内容协商不存在;要做 .md 变体就得为每页维护一份平行的 markdown 副本,等于给自己养一份必然过期的影子站。作者明确写了"放着"——留下两项诚实的未通过,好过塞一份假的机器可读层。

补充一句规范侧的事实:agent-docs-spec 自己的研究也指出,自动化爬虫目前基本不会去取 llms.txt 这类资源,真正的消费者是实时工作流里干活的编码代理。这也解释了为什么作者的验收重点落在"指令可见"和"单页不超截断线",而不是"让 Google 收录我的 llms.txt"。

标注

  • llms.txt 版本:v1 结构(H1 + blockquote 摘要 + H2 分节 markdown 链接);没有 v2 修订里的 rel=alternate type=text/markdown、rel=describedby 与内容协商支持。

  • 规范状态:llmstxt.org 提案与作者主张;LLM 厂商未官方确认该文件的消费方式。

  • 数据口径:8 过 6 失 9 跳过、27 页覆盖、5,337 字符、42K→173K/31K→10K 均为作者自述的 afdocs 检查器输出,非第三方复现。本机 2026-11-10 复核仅验证文件形态(状态码、类型、体积、结构、链接可达)与 sitemap 数量,不构成对作者检查结果的独立验证。

  • 无量化收益:本文不包含排名、AI 引用、流量或转化数据,效果部分仅为结构性观察,未宣称任何可见度增益。