@farming-labs/docs 0.2.84 → 0.2.87
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-qJNbZUCZ.d.mts → agent-C598zLsD.d.mts} +6 -3
- package/dist/{agent-D68WI6-N.mjs → agent-CTOUI2BK.mjs} +30 -245
- package/dist/{agent-DuwSq3C7.mjs → agent-FyPQ2AZ4.mjs} +2 -2
- package/dist/{agent-evals-CEvUQb_Z.mjs → agent-evals-DCptRrdP.mjs} +2 -2
- package/dist/{agent-export-BMljO4ll.mjs → agent-export-Di1Ofstb.mjs} +41 -25
- package/dist/agent-skills-bundle.d.mts +1 -1
- package/dist/agent-skills-progressive-disclosure-BVvj7nM8.mjs +308 -0
- package/dist/{agent-skills-server-D5qYU19g.mjs → agent-skills-server-CwmzAzf_.mjs} +1 -1
- package/dist/{agent-skills-server-aKgt9CR9.d.mts → agent-skills-server-DEqyYAhL.d.mts} +2 -2
- package/dist/agent-skills-vite.d.mts +3 -3
- package/dist/agent-skills-vite.mjs +2 -2
- package/dist/{agents-D1j3fKjv.mjs → agents-DjUz5voa.mjs} +6 -6
- package/dist/cli/index.mjs +44 -17
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-ask-ai-Bx-qtqHL.d.mts → cloud-ask-ai-uzDpy0um.d.mts} +1 -1
- package/dist/{content-change-hydration-CiMxs3LF.mjs → content-change-hydration-DLBinuFR.mjs} +2 -1
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/{doctor-DaZp33gE.mjs → doctor-DUnOjP9T.mjs} +11 -10
- package/dist/{downgrade-w7e6Se0L.mjs → downgrade-Wcgqbfv_.mjs} +1 -1
- package/dist/{golden-evaluations-BKxcJ1qw.mjs → golden-evaluations-CNDwoUOe.mjs} +2 -306
- package/dist/index.d.mts +29 -5
- package/dist/index.mjs +6 -6
- package/dist/{mcp-CwTbgrZm.mjs → mcp-7bvRe9Da.mjs} +6 -6
- package/dist/mcp.d.mts +3 -3
- package/dist/mcp.mjs +3 -3
- package/dist/{prompt-references-ClTizbsa.mjs → prompt-references-B6gxePm8.mjs} +3 -3
- package/dist/{retrieval-digest-CG9QWgw-.d.mts → retrieval-digest-sY-uhDFJ.d.mts} +10 -7
- package/dist/{review-BMO-4X_5.mjs → review-DEnzotkg.mjs} +7 -6
- package/dist/{robots-CLtbWSEJ.mjs → robots-DXy1xW3D.mjs} +3 -3
- package/dist/{robots-HWKsiZQS.mjs → robots-DaU38DZw.mjs} +2 -2
- package/dist/{search-ZY4rf1fy.mjs → search-DznddPqw.mjs} +6 -6
- package/dist/server.d.mts +5 -5
- package/dist/server.mjs +6 -6
- package/dist/{sitemap-CAzZFDic.mjs → sitemap-DCsrVSPf.mjs} +6 -6
- package/dist/{sitemap-server-_AeFxJQB.mjs → sitemap-server-Ds5z4WQB.mjs} +1 -1
- package/dist/skills-CPDwsMq4.mjs +467 -0
- package/dist/{standards-discovery-UdXpOfmN.d.mts → standards-discovery-CG_cZRSx.d.mts} +5 -2
- package/dist/{standards-discovery-BKlEnK_H.mjs → standards-discovery-Ckx0tN7B.mjs} +342 -41
- package/dist/{upgrade-oz-GChgt.mjs → upgrade-BSCjUH0V.mjs} +1 -1
- package/package.json +1 -1
- /package/dist/{init-DAkI0Lkf.mjs → init-D0GcUKJ0.mjs} +0 -0
- /package/dist/{package-version-n5AFur8a.mjs → package-version-B3PJ6IDr.mjs} +0 -0
- /package/dist/{types-DQl2dvDl.d.mts → types-CigzkdLB.d.mts} +0 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import "./agent-CTOUI2BK.mjs";
|
|
2
|
+
import { _ as hasStructuredPageAgentContract, v as normalizePageAgentFrontmatter } from "./markdown-sections-7OoA7ylx.mjs";
|
|
3
|
+
import "./standards-discovery-Ckx0tN7B.mjs";
|
|
4
|
+
import { isDocsAgentSkillName, validateDocsAgentSkillFrontmatter } from "./agent-skills-spec.mjs";
|
|
5
|
+
import "./content-change-hydration-DLBinuFR.mjs";
|
|
6
|
+
import "./sitemap-server-Ds5z4WQB.mjs";
|
|
7
|
+
import "./agent-skills-server-CwmzAzf_.mjs";
|
|
8
|
+
import "./agent-evals-DCptRrdP.mjs";
|
|
9
|
+
import { createFilesystemDocsMcpSource } from "./mcp.mjs";
|
|
10
|
+
import "./code-blocks-0wjOsqdJ.mjs";
|
|
11
|
+
import "./server.mjs";
|
|
12
|
+
import { _ as resolveDocsContentDir, a as loadDocsConfigModule, d as readNavTitle, g as resolveDocsConfigPath, h as readTopLevelStringProperty, p as readStringProperty, t as extractNestedObjectLiteral } from "./config-Wcdj-D0a.mjs";
|
|
13
|
+
import { n as estimateAgentSkillInstructionTokens, r as resolveDocsAgentSkillsProgressiveDisclosureConfig } from "./agent-skills-progressive-disclosure-BVvj7nM8.mjs";
|
|
14
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import pc from "picocolors";
|
|
18
|
+
|
|
19
|
+
//#region src/cli/skills.ts
|
|
20
|
+
const SKILL_SCAFFOLD_MARKER = "<!-- Generated by @farming-labs/docs skills scaffold. -->";
|
|
21
|
+
const DEFAULT_SKILL_NAME = "docs-workflows";
|
|
22
|
+
const SKILL_SCAFFOLD_VALUE_FLAGS = new Set([
|
|
23
|
+
"name",
|
|
24
|
+
"output",
|
|
25
|
+
"config",
|
|
26
|
+
"include"
|
|
27
|
+
]);
|
|
28
|
+
function inlineFlag(arg) {
|
|
29
|
+
const flag = arg.slice(2);
|
|
30
|
+
const separator = flag.indexOf("=");
|
|
31
|
+
return {
|
|
32
|
+
key: (separator === -1 ? flag : flag.slice(0, separator)).trim(),
|
|
33
|
+
value: separator === -1 ? void 0 : flag.slice(separator + 1)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function parseSkillScaffoldArgs(argv) {
|
|
37
|
+
const parsed = { include: [] };
|
|
38
|
+
let positionalName;
|
|
39
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
40
|
+
const arg = argv[index];
|
|
41
|
+
if (arg === "--help" || arg === "-h") {
|
|
42
|
+
parsed.help = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (arg === "--check") {
|
|
46
|
+
parsed.check = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg === "--dry-run") {
|
|
50
|
+
parsed.dryRun = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (arg === "--force") {
|
|
54
|
+
parsed.force = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!arg.startsWith("--")) {
|
|
58
|
+
if (positionalName) throw new Error(`Unexpected skill scaffold argument: ${arg}.`);
|
|
59
|
+
positionalName = arg;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const { key, value: inlineValue } = inlineFlag(arg);
|
|
63
|
+
if (!SKILL_SCAFFOLD_VALUE_FLAGS.has(key)) throw new Error(`Unknown skill scaffold flag: --${key}.`);
|
|
64
|
+
const value = inlineValue ?? (() => {
|
|
65
|
+
const next = argv[index + 1];
|
|
66
|
+
if (!next || next.startsWith("--")) throw new Error(`Missing value for --${key}.`);
|
|
67
|
+
index += 1;
|
|
68
|
+
return next;
|
|
69
|
+
})();
|
|
70
|
+
switch (key) {
|
|
71
|
+
case "name":
|
|
72
|
+
parsed.name = value;
|
|
73
|
+
break;
|
|
74
|
+
case "output":
|
|
75
|
+
parsed.output = value;
|
|
76
|
+
break;
|
|
77
|
+
case "config":
|
|
78
|
+
parsed.configPath = value;
|
|
79
|
+
break;
|
|
80
|
+
case "include":
|
|
81
|
+
parsed.include.push(value);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (positionalName && parsed.name && positionalName !== parsed.name) throw new Error("Use either the positional skill name or --name, not conflicting values.");
|
|
86
|
+
parsed.name ??= positionalName;
|
|
87
|
+
if (parsed.check && parsed.dryRun) throw new Error("Use either --check or --dry-run, not both.");
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
function normalizeSkillName(value) {
|
|
91
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/-{2,}/gu, "-").replace(/^-|-$/gu, "").slice(0, 64).replace(/-$/u, "") || DEFAULT_SKILL_NAME;
|
|
92
|
+
}
|
|
93
|
+
function inferSkillName(siteTitle) {
|
|
94
|
+
const base = normalizeSkillName(siteTitle);
|
|
95
|
+
if (base === DEFAULT_SKILL_NAME || base.endsWith("-docs") || base === "docs") return base;
|
|
96
|
+
return `${base.slice(0, 59).replace(/-$/u, "")}-docs`;
|
|
97
|
+
}
|
|
98
|
+
function normalizeRoutePrefix(value) {
|
|
99
|
+
const trimmed = value.trim();
|
|
100
|
+
if (!trimmed) throw new Error("--include requires a non-empty route prefix.");
|
|
101
|
+
const normalized = `/${(/^https?:\/\//iu.test(trimmed) ? new URL(trimmed).pathname : trimmed).replace(/^\/+|\/+$/gu, "")}`;
|
|
102
|
+
return normalized === "/" ? normalized : normalized.replace(/\/+$/gu, "");
|
|
103
|
+
}
|
|
104
|
+
function pageMatchesIncludes(page, includes) {
|
|
105
|
+
if (includes.length === 0) return true;
|
|
106
|
+
return includes.some((prefix) => prefix === "/" || page.url === prefix || page.url.startsWith(`${prefix}/`));
|
|
107
|
+
}
|
|
108
|
+
function relativeInside(rootDir, candidate) {
|
|
109
|
+
const relative = path.relative(rootDir, candidate);
|
|
110
|
+
if (relative === "" || path.isAbsolute(relative) || relative.startsWith("..")) return null;
|
|
111
|
+
return relative;
|
|
112
|
+
}
|
|
113
|
+
function assertSafeOutputDirectory(rootDir, outputDir) {
|
|
114
|
+
if (!relativeInside(rootDir, outputDir)) throw new Error("Skill output must be a directory inside the current project root.");
|
|
115
|
+
let cursor = outputDir;
|
|
116
|
+
const existing = [];
|
|
117
|
+
while (relativeInside(rootDir, cursor)) {
|
|
118
|
+
if (existsSync(cursor)) existing.push(cursor);
|
|
119
|
+
cursor = path.dirname(cursor);
|
|
120
|
+
}
|
|
121
|
+
for (const candidate of existing) if (lstatSync(candidate).isSymbolicLink()) throw new Error(`Skill output cannot traverse a symbolic link: ${candidate}`);
|
|
122
|
+
if (existsSync(outputDir) && !lstatSync(outputDir).isDirectory()) throw new Error(`Skill output must be a directory: ${outputDir}`);
|
|
123
|
+
}
|
|
124
|
+
function lstatIfPresent(filePath) {
|
|
125
|
+
try {
|
|
126
|
+
return lstatSync(filePath);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error.code === "ENOENT") return void 0;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function assertSafeScaffoldFile(skillDir, filePath) {
|
|
133
|
+
if (!relativeInside(skillDir, filePath)) throw new Error(`Skill output file must remain inside the selected skill directory: ${filePath}`);
|
|
134
|
+
let candidate = filePath;
|
|
135
|
+
while (true) {
|
|
136
|
+
const stats = lstatIfPresent(candidate);
|
|
137
|
+
if (stats?.isSymbolicLink()) throw new Error(`Refusing to traverse symbolic-link skill output: ${candidate}`);
|
|
138
|
+
if (candidate !== filePath && stats && !stats.isDirectory()) throw new Error(`Skill output parent must be a directory: ${candidate}`);
|
|
139
|
+
if (candidate === skillDir) break;
|
|
140
|
+
candidate = path.dirname(candidate);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function findNestedSkill(directory, root = directory) {
|
|
144
|
+
if (!existsSync(directory) || !lstatSync(directory).isDirectory()) return void 0;
|
|
145
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
146
|
+
if (entry.isSymbolicLink()) continue;
|
|
147
|
+
const entryPath = path.join(directory, entry.name);
|
|
148
|
+
if (entry.isFile() && entry.name === "SKILL.md" && directory !== root) return entryPath;
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
const nested = findNestedSkill(entryPath, root);
|
|
151
|
+
if (nested) return nested;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function yamlString(value) {
|
|
156
|
+
return JSON.stringify(value.replace(/\s+/gu, " ").trim());
|
|
157
|
+
}
|
|
158
|
+
function inlineCode(value) {
|
|
159
|
+
const longestRun = Math.max(0, ...[...value.matchAll(/`+/gu)].map((match) => match[0].length));
|
|
160
|
+
const fence = "`".repeat(longestRun + 1);
|
|
161
|
+
const padding = value.startsWith("`") || value.endsWith("`") ? " " : "";
|
|
162
|
+
return `${fence}${padding}${value}${padding}${fence}`;
|
|
163
|
+
}
|
|
164
|
+
function compactText(value) {
|
|
165
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
166
|
+
}
|
|
167
|
+
function compareText(left, right) {
|
|
168
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
169
|
+
}
|
|
170
|
+
function markdownLabel(value) {
|
|
171
|
+
return compactText(value).replace(/(\[|\]|\\)/gu, "\\$1");
|
|
172
|
+
}
|
|
173
|
+
function normalizeReferenceStem(value) {
|
|
174
|
+
return normalizeSkillName(value).slice(0, 56).replace(/-$/u, "") || "overview";
|
|
175
|
+
}
|
|
176
|
+
function referenceStem(page, entry) {
|
|
177
|
+
const pathname = page.url.split(/[?#]/u, 1)[0] ?? page.url;
|
|
178
|
+
const escapedEntry = entry.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
179
|
+
return normalizeReferenceStem(pathname.replace(new RegExp(`^/${escapedEntry}(?:/|$)`, "u"), "") || page.slug || page.title);
|
|
180
|
+
}
|
|
181
|
+
function shortDigest(value) {
|
|
182
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
183
|
+
}
|
|
184
|
+
function buildContractReferences(pages, entry) {
|
|
185
|
+
const references = [];
|
|
186
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
187
|
+
for (const page of [...pages].sort((left, right) => compareText(left.url, right.url) || compareText(left.title, right.title))) {
|
|
188
|
+
const contract = normalizePageAgentFrontmatter(page.agent);
|
|
189
|
+
if (!contract || !hasStructuredPageAgentContract(contract)) continue;
|
|
190
|
+
const stem = referenceStem(page, entry);
|
|
191
|
+
let relativePath = `references/${stem}.md`;
|
|
192
|
+
if (usedPaths.has(relativePath)) {
|
|
193
|
+
const digest = shortDigest(`${page.url}:${page.sourcePath ?? page.title}`);
|
|
194
|
+
relativePath = `references/${stem}-${digest}.md`;
|
|
195
|
+
let collision = 2;
|
|
196
|
+
while (usedPaths.has(relativePath)) {
|
|
197
|
+
relativePath = `references/${stem}-${digest}-${collision}.md`;
|
|
198
|
+
collision += 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
usedPaths.add(relativePath);
|
|
202
|
+
references.push({
|
|
203
|
+
page,
|
|
204
|
+
contract,
|
|
205
|
+
relativePath
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return references;
|
|
209
|
+
}
|
|
210
|
+
function values(value) {
|
|
211
|
+
if (typeof value === "string") return [value];
|
|
212
|
+
return value ?? [];
|
|
213
|
+
}
|
|
214
|
+
function renderApplicability(appliesTo) {
|
|
215
|
+
if (!appliesTo) return "all documented environments";
|
|
216
|
+
return [
|
|
217
|
+
values(appliesTo.framework).length ? `framework ${values(appliesTo.framework).map(inlineCode).join(", ")}` : void 0,
|
|
218
|
+
values(appliesTo.version).length ? `version ${values(appliesTo.version).map(inlineCode).join(", ")}` : void 0,
|
|
219
|
+
values(appliesTo.package).length ? `package ${values(appliesTo.package).map(inlineCode).join(", ")}` : void 0
|
|
220
|
+
].filter(Boolean).join("; ") || "all documented environments";
|
|
221
|
+
}
|
|
222
|
+
function canonicalPageUrl(page, baseUrl) {
|
|
223
|
+
if (!baseUrl) return page.url;
|
|
224
|
+
try {
|
|
225
|
+
return new URL(page.url, baseUrl).toString();
|
|
226
|
+
} catch {
|
|
227
|
+
return page.url;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function pushList(lines, heading, items) {
|
|
231
|
+
if (!items?.length) return;
|
|
232
|
+
lines.push("", `## ${heading}`, "");
|
|
233
|
+
for (const item of items) lines.push(`- ${item}`);
|
|
234
|
+
}
|
|
235
|
+
function renderContractReference(reference, baseUrl) {
|
|
236
|
+
const { page, contract } = reference;
|
|
237
|
+
const source = canonicalPageUrl(page, baseUrl);
|
|
238
|
+
const lines = [
|
|
239
|
+
SKILL_SCAFFOLD_MARKER,
|
|
240
|
+
`# ${compactText(page.title)}`,
|
|
241
|
+
"",
|
|
242
|
+
`Source: [${markdownLabel(source)}](<${source}>)`
|
|
243
|
+
];
|
|
244
|
+
if (contract.task) lines.push("", "## Task", "", contract.task);
|
|
245
|
+
if (contract.outcome) lines.push("", "## Expected result", "", contract.outcome);
|
|
246
|
+
if (contract.appliesTo) {
|
|
247
|
+
lines.push("", "## Applicability", "");
|
|
248
|
+
for (const [label, field] of [
|
|
249
|
+
["Framework", "framework"],
|
|
250
|
+
["Version", "version"],
|
|
251
|
+
["Package", "package"]
|
|
252
|
+
]) {
|
|
253
|
+
const fieldValues = values(contract.appliesTo[field]);
|
|
254
|
+
if (fieldValues.length) lines.push(`- ${label}: ${fieldValues.map(inlineCode).join(", ")}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
pushList(lines, "Prerequisites", contract.prerequisites);
|
|
258
|
+
pushList(lines, "Files", contract.files?.map(inlineCode));
|
|
259
|
+
if (contract.commands?.length) {
|
|
260
|
+
lines.push("", "## Commands", "");
|
|
261
|
+
for (const command of contract.commands) {
|
|
262
|
+
if (typeof command === "string") {
|
|
263
|
+
lines.push(`- ${inlineCode(command)}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const context = [command.cwd ? `working directory ${inlineCode(command.cwd)}` : void 0, command.description].filter(Boolean);
|
|
267
|
+
lines.push(`- ${inlineCode(command.run)}${context.length ? ` — ${context.join("; ")}` : ""}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
pushList(lines, "Side effects", contract.sideEffects);
|
|
271
|
+
if (contract.verification?.length) {
|
|
272
|
+
lines.push("", "## Verification", "");
|
|
273
|
+
for (const step of contract.verification) {
|
|
274
|
+
if (typeof step === "string") {
|
|
275
|
+
lines.push(`- ${step}`);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const summary = step.description ?? (step.run ? `Run ${inlineCode(step.run)}` : step.expect);
|
|
279
|
+
if (!summary) continue;
|
|
280
|
+
lines.push(`- ${summary}`);
|
|
281
|
+
if (step.run && step.description) lines.push(` - Run: ${inlineCode(step.run)}`);
|
|
282
|
+
if (step.expect) lines.push(` - Expected: ${step.expect}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (contract.failureModes?.length || contract.rollback?.length) {
|
|
286
|
+
lines.push("", "## Recovery and rollback", "");
|
|
287
|
+
for (const mode of contract.failureModes ?? []) if (typeof mode === "string") lines.push(`- ${mode}`);
|
|
288
|
+
else lines.push(`- ${mode.symptom}${mode.resolution ? ` — Recovery: ${mode.resolution}` : ""}`);
|
|
289
|
+
for (const rollback of contract.rollback ?? []) lines.push(`- Rollback: ${rollback}`);
|
|
290
|
+
}
|
|
291
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
292
|
+
}
|
|
293
|
+
function renderSkillDocument(options) {
|
|
294
|
+
const rawDescription = compactText(`Use ${options.siteTitle} structured task contracts to implement, configure, migrate, troubleshoot, or verify documented workflows. Use when a request needs authored prerequisites, commands, expected results, recovery, or rollback guidance.`);
|
|
295
|
+
const description = rawDescription.length <= 1024 ? rawDescription : `${rawDescription.slice(0, 1023)}…`;
|
|
296
|
+
const lines = [
|
|
297
|
+
"---",
|
|
298
|
+
`name: ${yamlString(options.name)}`,
|
|
299
|
+
`description: ${yamlString(description)}`,
|
|
300
|
+
`compatibility: ${yamlString("Requires access to the documented project and any tools, runtimes, credentials, or network capabilities named by the selected task reference.")}`,
|
|
301
|
+
"---",
|
|
302
|
+
"",
|
|
303
|
+
SKILL_SCAFFOLD_MARKER,
|
|
304
|
+
`# ${options.siteTitle} workflows`
|
|
305
|
+
];
|
|
306
|
+
if (options.siteDescription) lines.push("", compactText(options.siteDescription));
|
|
307
|
+
lines.push("", "## Workflow", "", "1. Match the request to the smallest task in the routing list.", "2. Read only the linked reference for that task; read another reference only when the request spans tasks.", "3. Confirm framework, version, package, prerequisites, files, and side effects before changing anything.", "4. Run documented commands from their stated working directories and verify the observable result.", "5. If verification fails, follow the task's recovery guidance; use rollback steps before trying a different approach.", "6. Cite the canonical source from the selected reference when reporting the result.", "", "## Task routing", "");
|
|
308
|
+
for (const reference of options.references) {
|
|
309
|
+
const task = compactText(reference.contract.task ?? reference.page.title);
|
|
310
|
+
lines.push(`- [${markdownLabel(task)}](${reference.relativePath}) — ${renderApplicability(reference.contract.appliesTo)}`);
|
|
311
|
+
}
|
|
312
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
313
|
+
}
|
|
314
|
+
function readStaticBaseUrl(configContent) {
|
|
315
|
+
for (const key of ["sitemap", "llmsTxt"]) {
|
|
316
|
+
const block = extractNestedObjectLiteral(configContent, [key]);
|
|
317
|
+
const baseUrl = block ? readStringProperty(block, "baseUrl") : void 0;
|
|
318
|
+
if (baseUrl) return baseUrl;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function configuredSkillPaths(input) {
|
|
322
|
+
if (!input) return [];
|
|
323
|
+
if (typeof input === "string") return [input];
|
|
324
|
+
if (Array.isArray(input)) return [...input];
|
|
325
|
+
const paths = input.paths;
|
|
326
|
+
return typeof paths === "string" ? [paths] : [...paths ?? []];
|
|
327
|
+
}
|
|
328
|
+
function isPublishedByConfig(rootDir, skillDir, input) {
|
|
329
|
+
return configuredSkillPaths(input).some((configuredPath) => {
|
|
330
|
+
const absolute = path.resolve(rootDir, configuredPath);
|
|
331
|
+
if (path.basename(absolute) === "SKILL.md" && path.dirname(absolute) === skillDir) return true;
|
|
332
|
+
const relative = path.relative(absolute, skillDir);
|
|
333
|
+
return relative === "" || !path.isAbsolute(relative) && !relative.startsWith("..");
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function isManagedFile(filePath) {
|
|
337
|
+
return existsSync(filePath) && readFileSync(filePath, "utf8").includes(SKILL_SCAFFOLD_MARKER);
|
|
338
|
+
}
|
|
339
|
+
function listManagedReferenceFiles(referenceDir) {
|
|
340
|
+
if (!existsSync(referenceDir) || !lstatSync(referenceDir).isDirectory()) return [];
|
|
341
|
+
return readdirSync(referenceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && !entry.isSymbolicLink()).map((entry) => path.join(referenceDir, entry.name)).filter(isManagedFile).sort();
|
|
342
|
+
}
|
|
343
|
+
function validateRenderedSkill(document, name, configuredSkills) {
|
|
344
|
+
const frontmatter = validateDocsAgentSkillFrontmatter(document, { directoryName: name });
|
|
345
|
+
if (!frontmatter.valid) throw new Error(`Generated Agent Skill frontmatter is invalid:\n${frontmatter.issues.map((issue) => `- ${issue.message}`).join("\n")}`);
|
|
346
|
+
const limits = resolveDocsAgentSkillsProgressiveDisclosureConfig(configuredSkills);
|
|
347
|
+
const lineCount = document.split(/\r?\n/gu).length;
|
|
348
|
+
const tokenEstimate = estimateAgentSkillInstructionTokens(document);
|
|
349
|
+
if (lineCount > limits.maxSkillLines || tokenEstimate > limits.instructionTokenBudget) throw new Error(`Generated SKILL.md needs ${lineCount} lines and approximately ${tokenEstimate} tokens, exceeding the configured ${limits.maxSkillLines}-line or ${limits.instructionTokenBudget}-token budget. Use one or more --include route prefixes to scaffold smaller focused skills.`);
|
|
350
|
+
}
|
|
351
|
+
function compareScaffoldFiles(skillDir, files, force) {
|
|
352
|
+
const expected = new Set(files.map((file) => path.resolve(skillDir, file.relativePath)));
|
|
353
|
+
const changedFiles = [];
|
|
354
|
+
for (const file of files) {
|
|
355
|
+
const filePath = path.resolve(skillDir, file.relativePath);
|
|
356
|
+
assertSafeScaffoldFile(skillDir, filePath);
|
|
357
|
+
const current = existsSync(filePath) ? readFileSync(filePath, "utf8") : void 0;
|
|
358
|
+
if (current === file.content) continue;
|
|
359
|
+
if (current !== void 0 && !force && !current.includes(SKILL_SCAFFOLD_MARKER)) throw new Error(`Refusing to replace user-owned skill file ${filePath}. Use --force or choose another --output directory.`);
|
|
360
|
+
changedFiles.push(filePath);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
changedFiles,
|
|
364
|
+
obsoleteFiles: listManagedReferenceFiles(path.join(skillDir, "references")).filter((filePath) => !expected.has(path.resolve(filePath)))
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function writeScaffoldFiles(skillDir, files, changedFiles, obsoleteFiles) {
|
|
368
|
+
const changed = new Set(changedFiles.map((filePath) => path.resolve(filePath)));
|
|
369
|
+
for (const file of files) {
|
|
370
|
+
const filePath = path.resolve(skillDir, file.relativePath);
|
|
371
|
+
if (!changed.has(filePath)) continue;
|
|
372
|
+
assertSafeScaffoldFile(skillDir, filePath);
|
|
373
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
374
|
+
assertSafeScaffoldFile(skillDir, filePath);
|
|
375
|
+
writeFileSync(filePath, file.content, "utf8");
|
|
376
|
+
}
|
|
377
|
+
for (const filePath of obsoleteFiles) rmSync(filePath);
|
|
378
|
+
}
|
|
379
|
+
async function scaffoldSkillFromContracts(options = {}) {
|
|
380
|
+
const rootDir = process.cwd();
|
|
381
|
+
const loaded = await loadDocsConfigModule(rootDir, options.configPath);
|
|
382
|
+
const configContent = readFileSync(loaded?.path ?? resolveDocsConfigPath(rootDir, options.configPath), "utf8");
|
|
383
|
+
const config = loaded?.config;
|
|
384
|
+
const entry = config?.entry ?? readTopLevelStringProperty(configContent, "entry") ?? "docs";
|
|
385
|
+
const contentDir = config?.contentDir ?? resolveDocsContentDir(rootDir, configContent, entry);
|
|
386
|
+
const siteTitle = compactText(typeof config?.nav?.title === "string" ? config.nav.title : readNavTitle(configContent) ?? "Documentation");
|
|
387
|
+
const siteDescription = typeof config?.metadata?.description === "string" ? config.metadata.description : void 0;
|
|
388
|
+
const configuredBaseUrl = (typeof config?.sitemap === "object" ? config.sitemap.baseUrl : void 0) ?? (typeof config?.llmsTxt === "object" ? config.llmsTxt.baseUrl : void 0) ?? readStaticBaseUrl(configContent);
|
|
389
|
+
const inferredName = inferSkillName(siteTitle);
|
|
390
|
+
const name = options.name ? options.name.normalize("NFKC") : options.output ? path.basename(path.resolve(rootDir, options.output)).normalize("NFKC") : inferredName;
|
|
391
|
+
if (!isDocsAgentSkillName(name)) throw new Error(`Invalid Agent Skill name ${JSON.stringify(name)}. Use lowercase letters, numbers, and single hyphens, up to 64 characters.`);
|
|
392
|
+
const skillDir = path.resolve(rootDir, options.output ?? path.join("skills", name));
|
|
393
|
+
assertSafeOutputDirectory(rootDir, skillDir);
|
|
394
|
+
if (path.basename(skillDir).normalize("NFKC") !== name) throw new Error(`Agent Skill name ${JSON.stringify(name)} must match the --output directory name ${JSON.stringify(path.basename(skillDir))}.`);
|
|
395
|
+
if (!existsSync(path.join(skillDir, "SKILL.md"))) {
|
|
396
|
+
const nestedSkill = findNestedSkill(skillDir);
|
|
397
|
+
if (nestedSkill) throw new Error(`Refusing to turn an Agent Skill collection into one skill because it already contains ${nestedSkill}. Choose a different --output directory.`);
|
|
398
|
+
}
|
|
399
|
+
const includes = (options.include ?? []).map(normalizeRoutePrefix);
|
|
400
|
+
const references = buildContractReferences((await createFilesystemDocsMcpSource({
|
|
401
|
+
rootDir,
|
|
402
|
+
entry,
|
|
403
|
+
contentDir,
|
|
404
|
+
siteTitle
|
|
405
|
+
}).getPages()).filter((page) => pageMatchesIncludes(page, includes)), entry);
|
|
406
|
+
if (references.length === 0) {
|
|
407
|
+
const detail = includes.length ? ` matching ${includes.join(", ")}` : "";
|
|
408
|
+
throw new Error(`No structured page agent contracts were found${detail}.`);
|
|
409
|
+
}
|
|
410
|
+
const skillDocument = renderSkillDocument({
|
|
411
|
+
name,
|
|
412
|
+
siteTitle,
|
|
413
|
+
siteDescription,
|
|
414
|
+
references
|
|
415
|
+
});
|
|
416
|
+
validateRenderedSkill(skillDocument, name, config?.agent?.skills);
|
|
417
|
+
const files = [{
|
|
418
|
+
relativePath: "SKILL.md",
|
|
419
|
+
content: skillDocument
|
|
420
|
+
}, ...references.map((reference) => ({
|
|
421
|
+
relativePath: reference.relativePath,
|
|
422
|
+
content: renderContractReference(reference, configuredBaseUrl)
|
|
423
|
+
}))];
|
|
424
|
+
const { changedFiles, obsoleteFiles } = compareScaffoldFiles(skillDir, files, options.force === true);
|
|
425
|
+
if (options.check && (changedFiles.length > 0 || obsoleteFiles.length > 0)) throw new Error(`Generated Agent Skill is stale (${changedFiles.length + obsoleteFiles.length} file(s)). Rerun the same \`docs skills scaffold\` command without --check to update it.`);
|
|
426
|
+
if (!options.check && !options.dryRun) writeScaffoldFiles(skillDir, files, changedFiles, obsoleteFiles);
|
|
427
|
+
const relativeSkillDir = path.relative(rootDir, skillDir).replaceAll("\\", "/");
|
|
428
|
+
const status = options.dryRun ? "dry-run" : changedFiles.length > 0 || obsoleteFiles.length > 0 ? "generated" : "current";
|
|
429
|
+
console.log(status === "dry-run" ? pc.cyan(`Would scaffold ${references.length} task contract(s) into ${relativeSkillDir}/.`) : status === "generated" ? pc.green(`Scaffolded ${references.length} task contract(s) into ${relativeSkillDir}/.`) : pc.green(`Generated Agent Skill is current at ${relativeSkillDir}/.`));
|
|
430
|
+
if (!options.check && status !== "current") {
|
|
431
|
+
for (const filePath of changedFiles) console.log(pc.dim(path.relative(rootDir, filePath).replaceAll("\\", "/")));
|
|
432
|
+
for (const filePath of obsoleteFiles) console.log(pc.dim(`removed ${path.relative(rootDir, filePath).replaceAll("\\", "/")}`));
|
|
433
|
+
}
|
|
434
|
+
if (!isPublishedByConfig(rootDir, skillDir, config?.agent?.skills)) console.log(pc.yellow(`Add ${JSON.stringify(relativeSkillDir)} to agent.skills.paths to publish this skill through discovery, exports, and MCP.`));
|
|
435
|
+
return {
|
|
436
|
+
skillDir,
|
|
437
|
+
name,
|
|
438
|
+
contractCount: references.length,
|
|
439
|
+
changedFiles,
|
|
440
|
+
removedFiles: obsoleteFiles,
|
|
441
|
+
status
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function printSkillScaffoldHelp() {
|
|
445
|
+
console.log(`
|
|
446
|
+
${pc.bold("docs skills scaffold")} — Compile structured page contracts into a compact Agent Skill.
|
|
447
|
+
|
|
448
|
+
${pc.dim("Usage:")}
|
|
449
|
+
pnpm exec docs ${pc.cyan("skills scaffold")} ${pc.dim("[name]")}
|
|
450
|
+
|
|
451
|
+
${pc.dim("Options:")}
|
|
452
|
+
${pc.cyan("--name <name>")} Agent Skill name; defaults to a slug of the docs site title
|
|
453
|
+
${pc.cyan("--output <directory>")} Full skill directory; defaults to ${pc.dim("skills/<name>")}
|
|
454
|
+
${pc.cyan("--include <route>")} Include one route prefix; repeat to combine focused sections
|
|
455
|
+
${pc.cyan("--check")} Fail when generated skill files are missing or stale
|
|
456
|
+
${pc.cyan("--dry-run")} Preview file changes without writing them
|
|
457
|
+
${pc.cyan("--force")} Replace colliding user-owned files in the selected directory
|
|
458
|
+
${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
|
|
459
|
+
${pc.cyan("-h, --help")} Show this help message
|
|
460
|
+
|
|
461
|
+
The command is offline and deterministic. It writes one compact ${pc.dim("SKILL.md")} router and
|
|
462
|
+
one direct ${pc.dim("references/*.md")} file for every selected page with structured agent frontmatter.
|
|
463
|
+
`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
//#endregion
|
|
467
|
+
export { parseSkillScaffoldArgs, printSkillScaffoldHelp, scaffoldSkillFromContracts };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as DocsAgentA2ASecurityScheme, M as DocsAgentA2ASkill, O as DocsAgentA2AProtocolBinding, h as DocsAgentA2AConfig, k as DocsAgentA2ASecurityRequirement, m as DocsAgentA2ACapabilities } from "./types-
|
|
1
|
+
import { A as DocsAgentA2ASecurityScheme, M as DocsAgentA2ASkill, O as DocsAgentA2AProtocolBinding, h as DocsAgentA2AConfig, k as DocsAgentA2ASecurityRequirement, m as DocsAgentA2ACapabilities } from "./types-CigzkdLB.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/standards-discovery.d.ts
|
|
4
4
|
declare const DEFAULT_API_CATALOG_ROUTE = "/.well-known/api-catalog";
|
|
@@ -200,6 +200,8 @@ interface CreateDocsStandardsResponseOptions {
|
|
|
200
200
|
publishedSkills?: readonly DocsPublishedAgentSkill[];
|
|
201
201
|
/** Identity used to publish the optional A2A Agent Card. */
|
|
202
202
|
agentCard?: DocsA2AAgentCardOptions;
|
|
203
|
+
/** Stable representation generation or source modification time. */
|
|
204
|
+
lastModified?: string | Date | null;
|
|
203
205
|
}
|
|
204
206
|
/** Resolve the same-origin Docs API pathname used by query-form discovery routes. */
|
|
205
207
|
declare function resolveDocsDiscoveryApiRoute(apiRoute?: string): string;
|
|
@@ -242,7 +244,8 @@ declare function createDocsStandardsResponse({
|
|
|
242
244
|
preferredSkillDocument,
|
|
243
245
|
fallbackSkillDocument,
|
|
244
246
|
publishedSkills,
|
|
245
|
-
agentCard
|
|
247
|
+
agentCard,
|
|
248
|
+
lastModified
|
|
246
249
|
}: CreateDocsStandardsResponseOptions): Promise<Response | null>;
|
|
247
250
|
//#endregion
|
|
248
251
|
export { resolveDocsDiscoveryApiRoute as $, DocsAgentSkillIndexEntry as A, DocsPublishedAgentSkillFile as B, DOCS_AGENT_MANIFEST_FORMAT as C, DocsA2AAgentCard as D, DOCS_AGENT_MANIFEST_VERSION as E, DocsApiCatalogLinkTarget as F, buildDocsA2AAgentCard as G, DocsStandardsDiscoveryRequest as H, DocsApiCatalogOpenApiDefinition as I, buildDocsLegacySkillsIndex as J, buildDocsAgentSkillsIndex as K, DocsApiCatalogOptions as L, DocsApiCatalog as M, DocsApiCatalogApiTarget as N, DocsA2AAgentCardOptions as O, DocsApiCatalogLinkContext as P, isDocsStandardsDiscoveryRequest as Q, DocsDiscoveryApiRouteOptions as R, DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX as S, DOCS_AGENT_MANIFEST_SCHEMA_URI as T, DocsStandardsDiscoveryRouteOptions as U, DocsPublishedAgentSkillOptions as V, appendDocsDiscoveryLinkHeader as W, getDocsAgentManifestLinkHeader as X, createDocsStandardsResponse as Y, getDocsDiscoveryLinkHeader as Z, DEFAULT_AGENT_SKILL_RESOURCE_FORMAT as _, DEFAULT_A2A_AGENT_CARD_FORMAT as a, DEFAULT_LEGACY_SKILLS_INDEX_FORMAT as b, DEFAULT_A2A_PROTOCOL_VERSION as c, DEFAULT_AGENT_SKILLS_INDEX_ROUTE as d, resolveDocsPublishedAgentSkill as et, DEFAULT_AGENT_SKILLS_ROUTE_PATTERN as f, DEFAULT_AGENT_SKILL_FORMAT as g, DEFAULT_AGENT_SKILL_FILE_FORMAT as h, CreateDocsStandardsResponseOptions as i, DocsAgentSkillsIndex as j, DocsA2AAgentInterface as k, DEFAULT_AGENT_SKILLS_ARCHIVE_ROUTE_PATTERN as l, DEFAULT_AGENT_SKILL_ARCHIVE_FORMAT as m, API_CATALOG_MEDIA_TYPE as n, sha256DocsDiscoveryContent as nt, DEFAULT_A2A_AGENT_CARD_ROUTE as o, DEFAULT_AGENT_SKILLS_ROUTE_PREFIX as p, buildDocsApiCatalog as q, API_CATALOG_PROFILE_URI as r, DEFAULT_A2A_PROTOCOL_BINDING as s, AGENT_SKILLS_DISCOVERY_SCHEMA_URI as t, resolveDocsStandardsDiscoveryRequest as tt, DEFAULT_AGENT_SKILLS_INDEX_FORMAT as u, DEFAULT_API_CATALOG_FORMAT as v, DOCS_AGENT_MANIFEST_SCHEMA_MEDIA_TYPE as w, DEFAULT_LEGACY_SKILLS_INDEX_ROUTE as x, DEFAULT_API_CATALOG_ROUTE as y, DocsPublishedAgentSkill as z };
|