@farming-labs/docs 0.2.57 → 0.2.59
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-CxDzPqtl.mjs → agent-BFqyqEnC.mjs} +557 -71
- package/dist/{agent-v6R0V3lm.mjs → agent-DlxriaTs.mjs} +3 -3
- package/dist/agent-evals-kJs2Y9xR.mjs +2144 -0
- package/dist/{agent-export-CZR20g0e.mjs → agent-export-BgUaiW8f.mjs} +20 -10
- package/dist/{agents-BPonjolo.mjs → agents-Djh-HXih.mjs} +15 -7
- package/dist/cli/index.mjs +29 -29
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-C45jpBtp.mjs → cloud-BH_sHX64.mjs} +3 -3
- package/dist/{cloud-ask-ai-CvRVnwdK.d.mts → cloud-ask-ai-B2WnG4fF.d.mts} +1 -1
- package/dist/{code-blocks-C9awWzEQ.mjs → code-blocks-qe0T8-xe.mjs} +1 -1
- package/dist/{codeblocks-CSqgqbg-.mjs → codeblocks-Bq67u32v.mjs} +2 -2
- package/dist/{dev-BA2kRuCn.mjs → dev-CAQlbguk.mjs} +2 -2
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/docs-cloud-server.mjs +1 -1
- package/dist/{doctor-svA9Flsx.mjs → doctor-CO1VMcF_.mjs} +208 -41
- package/dist/{downgrade-BStNhyCc.mjs → downgrade-BZs86NVr.mjs} +2 -2
- package/dist/{agent-surface-drift-rGFS38Ak.mjs → golden-evaluations-BN9u2wxw.mjs} +60 -4
- package/dist/index.d.mts +98 -20
- package/dist/index.mjs +6 -5
- package/dist/{init-Dm0yDz6v.mjs → init-B_9ENq8Z.mjs} +3 -3
- package/dist/{mcp-BYIgKsPn.mjs → mcp-BAJr3wC2.mjs} +6 -6
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.mjs +149 -7
- package/dist/metadata-BDuewuzq.mjs +237 -0
- package/dist/{package-version-DRIc08EU.mjs → package-version-qik_4J6C.mjs} +1 -1
- package/dist/{reading-time-W_Id2ayC.mjs → reading-time-BkEft6SD.mjs} +307 -250
- package/dist/{review-DZ81wm7h.mjs → review-NC-sOdXn.mjs} +44 -32
- package/dist/{robots-mSc98PMp.mjs → robots-DskPvGPw.mjs} +4 -4
- package/dist/{robots-DRpoiYAV.mjs → robots-ltltiLJF.mjs} +4 -1
- package/dist/{search-9uEaC23e.d.mts → search-C1JitPwi.d.mts} +153 -6
- package/dist/{search-OHp36znm.mjs → search-D57JXQLj.mjs} +150 -39
- package/dist/{search-AMEoabYU.mjs → search-o4Ud6OXv.mjs} +6 -6
- package/dist/server.d.mts +108 -6
- package/dist/server.mjs +6 -6
- package/dist/{sitemap-Cyc0Bbbn.mjs → sitemap-Cq-Yj_iA.mjs} +7 -7
- package/dist/{sitemap-server-zFYctvCa.mjs → sitemap-server-C1ibVKOy.mjs} +2 -1
- package/dist/{types-R-DN23yw.d.mts → types-XHABMh_f.d.mts} +96 -6
- package/dist/{upgrade-CXYRNV0C.mjs → upgrade-BCJTCW3O.mjs} +2 -2
- package/package.json +1 -1
- package/dist/agent-evals-BD17jOqL.mjs +0 -1166
- /package/dist/{agent-scope-B8lptqCd.mjs → agent-scope-CCaIY1aK.mjs} +0 -0
- /package/dist/{cloud-ask-ai-1k0q6OAH.mjs → cloud-ask-ai-hnJfj8-X.mjs} +0 -0
- /package/dist/{config-DZbZiYEa.mjs → config-DASewQ0x.mjs} +0 -0
- /package/dist/{errors-BFUtdZfC.mjs → errors-CVqZ3kOO.mjs} +0 -0
- /package/dist/{templates-1Cod8KrJ.mjs → templates-CfQjpDWW.mjs} +0 -0
- /package/dist/{utils-DBCCkkJS.mjs → utils-6UCLxv4B.mjs} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as DEFAULT_MCP_WELL_KNOWN_ROUTE,
|
|
2
|
-
import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-
|
|
1
|
+
import { Gt as API_CATALOG_PROFILE_URI, Nt as DEFAULT_SITEMAP_XML_ROUTE, Qt as DEFAULT_API_CATALOG_ROUTE, S as DEFAULT_MCP_WELL_KNOWN_ROUTE, Ut as AGENT_SKILLS_DISCOVERY_SCHEMA_URI, Wt as API_CATALOG_MEDIA_TYPE, Yt as DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, 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, jt as DEFAULT_SITEMAP_MD_ROUTE, m as DEFAULT_DOCS_DIAGNOSTICS_ROUTE, qt as DEFAULT_AGENT_SKILLS_INDEX_ROUTE, t as DEFAULT_AGENTS_MD_ROUTE, v as DEFAULT_LLMS_TXT_ROUTE, w as DEFAULT_SKILL_MD_ROUTE } from "./agent-BFqyqEnC.mjs";
|
|
2
|
+
import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-ltltiLJF.mjs";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
|
|
5
5
|
//#region src/define-docs.ts
|
|
@@ -159,9 +159,89 @@ function extendTheme(baseTheme, extensions) {
|
|
|
159
159
|
return deepMerge(baseTheme, extensions);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/http-link.ts
|
|
164
|
+
/** Split an HTTP list without treating delimiters inside quotes or URI references as separators. */
|
|
165
|
+
function splitHttpList(value, delimiter) {
|
|
166
|
+
const parts = [];
|
|
167
|
+
let start = 0;
|
|
168
|
+
let quoted = false;
|
|
169
|
+
let escaped = false;
|
|
170
|
+
let angleDepth = 0;
|
|
171
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
172
|
+
const character = value[index];
|
|
173
|
+
if (escaped) {
|
|
174
|
+
escaped = false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (quoted && character === "\\") {
|
|
178
|
+
escaped = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (character === "\"") {
|
|
182
|
+
quoted = !quoted;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (!quoted && character === "<") angleDepth += 1;
|
|
186
|
+
if (!quoted && character === ">" && angleDepth > 0) angleDepth -= 1;
|
|
187
|
+
if (!quoted && angleDepth === 0 && character === delimiter) {
|
|
188
|
+
parts.push(value.slice(start, index).trim());
|
|
189
|
+
start = index + 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
parts.push(value.slice(start).trim());
|
|
193
|
+
return parts.filter(Boolean);
|
|
194
|
+
}
|
|
195
|
+
function unquoteHttpValue(value) {
|
|
196
|
+
const trimmed = value.trim();
|
|
197
|
+
if (!trimmed.startsWith("\"") || !trimmed.endsWith("\"")) return trimmed;
|
|
198
|
+
return trimmed.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
199
|
+
}
|
|
200
|
+
/** Parse Link header values while preserving the target/relation association. */
|
|
201
|
+
function parseHttpLinkHeader(header) {
|
|
202
|
+
if (!header) return [];
|
|
203
|
+
const links = [];
|
|
204
|
+
for (const value of splitHttpList(header, ",")) {
|
|
205
|
+
const target = value.match(/^\s*<([^>]*)>/);
|
|
206
|
+
if (!target) continue;
|
|
207
|
+
const relations = [];
|
|
208
|
+
for (const rawParameter of splitHttpList(value.slice(target[0].length), ";")) {
|
|
209
|
+
const separator = rawParameter.indexOf("=");
|
|
210
|
+
if (separator < 0 || rawParameter.slice(0, separator).trim().toLowerCase() !== "rel") continue;
|
|
211
|
+
relations.push(...unquoteHttpValue(rawParameter.slice(separator + 1)).toLowerCase().split(/\s+/));
|
|
212
|
+
}
|
|
213
|
+
links.push({
|
|
214
|
+
href: target[1],
|
|
215
|
+
relations: relations.filter(Boolean)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return links;
|
|
219
|
+
}
|
|
220
|
+
function httpLinkMatchesExpectation(links, expectation, responseUrl) {
|
|
221
|
+
let expectedUrl;
|
|
222
|
+
try {
|
|
223
|
+
expectedUrl = new URL(expectation.href, responseUrl).toString();
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return links.some((link) => {
|
|
228
|
+
try {
|
|
229
|
+
return new URL(link.href, responseUrl).toString() === expectedUrl && link.relations.includes(expectation.rel.toLowerCase());
|
|
230
|
+
} catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function httpLinkHeaderHasTargetRelation(header, href, rel, responseUrl) {
|
|
236
|
+
return httpLinkMatchesExpectation(parseHttpLinkHeader(header), {
|
|
237
|
+
href,
|
|
238
|
+
rel
|
|
239
|
+
}, responseUrl);
|
|
240
|
+
}
|
|
241
|
+
|
|
162
242
|
//#endregion
|
|
163
243
|
//#region src/agent-conformance.ts
|
|
164
|
-
const DOCS_AGENT_CONTRACT_VERSION = "1.
|
|
244
|
+
const DOCS_AGENT_CONTRACT_VERSION = "1.1";
|
|
165
245
|
function normalizeOrigin(value) {
|
|
166
246
|
return value.replace(/\/+$/, "");
|
|
167
247
|
}
|
|
@@ -185,7 +265,118 @@ function createDocsAgentContractCases(options = {}) {
|
|
|
185
265
|
request: { url: url(DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE) },
|
|
186
266
|
expect: {
|
|
187
267
|
statuses: [200],
|
|
188
|
-
contentTypes: json
|
|
268
|
+
contentTypes: json,
|
|
269
|
+
linkRelations: [{
|
|
270
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
271
|
+
rel: "api-catalog"
|
|
272
|
+
}, {
|
|
273
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
274
|
+
rel: "service-meta"
|
|
275
|
+
}]
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
surface: "api-catalog",
|
|
280
|
+
request: { url: url(DEFAULT_API_CATALOG_ROUTE) },
|
|
281
|
+
expect: {
|
|
282
|
+
statuses: [200],
|
|
283
|
+
contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
|
|
284
|
+
bodyIncludes: [
|
|
285
|
+
"linkset",
|
|
286
|
+
DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
|
|
287
|
+
DEFAULT_AGENT_SKILLS_INDEX_ROUTE
|
|
288
|
+
],
|
|
289
|
+
linkRelations: [{
|
|
290
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
291
|
+
rel: "api-catalog"
|
|
292
|
+
}]
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
surface: "api-catalog-head",
|
|
297
|
+
request: {
|
|
298
|
+
url: url(DEFAULT_API_CATALOG_ROUTE),
|
|
299
|
+
init: { method: "HEAD" }
|
|
300
|
+
},
|
|
301
|
+
expect: {
|
|
302
|
+
statuses: [200],
|
|
303
|
+
contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
|
|
304
|
+
bodyEmpty: true,
|
|
305
|
+
linkRelations: [{
|
|
306
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
307
|
+
rel: "api-catalog"
|
|
308
|
+
}]
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
surface: "agent-skills-index",
|
|
313
|
+
request: { url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE) },
|
|
314
|
+
expect: {
|
|
315
|
+
statuses: [200],
|
|
316
|
+
contentTypes: json,
|
|
317
|
+
bodyIncludes: [
|
|
318
|
+
"https://schemas.agentskills.io/discovery/0.2.0/schema.json",
|
|
319
|
+
`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
|
|
320
|
+
"sha256:"
|
|
321
|
+
],
|
|
322
|
+
linkRelations: [{
|
|
323
|
+
href: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
|
|
324
|
+
rel: "item"
|
|
325
|
+
}, {
|
|
326
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
327
|
+
rel: "api-catalog"
|
|
328
|
+
}]
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
surface: "agent-skills-index-head",
|
|
333
|
+
request: {
|
|
334
|
+
url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE),
|
|
335
|
+
init: { method: "HEAD" }
|
|
336
|
+
},
|
|
337
|
+
expect: {
|
|
338
|
+
statuses: [200],
|
|
339
|
+
contentTypes: json,
|
|
340
|
+
bodyEmpty: true,
|
|
341
|
+
linkRelations: [{
|
|
342
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
343
|
+
rel: "api-catalog"
|
|
344
|
+
}]
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
surface: "agent-skill",
|
|
349
|
+
request: { url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`) },
|
|
350
|
+
expect: {
|
|
351
|
+
statuses: [200],
|
|
352
|
+
contentTypes: markdown,
|
|
353
|
+
bodyIncludes: ["name: docs"],
|
|
354
|
+
linkRelations: [{
|
|
355
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
356
|
+
rel: "collection"
|
|
357
|
+
}, {
|
|
358
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
359
|
+
rel: "api-catalog"
|
|
360
|
+
}]
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
surface: "agent-skill-head",
|
|
365
|
+
request: {
|
|
366
|
+
url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`),
|
|
367
|
+
init: { method: "HEAD" }
|
|
368
|
+
},
|
|
369
|
+
expect: {
|
|
370
|
+
statuses: [200],
|
|
371
|
+
contentTypes: markdown,
|
|
372
|
+
bodyEmpty: true,
|
|
373
|
+
linkRelations: [{
|
|
374
|
+
href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
375
|
+
rel: "collection"
|
|
376
|
+
}, {
|
|
377
|
+
href: DEFAULT_API_CATALOG_ROUTE,
|
|
378
|
+
rel: "api-catalog"
|
|
379
|
+
}]
|
|
189
380
|
}
|
|
190
381
|
},
|
|
191
382
|
{
|
|
@@ -340,22 +531,112 @@ function createDocsAgentContractCases(options = {}) {
|
|
|
340
531
|
}
|
|
341
532
|
}
|
|
342
533
|
];
|
|
343
|
-
if (locale)
|
|
344
|
-
surface
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
534
|
+
if (locale) {
|
|
535
|
+
const markdownMissingIndex = cases.findIndex((contractCase) => contractCase.surface === "markdown-missing");
|
|
536
|
+
cases.splice(markdownMissingIndex, 0, {
|
|
537
|
+
surface: "markdown-locale",
|
|
538
|
+
request: { url: url(`/${entry}.md?lang=${encodeURIComponent(locale)}`) },
|
|
539
|
+
expect: {
|
|
540
|
+
statuses: [200],
|
|
541
|
+
contentTypes: markdown,
|
|
542
|
+
bodyIncludes: ["Bonjour"]
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
352
546
|
return cases;
|
|
353
547
|
}
|
|
548
|
+
function parseParameterizedValue(value) {
|
|
549
|
+
const [rawValue, ...rawParameters] = splitHttpList(value, ";");
|
|
550
|
+
const normalizedValue = rawValue?.trim().toLowerCase();
|
|
551
|
+
if (!normalizedValue) return null;
|
|
552
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
553
|
+
for (const rawParameter of rawParameters) {
|
|
554
|
+
const separator = rawParameter.indexOf("=");
|
|
555
|
+
if (separator < 0) continue;
|
|
556
|
+
const name = rawParameter.slice(0, separator).trim().toLowerCase();
|
|
557
|
+
if (!name) continue;
|
|
558
|
+
parameters.set(name, unquoteHttpValue(rawParameter.slice(separator + 1)));
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
value: normalizedValue,
|
|
562
|
+
parameters
|
|
563
|
+
};
|
|
564
|
+
}
|
|
354
565
|
function matchesContentType(actual, expected) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
566
|
+
const parsedActual = parseParameterizedValue(actual);
|
|
567
|
+
if (!parsedActual) return false;
|
|
568
|
+
return expected.some((value) => {
|
|
569
|
+
const parsedExpected = parseParameterizedValue(value);
|
|
570
|
+
if (!parsedExpected || parsedExpected.value !== parsedActual.value) return false;
|
|
571
|
+
return [...parsedExpected.parameters].every(([name, expectedValue]) => parsedActual.parameters.get(name) === expectedValue);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function asRecord(value) {
|
|
575
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
576
|
+
}
|
|
577
|
+
function isNonEmptyString(value) {
|
|
578
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
579
|
+
}
|
|
580
|
+
async function sha256Utf8(content) {
|
|
581
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
|
|
582
|
+
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
583
|
+
}
|
|
584
|
+
async function validateAgentSkillsIndex(content, indexUrl, handle) {
|
|
585
|
+
const issues = [];
|
|
586
|
+
let parsed;
|
|
587
|
+
try {
|
|
588
|
+
parsed = JSON.parse(content);
|
|
589
|
+
} catch {
|
|
590
|
+
return ["Agent Skills index did not contain valid JSON"];
|
|
591
|
+
}
|
|
592
|
+
const root = asRecord(parsed);
|
|
593
|
+
if (root?.$schema !== AGENT_SKILLS_DISCOVERY_SCHEMA_URI) issues.push(`Agent Skills index did not declare ${AGENT_SKILLS_DISCOVERY_SCHEMA_URI}`);
|
|
594
|
+
if (!Array.isArray(root?.skills) || root.skills.length === 0) {
|
|
595
|
+
issues.push("Agent Skills index did not declare any skills");
|
|
596
|
+
return issues;
|
|
597
|
+
}
|
|
598
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
599
|
+
const origin = new URL(indexUrl).origin;
|
|
600
|
+
for (const rawSkill of root.skills) {
|
|
601
|
+
const skill = asRecord(rawSkill);
|
|
602
|
+
const name = skill?.name;
|
|
603
|
+
const description = skill?.description;
|
|
604
|
+
const artifactRoute = skill?.url;
|
|
605
|
+
const digest = skill?.digest;
|
|
606
|
+
if (!isNonEmptyString(name) || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) || name.length > 64 || skill?.type !== "skill-md" || !isNonEmptyString(description) || description.length > 1024 || !isNonEmptyString(artifactRoute) || typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest)) {
|
|
607
|
+
issues.push("Agent Skills index contained an invalid skill entry");
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (seenNames.has(name)) {
|
|
611
|
+
issues.push(`Agent Skills index declared duplicate skill ${JSON.stringify(name)}`);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
seenNames.add(name);
|
|
615
|
+
let artifactUrl;
|
|
616
|
+
try {
|
|
617
|
+
artifactUrl = new URL(artifactRoute, indexUrl);
|
|
618
|
+
} catch {
|
|
619
|
+
issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} was invalid`);
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const expectedPath = `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${name}/SKILL.md`;
|
|
623
|
+
if (artifactUrl.origin !== origin || !artifactUrl.pathname.endsWith(expectedPath)) {
|
|
624
|
+
issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} did not resolve to same-origin ${expectedPath}`);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const response = await handle(new Request(artifactUrl), "agent-skill");
|
|
629
|
+
const artifact = await response.text();
|
|
630
|
+
if (response.status !== 200) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned status ${response.status}`);
|
|
631
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
632
|
+
if (!matchesContentType(contentType, ["text/markdown"])) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned content-type ${contentType || "<missing>"}`);
|
|
633
|
+
const actualDigest = `sha256:${await sha256Utf8(artifact)}`;
|
|
634
|
+
if (actualDigest !== digest) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} digest ${actualDigest} did not match ${digest}`);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return issues;
|
|
359
640
|
}
|
|
360
641
|
async function runDocsAgentConformance(options) {
|
|
361
642
|
const cases = createDocsAgentContractCases(options);
|
|
@@ -369,6 +650,14 @@ async function runDocsAgentConformance(options) {
|
|
|
369
650
|
if (!contractCase.expect.statuses.includes(response.status)) issues.push(`expected status ${contractCase.expect.statuses.join(" or ")}, received ${response.status}`);
|
|
370
651
|
if (!matchesContentType(contentType, contractCase.expect.contentTypes)) issues.push(`expected content-type ${contractCase.expect.contentTypes.join(" or ")}, received ${contentType || "<missing>"}`);
|
|
371
652
|
for (const requiredText of contractCase.expect.bodyIncludes ?? []) if (!body.toLowerCase().includes(requiredText.toLowerCase())) issues.push(`response body did not include ${JSON.stringify(requiredText)}`);
|
|
653
|
+
if (contractCase.expect.bodyEmpty && body !== "") issues.push(`expected an empty response body, received ${body.length} characters`);
|
|
654
|
+
if (contractCase.surface === "agent-skills-index") issues.push(...await validateAgentSkillsIndex(body, contractCase.request.url, options.handle));
|
|
655
|
+
for (const [header, requiredValues] of Object.entries(contractCase.expect.headerIncludes ?? {})) {
|
|
656
|
+
const actual = response.headers.get(header) ?? "";
|
|
657
|
+
for (const requiredValue of requiredValues) if (!actual.toLowerCase().includes(requiredValue.toLowerCase())) issues.push(`response header ${JSON.stringify(header)} did not include ${JSON.stringify(requiredValue)}`);
|
|
658
|
+
}
|
|
659
|
+
const parsedLinks = parseHttpLinkHeader(response.headers.get("link"));
|
|
660
|
+
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`);
|
|
372
661
|
results.push({
|
|
373
662
|
surface: contractCase.surface,
|
|
374
663
|
passed: issues.length === 0,
|
|
@@ -393,238 +682,6 @@ async function runDocsAgentConformance(options) {
|
|
|
393
682
|
};
|
|
394
683
|
}
|
|
395
684
|
|
|
396
|
-
//#endregion
|
|
397
|
-
//#region src/metadata.ts
|
|
398
|
-
/**
|
|
399
|
-
* Resolve page title using metadata titleTemplate.
|
|
400
|
-
* %s is replaced with page title.
|
|
401
|
-
*/
|
|
402
|
-
function resolveTitle(pageTitle, metadata) {
|
|
403
|
-
return (metadata?.titleTemplate ?? "%s").replace("%s", pageTitle);
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Resolve OG image URL for a page.
|
|
407
|
-
* Prefers page.openGraph.images[0], then page.ogImage, then config endpoint/default.
|
|
408
|
-
*/
|
|
409
|
-
function resolveOGImage(page, ogConfig, baseUrl) {
|
|
410
|
-
if (page.openGraph?.images?.length) return resolveImageUrl(page.openGraph.images[0].url, baseUrl);
|
|
411
|
-
if (!ogConfig?.enabled) return void 0;
|
|
412
|
-
if (page.ogImage) return resolveImageUrl(page.ogImage, baseUrl);
|
|
413
|
-
if (ogConfig.type === "dynamic" && ogConfig.endpoint) return `${baseUrl ?? ""}${ogConfig.endpoint}`;
|
|
414
|
-
return ogConfig.defaultImage;
|
|
415
|
-
}
|
|
416
|
-
function resolveImageUrl(url, baseUrl) {
|
|
417
|
-
if (url.startsWith("/") || url.startsWith("http")) return url;
|
|
418
|
-
const base = baseUrl ?? "";
|
|
419
|
-
return `${base}${base.length > 0 && !base.endsWith("/") ? "/" : ""}${url}`;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Build the Open Graph metadata object for a page.
|
|
423
|
-
* When the page has openGraph in frontmatter, uses it (with title/description filled from page if omitted).
|
|
424
|
-
* Otherwise uses ogImage or config (dynamic endpoint / defaultImage).
|
|
425
|
-
*/
|
|
426
|
-
function buildPageOpenGraph(page, ogConfig, baseUrl) {
|
|
427
|
-
if (page.openGraph) {
|
|
428
|
-
const images = page.openGraph.images?.length ? page.openGraph.images.map((img) => ({
|
|
429
|
-
url: resolveImageUrl(img.url, baseUrl),
|
|
430
|
-
width: img.width ?? 1200,
|
|
431
|
-
height: img.height ?? 630
|
|
432
|
-
})) : void 0;
|
|
433
|
-
return {
|
|
434
|
-
title: page.openGraph.title ?? page.title,
|
|
435
|
-
description: page.openGraph.description ?? page.description,
|
|
436
|
-
...images && { images }
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
const url = resolveOGImage(page, ogConfig, baseUrl);
|
|
440
|
-
if (!url) return void 0;
|
|
441
|
-
return {
|
|
442
|
-
title: page.title,
|
|
443
|
-
...page.description && { description: page.description },
|
|
444
|
-
images: [{
|
|
445
|
-
url,
|
|
446
|
-
width: 1200,
|
|
447
|
-
height: 630
|
|
448
|
-
}]
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Build the Twitter card metadata object for a page.
|
|
453
|
-
* When the page has twitter in frontmatter, uses it.
|
|
454
|
-
* Otherwise builds from ogImage or config (dynamic endpoint).
|
|
455
|
-
*/
|
|
456
|
-
function buildPageTwitter(page, ogConfig, baseUrl) {
|
|
457
|
-
if (page.twitter) {
|
|
458
|
-
const images = page.twitter.images?.length ? page.twitter.images.map((url) => resolveImageUrl(url, baseUrl)) : void 0;
|
|
459
|
-
return {
|
|
460
|
-
...page.twitter.card && { card: page.twitter.card },
|
|
461
|
-
...page.twitter.title !== void 0 && { title: page.twitter.title },
|
|
462
|
-
...page.twitter.description !== void 0 && { description: page.twitter.description },
|
|
463
|
-
...images && { images }
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
const url = resolveOGImage(page, ogConfig, baseUrl);
|
|
467
|
-
if (!url) return void 0;
|
|
468
|
-
return {
|
|
469
|
-
card: "summary_large_image",
|
|
470
|
-
title: page.title,
|
|
471
|
-
...page.description && { description: page.description },
|
|
472
|
-
images: [url]
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
function normalizeBaseUrl(value) {
|
|
476
|
-
if (!value) return void 0;
|
|
477
|
-
try {
|
|
478
|
-
return new URL(value).toString().replace(/\/+$/, "");
|
|
479
|
-
} catch {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function absolutizeUrl(url, baseUrl) {
|
|
484
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
485
|
-
if (!base) return url;
|
|
486
|
-
try {
|
|
487
|
-
return new URL(url, `${base}/`).toString();
|
|
488
|
-
} catch {
|
|
489
|
-
return url;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
function titleFromSegment(segment) {
|
|
493
|
-
return segment.replace(/\.[a-z0-9]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
494
|
-
}
|
|
495
|
-
function normalizeRoutePath(url) {
|
|
496
|
-
try {
|
|
497
|
-
return new URL(url, "https://farming-labs.local").pathname.replace(/\/+$/, "") || "/";
|
|
498
|
-
} catch {
|
|
499
|
-
return `/${url.replace(/^\/+/, "")}`.replace(/\/+$/, "") || "/";
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
function buildDefaultBreadcrumbs(input) {
|
|
503
|
-
const routePath = normalizeRoutePath(input.url);
|
|
504
|
-
const entry = (input.entry ?? "docs").replace(/^\/+|\/+$/g, "") || "docs";
|
|
505
|
-
const segments = routePath.split("/").filter(Boolean);
|
|
506
|
-
const entryParts = entry.split("/").filter(Boolean);
|
|
507
|
-
const contentSegments = segments.slice(0, entryParts.length).join("/") === entryParts.join("/") ? segments.slice(entryParts.length) : segments;
|
|
508
|
-
const breadcrumbs = [{
|
|
509
|
-
name: titleFromSegment(entryParts[entryParts.length - 1] ?? "Docs"),
|
|
510
|
-
url: `/${entry}`
|
|
511
|
-
}];
|
|
512
|
-
let current = `/${entry}`;
|
|
513
|
-
contentSegments.forEach((segment, index) => {
|
|
514
|
-
current = `${current}/${segment}`.replace(/\/+/g, "/");
|
|
515
|
-
breadcrumbs.push({
|
|
516
|
-
name: index === contentSegments.length - 1 ? input.title : titleFromSegment(segment),
|
|
517
|
-
url: current
|
|
518
|
-
});
|
|
519
|
-
});
|
|
520
|
-
return breadcrumbs;
|
|
521
|
-
}
|
|
522
|
-
function normalizeDateModified(value) {
|
|
523
|
-
if (!value) return void 0;
|
|
524
|
-
const date = new Date(value);
|
|
525
|
-
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
526
|
-
}
|
|
527
|
-
function buildAgentContractHowTo(value, fallback) {
|
|
528
|
-
const agent = normalizePageAgentFrontmatter(value);
|
|
529
|
-
if (!agent || !hasStructuredPageAgentContract(agent)) return void 0;
|
|
530
|
-
const steps = [];
|
|
531
|
-
for (const command of agent.commands ?? []) {
|
|
532
|
-
const run = typeof command === "string" ? command : command.run;
|
|
533
|
-
const description = typeof command === "string" ? void 0 : command.description;
|
|
534
|
-
const cwd = typeof command === "string" ? void 0 : command.cwd;
|
|
535
|
-
steps.push({
|
|
536
|
-
"@type": "HowToStep",
|
|
537
|
-
position: steps.length + 1,
|
|
538
|
-
name: description ?? "Run command",
|
|
539
|
-
text: cwd ? `${run} (from ${cwd})` : run
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
for (const verification of agent.verification ?? []) {
|
|
543
|
-
const description = typeof verification === "string" ? verification : verification.description ?? "Verify the result";
|
|
544
|
-
const details = typeof verification === "string" ? verification : [verification.run, verification.expect ? `Expected: ${verification.expect}` : void 0].filter(Boolean).join(". ");
|
|
545
|
-
steps.push({
|
|
546
|
-
"@type": "HowToStep",
|
|
547
|
-
position: steps.length + 1,
|
|
548
|
-
name: description,
|
|
549
|
-
text: details || description
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
const supplies = [...(agent.prerequisites ?? []).map((name) => ({
|
|
553
|
-
"@type": "HowToSupply",
|
|
554
|
-
name
|
|
555
|
-
})), ...(agent.files ?? []).map((name) => ({
|
|
556
|
-
"@type": "HowToSupply",
|
|
557
|
-
name
|
|
558
|
-
}))];
|
|
559
|
-
const about = Object.entries(agent.appliesTo ?? {}).flatMap(([kind, values]) => (typeof values === "string" ? [values] : values ?? []).map((name) => ({
|
|
560
|
-
"@type": "Thing",
|
|
561
|
-
name: `${kind}: ${name}`
|
|
562
|
-
})));
|
|
563
|
-
return {
|
|
564
|
-
"@type": "HowTo",
|
|
565
|
-
name: agent.task ?? fallback.title,
|
|
566
|
-
...agent.outcome ?? fallback.description ? { description: agent.outcome ?? fallback.description } : {},
|
|
567
|
-
...about.length > 0 ? { about } : {},
|
|
568
|
-
...supplies.length > 0 ? { supply: supplies } : {},
|
|
569
|
-
...steps.length > 0 ? { step: steps } : {}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Resolve the public docs site URL from existing agent-facing config.
|
|
574
|
-
*
|
|
575
|
-
* The framework intentionally reuses `sitemap.baseUrl`, `llmsTxt.baseUrl`,
|
|
576
|
-
* `robots.baseUrl`, or `ai.docsUrl` instead of requiring another setting.
|
|
577
|
-
*/
|
|
578
|
-
function resolveDocsMetadataBaseUrl(config) {
|
|
579
|
-
return [
|
|
580
|
-
typeof config.sitemap === "object" ? config.sitemap.baseUrl : void 0,
|
|
581
|
-
typeof config.llmsTxt === "object" ? config.llmsTxt.baseUrl : void 0,
|
|
582
|
-
typeof config.robots === "object" ? config.robots.baseUrl : void 0,
|
|
583
|
-
config.ai?.docsUrl
|
|
584
|
-
].map((value) => normalizeBaseUrl(value)).find((value) => Boolean(value));
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Build Schema.org JSON-LD for a docs page.
|
|
588
|
-
*
|
|
589
|
-
* The shape follows Vercel's agent readability guidance: a `TechArticle`
|
|
590
|
-
* with title, description, canonical URL, freshness, and breadcrumbs.
|
|
591
|
-
*/
|
|
592
|
-
function buildDocsPageStructuredData(input) {
|
|
593
|
-
const breadcrumbs = input.breadcrumbs?.length ? input.breadcrumbs : buildDefaultBreadcrumbs(input);
|
|
594
|
-
const result = {
|
|
595
|
-
"@context": "https://schema.org",
|
|
596
|
-
"@type": "TechArticle",
|
|
597
|
-
headline: input.title,
|
|
598
|
-
name: input.title,
|
|
599
|
-
url: absolutizeUrl(input.url, input.baseUrl),
|
|
600
|
-
mainEntityOfPage: {
|
|
601
|
-
"@type": "WebPage",
|
|
602
|
-
"@id": absolutizeUrl(input.url, input.baseUrl)
|
|
603
|
-
},
|
|
604
|
-
breadcrumb: {
|
|
605
|
-
"@type": "BreadcrumbList",
|
|
606
|
-
itemListElement: breadcrumbs.map((item, index) => ({
|
|
607
|
-
"@type": "ListItem",
|
|
608
|
-
position: index + 1,
|
|
609
|
-
name: item.name,
|
|
610
|
-
item: absolutizeUrl(item.url, input.baseUrl)
|
|
611
|
-
}))
|
|
612
|
-
}
|
|
613
|
-
};
|
|
614
|
-
if (input.description) result.description = input.description;
|
|
615
|
-
const agentContract = buildAgentContractHowTo(input.agent, input);
|
|
616
|
-
if (agentContract) result.mainEntity = agentContract;
|
|
617
|
-
const dateModified = normalizeDateModified(input.dateModified);
|
|
618
|
-
if (dateModified) result.dateModified = dateModified;
|
|
619
|
-
return result;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Serialize Schema.org JSON-LD for safe insertion into a `<script>` tag.
|
|
623
|
-
*/
|
|
624
|
-
function renderDocsPageStructuredDataJson(input) {
|
|
625
|
-
return JSON.stringify(buildDocsPageStructuredData(input)).replace(/</g, "\\u003c");
|
|
626
|
-
}
|
|
627
|
-
|
|
628
685
|
//#endregion
|
|
629
686
|
//#region src/reading-time.ts
|
|
630
687
|
function hasExplicitReadingTime(frontmatter) {
|
|
@@ -681,4 +738,4 @@ function resolveReadingTimeFromSource(source, wordsPerMinute, options) {
|
|
|
681
738
|
}
|
|
682
739
|
|
|
683
740
|
//#endregion
|
|
684
|
-
export {
|
|
741
|
+
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 };
|