@farming-labs/docs 0.2.62 → 0.2.63
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.
- package/dist/agent-CQTH7NFu.mjs +624 -0
- package/dist/agent-DKKptIgy.mjs +4365 -0
- package/dist/agent-evals-B7MIxuEX.mjs +2144 -0
- package/dist/agent-export-CBgWgPvH.mjs +910 -0
- package/dist/agent-scope-C_U--OZ7.mjs +283 -0
- package/dist/agent-skills-bundle.d.mts +13 -0
- package/dist/agent-skills-bundle.mjs +12 -0
- package/dist/agent-skills-server-CPja6Syt.d.mts +14 -0
- package/dist/agent-skills-server-DraIb6FV.mjs +415 -0
- package/dist/agent-skills-vite.d.mts +31 -0
- package/dist/agent-skills-vite.mjs +70 -0
- package/dist/agents-XWZBub6f.mjs +221 -0
- package/dist/analytics-Bx44lg6d.mjs +177 -0
- package/dist/cli/index.d.mts +15 -0
- package/dist/cli/index.mjs +452 -0
- package/dist/client/react.d.mts +45 -0
- package/dist/client/react.mjs +223 -0
- package/dist/cloud-analytics-CSyFE6SS.mjs +132 -0
- package/dist/cloud-ask-ai-sbpjOR2K.mjs +382 -0
- package/dist/cloud-ask-ai-zpwkdwnF.d.mts +23 -0
- package/dist/cloud-pdNC-tyj.mjs +1615 -0
- package/dist/code-blocks-DnNVNK2M.mjs +871 -0
- package/dist/codeblocks-CFuurVIH.mjs +250 -0
- package/dist/config-Wcdj-D0a.mjs +369 -0
- package/dist/dev-Cmy6DtdF.mjs +1333 -0
- package/dist/docs-cloud-server.d.mts +70 -0
- package/dist/docs-cloud-server.mjs +310 -0
- package/dist/doctor-CJqtzKZT.mjs +2036 -0
- package/dist/downgrade-w7e6Se0L.mjs +184 -0
- package/dist/errors-DbOhkE1h.mjs +20 -0
- package/dist/golden-evaluations-Dsvh0etV.mjs +1503 -0
- package/dist/i18n-CCaFUnAN.mjs +40 -0
- package/dist/index.d.mts +1150 -0
- package/dist/index.mjs +10 -0
- package/dist/init-CQY0Woe3.mjs +1264 -0
- package/dist/mcp-B9dcsivk.mjs +156 -0
- package/dist/mcp.d.mts +298 -0
- package/dist/mcp.mjs +4430 -0
- package/dist/metadata-DWExHQnx.mjs +237 -0
- package/dist/package-version-n5AFur8a.mjs +128 -0
- package/dist/reading-time-CYZ5VvKU.mjs +742 -0
- package/dist/review-Fndl5brR.mjs +673 -0
- package/dist/robots-BIpC4j4P.mjs +201 -0
- package/dist/robots-CUTahhoY.mjs +179 -0
- package/dist/search-B6V6qtiI.mjs +1826 -0
- package/dist/search-CaSyi6H6.d.mts +279 -0
- package/dist/search-DSjCeOk7.mjs +104 -0
- package/dist/server.d.mts +343 -0
- package/dist/server.mjs +14 -0
- package/dist/sitemap-Cykpe3Tz.mjs +249 -0
- package/dist/sitemap-server-C_6Wes83.mjs +1137 -0
- package/dist/standards-discovery-C4HUqMd2.d.mts +227 -0
- package/dist/standards-discovery-jkykaXq1.mjs +519 -0
- package/dist/templates-Bq_P7ctv.mjs +2465 -0
- package/dist/types-lMBIdZg0.d.mts +3315 -0
- package/dist/upgrade-oz-GChgt.mjs +56 -0
- package/dist/utils-DpiIioYb.mjs +225 -0
- package/package.json +1 -1
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { Dt as DEFAULT_SITEMAP_MD_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, kt as DEFAULT_SITEMAP_XML_ROUTE, m as DEFAULT_DOCS_DIAGNOSTICS_ROUTE, nn as DEFAULT_MCP_WELL_KNOWN_ROUTE, t as DEFAULT_AGENTS_MD_ROUTE, v as DEFAULT_LLMS_TXT_ROUTE, x as DEFAULT_SKILL_MD_ROUTE } from "./agent-DKKptIgy.mjs";
|
|
2
|
+
import { c as DEFAULT_AGENT_SKILLS_INDEX_ROUTE, g as DEFAULT_API_CATALOG_ROUTE, n as API_CATALOG_MEDIA_TYPE, r as API_CATALOG_PROFILE_URI, t as AGENT_SKILLS_DISCOVERY_SCHEMA_URI, u as DEFAULT_AGENT_SKILLS_ROUTE_PREFIX } from "./standards-discovery-jkykaXq1.mjs";
|
|
3
|
+
import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-BIpC4j4P.mjs";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
|
|
6
|
+
//#region src/define-docs.ts
|
|
7
|
+
/**
|
|
8
|
+
* Define docs configuration. Validates and returns the config.
|
|
9
|
+
*/
|
|
10
|
+
function defineDocs(config) {
|
|
11
|
+
return {
|
|
12
|
+
entry: config.entry ?? "docs",
|
|
13
|
+
docsPath: config.docsPath,
|
|
14
|
+
contentDir: config.contentDir,
|
|
15
|
+
i18n: config.i18n,
|
|
16
|
+
theme: config.theme,
|
|
17
|
+
nav: config.nav,
|
|
18
|
+
github: config.github,
|
|
19
|
+
themeToggle: config.themeToggle,
|
|
20
|
+
breadcrumb: config.breadcrumb,
|
|
21
|
+
sidebar: config.sidebar,
|
|
22
|
+
components: config.components,
|
|
23
|
+
analytics: config.analytics,
|
|
24
|
+
telemetry: config.telemetry,
|
|
25
|
+
cloud: config.cloud,
|
|
26
|
+
observability: config.observability,
|
|
27
|
+
onCopyClick: config.onCopyClick,
|
|
28
|
+
codeBlocks: config.codeBlocks,
|
|
29
|
+
feedback: config.feedback,
|
|
30
|
+
search: config.search,
|
|
31
|
+
mcp: config.mcp,
|
|
32
|
+
icons: config.icons,
|
|
33
|
+
pageActions: config.pageActions,
|
|
34
|
+
lastUpdated: config.lastUpdated,
|
|
35
|
+
readingTime: config.readingTime,
|
|
36
|
+
llmsTxt: config.llmsTxt,
|
|
37
|
+
sitemap: config.sitemap,
|
|
38
|
+
robots: config.robots,
|
|
39
|
+
ai: config.ai,
|
|
40
|
+
ordering: config.ordering,
|
|
41
|
+
metadata: config.metadata,
|
|
42
|
+
og: config.og,
|
|
43
|
+
changelog: config.changelog,
|
|
44
|
+
apiReference: config.apiReference,
|
|
45
|
+
agent: config.agent,
|
|
46
|
+
review: config.review
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/changelog.ts
|
|
52
|
+
function normalizePathSegment(value, fallback) {
|
|
53
|
+
return (value ?? fallback).trim().replace(/^\/+|\/+$/g, "") || fallback;
|
|
54
|
+
}
|
|
55
|
+
function normalizeContentDir(value) {
|
|
56
|
+
const trimmed = value?.trim();
|
|
57
|
+
if (!trimmed) return "changelog";
|
|
58
|
+
return trimmed.replace(/\/+$/, "") || "changelog";
|
|
59
|
+
}
|
|
60
|
+
function resolveChangelogConfig(value) {
|
|
61
|
+
if (value === false || value === void 0) return {
|
|
62
|
+
enabled: false,
|
|
63
|
+
path: "changelog",
|
|
64
|
+
contentDir: "changelog",
|
|
65
|
+
title: "Changelog",
|
|
66
|
+
description: void 0,
|
|
67
|
+
search: true
|
|
68
|
+
};
|
|
69
|
+
if (value === true) return {
|
|
70
|
+
enabled: true,
|
|
71
|
+
path: "changelog",
|
|
72
|
+
contentDir: "changelog",
|
|
73
|
+
title: "Changelog",
|
|
74
|
+
description: void 0,
|
|
75
|
+
search: true
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
enabled: value.enabled !== false,
|
|
79
|
+
path: normalizePathSegment(value.path, "changelog"),
|
|
80
|
+
contentDir: normalizeContentDir(value.contentDir),
|
|
81
|
+
title: value.title?.trim() || "Changelog",
|
|
82
|
+
description: value.description?.trim() || void 0,
|
|
83
|
+
search: value.search !== false,
|
|
84
|
+
actionsComponent: value.actionsComponent
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/utils.ts
|
|
90
|
+
/**
|
|
91
|
+
* Deep merge utility for theme overrides.
|
|
92
|
+
* Merges objects recursively; later values override earlier ones.
|
|
93
|
+
*/
|
|
94
|
+
function deepMerge(target, ...sources) {
|
|
95
|
+
if (!sources.length) return target;
|
|
96
|
+
const source = sources.shift();
|
|
97
|
+
if (!source) return target;
|
|
98
|
+
const result = { ...target };
|
|
99
|
+
for (const key of Object.keys(source)) {
|
|
100
|
+
const sourceVal = source[key];
|
|
101
|
+
const targetVal = result[key];
|
|
102
|
+
if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) result[key] = deepMerge(targetVal, sourceVal);
|
|
103
|
+
else if (sourceVal !== void 0) result[key] = sourceVal;
|
|
104
|
+
}
|
|
105
|
+
if (sources.length) return deepMerge(result, ...sources);
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/create-theme.ts
|
|
111
|
+
/**
|
|
112
|
+
* Create a theme preset factory.
|
|
113
|
+
*
|
|
114
|
+
* Returns a function that accepts optional overrides and deep-merges them
|
|
115
|
+
* with the base theme defaults. This is the same pattern used by the
|
|
116
|
+
* built-in `fumadocs()`, `darksharp()`, and `pixelBorder()` presets.
|
|
117
|
+
*
|
|
118
|
+
* @param baseTheme - The default theme configuration
|
|
119
|
+
* @returns A factory function `(overrides?) => DocsTheme`
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { createTheme } from "@farming-labs/docs";
|
|
124
|
+
*
|
|
125
|
+
* export const myTheme = createTheme({
|
|
126
|
+
* name: "my-theme",
|
|
127
|
+
* ui: {
|
|
128
|
+
* colors: { primary: "#6366f1" },
|
|
129
|
+
* layout: { contentWidth: 800 },
|
|
130
|
+
* },
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
function createTheme(baseTheme) {
|
|
135
|
+
return function themeFactory(overrides = {}) {
|
|
136
|
+
const merged = deepMerge(baseTheme, overrides);
|
|
137
|
+
if (overrides.ui?.colors) merged._userColorOverrides = { ...overrides.ui.colors };
|
|
138
|
+
return merged;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extend an existing theme preset with additional defaults.
|
|
143
|
+
*
|
|
144
|
+
* Useful when you want to build on top of an existing theme (e.g. fumadocs)
|
|
145
|
+
* rather than starting from scratch.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { extendTheme } from "@farming-labs/docs";
|
|
150
|
+
* import { fumadocs } from "@farming-labs/theme/default";
|
|
151
|
+
*
|
|
152
|
+
* // Start with fumadocs defaults, override some values
|
|
153
|
+
* export const myTheme = extendTheme(fumadocs(), {
|
|
154
|
+
* name: "my-custom-fumadocs",
|
|
155
|
+
* ui: { colors: { primary: "#22c55e" } },
|
|
156
|
+
* });
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
function extendTheme(baseTheme, extensions) {
|
|
160
|
+
return deepMerge(baseTheme, extensions);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/http-link.ts
|
|
165
|
+
/** Split an HTTP list without treating delimiters inside quotes or URI references as separators. */
|
|
166
|
+
function splitHttpList(value, delimiter) {
|
|
167
|
+
const parts = [];
|
|
168
|
+
let start = 0;
|
|
169
|
+
let quoted = false;
|
|
170
|
+
let escaped = false;
|
|
171
|
+
let angleDepth = 0;
|
|
172
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
173
|
+
const character = value[index];
|
|
174
|
+
if (escaped) {
|
|
175
|
+
escaped = false;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (quoted && character === "\\") {
|
|
179
|
+
escaped = true;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (character === "\"") {
|
|
183
|
+
quoted = !quoted;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!quoted && character === "<") angleDepth += 1;
|
|
187
|
+
if (!quoted && character === ">" && angleDepth > 0) angleDepth -= 1;
|
|
188
|
+
if (!quoted && angleDepth === 0 && character === delimiter) {
|
|
189
|
+
parts.push(value.slice(start, index).trim());
|
|
190
|
+
start = index + 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
parts.push(value.slice(start).trim());
|
|
194
|
+
return parts.filter(Boolean);
|
|
195
|
+
}
|
|
196
|
+
function unquoteHttpValue(value) {
|
|
197
|
+
const trimmed = value.trim();
|
|
198
|
+
if (!trimmed.startsWith("\"") || !trimmed.endsWith("\"")) return trimmed;
|
|
199
|
+
return trimmed.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
200
|
+
}
|
|
201
|
+
/** Parse Link header values while preserving the target/relation association. */
|
|
202
|
+
function parseHttpLinkHeader(header) {
|
|
203
|
+
if (!header) return [];
|
|
204
|
+
const links = [];
|
|
205
|
+
for (const value of splitHttpList(header, ",")) {
|
|
206
|
+
const target = value.match(/^\s*<([^>]*)>/);
|
|
207
|
+
if (!target) continue;
|
|
208
|
+
const relations = [];
|
|
209
|
+
for (const rawParameter of splitHttpList(value.slice(target[0].length), ";")) {
|
|
210
|
+
const separator = rawParameter.indexOf("=");
|
|
211
|
+
if (separator < 0 || rawParameter.slice(0, separator).trim().toLowerCase() !== "rel") continue;
|
|
212
|
+
relations.push(...unquoteHttpValue(rawParameter.slice(separator + 1)).toLowerCase().split(/\s+/));
|
|
213
|
+
}
|
|
214
|
+
links.push({
|
|
215
|
+
href: target[1],
|
|
216
|
+
relations: relations.filter(Boolean)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return links;
|
|
220
|
+
}
|
|
221
|
+
function httpLinkMatchesExpectation(links, expectation, responseUrl) {
|
|
222
|
+
let expectedUrl;
|
|
223
|
+
try {
|
|
224
|
+
expectedUrl = new URL(expectation.href, responseUrl).toString();
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return links.some((link) => {
|
|
229
|
+
try {
|
|
230
|
+
return new URL(link.href, responseUrl).toString() === expectedUrl && link.relations.includes(expectation.rel.toLowerCase());
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function httpLinkHeaderHasTargetRelation(header, href, rel, responseUrl) {
|
|
237
|
+
return httpLinkMatchesExpectation(parseHttpLinkHeader(header), {
|
|
238
|
+
href,
|
|
239
|
+
rel
|
|
240
|
+
}, responseUrl);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/agent-conformance.ts
|
|
245
|
+
const DOCS_AGENT_CONTRACT_VERSION = "1.1";
|
|
246
|
+
function normalizeOrigin(value) {
|
|
247
|
+
return value.replace(/\/+$/, "");
|
|
248
|
+
}
|
|
249
|
+
function normalizeEntry(value) {
|
|
250
|
+
return value.replace(/^\/+|\/+$/g, "") || "docs";
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The framework-neutral public surface every first-party adapter must implement.
|
|
254
|
+
* Adapter tests consume this list so new routes and invariants are added once.
|
|
255
|
+
*/
|
|
256
|
+
function createDocsAgentContractCases(options = {}) {
|
|
257
|
+
const origin = normalizeOrigin(options.origin ?? "http://localhost");
|
|
258
|
+
const entry = normalizeEntry(options.entry ?? "docs");
|
|
259
|
+
const locale = options.locale === void 0 ? "fr" : options.locale;
|
|
260
|
+
const url = (route) => `${origin}${route.startsWith("/") ? route : `/${route}`}`;
|
|
261
|
+
const markdown = ["text/markdown"];
|
|
262
|
+
const json = ["application/json"];
|
|
263
|
+
const cases = [
|
|
264
|
+
{
|
|
265
|
+
surface: "discovery",
|
|
266
|
+
request: { url: url(DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE) },
|
|
267
|
+
expect: {
|
|
268
|
+
statuses: [200],
|
|
269
|
+
contentTypes: json,
|
|
270
|
+
linkRelations: [{
|
|
271
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
272
|
+
rel: "api-catalog"
|
|
273
|
+
}, {
|
|
274
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
275
|
+
rel: "service-meta"
|
|
276
|
+
}]
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
surface: "api-catalog",
|
|
281
|
+
request: { url: url(DEFAULT_API_CATALOG_ROUTE) },
|
|
282
|
+
expect: {
|
|
283
|
+
statuses: [200],
|
|
284
|
+
contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
|
|
285
|
+
bodyIncludes: [
|
|
286
|
+
"linkset",
|
|
287
|
+
DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
|
|
288
|
+
DEFAULT_AGENT_SKILLS_INDEX_ROUTE
|
|
289
|
+
],
|
|
290
|
+
linkRelations: [{
|
|
291
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
292
|
+
rel: "api-catalog"
|
|
293
|
+
}]
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
surface: "api-catalog-head",
|
|
298
|
+
request: {
|
|
299
|
+
url: url(DEFAULT_API_CATALOG_ROUTE),
|
|
300
|
+
init: { method: "HEAD" }
|
|
301
|
+
},
|
|
302
|
+
expect: {
|
|
303
|
+
statuses: [200],
|
|
304
|
+
contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
|
|
305
|
+
bodyEmpty: true,
|
|
306
|
+
linkRelations: [{
|
|
307
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
308
|
+
rel: "api-catalog"
|
|
309
|
+
}]
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
surface: "agent-skills-index",
|
|
314
|
+
request: { url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE) },
|
|
315
|
+
expect: {
|
|
316
|
+
statuses: [200],
|
|
317
|
+
contentTypes: json,
|
|
318
|
+
bodyIncludes: [
|
|
319
|
+
"https://schemas.agentskills.io/discovery/0.2.0/schema.json",
|
|
320
|
+
`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
|
|
321
|
+
"sha256:"
|
|
322
|
+
],
|
|
323
|
+
linkRelations: [{
|
|
324
|
+
href: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
|
|
325
|
+
rel: "item"
|
|
326
|
+
}, {
|
|
327
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
328
|
+
rel: "api-catalog"
|
|
329
|
+
}]
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
surface: "agent-skills-index-head",
|
|
334
|
+
request: {
|
|
335
|
+
url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE),
|
|
336
|
+
init: { method: "HEAD" }
|
|
337
|
+
},
|
|
338
|
+
expect: {
|
|
339
|
+
statuses: [200],
|
|
340
|
+
contentTypes: json,
|
|
341
|
+
bodyEmpty: true,
|
|
342
|
+
linkRelations: [{
|
|
343
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
344
|
+
rel: "api-catalog"
|
|
345
|
+
}]
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
surface: "agent-skill",
|
|
350
|
+
request: { url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`) },
|
|
351
|
+
expect: {
|
|
352
|
+
statuses: [200],
|
|
353
|
+
contentTypes: markdown,
|
|
354
|
+
bodyIncludes: ["name: docs"],
|
|
355
|
+
linkRelations: [{
|
|
356
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
357
|
+
rel: "collection"
|
|
358
|
+
}, {
|
|
359
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
360
|
+
rel: "api-catalog"
|
|
361
|
+
}]
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
surface: "agent-skill-head",
|
|
366
|
+
request: {
|
|
367
|
+
url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`),
|
|
368
|
+
init: { method: "HEAD" }
|
|
369
|
+
},
|
|
370
|
+
expect: {
|
|
371
|
+
statuses: [200],
|
|
372
|
+
contentTypes: markdown,
|
|
373
|
+
bodyEmpty: true,
|
|
374
|
+
linkRelations: [{
|
|
375
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
376
|
+
rel: "collection"
|
|
377
|
+
}, {
|
|
378
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
379
|
+
rel: "api-catalog"
|
|
380
|
+
}]
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
surface: "config",
|
|
385
|
+
request: { url: url(DEFAULT_DOCS_CONFIG_ROUTE) },
|
|
386
|
+
expect: {
|
|
387
|
+
statuses: [200],
|
|
388
|
+
contentTypes: json,
|
|
389
|
+
bodyIncludes: ["docs-config-map.v1"]
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
surface: "diagnostics",
|
|
394
|
+
request: { url: url(DEFAULT_DOCS_DIAGNOSTICS_ROUTE) },
|
|
395
|
+
expect: {
|
|
396
|
+
statuses: [200],
|
|
397
|
+
contentTypes: json,
|
|
398
|
+
bodyIncludes: ["docs-diagnostics.v1"]
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
surface: "feedback-schema",
|
|
403
|
+
request: { url: url(`${DEFAULT_AGENT_FEEDBACK_ROUTE}/schema`) },
|
|
404
|
+
expect: {
|
|
405
|
+
statuses: [200],
|
|
406
|
+
contentTypes: ["application/schema+json"]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
surface: "markdown",
|
|
411
|
+
request: { url: url(`/${entry}.md`) },
|
|
412
|
+
expect: {
|
|
413
|
+
statuses: [200],
|
|
414
|
+
contentTypes: markdown,
|
|
415
|
+
bodyIncludes: ["Introduction"]
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
surface: "markdown-accept",
|
|
420
|
+
request: {
|
|
421
|
+
url: url(`/${entry}`),
|
|
422
|
+
init: { headers: { Accept: "text/markdown" } }
|
|
423
|
+
},
|
|
424
|
+
expect: {
|
|
425
|
+
statuses: [200],
|
|
426
|
+
contentTypes: markdown,
|
|
427
|
+
bodyIncludes: ["Introduction"]
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
surface: "markdown-missing",
|
|
432
|
+
request: { url: url(`/${entry}/missing.md`) },
|
|
433
|
+
expect: {
|
|
434
|
+
statuses: [200, 404],
|
|
435
|
+
contentTypes: markdown,
|
|
436
|
+
bodyIncludes: ["not found"]
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
surface: "llms",
|
|
441
|
+
request: { url: url(DEFAULT_LLMS_TXT_ROUTE) },
|
|
442
|
+
expect: {
|
|
443
|
+
statuses: [200],
|
|
444
|
+
contentTypes: ["text/plain"],
|
|
445
|
+
bodyIncludes: ["Introduction"]
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
surface: "llms-full",
|
|
450
|
+
request: { url: url(DEFAULT_LLMS_FULL_TXT_ROUTE) },
|
|
451
|
+
expect: {
|
|
452
|
+
statuses: [200],
|
|
453
|
+
contentTypes: ["text/plain"],
|
|
454
|
+
bodyIncludes: ["Introduction"]
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
surface: "agents",
|
|
459
|
+
request: { url: url(DEFAULT_AGENTS_MD_ROUTE) },
|
|
460
|
+
expect: {
|
|
461
|
+
statuses: [200],
|
|
462
|
+
contentTypes: markdown,
|
|
463
|
+
bodyIncludes: ["AGENTS.md"]
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
surface: "skill",
|
|
468
|
+
request: { url: url(DEFAULT_SKILL_MD_ROUTE) },
|
|
469
|
+
expect: {
|
|
470
|
+
statuses: [200],
|
|
471
|
+
contentTypes: markdown,
|
|
472
|
+
bodyIncludes: ["name:"]
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
surface: "sitemap-xml",
|
|
477
|
+
request: { url: url(DEFAULT_SITEMAP_XML_ROUTE) },
|
|
478
|
+
expect: {
|
|
479
|
+
statuses: [200],
|
|
480
|
+
contentTypes: ["application/xml", "text/xml"],
|
|
481
|
+
bodyIncludes: ["<urlset"]
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
surface: "sitemap-markdown",
|
|
486
|
+
request: { url: url(DEFAULT_SITEMAP_MD_ROUTE) },
|
|
487
|
+
expect: {
|
|
488
|
+
statuses: [200],
|
|
489
|
+
contentTypes: markdown,
|
|
490
|
+
bodyIncludes: ["Introduction"]
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
surface: "robots",
|
|
495
|
+
request: { url: url(DEFAULT_ROBOTS_TXT_ROUTE) },
|
|
496
|
+
expect: {
|
|
497
|
+
statuses: [200],
|
|
498
|
+
contentTypes: ["text/plain"],
|
|
499
|
+
bodyIncludes: ["User-agent:"]
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
surface: "mcp",
|
|
504
|
+
request: {
|
|
505
|
+
url: url(DEFAULT_MCP_WELL_KNOWN_ROUTE),
|
|
506
|
+
init: {
|
|
507
|
+
method: "POST",
|
|
508
|
+
headers: {
|
|
509
|
+
Accept: "application/json, text/event-stream",
|
|
510
|
+
"Content-Type": "application/json",
|
|
511
|
+
"MCP-Protocol-Version": "2025-11-25"
|
|
512
|
+
},
|
|
513
|
+
body: JSON.stringify({
|
|
514
|
+
jsonrpc: "2.0",
|
|
515
|
+
id: 1,
|
|
516
|
+
method: "initialize",
|
|
517
|
+
params: {
|
|
518
|
+
protocolVersion: "2025-11-25",
|
|
519
|
+
capabilities: {},
|
|
520
|
+
clientInfo: {
|
|
521
|
+
name: "agent-conformance",
|
|
522
|
+
version: DOCS_AGENT_CONTRACT_VERSION
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
expect: {
|
|
529
|
+
statuses: [200],
|
|
530
|
+
contentTypes: ["application/json", "text/event-stream"],
|
|
531
|
+
bodyIncludes: ["serverInfo", "protocolVersion"]
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
];
|
|
535
|
+
if (locale) {
|
|
536
|
+
const markdownMissingIndex = cases.findIndex((contractCase) => contractCase.surface === "markdown-missing");
|
|
537
|
+
cases.splice(markdownMissingIndex, 0, {
|
|
538
|
+
surface: "markdown-locale",
|
|
539
|
+
request: { url: url(`/${entry}.md?lang=${encodeURIComponent(locale)}`) },
|
|
540
|
+
expect: {
|
|
541
|
+
statuses: [200],
|
|
542
|
+
contentTypes: markdown,
|
|
543
|
+
bodyIncludes: ["Bonjour"]
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return cases;
|
|
548
|
+
}
|
|
549
|
+
function parseParameterizedValue(value) {
|
|
550
|
+
const [rawValue, ...rawParameters] = splitHttpList(value, ";");
|
|
551
|
+
const normalizedValue = rawValue?.trim().toLowerCase();
|
|
552
|
+
if (!normalizedValue) return null;
|
|
553
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const rawParameter of rawParameters) {
|
|
555
|
+
const separator = rawParameter.indexOf("=");
|
|
556
|
+
if (separator < 0) continue;
|
|
557
|
+
const name = rawParameter.slice(0, separator).trim().toLowerCase();
|
|
558
|
+
if (!name) continue;
|
|
559
|
+
parameters.set(name, unquoteHttpValue(rawParameter.slice(separator + 1)));
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
value: normalizedValue,
|
|
563
|
+
parameters
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function matchesContentType(actual, expected) {
|
|
567
|
+
const parsedActual = parseParameterizedValue(actual);
|
|
568
|
+
if (!parsedActual) return false;
|
|
569
|
+
return expected.some((value) => {
|
|
570
|
+
const parsedExpected = parseParameterizedValue(value);
|
|
571
|
+
if (!parsedExpected || parsedExpected.value !== parsedActual.value) return false;
|
|
572
|
+
return [...parsedExpected.parameters].every(([name, expectedValue]) => parsedActual.parameters.get(name) === expectedValue);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function asRecord(value) {
|
|
576
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
577
|
+
}
|
|
578
|
+
function isNonEmptyString(value) {
|
|
579
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
580
|
+
}
|
|
581
|
+
async function sha256Content(content) {
|
|
582
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", content);
|
|
583
|
+
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
584
|
+
}
|
|
585
|
+
async function validateAgentSkillsIndex(content, indexUrl, handle) {
|
|
586
|
+
const issues = [];
|
|
587
|
+
let parsed;
|
|
588
|
+
try {
|
|
589
|
+
parsed = JSON.parse(content);
|
|
590
|
+
} catch {
|
|
591
|
+
return ["Agent Skills index did not contain valid JSON"];
|
|
592
|
+
}
|
|
593
|
+
const root = asRecord(parsed);
|
|
594
|
+
if (root?.$schema !== AGENT_SKILLS_DISCOVERY_SCHEMA_URI) issues.push(`Agent Skills index did not declare ${AGENT_SKILLS_DISCOVERY_SCHEMA_URI}`);
|
|
595
|
+
if (!Array.isArray(root?.skills) || root.skills.length === 0) {
|
|
596
|
+
issues.push("Agent Skills index did not declare any skills");
|
|
597
|
+
return issues;
|
|
598
|
+
}
|
|
599
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
600
|
+
const origin = new URL(indexUrl).origin;
|
|
601
|
+
for (const rawSkill of root.skills) {
|
|
602
|
+
const skill = asRecord(rawSkill);
|
|
603
|
+
const name = skill?.name;
|
|
604
|
+
const description = skill?.description;
|
|
605
|
+
const artifactRoute = skill?.url;
|
|
606
|
+
const digest = skill?.digest;
|
|
607
|
+
if (!isNonEmptyString(name) || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) || name.length > 64 || skill?.type !== "skill-md" && skill?.type !== "archive" || !isNonEmptyString(description) || description.length > 1024 || !isNonEmptyString(artifactRoute) || typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest)) {
|
|
608
|
+
issues.push("Agent Skills index contained an invalid skill entry");
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (seenNames.has(name)) {
|
|
612
|
+
issues.push(`Agent Skills index declared duplicate skill ${JSON.stringify(name)}`);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
seenNames.add(name);
|
|
616
|
+
let artifactUrl;
|
|
617
|
+
try {
|
|
618
|
+
artifactUrl = new URL(artifactRoute, indexUrl);
|
|
619
|
+
} catch {
|
|
620
|
+
issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} was invalid`);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const expectedPath = skill.type === "archive" ? `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${name}.tar.gz` : `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${name}/SKILL.md`;
|
|
624
|
+
if (artifactUrl.origin !== origin || !artifactUrl.pathname.endsWith(expectedPath)) {
|
|
625
|
+
issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} did not resolve to same-origin ${expectedPath}`);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
const response = await handle(new Request(artifactUrl), "agent-skill");
|
|
630
|
+
const artifact = new Uint8Array(await response.arrayBuffer());
|
|
631
|
+
if (response.status !== 200) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned status ${response.status}`);
|
|
632
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
633
|
+
if (!matchesContentType(contentType, [skill.type === "archive" ? "application/gzip" : "text/markdown"])) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned content-type ${contentType || "<missing>"}`);
|
|
634
|
+
const actualDigest = `sha256:${await sha256Content(artifact)}`;
|
|
635
|
+
if (actualDigest !== digest) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} digest ${actualDigest} did not match ${digest}`);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return issues;
|
|
641
|
+
}
|
|
642
|
+
async function runDocsAgentConformance(options) {
|
|
643
|
+
const cases = createDocsAgentContractCases(options);
|
|
644
|
+
const results = [];
|
|
645
|
+
for (const contractCase of cases) {
|
|
646
|
+
const issues = [];
|
|
647
|
+
try {
|
|
648
|
+
const response = await options.handle(new Request(contractCase.request.url, contractCase.request.init), contractCase.surface);
|
|
649
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
650
|
+
const body = await response.text();
|
|
651
|
+
if (!contractCase.expect.statuses.includes(response.status)) issues.push(`expected status ${contractCase.expect.statuses.join(" or ")}, received ${response.status}`);
|
|
652
|
+
if (!matchesContentType(contentType, contractCase.expect.contentTypes)) issues.push(`expected content-type ${contractCase.expect.contentTypes.join(" or ")}, received ${contentType || "<missing>"}`);
|
|
653
|
+
for (const requiredText of contractCase.expect.bodyIncludes ?? []) if (!body.toLowerCase().includes(requiredText.toLowerCase())) issues.push(`response body did not include ${JSON.stringify(requiredText)}`);
|
|
654
|
+
if (contractCase.expect.bodyEmpty && body !== "") issues.push(`expected an empty response body, received ${body.length} characters`);
|
|
655
|
+
if (contractCase.surface === "agent-skills-index") issues.push(...await validateAgentSkillsIndex(body, contractCase.request.url, options.handle));
|
|
656
|
+
for (const [header, requiredValues] of Object.entries(contractCase.expect.headerIncludes ?? {})) {
|
|
657
|
+
const actual = response.headers.get(header) ?? "";
|
|
658
|
+
for (const requiredValue of requiredValues) if (!actual.toLowerCase().includes(requiredValue.toLowerCase())) issues.push(`response header ${JSON.stringify(header)} did not include ${JSON.stringify(requiredValue)}`);
|
|
659
|
+
}
|
|
660
|
+
const parsedLinks = parseHttpLinkHeader(response.headers.get("link"));
|
|
661
|
+
for (const expectation of contractCase.expect.linkRelations ?? []) if (!httpLinkMatchesExpectation(parsedLinks, expectation, contractCase.request.url)) issues.push(`response Link header did not include ${JSON.stringify(expectation.href)} with rel=${JSON.stringify(expectation.rel)} in the same link-value`);
|
|
662
|
+
results.push({
|
|
663
|
+
surface: contractCase.surface,
|
|
664
|
+
passed: issues.length === 0,
|
|
665
|
+
status: response.status,
|
|
666
|
+
contentType,
|
|
667
|
+
issues
|
|
668
|
+
});
|
|
669
|
+
} catch (error) {
|
|
670
|
+
issues.push(error instanceof Error ? error.message : String(error));
|
|
671
|
+
results.push({
|
|
672
|
+
surface: contractCase.surface,
|
|
673
|
+
passed: false,
|
|
674
|
+
issues
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
adapter: options.adapter,
|
|
680
|
+
contractVersion: DOCS_AGENT_CONTRACT_VERSION,
|
|
681
|
+
passed: results.every((result) => result.passed),
|
|
682
|
+
cases: results
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/reading-time.ts
|
|
688
|
+
function hasExplicitReadingTime(frontmatter) {
|
|
689
|
+
return Object.prototype.hasOwnProperty.call(frontmatter ?? {}, "readingTime");
|
|
690
|
+
}
|
|
691
|
+
function normalizeWordsPerMinute(wordsPerMinute) {
|
|
692
|
+
if (typeof wordsPerMinute !== "number" || !Number.isFinite(wordsPerMinute)) return 220;
|
|
693
|
+
return Math.max(1, Math.floor(wordsPerMinute));
|
|
694
|
+
}
|
|
695
|
+
function stripNonReadingContent(content, options) {
|
|
696
|
+
const includeCode = options?.includeCode === true;
|
|
697
|
+
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, " ");
|
|
698
|
+
}
|
|
699
|
+
function estimateReadingTimeMinutes(content, wordsPerMinute, options) {
|
|
700
|
+
const wordCount = stripNonReadingContent(content, options).match(/\b[\p{L}\p{N}][\p{L}\p{N}'’-]*\b/gu)?.length ?? 0;
|
|
701
|
+
return Math.max(1, Math.ceil(wordCount / normalizeWordsPerMinute(wordsPerMinute)));
|
|
702
|
+
}
|
|
703
|
+
function resolveReadingTimeOptions(readingTime) {
|
|
704
|
+
if (readingTime === true) return {
|
|
705
|
+
enabled: true,
|
|
706
|
+
format: "long",
|
|
707
|
+
includeCode: false
|
|
708
|
+
};
|
|
709
|
+
if (readingTime === false || readingTime === void 0 || readingTime === null) return {
|
|
710
|
+
enabled: false,
|
|
711
|
+
format: "long",
|
|
712
|
+
includeCode: false
|
|
713
|
+
};
|
|
714
|
+
if (typeof readingTime !== "object") return {
|
|
715
|
+
enabled: false,
|
|
716
|
+
format: "long",
|
|
717
|
+
includeCode: false
|
|
718
|
+
};
|
|
719
|
+
return {
|
|
720
|
+
enabled: readingTime.enabled !== false,
|
|
721
|
+
wordsPerMinute: typeof readingTime.wordsPerMinute === "number" && Number.isFinite(readingTime.wordsPerMinute) ? readingTime.wordsPerMinute : void 0,
|
|
722
|
+
format: readingTime.format === "short" ? "short" : "long",
|
|
723
|
+
includeCode: readingTime.includeCode === true
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function resolveReadingTimeFromContent(frontmatter, content, wordsPerMinute, options) {
|
|
727
|
+
const pageData = frontmatter ?? {};
|
|
728
|
+
if (pageData.readingTime === false) return null;
|
|
729
|
+
if (typeof pageData.readingTime === "number" && Number.isFinite(pageData.readingTime)) return Math.max(1, Math.ceil(pageData.readingTime));
|
|
730
|
+
return estimateReadingTimeMinutes(content, wordsPerMinute, options);
|
|
731
|
+
}
|
|
732
|
+
function resolvePageReadingTime(frontmatter, content, options) {
|
|
733
|
+
if (!(options?.enabledByDefault ?? false) && !hasExplicitReadingTime(frontmatter)) return;
|
|
734
|
+
return resolveReadingTimeFromContent(frontmatter, content, options?.wordsPerMinute, { includeCode: options?.includeCode });
|
|
735
|
+
}
|
|
736
|
+
function resolveReadingTimeFromSource(source, wordsPerMinute, options) {
|
|
737
|
+
const { data, content } = matter(source);
|
|
738
|
+
return resolveReadingTimeFromContent(data, content, wordsPerMinute, options);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
//#endregion
|
|
742
|
+
export { resolveReadingTimeOptions as a, runDocsAgentConformance as c, extendTheme as d, deepMerge as f, resolveReadingTimeFromSource as i, httpLinkHeaderHasTargetRelation as l, defineDocs as m, resolvePageReadingTime as n, DOCS_AGENT_CONTRACT_VERSION as o, resolveChangelogConfig as p, resolveReadingTimeFromContent as r, createDocsAgentContractCases as s, estimateReadingTimeMinutes as t, createTheme as u };
|