@farming-labs/docs 0.2.120 → 0.2.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +8 -0
  2. package/dist/access.d.mts +13 -0
  3. package/dist/access.mjs +56 -0
  4. package/dist/agent-conformance.d.mts +60 -0
  5. package/dist/agent-conformance.mjs +538 -0
  6. package/dist/agent-contract.d.mts +47 -0
  7. package/dist/agent-contract.mjs +645 -0
  8. package/dist/agent-detection.d.mts +6 -0
  9. package/dist/agent-detection.mjs +83 -0
  10. package/dist/agent-evals.d.mts +224 -0
  11. package/dist/{agent-evals-BLNLvXbG.mjs → agent-evals.mjs} +7 -565
  12. package/dist/agent-feedback-evaluations-node.mjs +38 -0
  13. package/dist/agent-feedback-evaluations.d.mts +91 -0
  14. package/dist/{agent-feedback-evaluations-CcMGlgsO.mjs → agent-feedback-evaluations.mjs} +2 -2
  15. package/dist/agent-feedback-loop.d.mts +45 -0
  16. package/dist/{agent-feedback-loop-CcZL_sb1.mjs → agent-feedback-loop.mjs} +1 -1
  17. package/dist/agent-maintenance.d.mts +71 -0
  18. package/dist/{agent-maintenance-CuZ7iAKq.mjs → agent-maintenance.mjs} +1 -1
  19. package/dist/agent-provenance.d.mts +23 -0
  20. package/dist/agent-provenance.mjs +77 -0
  21. package/dist/agent-scope.mjs +321 -0
  22. package/dist/agent-skills-archive.mjs +86 -0
  23. package/dist/agent-skills-bundle.d.mts +1 -1
  24. package/dist/{agent-skills-progressive-disclosure-PjPtTYL-.mjs → agent-skills-progressive-disclosure.mjs} +2 -2
  25. package/dist/{agent-skills-server-D5IuvwM7.d.mts → agent-skills-server.d.mts} +3 -3
  26. package/dist/{agent-skills-server-BC7md56k.mjs → agent-skills-server.mjs} +2 -2
  27. package/dist/agent-skills-vite.d.mts +3 -3
  28. package/dist/agent-skills-vite.mjs +1 -2
  29. package/dist/agent-surface-drift.mjs +236 -0
  30. package/dist/{golden-evaluations-CLUViycJ.mjs → agent-usefulness.mjs} +5 -290
  31. package/dist/{content-change-hydration-wqXIZHp6.d.mts → agent.d.mts} +10 -411
  32. package/dist/agent.mjs +2729 -0
  33. package/dist/{analytics-w3FDOksr.d.mts → analytics.d.mts} +2 -2
  34. package/dist/{analytics-p8Vhhu2P.mjs → analytics.mjs} +2 -2
  35. package/dist/{sitemap-server-BUdPBcGs.d.mts → api-reference.d.mts} +2 -6
  36. package/dist/api-reference.mjs +1481 -0
  37. package/dist/{audience-exposure-DBrFWULA.d.mts → audience-exposure.d.mts} +1 -1
  38. package/dist/audience-exposure.mjs +8 -0
  39. package/dist/audience.d.mts +44 -0
  40. package/dist/audience.mjs +1654 -0
  41. package/dist/authoring-mcp.d.mts +44 -0
  42. package/dist/{authoring-mcp-CiCUQe3o.mjs → authoring-mcp.mjs} +3 -3
  43. package/dist/browser.d.mts +6 -4
  44. package/dist/browser.mjs +6 -7
  45. package/dist/build.d.mts +8 -0
  46. package/dist/build.mjs +8 -0
  47. package/dist/changelog.d.mts +20 -0
  48. package/dist/changelog.mjs +39 -0
  49. package/dist/{agent-export-CA6IUQ7T.mjs → cli/agent-export.mjs} +19 -45
  50. package/dist/{agent-CYk2DxY7.mjs → cli/agent.mjs} +6 -35
  51. package/dist/{agents-BUiVmj8i.mjs → cli/agents.mjs} +5 -18
  52. package/dist/cli/api-doctor.mjs +206 -0
  53. package/dist/{authoring-C2ymvYWl.mjs → cli/authoring.mjs} +2 -8
  54. package/dist/{cloud-6eKrlmKd.mjs → cli/cloud.mjs} +3 -3
  55. package/dist/{codeblocks-CHpgAwY-.mjs → cli/codeblocks.mjs} +2 -2
  56. package/dist/cli/config.d.mts +11 -0
  57. package/dist/{config-BZnKAj_Y.mjs → cli/config.mjs} +1 -1
  58. package/dist/cli/dev-banner.mjs +58 -0
  59. package/dist/{dev-Bnn-NUNa.mjs → cli/dev.mjs} +3 -57
  60. package/dist/{doctor-Bfkrkzad.mjs → cli/doctor.mjs} +41 -226
  61. package/dist/{downgrade-CHMta85e.mjs → cli/downgrade.mjs} +2 -2
  62. package/dist/cli/errors.d.mts +4 -0
  63. package/dist/{errors-BtnVLhNF.mjs → cli/errors.mjs} +1 -1
  64. package/dist/{feedback-evals-DMilg9pY.mjs → cli/feedback-evals.mjs} +3 -36
  65. package/dist/{feedback-DL4T5oIO.mjs → cli/feedback.mjs} +1 -1
  66. package/dist/cli/git-dates.mjs +35 -0
  67. package/dist/cli/golden-evaluations.mjs +52 -0
  68. package/dist/cli/index.d.mts +2 -3
  69. package/dist/cli/index.mjs +38 -38
  70. package/dist/{init-K0LmXDZ5.mjs → cli/init.mjs} +3 -3
  71. package/dist/{mcp-iLCmnPJI.mjs → cli/mcp.mjs} +7 -19
  72. package/dist/{openapi-diff-HcpUNjA-.mjs → cli/openapi-diff.mjs} +2 -7
  73. package/dist/{openapi-sdk-BQKC6H-_.mjs → cli/openapi-sdk.mjs} +2 -7
  74. package/dist/{openapi-source-DK6ts_Bk.mjs → cli/openapi-source.mjs} +2 -2
  75. package/dist/{package-version-HJhYeq4M.mjs → cli/package-version.mjs} +2 -2
  76. package/dist/{propose-JmUv_BTt.mjs → cli/propose.mjs} +1 -1
  77. package/dist/{review-C6EmbV0l.mjs → cli/review.mjs} +16 -15
  78. package/dist/{robots-DteZB8vl.mjs → cli/robots.mjs} +3 -7
  79. package/dist/{search-Bhuv_YsL.mjs → cli/search.mjs} +7 -20
  80. package/dist/{sitemap-DW5LDKDa.mjs → cli/sitemap.mjs} +7 -42
  81. package/dist/{skills-BAPQLSCm.mjs → cli/skills.mjs} +5 -19
  82. package/dist/{templates-BuQ8YsGv.mjs → cli/templates.mjs} +1 -1
  83. package/dist/{upgrade-DlQMOFV1.mjs → cli/upgrade.mjs} +2 -2
  84. package/dist/{utils-C7KDrPpA.mjs → cli/utils.mjs} +1 -1
  85. package/dist/client/react.d.mts +2 -18
  86. package/dist/client/react.mjs +1 -157
  87. package/dist/{cloud-analytics-D14HplI7.mjs → cloud-analytics.mjs} +1 -1
  88. package/dist/{cloud-ask-ai-BrsTXQf6.d.mts → cloud-ask-ai.d.mts} +2 -2
  89. package/dist/{cloud-ask-ai-BqKhjpS5.mjs → cloud-ask-ai.mjs} +3 -3
  90. package/dist/code-blocks.d.mts +51 -0
  91. package/dist/{code-blocks-GYdvS3BH.mjs → code-blocks.mjs} +2 -2
  92. package/dist/code-group-mdx.d.mts +11 -0
  93. package/dist/code-group-mdx.mjs +57 -0
  94. package/dist/content-change-hydration.d.mts +87 -0
  95. package/dist/{openapi-mcp-BIOTLiA7.mjs → content-change-hydration.mjs} +5 -210
  96. package/dist/content-changes.d.mts +47 -0
  97. package/dist/content-changes.mjs +216 -0
  98. package/dist/create-theme.d.mts +48 -0
  99. package/dist/create-theme.mjs +57 -0
  100. package/dist/date.d.mts +5 -0
  101. package/dist/date.mjs +17 -0
  102. package/dist/define-docs.d.mts +9 -0
  103. package/dist/define-docs.mjs +46 -0
  104. package/dist/docs-cloud-client.d.mts +18 -0
  105. package/dist/docs-cloud-client.mjs +161 -0
  106. package/dist/docs-cloud-server.d.mts +2 -2
  107. package/dist/docs-cloud-server.mjs +2 -3
  108. package/dist/heading-toc.d.mts +10 -0
  109. package/dist/heading-toc.mjs +23 -0
  110. package/dist/http-body.d.mts +14 -0
  111. package/dist/{http-body-DkAPk6D5.mjs → http-body.mjs} +1 -1
  112. package/dist/http-cache.d.mts +25 -0
  113. package/dist/http-cache.mjs +140 -0
  114. package/dist/http-link.mjs +81 -0
  115. package/dist/i18n.d.mts +18 -0
  116. package/dist/{i18n-B1gJ9EW0.mjs → i18n.mjs} +1 -1
  117. package/dist/index.d.mts +42 -674
  118. package/dist/index.mjs +40 -14
  119. package/dist/markdown-rendering.mjs +1 -1
  120. package/dist/markdown-section-cache.mjs +49 -0
  121. package/dist/markdown-sections.d.mts +47 -0
  122. package/dist/{markdown-sections-9Oxr33b-.mjs → markdown-sections.mjs} +29 -703
  123. package/dist/mcp-auth.d.mts +14 -0
  124. package/dist/mcp-auth.mjs +99 -0
  125. package/dist/mcp.d.mts +5 -3
  126. package/dist/mcp.mjs +21 -9
  127. package/dist/mdx.d.mts +6 -0
  128. package/dist/mdx.mjs +7 -0
  129. package/dist/metadata.d.mts +60 -0
  130. package/dist/{metadata-BOHn0Y5K.mjs → metadata.mjs} +2 -2
  131. package/dist/native-api-reference.mjs +968 -0
  132. package/dist/okf.d.mts +42 -0
  133. package/dist/okf.mjs +145 -0
  134. package/dist/openapi-contract.mjs +154 -0
  135. package/dist/{openapi-mcp-node-C4jUpZG6.mjs → openapi-mcp-node.mjs} +1 -1
  136. package/dist/openapi-mcp.d.mts +26 -0
  137. package/dist/openapi-mcp.mjs +132 -0
  138. package/dist/openapi-operations.d.mts +71 -0
  139. package/dist/{openapi-operations-DFpaqOqA.mjs → openapi-operations.mjs} +1 -1
  140. package/dist/openapi-overlays.mjs +221 -0
  141. package/dist/openapi-references.mjs +233 -0
  142. package/dist/pagination.d.mts +7 -0
  143. package/dist/pagination.mjs +100 -0
  144. package/dist/prompt-references.d.mts +12 -0
  145. package/dist/prompt-references.mjs +132 -0
  146. package/dist/prompt-utils.d.mts +36 -0
  147. package/dist/{prompt-utils-B5g-bv7o.mjs → prompt-utils.mjs} +1 -57
  148. package/dist/reading-time.d.mts +23 -0
  149. package/dist/reading-time.mjs +58 -0
  150. package/dist/related.d.mts +7 -0
  151. package/dist/related.mjs +50 -0
  152. package/dist/retrieval-digest.d.mts +16 -0
  153. package/dist/{retrieval-digest-CT-eIEcp.mjs → retrieval-digest.mjs} +1 -1
  154. package/dist/review.d.mts +69 -0
  155. package/dist/review.mjs +562 -0
  156. package/dist/robots.d.mts +54 -0
  157. package/dist/{robots-BYJAVeI6.mjs → robots.mjs} +5 -3
  158. package/dist/runtime.d.mts +3 -3
  159. package/dist/runtime.mjs +3 -9
  160. package/dist/search.d.mts +210 -0
  161. package/dist/search.mjs +3401 -0
  162. package/dist/server.d.mts +22 -198
  163. package/dist/server.mjs +21 -19
  164. package/dist/sidebar.d.mts +15 -0
  165. package/dist/sidebar.mjs +73 -0
  166. package/dist/sitemap-server.d.mts +7 -0
  167. package/dist/{sitemap-server-Lg3YhUx4.mjs → sitemap-server.mjs} +2 -2
  168. package/dist/{sitemap-Cl8vrixd.d.mts → sitemap.d.mts} +3 -3
  169. package/dist/sitemap.mjs +254 -0
  170. package/dist/{standards-discovery-BHPc7YIX.d.mts → standards-discovery.d.mts} +2 -2
  171. package/dist/{standards-discovery-sSEojU80.mjs → standards-discovery.mjs} +3 -139
  172. package/dist/telemetry-runtime.d.mts +42 -0
  173. package/dist/telemetry-runtime.mjs +238 -0
  174. package/dist/telemetry.d.mts +15 -0
  175. package/dist/telemetry.mjs +117 -0
  176. package/dist/{types-BJFeMXWZ.d.mts → types.d.mts} +3 -2
  177. package/dist/urls.d.mts +7 -0
  178. package/dist/urls.mjs +21 -0
  179. package/dist/utils.d.mts +8 -0
  180. package/dist/utils.mjs +22 -0
  181. package/package.json +21 -3
  182. package/dist/agent-DkAidqtV.mjs +0 -9026
  183. package/dist/api-reference-BYee4zMo.mjs +0 -3046
  184. package/dist/prompt-references-DMgbu_dz.mjs +0 -1082
  185. package/dist/retrieval-digest-AOx2-LbC.d.mts +0 -367
  186. package/dist/sidebar-CFHBj29f.mjs +0 -421
  187. package/dist/sidebar-CMPbVfGP.d.mts +0 -63
  188. /package/dist/{openapi-CfCzX_a1.mjs → cli/openapi.mjs} +0 -0
@@ -1,1082 +0,0 @@
1
- import { At as DEFAULT_SITEMAP_MD_ROUTE, Gn as DEFAULT_MCP_WELL_KNOWN_ROUTE, Mt as DEFAULT_SITEMAP_XML_ROUTE, c as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, f as DEFAULT_DOCS_CONFIG_ROUTE, h as DEFAULT_LLMS_FULL_TXT_ROUTE, i as DEFAULT_AGENT_FEEDBACK_ROUTE, m as DEFAULT_DOCS_DIAGNOSTICS_ROUTE, t as DEFAULT_AGENTS_MD_ROUTE, v as DEFAULT_LLMS_TXT_ROUTE, x as DEFAULT_SKILL_MD_ROUTE } from "./agent-DkAidqtV.mjs";
2
- import { C as DOCS_AGENT_MANIFEST_SCHEMA_MEDIA_TYPE, S as DOCS_AGENT_MANIFEST_FORMAT, T as DOCS_AGENT_MANIFEST_VERSION, f as DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, n as API_CATALOG_MEDIA_TYPE, r as API_CATALOG_PROFILE_URI, t as AGENT_SKILLS_DISCOVERY_SCHEMA_URI, u as DEFAULT_AGENT_SKILLS_INDEX_ROUTE, v as DEFAULT_API_CATALOG_ROUTE, w as DOCS_AGENT_MANIFEST_SCHEMA_URI } from "./standards-discovery-sSEojU80.mjs";
3
- import { isDocsAgentSkillName, validateDocsAgentSkillFrontmatter } from "./agent-skills-spec.mjs";
4
- import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-BYJAVeI6.mjs";
5
- import matter from "gray-matter";
6
-
7
- //#region src/define-docs.ts
8
- /**
9
- * Define docs configuration. Validates and returns the config.
10
- */
11
- function defineDocs(config) {
12
- return {
13
- entry: config.entry ?? "docs",
14
- docsPath: config.docsPath,
15
- contentDir: config.contentDir,
16
- i18n: config.i18n,
17
- theme: config.theme,
18
- nav: config.nav,
19
- github: config.github,
20
- themeToggle: config.themeToggle,
21
- breadcrumb: config.breadcrumb,
22
- sidebar: config.sidebar,
23
- components: config.components,
24
- analytics: config.analytics,
25
- telemetry: config.telemetry,
26
- cloud: config.cloud,
27
- observability: config.observability,
28
- onCopyClick: config.onCopyClick,
29
- codeBlocks: config.codeBlocks,
30
- feedback: config.feedback,
31
- search: config.search,
32
- mcp: config.mcp,
33
- icons: config.icons,
34
- pageActions: config.pageActions,
35
- lastUpdated: config.lastUpdated,
36
- readingTime: config.readingTime,
37
- llmsTxt: config.llmsTxt,
38
- sitemap: config.sitemap,
39
- robots: config.robots,
40
- ai: config.ai,
41
- ordering: config.ordering,
42
- metadata: config.metadata,
43
- og: config.og,
44
- changelog: config.changelog,
45
- apiReference: config.apiReference,
46
- agent: config.agent,
47
- review: config.review
48
- };
49
- }
50
-
51
- //#endregion
52
- //#region src/changelog.ts
53
- function normalizePathSegment(value, fallback) {
54
- return (value ?? fallback).trim().replace(/^\/+|\/+$/g, "") || fallback;
55
- }
56
- function normalizeContentDir(value) {
57
- const trimmed = value?.trim();
58
- if (!trimmed) return "changelog";
59
- return trimmed.replace(/\/+$/, "") || "changelog";
60
- }
61
- function resolveChangelogConfig(value) {
62
- if (value === false || value === void 0) return {
63
- enabled: false,
64
- path: "changelog",
65
- contentDir: "changelog",
66
- title: "Changelog",
67
- description: void 0,
68
- search: true
69
- };
70
- if (value === true) return {
71
- enabled: true,
72
- path: "changelog",
73
- contentDir: "changelog",
74
- title: "Changelog",
75
- description: void 0,
76
- search: true
77
- };
78
- return {
79
- enabled: value.enabled !== false,
80
- path: normalizePathSegment(value.path, "changelog"),
81
- contentDir: normalizeContentDir(value.contentDir),
82
- title: value.title?.trim() || "Changelog",
83
- description: value.description?.trim() || void 0,
84
- search: value.search !== false,
85
- actionsComponent: value.actionsComponent
86
- };
87
- }
88
-
89
- //#endregion
90
- //#region src/date.ts
91
- const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
92
- /** Formats docs timestamps without allowing the deployment timezone to change the calendar day. */
93
- function formatDocsLastModifiedDate(value) {
94
- const normalized = typeof value === "string" && DATE_ONLY_PATTERN.test(value) ? `${value}T00:00:00.000Z` : value;
95
- const date = normalized instanceof Date ? normalized : new Date(normalized);
96
- if (Number.isNaN(date.getTime())) return void 0;
97
- return date.toLocaleDateString("en-US", {
98
- year: "numeric",
99
- month: "long",
100
- day: "numeric",
101
- timeZone: "UTC"
102
- });
103
- }
104
-
105
- //#endregion
106
- //#region src/utils.ts
107
- /**
108
- * Deep merge utility for theme overrides.
109
- * Merges objects recursively; later values override earlier ones.
110
- */
111
- function deepMerge(target, ...sources) {
112
- if (!sources.length) return target;
113
- const source = sources.shift();
114
- if (!source) return target;
115
- const result = { ...target };
116
- for (const key of Object.keys(source)) {
117
- const sourceVal = source[key];
118
- const targetVal = result[key];
119
- if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) result[key] = deepMerge(targetVal, sourceVal);
120
- else if (sourceVal !== void 0) result[key] = sourceVal;
121
- }
122
- if (sources.length) return deepMerge(result, ...sources);
123
- return result;
124
- }
125
-
126
- //#endregion
127
- //#region src/create-theme.ts
128
- /**
129
- * Create a theme preset factory.
130
- *
131
- * Returns a function that accepts optional overrides and deep-merges them
132
- * with the base theme defaults. This is the same pattern used by the
133
- * built-in `fumadocs()`, `darksharp()`, and `pixelBorder()` presets.
134
- *
135
- * @param baseTheme - The default theme configuration
136
- * @returns A factory function `(overrides?) => DocsTheme`
137
- *
138
- * @example
139
- * ```ts
140
- * import { createTheme } from "@farming-labs/docs";
141
- *
142
- * export const myTheme = createTheme({
143
- * name: "my-theme",
144
- * ui: {
145
- * colors: { primary: "#6366f1" },
146
- * layout: { contentWidth: 800 },
147
- * },
148
- * });
149
- * ```
150
- */
151
- function createTheme(baseTheme) {
152
- return function themeFactory(overrides = {}) {
153
- const merged = deepMerge(baseTheme, overrides);
154
- if (overrides.ui?.colors) merged._userColorOverrides = { ...overrides.ui.colors };
155
- return merged;
156
- };
157
- }
158
- /**
159
- * Extend an existing theme preset with additional defaults.
160
- *
161
- * Useful when you want to build on top of an existing theme (e.g. fumadocs)
162
- * rather than starting from scratch.
163
- *
164
- * @example
165
- * ```ts
166
- * import { extendTheme } from "@farming-labs/docs";
167
- * import { fumadocs } from "@farming-labs/theme/default";
168
- *
169
- * // Start with fumadocs defaults, override some values
170
- * export const myTheme = extendTheme(fumadocs(), {
171
- * name: "my-custom-fumadocs",
172
- * ui: { colors: { primary: "#22c55e" } },
173
- * });
174
- * ```
175
- */
176
- function extendTheme(baseTheme, extensions) {
177
- return deepMerge(baseTheme, extensions);
178
- }
179
-
180
- //#endregion
181
- //#region src/agent-skills-archive.ts
182
- const TAR_BLOCK_BYTES = 512;
183
- const TAR_NAME_BYTES = 100;
184
- const TAR_PREFIX_OFFSET = 345;
185
- const TAR_PREFIX_BYTES = 155;
186
- const AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES = 10 * 1024 * 1024;
187
- const AGENT_SKILL_DOCUMENT_MAX_BYTES = 1024 * 1024;
188
- function isZeroBlock(block) {
189
- return block.every((byte) => byte === 0);
190
- }
191
- function readTarText(header, offset, length, field) {
192
- const bytes = header.subarray(offset, offset + length);
193
- const terminator = bytes.indexOf(0);
194
- const value = terminator < 0 ? bytes : bytes.subarray(0, terminator);
195
- try {
196
- return new TextDecoder("utf-8", { fatal: true }).decode(value);
197
- } catch {
198
- throw new Error(`Agent Skill archive has invalid UTF-8 in its ${field}.`);
199
- }
200
- }
201
- function readTarOctal(header, offset, length, field) {
202
- const value = readTarText(header, offset, length, field).trim();
203
- if (!/^[0-7]+$/u.test(value)) throw new Error(`Agent Skill archive has an invalid ${field}.`);
204
- const parsed = Number.parseInt(value, 8);
205
- if (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error(`Agent Skill archive has an unsafe ${field}.`);
206
- return parsed;
207
- }
208
- function validateTarChecksum(header) {
209
- const expected = readTarOctal(header, 148, 8, "header checksum");
210
- if (header.reduce((sum, byte, index) => {
211
- return sum + (index >= 148 && index < 156 ? 32 : byte);
212
- }, 0) !== expected) throw new Error("Agent Skill archive has an invalid tar header checksum.");
213
- }
214
- /**
215
- * Extract the root SKILL.md from an uncompressed Agent Skill tar archive.
216
- *
217
- * Only the regular-file payload is returned; duplicate, oversized, malformed,
218
- * or non-UTF-8 documents are rejected before frontmatter validation.
219
- */
220
- function readAgentSkillDocumentFromTar(archive) {
221
- if (archive.byteLength > AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES) throw new Error(`Agent Skill archive exceeds ${AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES} uncompressed bytes.`);
222
- let skillDocument;
223
- let offset = 0;
224
- let foundEndOfArchive = false;
225
- while (offset < archive.byteLength) {
226
- if (offset + TAR_BLOCK_BYTES > archive.byteLength) throw new Error("Agent Skill archive contains a truncated tar header.");
227
- const header = archive.subarray(offset, offset + TAR_BLOCK_BYTES);
228
- if (isZeroBlock(header)) {
229
- const secondZeroBlockStart = offset + TAR_BLOCK_BYTES;
230
- const secondZeroBlockEnd = secondZeroBlockStart + TAR_BLOCK_BYTES;
231
- if (secondZeroBlockEnd > archive.byteLength) throw new Error("Agent Skill archive contains a truncated end-of-archive marker.");
232
- if (!isZeroBlock(archive.subarray(secondZeroBlockStart, secondZeroBlockEnd))) throw new Error("Agent Skill archive is missing the second end-of-archive block.");
233
- if (!archive.subarray(secondZeroBlockEnd).every((byte) => byte === 0)) throw new Error("Agent Skill archive contains non-zero bytes after its end marker.");
234
- foundEndOfArchive = true;
235
- break;
236
- }
237
- validateTarChecksum(header);
238
- const name = readTarText(header, 0, TAR_NAME_BYTES, "file name");
239
- const prefix = readTarText(header, TAR_PREFIX_OFFSET, TAR_PREFIX_BYTES, "file prefix");
240
- const path = prefix ? `${prefix}/${name}` : name;
241
- const size = readTarOctal(header, 124, 12, "file size");
242
- const contentStart = offset + TAR_BLOCK_BYTES;
243
- const contentEnd = contentStart + size;
244
- if (contentEnd > archive.byteLength) throw new Error(`Agent Skill archive entry ${JSON.stringify(path)} is truncated.`);
245
- const typeFlag = header[156];
246
- if (path === "SKILL.md") {
247
- if (typeFlag !== 0 && typeFlag !== "0".charCodeAt(0)) throw new Error("Agent Skill archive root SKILL.md must be a regular file.");
248
- if (skillDocument) throw new Error("Agent Skill archive contains more than one root SKILL.md.");
249
- if (size > AGENT_SKILL_DOCUMENT_MAX_BYTES) throw new Error(`Agent Skill archive SKILL.md exceeds ${AGENT_SKILL_DOCUMENT_MAX_BYTES} bytes.`);
250
- skillDocument = archive.subarray(contentStart, contentEnd);
251
- }
252
- const nextOffset = contentStart + Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
253
- if (nextOffset > archive.byteLength) throw new Error(`Agent Skill archive entry ${JSON.stringify(path)} has truncated padding.`);
254
- offset = nextOffset;
255
- }
256
- if (!foundEndOfArchive) throw new Error("Agent Skill archive is missing its end-of-archive marker.");
257
- if (!skillDocument) throw new Error("Agent Skill archive does not contain a root SKILL.md.");
258
- try {
259
- return new TextDecoder("utf-8", { fatal: true }).decode(skillDocument);
260
- } catch {
261
- throw new Error("Agent Skill archive SKILL.md is not valid UTF-8.");
262
- }
263
- }
264
-
265
- //#endregion
266
- //#region src/http-link.ts
267
- /** Split an HTTP list without treating delimiters inside quotes or URI references as separators. */
268
- function splitHttpList(value, delimiter) {
269
- const parts = [];
270
- let start = 0;
271
- let quoted = false;
272
- let escaped = false;
273
- let angleDepth = 0;
274
- for (let index = 0; index < value.length; index += 1) {
275
- const character = value[index];
276
- if (escaped) {
277
- escaped = false;
278
- continue;
279
- }
280
- if (quoted && character === "\\") {
281
- escaped = true;
282
- continue;
283
- }
284
- if (character === "\"") {
285
- quoted = !quoted;
286
- continue;
287
- }
288
- if (!quoted && character === "<") angleDepth += 1;
289
- if (!quoted && character === ">" && angleDepth > 0) angleDepth -= 1;
290
- if (!quoted && angleDepth === 0 && character === delimiter) {
291
- parts.push(value.slice(start, index).trim());
292
- start = index + 1;
293
- }
294
- }
295
- parts.push(value.slice(start).trim());
296
- return parts.filter(Boolean);
297
- }
298
- function unquoteHttpValue(value) {
299
- const trimmed = value.trim();
300
- if (!trimmed.startsWith("\"") || !trimmed.endsWith("\"")) return trimmed;
301
- return trimmed.slice(1, -1).replace(/\\(.)/g, "$1");
302
- }
303
- /** Parse Link header values while preserving the target/relation association. */
304
- function parseHttpLinkHeader(header) {
305
- if (!header) return [];
306
- const links = [];
307
- for (const value of splitHttpList(header, ",")) {
308
- const target = value.match(/^\s*<([^>]*)>/);
309
- if (!target) continue;
310
- const relations = [];
311
- for (const rawParameter of splitHttpList(value.slice(target[0].length), ";")) {
312
- const separator = rawParameter.indexOf("=");
313
- if (separator < 0 || rawParameter.slice(0, separator).trim().toLowerCase() !== "rel") continue;
314
- relations.push(...unquoteHttpValue(rawParameter.slice(separator + 1)).toLowerCase().split(/\s+/));
315
- }
316
- links.push({
317
- href: target[1],
318
- relations: relations.filter(Boolean)
319
- });
320
- }
321
- return links;
322
- }
323
- function httpLinkMatchesExpectation(links, expectation, responseUrl) {
324
- let expectedUrl;
325
- try {
326
- expectedUrl = new URL(expectation.href, responseUrl).toString();
327
- } catch {
328
- return false;
329
- }
330
- return links.some((link) => {
331
- try {
332
- return new URL(link.href, responseUrl).toString() === expectedUrl && link.relations.includes(expectation.rel.toLowerCase());
333
- } catch {
334
- return false;
335
- }
336
- });
337
- }
338
- function httpLinkHeaderHasTargetRelation(header, href, rel, responseUrl) {
339
- return httpLinkMatchesExpectation(parseHttpLinkHeader(header), {
340
- href,
341
- rel
342
- }, responseUrl);
343
- }
344
-
345
- //#endregion
346
- //#region src/agent-conformance.ts
347
- const DOCS_AGENT_CONTRACT_VERSION = "1.3";
348
- function normalizeOrigin(value) {
349
- return value.replace(/\/+$/, "");
350
- }
351
- function normalizeEntry(value) {
352
- return value.replace(/^\/+|\/+$/g, "") || "docs";
353
- }
354
- /**
355
- * The framework-neutral public surface every first-party adapter must implement.
356
- * Adapter tests consume this list so new routes and invariants are added once.
357
- */
358
- function createDocsAgentContractCases(options = {}) {
359
- const origin = normalizeOrigin(options.origin ?? "http://localhost");
360
- const entry = normalizeEntry(options.entry ?? "docs");
361
- const locale = options.locale === void 0 ? "fr" : options.locale;
362
- const url = (route) => `${origin}${route.startsWith("/") ? route : `/${route}`}`;
363
- const markdown = ["text/markdown"];
364
- const json = ["application/json"];
365
- const cases = [
366
- {
367
- surface: "discovery",
368
- request: { url: url(DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE) },
369
- expect: {
370
- statuses: [200],
371
- contentTypes: json,
372
- linkRelations: [
373
- {
374
- href: DEFAULT_API_CATALOG_ROUTE,
375
- rel: "api-catalog"
376
- },
377
- {
378
- href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
379
- rel: "service-meta"
380
- },
381
- {
382
- href: DOCS_AGENT_MANIFEST_SCHEMA_URI,
383
- rel: "describedby",
384
- type: DOCS_AGENT_MANIFEST_SCHEMA_MEDIA_TYPE
385
- }
386
- ]
387
- }
388
- },
389
- {
390
- surface: "api-catalog",
391
- request: { url: url(DEFAULT_API_CATALOG_ROUTE) },
392
- expect: {
393
- statuses: [200],
394
- contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
395
- bodyIncludes: [
396
- "linkset",
397
- DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
398
- DEFAULT_AGENT_SKILLS_INDEX_ROUTE
399
- ],
400
- linkRelations: [{
401
- href: DEFAULT_API_CATALOG_ROUTE,
402
- rel: "api-catalog"
403
- }]
404
- }
405
- },
406
- {
407
- surface: "api-catalog-head",
408
- request: {
409
- url: url(DEFAULT_API_CATALOG_ROUTE),
410
- init: { method: "HEAD" }
411
- },
412
- expect: {
413
- statuses: [200],
414
- contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
415
- bodyEmpty: true,
416
- linkRelations: [{
417
- href: DEFAULT_API_CATALOG_ROUTE,
418
- rel: "api-catalog"
419
- }]
420
- }
421
- },
422
- {
423
- surface: "agent-skills-index",
424
- request: { url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE) },
425
- expect: {
426
- statuses: [200],
427
- contentTypes: json,
428
- bodyIncludes: [
429
- "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
430
- `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
431
- "sha256:"
432
- ],
433
- linkRelations: [{
434
- href: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
435
- rel: "item"
436
- }, {
437
- href: DEFAULT_API_CATALOG_ROUTE,
438
- rel: "api-catalog"
439
- }]
440
- }
441
- },
442
- {
443
- surface: "agent-skills-index-head",
444
- request: {
445
- url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE),
446
- init: { method: "HEAD" }
447
- },
448
- expect: {
449
- statuses: [200],
450
- contentTypes: json,
451
- bodyEmpty: true,
452
- linkRelations: [{
453
- href: DEFAULT_API_CATALOG_ROUTE,
454
- rel: "api-catalog"
455
- }]
456
- }
457
- },
458
- {
459
- surface: "agent-skill",
460
- request: { url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`) },
461
- expect: {
462
- statuses: [200],
463
- contentTypes: markdown,
464
- bodyIncludes: ["name: docs"],
465
- linkRelations: [{
466
- href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
467
- rel: "collection"
468
- }, {
469
- href: DEFAULT_API_CATALOG_ROUTE,
470
- rel: "api-catalog"
471
- }]
472
- }
473
- },
474
- {
475
- surface: "agent-skill-head",
476
- request: {
477
- url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`),
478
- init: { method: "HEAD" }
479
- },
480
- expect: {
481
- statuses: [200],
482
- contentTypes: markdown,
483
- bodyEmpty: true,
484
- linkRelations: [{
485
- href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
486
- rel: "collection"
487
- }, {
488
- href: DEFAULT_API_CATALOG_ROUTE,
489
- rel: "api-catalog"
490
- }]
491
- }
492
- },
493
- {
494
- surface: "config",
495
- request: { url: url(DEFAULT_DOCS_CONFIG_ROUTE) },
496
- expect: {
497
- statuses: [200],
498
- contentTypes: json,
499
- bodyIncludes: ["docs-config-map.v1"]
500
- }
501
- },
502
- {
503
- surface: "diagnostics",
504
- request: { url: url(DEFAULT_DOCS_DIAGNOSTICS_ROUTE) },
505
- expect: {
506
- statuses: [200],
507
- contentTypes: json,
508
- bodyIncludes: ["docs-diagnostics.v1"]
509
- }
510
- },
511
- {
512
- surface: "feedback-schema",
513
- request: { url: url(`${DEFAULT_AGENT_FEEDBACK_ROUTE}/schema`) },
514
- expect: {
515
- statuses: [200],
516
- contentTypes: ["application/schema+json"]
517
- }
518
- },
519
- {
520
- surface: "markdown",
521
- request: { url: url(`/${entry}.md`) },
522
- expect: {
523
- statuses: [200],
524
- contentTypes: markdown,
525
- bodyIncludes: ["Introduction"]
526
- }
527
- },
528
- {
529
- surface: "markdown-accept",
530
- request: {
531
- url: url(`/${entry}`),
532
- init: { headers: { Accept: "text/markdown" } }
533
- },
534
- expect: {
535
- statuses: [200],
536
- contentTypes: markdown,
537
- bodyIncludes: ["Introduction"]
538
- }
539
- },
540
- {
541
- surface: "markdown-missing",
542
- request: { url: url(`/${entry}/missing.md`) },
543
- expect: {
544
- statuses: [200, 404],
545
- contentTypes: markdown,
546
- bodyIncludes: ["not found"]
547
- }
548
- },
549
- {
550
- surface: "llms",
551
- request: { url: url(DEFAULT_LLMS_TXT_ROUTE) },
552
- expect: {
553
- statuses: [200],
554
- contentTypes: ["text/plain"],
555
- bodyIncludes: ["Introduction"]
556
- }
557
- },
558
- {
559
- surface: "llms-full",
560
- request: { url: url(DEFAULT_LLMS_FULL_TXT_ROUTE) },
561
- expect: {
562
- statuses: [200],
563
- contentTypes: ["text/plain"],
564
- bodyIncludes: ["Introduction"]
565
- }
566
- },
567
- {
568
- surface: "agents",
569
- request: { url: url(DEFAULT_AGENTS_MD_ROUTE) },
570
- expect: {
571
- statuses: [200],
572
- contentTypes: markdown,
573
- bodyIncludes: ["AGENTS.md"]
574
- }
575
- },
576
- {
577
- surface: "skill",
578
- request: { url: url(DEFAULT_SKILL_MD_ROUTE) },
579
- expect: {
580
- statuses: [200],
581
- contentTypes: markdown,
582
- bodyIncludes: ["name:"]
583
- }
584
- },
585
- {
586
- surface: "sitemap-xml",
587
- request: { url: url(DEFAULT_SITEMAP_XML_ROUTE) },
588
- expect: {
589
- statuses: [200],
590
- contentTypes: ["application/xml", "text/xml"],
591
- bodyIncludes: ["<urlset"]
592
- }
593
- },
594
- {
595
- surface: "sitemap-markdown",
596
- request: { url: url(DEFAULT_SITEMAP_MD_ROUTE) },
597
- expect: {
598
- statuses: [200],
599
- contentTypes: markdown,
600
- bodyIncludes: ["Introduction"]
601
- }
602
- },
603
- {
604
- surface: "robots",
605
- request: { url: url(DEFAULT_ROBOTS_TXT_ROUTE) },
606
- expect: {
607
- statuses: [200],
608
- contentTypes: ["text/plain"],
609
- bodyIncludes: ["User-agent:"]
610
- }
611
- },
612
- {
613
- surface: "mcp",
614
- request: {
615
- url: url(DEFAULT_MCP_WELL_KNOWN_ROUTE),
616
- init: {
617
- method: "POST",
618
- headers: {
619
- Accept: "application/json, text/event-stream",
620
- "Content-Type": "application/json",
621
- "MCP-Protocol-Version": "2025-11-25"
622
- },
623
- body: JSON.stringify({
624
- jsonrpc: "2.0",
625
- id: 1,
626
- method: "initialize",
627
- params: {
628
- protocolVersion: "2025-11-25",
629
- capabilities: {},
630
- clientInfo: {
631
- name: "agent-conformance",
632
- version: DOCS_AGENT_CONTRACT_VERSION
633
- }
634
- }
635
- })
636
- }
637
- },
638
- expect: {
639
- statuses: [200],
640
- contentTypes: ["application/json", "text/event-stream"],
641
- bodyIncludes: ["serverInfo", "protocolVersion"]
642
- }
643
- }
644
- ];
645
- if (locale) {
646
- const markdownMissingIndex = cases.findIndex((contractCase) => contractCase.surface === "markdown-missing");
647
- cases.splice(markdownMissingIndex, 0, {
648
- surface: "markdown-locale",
649
- request: { url: url(`/${entry}.md?lang=${encodeURIComponent(locale)}`) },
650
- expect: {
651
- statuses: [200],
652
- contentTypes: markdown,
653
- bodyIncludes: ["Bonjour"]
654
- }
655
- });
656
- }
657
- return cases;
658
- }
659
- function parseParameterizedValue(value) {
660
- const [rawValue, ...rawParameters] = splitHttpList(value, ";");
661
- const normalizedValue = rawValue?.trim().toLowerCase();
662
- if (!normalizedValue) return null;
663
- const parameters = /* @__PURE__ */ new Map();
664
- for (const rawParameter of rawParameters) {
665
- const separator = rawParameter.indexOf("=");
666
- if (separator < 0) continue;
667
- const name = rawParameter.slice(0, separator).trim().toLowerCase();
668
- if (!name) continue;
669
- parameters.set(name, unquoteHttpValue(rawParameter.slice(separator + 1)));
670
- }
671
- return {
672
- value: normalizedValue,
673
- parameters
674
- };
675
- }
676
- function matchesContentType(actual, expected) {
677
- const parsedActual = parseParameterizedValue(actual);
678
- if (!parsedActual) return false;
679
- return expected.some((value) => {
680
- const parsedExpected = parseParameterizedValue(value);
681
- if (!parsedExpected || parsedExpected.value !== parsedActual.value) return false;
682
- return [...parsedExpected.parameters].every(([name, expectedValue]) => parsedActual.parameters.get(name) === expectedValue);
683
- });
684
- }
685
- function asRecord(value) {
686
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
687
- }
688
- function isNonEmptyString(value) {
689
- return typeof value === "string" && value.trim().length > 0;
690
- }
691
- function validateAgentManifest(content) {
692
- let parsed;
693
- try {
694
- parsed = JSON.parse(content);
695
- } catch {
696
- return ["Agent manifest did not contain valid JSON"];
697
- }
698
- const root = asRecord(parsed);
699
- const issues = [];
700
- if (root?.$schema !== DOCS_AGENT_MANIFEST_SCHEMA_URI) issues.push(`Agent manifest did not declare top-level $schema ${DOCS_AGENT_MANIFEST_SCHEMA_URI}`);
701
- if (root?.format !== DOCS_AGENT_MANIFEST_FORMAT) issues.push(`Agent manifest did not declare top-level format ${DOCS_AGENT_MANIFEST_FORMAT}`);
702
- if (root?.version !== DOCS_AGENT_MANIFEST_VERSION) issues.push(`Agent manifest did not declare top-level version ${DOCS_AGENT_MANIFEST_VERSION}`);
703
- return issues;
704
- }
705
- function httpLinkMatchesContractExpectation(header, expectation, responseUrl) {
706
- if (!httpLinkMatchesExpectation(parseHttpLinkHeader(header), expectation, responseUrl)) return false;
707
- if (!expectation.type) return true;
708
- let expectedUrl;
709
- try {
710
- expectedUrl = new URL(expectation.href, responseUrl).toString();
711
- } catch {
712
- return false;
713
- }
714
- for (const value of splitHttpList(header ?? "", ",")) {
715
- const target = value.match(/^\s*<([^>]*)>/);
716
- if (!target) continue;
717
- let actualUrl;
718
- try {
719
- actualUrl = new URL(target[1], responseUrl).toString();
720
- } catch {
721
- continue;
722
- }
723
- if (actualUrl !== expectedUrl) continue;
724
- const relations = [];
725
- let type;
726
- for (const rawParameter of splitHttpList(value.slice(target[0].length), ";")) {
727
- const separator = rawParameter.indexOf("=");
728
- if (separator < 0) continue;
729
- const name = rawParameter.slice(0, separator).trim().toLowerCase();
730
- const parameterValue = unquoteHttpValue(rawParameter.slice(separator + 1));
731
- if (name === "rel") relations.push(...parameterValue.toLowerCase().split(/\s+/).filter(Boolean));
732
- else if (name === "type") type = parameterValue.toLowerCase();
733
- }
734
- if (relations.includes(expectation.rel.toLowerCase()) && type === expectation.type.toLowerCase()) return true;
735
- }
736
- return false;
737
- }
738
- async function sha256Content(content) {
739
- const digest = await globalThis.crypto.subtle.digest("SHA-256", content);
740
- return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
741
- }
742
- async function decompressAgentSkillArchive(content) {
743
- if (typeof DecompressionStream !== "function") throw new Error("gzip decompression is unavailable in this runtime");
744
- const stream = new Blob([content]).stream().pipeThrough(new DecompressionStream("gzip"));
745
- const chunks = [];
746
- let totalBytes = 0;
747
- for await (const chunk of stream) {
748
- totalBytes += chunk.byteLength;
749
- if (totalBytes > AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES) throw new Error(`archive exceeds ${AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES} uncompressed bytes`);
750
- chunks.push(chunk);
751
- }
752
- const archive = new Uint8Array(new ArrayBuffer(totalBytes));
753
- let offset = 0;
754
- for (const chunk of chunks) {
755
- archive.set(chunk, offset);
756
- offset += chunk.byteLength;
757
- }
758
- return archive;
759
- }
760
- async function validateAgentSkillsIndex(content, indexUrl, handle) {
761
- const issues = [];
762
- let parsed;
763
- try {
764
- parsed = JSON.parse(content);
765
- } catch {
766
- return ["Agent Skills index did not contain valid JSON"];
767
- }
768
- const root = asRecord(parsed);
769
- if (root?.$schema !== AGENT_SKILLS_DISCOVERY_SCHEMA_URI) issues.push(`Agent Skills index did not declare ${AGENT_SKILLS_DISCOVERY_SCHEMA_URI}`);
770
- if (!Array.isArray(root?.skills) || root.skills.length === 0) {
771
- issues.push("Agent Skills index did not declare any skills");
772
- return issues;
773
- }
774
- const seenNames = /* @__PURE__ */ new Set();
775
- const origin = new URL(indexUrl).origin;
776
- for (const rawSkill of root.skills) {
777
- const skill = asRecord(rawSkill);
778
- const name = skill?.name;
779
- const description = skill?.description;
780
- const artifactRoute = skill?.url;
781
- const digest = skill?.digest;
782
- if (!isNonEmptyString(name) || !isDocsAgentSkillName(name) || skill?.type !== "skill-md" && skill?.type !== "archive" || !isNonEmptyString(description) || Array.from(description).length > 1024 || !isNonEmptyString(artifactRoute) || typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest)) {
783
- issues.push("Agent Skills index contained an invalid skill entry");
784
- continue;
785
- }
786
- if (seenNames.has(name)) {
787
- issues.push(`Agent Skills index declared duplicate skill ${JSON.stringify(name)}`);
788
- continue;
789
- }
790
- seenNames.add(name);
791
- let artifactUrl;
792
- try {
793
- artifactUrl = new URL(artifactRoute, indexUrl);
794
- } catch {
795
- issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} was invalid`);
796
- continue;
797
- }
798
- const expectedPath = skill.type === "archive" ? `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${encodeURIComponent(name)}.tar.gz` : `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${encodeURIComponent(name)}/SKILL.md`;
799
- if (artifactUrl.origin !== origin || !artifactUrl.pathname.endsWith(expectedPath)) {
800
- issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} did not resolve to same-origin ${expectedPath}`);
801
- continue;
802
- }
803
- let skillDocument = null;
804
- try {
805
- const response = await handle(new Request(artifactUrl), "agent-skill");
806
- const artifact = new Uint8Array(await response.arrayBuffer());
807
- if (response.status !== 200) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned status ${response.status}`);
808
- const contentType = response.headers.get("content-type") ?? "";
809
- if (!matchesContentType(contentType, [skill.type === "archive" ? "application/gzip" : "text/markdown"])) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned content-type ${contentType || "<missing>"}`);
810
- const actualDigest = `sha256:${await sha256Content(artifact)}`;
811
- if (actualDigest !== digest) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} digest ${actualDigest} did not match ${digest}`);
812
- if (skill.type === "skill-md") skillDocument = new TextDecoder("utf-8", { fatal: true }).decode(artifact);
813
- else skillDocument = readAgentSkillDocumentFromTar(await decompressAgentSkillArchive(artifact));
814
- } catch (error) {
815
- issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} failed: ${error instanceof Error ? error.message : String(error)}`);
816
- }
817
- if (skillDocument !== null) {
818
- const validation = validateDocsAgentSkillFrontmatter(skillDocument, { directoryName: name });
819
- if (!validation.valid) issues.push(`Agent Skill ${JSON.stringify(name)} has invalid SKILL.md frontmatter: ${validation.issues.map((issue) => issue.message).join("; ")}`);
820
- else if (validation.data.description !== description) issues.push(`Agent Skill ${JSON.stringify(name)} SKILL.md description does not match the discovery index`);
821
- }
822
- }
823
- return issues;
824
- }
825
- async function runDocsAgentConformance(options) {
826
- const cases = createDocsAgentContractCases(options);
827
- const results = [];
828
- for (const contractCase of cases) {
829
- const issues = [];
830
- try {
831
- const response = await options.handle(new Request(contractCase.request.url, contractCase.request.init), contractCase.surface);
832
- const contentType = response.headers.get("content-type") ?? "";
833
- const body = await response.text();
834
- if (!contractCase.expect.statuses.includes(response.status)) issues.push(`expected status ${contractCase.expect.statuses.join(" or ")}, received ${response.status}`);
835
- if (!matchesContentType(contentType, contractCase.expect.contentTypes)) issues.push(`expected content-type ${contractCase.expect.contentTypes.join(" or ")}, received ${contentType || "<missing>"}`);
836
- for (const requiredText of contractCase.expect.bodyIncludes ?? []) if (!body.toLowerCase().includes(requiredText.toLowerCase())) issues.push(`response body did not include ${JSON.stringify(requiredText)}`);
837
- if (contractCase.expect.bodyEmpty && body !== "") issues.push(`expected an empty response body, received ${body.length} characters`);
838
- if (contractCase.surface === "discovery") issues.push(...validateAgentManifest(body));
839
- if (contractCase.surface === "agent-skills-index") issues.push(...await validateAgentSkillsIndex(body, contractCase.request.url, options.handle));
840
- for (const [header, requiredValues] of Object.entries(contractCase.expect.headerIncludes ?? {})) {
841
- const actual = response.headers.get(header) ?? "";
842
- for (const requiredValue of requiredValues) if (!actual.toLowerCase().includes(requiredValue.toLowerCase())) issues.push(`response header ${JSON.stringify(header)} did not include ${JSON.stringify(requiredValue)}`);
843
- }
844
- const linkHeader = response.headers.get("link");
845
- for (const expectation of contractCase.expect.linkRelations ?? []) if (!httpLinkMatchesContractExpectation(linkHeader, expectation, contractCase.request.url)) {
846
- const typeRequirement = expectation.type ? ` and type=${JSON.stringify(expectation.type)}` : "";
847
- issues.push(`response Link header did not include ${JSON.stringify(expectation.href)} with rel=${JSON.stringify(expectation.rel)}${typeRequirement} in the same link-value`);
848
- }
849
- results.push({
850
- surface: contractCase.surface,
851
- passed: issues.length === 0,
852
- status: response.status,
853
- contentType,
854
- issues
855
- });
856
- } catch (error) {
857
- issues.push(error instanceof Error ? error.message : String(error));
858
- results.push({
859
- surface: contractCase.surface,
860
- passed: false,
861
- issues
862
- });
863
- }
864
- }
865
- return {
866
- adapter: options.adapter,
867
- contractVersion: DOCS_AGENT_CONTRACT_VERSION,
868
- passed: results.every((result) => result.passed),
869
- cases: results
870
- };
871
- }
872
-
873
- //#endregion
874
- //#region src/reading-time.ts
875
- function hasExplicitReadingTime(frontmatter) {
876
- return Object.prototype.hasOwnProperty.call(frontmatter ?? {}, "readingTime");
877
- }
878
- function normalizeWordsPerMinute(wordsPerMinute) {
879
- if (typeof wordsPerMinute !== "number" || !Number.isFinite(wordsPerMinute)) return 220;
880
- return Math.max(1, Math.floor(wordsPerMinute));
881
- }
882
- function stripNonReadingContent(content, options) {
883
- const includeCode = options?.includeCode === true;
884
- return (includeCode ? content.replace(/^(`{3,}|~{3,})[^\n]*$/gm, " ").replace(/`([^`\n]+)`/g, " $1 ") : content.replace(/(`{3,})[^\n]*\n[\s\S]*?\1/g, " ").replace(/(~{3,})[^\n]*\n[\s\S]*?\1/g, " ").replace(/`[^`\n]+`/g, " ")).replace(/!\[[^\]]*\]\([^)]+\)/g, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, " $1 ").replace(/<[^>]+>/g, " ").replace(includeCode ? /[{}]/g : /\{[^{}]*\}/g, " ").replace(/https?:\/\/\S+/g, " ").replace(/[#>*_~|]/g, " ");
885
- }
886
- function estimateReadingTimeMinutes(content, wordsPerMinute, options) {
887
- const wordCount = stripNonReadingContent(content, options).match(/\b[\p{L}\p{N}][\p{L}\p{N}'’-]*\b/gu)?.length ?? 0;
888
- return Math.max(1, Math.ceil(wordCount / normalizeWordsPerMinute(wordsPerMinute)));
889
- }
890
- function resolveReadingTimeOptions(readingTime) {
891
- if (readingTime === true) return {
892
- enabled: true,
893
- format: "long",
894
- includeCode: false
895
- };
896
- if (readingTime === false || readingTime === void 0 || readingTime === null) return {
897
- enabled: false,
898
- format: "long",
899
- includeCode: false
900
- };
901
- if (typeof readingTime !== "object") return {
902
- enabled: false,
903
- format: "long",
904
- includeCode: false
905
- };
906
- return {
907
- enabled: readingTime.enabled !== false,
908
- wordsPerMinute: typeof readingTime.wordsPerMinute === "number" && Number.isFinite(readingTime.wordsPerMinute) ? readingTime.wordsPerMinute : void 0,
909
- format: readingTime.format === "short" ? "short" : "long",
910
- includeCode: readingTime.includeCode === true
911
- };
912
- }
913
- function resolveReadingTimeFromContent(frontmatter, content, wordsPerMinute, options) {
914
- const pageData = frontmatter ?? {};
915
- if (pageData.readingTime === false) return null;
916
- if (typeof pageData.readingTime === "number" && Number.isFinite(pageData.readingTime)) return Math.max(1, Math.ceil(pageData.readingTime));
917
- return estimateReadingTimeMinutes(content, wordsPerMinute, options);
918
- }
919
- function resolvePageReadingTime(frontmatter, content, options) {
920
- if (!(options?.enabledByDefault ?? false) && !hasExplicitReadingTime(frontmatter)) return;
921
- return resolveReadingTimeFromContent(frontmatter, content, options?.wordsPerMinute, { includeCode: options?.includeCode });
922
- }
923
- function resolveReadingTimeFromSource(source, wordsPerMinute, options) {
924
- const { data, content } = matter(source);
925
- return resolveReadingTimeFromContent(data, content, wordsPerMinute, options);
926
- }
927
-
928
- //#endregion
929
- //#region src/heading-toc.ts
930
- /**
931
- * Encode heading fragments emitted by a renderer's nested table of contents.
932
- *
933
- * Fumadocs stores fragment URLs as raw DOM ids. Encoding the fragment after its
934
- * heading visitor runs keeps links valid without changing path prefixes.
935
- */
936
- function encodeDocsHeadingTocUrls(value) {
937
- if (Array.isArray(value)) {
938
- for (const item of value) encodeDocsHeadingTocUrls(item);
939
- return;
940
- }
941
- if (!value || typeof value !== "object") return;
942
- const item = value;
943
- if (typeof item.url === "string") {
944
- const hashIndex = item.url.indexOf("#");
945
- if (hashIndex >= 0) item.url = `${item.url.slice(0, hashIndex)}#${encodeURIComponent(item.url.slice(hashIndex + 1))}`;
946
- }
947
- encodeDocsHeadingTocUrls(item.children);
948
- }
949
-
950
- //#endregion
951
- //#region src/prompt-references.ts
952
- function isDocsMarkdownPromptNode(node) {
953
- return (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name === "Prompt";
954
- }
955
- function normalizeDocsMarkdownReferenceIdentifier(value) {
956
- return typeof value === "string" ? value.trim().replace(/\s+/gu, " ").toLowerCase() : "";
957
- }
958
- function readDocsMarkdownPromptReferenceSource(value) {
959
- const source = typeof value === "string" ? value : value instanceof Uint8Array ? new TextDecoder().decode(value) : void 0;
960
- if (source === void 0) return void 0;
961
- return source.startsWith("") ? source.slice(1) : source;
962
- }
963
- function sliceDocsMarkdownPromptReferenceSource(node, source) {
964
- if (source === void 0) return void 0;
965
- const start = node.position?.start?.offset;
966
- const end = node.position?.end?.offset;
967
- if (typeof start !== "number" || typeof end !== "number" || !Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > source.length) return;
968
- return source.slice(start, end);
969
- }
970
- function sliceDocsMarkdownPromptChildrenSource(node, source) {
971
- const nodeSource = sliceDocsMarkdownPromptReferenceSource(node, source);
972
- if (nodeSource === void 0) return void 0;
973
- const closingStart = nodeSource.lastIndexOf("</Prompt");
974
- if (closingStart < 0) return void 0;
975
- const children = node.children ?? [];
976
- const isolatedChild = children.length === 1 ? children[0] : void 0;
977
- if (isolatedChild?.type === "text" && typeof isolatedChild.value === "string" && isolatedChild.position === void 0) return isolatedChild.value;
978
- const nodeStart = node.position?.start?.offset;
979
- if (typeof nodeStart !== "number") return void 0;
980
- let firstChildStart;
981
- for (const child of children) {
982
- const childStart = child.position?.start?.offset;
983
- if (typeof childStart === "number" && Number.isInteger(childStart) && childStart >= nodeStart && (firstChildStart === void 0 || childStart - nodeStart < firstChildStart)) firstChildStart = childStart - nodeStart;
984
- }
985
- if (children.length > 0 && firstChildStart === void 0) return void 0;
986
- const openingEnd = nodeSource.lastIndexOf(">", (firstChildStart ?? closingStart) - 1);
987
- if (openingEnd < 0 || openingEnd >= closingStart) return void 0;
988
- return nodeSource.slice(openingEnd + 1, closingStart);
989
- }
990
- function flattenDocsMarkdownPromptReferenceNode(node) {
991
- if (typeof node.value === "string") return node.value;
992
- return (node.children ?? []).map((child) => flattenDocsMarkdownPromptReferenceNode(child)).join("");
993
- }
994
- function formatDocsMarkdownPromptReference(node) {
995
- const image = node.type === "imageReference";
996
- const visibleLabel = image ? typeof node.alt === "string" ? node.alt : "" : flattenDocsMarkdownPromptReferenceNode(node);
997
- const referenceLabel = typeof node.label === "string" ? node.label : typeof node.identifier === "string" ? node.identifier : visibleLabel;
998
- const prefix = image ? "!" : "";
999
- if (node.referenceType === "collapsed") return `${prefix}[${visibleLabel}][]`;
1000
- if (node.referenceType === "shortcut") return `${prefix}[${visibleLabel}]`;
1001
- return `${prefix}[${visibleLabel}][${referenceLabel}]`;
1002
- }
1003
- function formatDocsMarkdownPromptDefinition(node) {
1004
- const label = typeof node.label === "string" ? node.label : typeof node.identifier === "string" ? node.identifier : "";
1005
- const rawUrl = typeof node.url === "string" ? node.url : "";
1006
- return `[${label}]: ${/[<>\s]/u.test(rawUrl) ? `<${rawUrl.replace(/>/gu, "\\>")}>` : rawUrl}${typeof node.title === "string" ? ` "${node.title.replace(/"/gu, "\\\"")}"` : ""}`;
1007
- }
1008
- function createDocsMarkdownPromptReferenceText(node, source) {
1009
- return {
1010
- type: "text",
1011
- value: sliceDocsMarkdownPromptReferenceSource(node, source) ?? formatDocsMarkdownPromptReference(node),
1012
- position: node.position
1013
- };
1014
- }
1015
- function createDocsMarkdownPromptDefinitionParagraph(node, source) {
1016
- return {
1017
- type: "paragraph",
1018
- children: [{
1019
- type: "text",
1020
- value: sliceDocsMarkdownPromptReferenceSource(node, source) ?? formatDocsMarkdownPromptDefinition(node),
1021
- position: node.position
1022
- }],
1023
- position: node.position
1024
- };
1025
- }
1026
- /**
1027
- * Keep `<Prompt>` children literal before Markdown reference definitions are
1028
- * resolved by the MDX compiler.
1029
- *
1030
- * Definitions inside Prompt are copyable source, not page-level definitions.
1031
- * References inside Prompt always remain source text, while references outside
1032
- * Prompt only remain live when they have a definition outside Prompt.
1033
- */
1034
- function isolateDocsMarkdownPromptReferences(root, value) {
1035
- if (!root || typeof root !== "object") return;
1036
- const source = readDocsMarkdownPromptReferenceSource(value);
1037
- const promptDefinitions = /* @__PURE__ */ new Set();
1038
- const pageDefinitions = /* @__PURE__ */ new Set();
1039
- let hasPrompt = false;
1040
- const collectDefinitions = (node, insidePrompt) => {
1041
- const prompt = isDocsMarkdownPromptNode(node);
1042
- const literal = insidePrompt || prompt;
1043
- if (prompt) hasPrompt = true;
1044
- if (node.type === "definition") {
1045
- const identifier = normalizeDocsMarkdownReferenceIdentifier(node.identifier);
1046
- if (identifier) (literal ? promptDefinitions : pageDefinitions).add(identifier);
1047
- }
1048
- for (const child of node.children ?? []) collectDefinitions(child, literal);
1049
- };
1050
- collectDefinitions(root, false);
1051
- if (!hasPrompt) return;
1052
- const isolateReferences = (node, insidePrompt) => {
1053
- const prompt = isDocsMarkdownPromptNode(node);
1054
- if (prompt) {
1055
- const promptSource = sliceDocsMarkdownPromptChildrenSource(node, source);
1056
- if (promptSource !== void 0) {
1057
- node.children = [{
1058
- type: "text",
1059
- value: promptSource
1060
- }];
1061
- return;
1062
- }
1063
- }
1064
- const literal = insidePrompt || prompt;
1065
- const children = node.children;
1066
- if (!children) return;
1067
- node.children = children.map((child) => {
1068
- const childInsidePrompt = literal || isDocsMarkdownPromptNode(child);
1069
- if (childInsidePrompt && child.type === "definition") return createDocsMarkdownPromptDefinitionParagraph(child, source);
1070
- if (child.type === "linkReference" || child.type === "imageReference") {
1071
- const identifier = normalizeDocsMarkdownReferenceIdentifier(child.identifier);
1072
- if (childInsidePrompt || promptDefinitions.has(identifier) && !pageDefinitions.has(identifier)) return createDocsMarkdownPromptReferenceText(child, source);
1073
- }
1074
- isolateReferences(child, childInsidePrompt);
1075
- return child;
1076
- });
1077
- };
1078
- isolateReferences(root, false);
1079
- }
1080
-
1081
- //#endregion
1082
- export { formatDocsLastModifiedDate as _, resolveReadingTimeFromContent as a, DOCS_AGENT_CONTRACT_VERSION as c, httpLinkHeaderHasTargetRelation as d, AGENT_SKILL_ARCHIVE_MAX_UNCOMPRESSED_BYTES as f, deepMerge as g, extendTheme as h, resolvePageReadingTime as i, createDocsAgentContractCases as l, createTheme as m, encodeDocsHeadingTocUrls as n, resolveReadingTimeFromSource as o, readAgentSkillDocumentFromTar as p, estimateReadingTimeMinutes as r, resolveReadingTimeOptions as s, isolateDocsMarkdownPromptReferences as t, runDocsAgentConformance as u, resolveChangelogConfig as v, defineDocs as y };