@farming-labs/docs 0.1.48 → 0.1.50
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-D6Fa41gs.mjs → agent-CuYI2Nji.mjs} +16 -1
- package/dist/cli/index.mjs +8 -4
- package/dist/{doctor-Jm7T0qYS.mjs → doctor-D0UYKEFN.mjs} +269 -5
- package/dist/index.d.mts +2 -2
- package/dist/mcp.d.mts +1 -1
- package/dist/{search-BxJywXXB.d.mts → search-DcI00OQT.d.mts} +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/{types-Boobvv2I.d.mts → types-Agkn2EQE.d.mts} +9 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import "./api-reference-y7cqtq4w.mjs";
|
|
|
5
5
|
import { createFilesystemDocsMcpSource } from "./mcp.mjs";
|
|
6
6
|
import "./server.mjs";
|
|
7
7
|
import { a as loadProjectEnv, c as readNavTitle, d as readTopLevelStringProperty, f as resolveDocsConfigPath, i as loadDocsConfigModule, l as readNumberProperty, o as readBooleanProperty, p as resolveDocsContentDir, s as readEnvReferenceProperty, t as extractNestedObjectLiteral, u as readStringProperty } from "./config-C7sUsMkm.mjs";
|
|
8
|
+
import matter from "gray-matter";
|
|
8
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import pc from "picocolors";
|
|
@@ -212,6 +213,14 @@ function mergeAgentCompactOptions(defaults, overrides) {
|
|
|
212
213
|
...Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== void 0))
|
|
213
214
|
};
|
|
214
215
|
}
|
|
216
|
+
function normalizeTokenBudget(value) {
|
|
217
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
218
|
+
return Math.max(1, Math.ceil(value));
|
|
219
|
+
}
|
|
220
|
+
function readPageTokenBudget(pagePath) {
|
|
221
|
+
const { data } = matter(readFileSync(pagePath, "utf-8"));
|
|
222
|
+
return normalizeTokenBudget(data.agent?.tokenBudget);
|
|
223
|
+
}
|
|
215
224
|
function protectForCompression(input) {
|
|
216
225
|
const segments = [];
|
|
217
226
|
const stash = (value) => {
|
|
@@ -316,7 +325,10 @@ async function compactAgentDocs(options = {}) {
|
|
|
316
325
|
let created = 0;
|
|
317
326
|
let overwritten = 0;
|
|
318
327
|
for (const { page, target } of selectedPages) {
|
|
319
|
-
const
|
|
328
|
+
const sourceDocument = renderDocsMarkdownDocument(page);
|
|
329
|
+
const pageOptions = mergeAgentCompactOptions(resolvedOptions, { maxOutputTokens: readPageTokenBudget(target.pagePath) });
|
|
330
|
+
if (pageOptions.minOutputTokens !== void 0 && pageOptions.maxOutputTokens !== void 0 && pageOptions.minOutputTokens > pageOptions.maxOutputTokens) pageOptions.minOutputTokens = pageOptions.maxOutputTokens;
|
|
331
|
+
const compressed = await compressDocument(sourceDocument, pageOptions);
|
|
320
332
|
const nextContent = compressed.output.trimEnd();
|
|
321
333
|
console.log(pc.dim(`Compacting ${page.url} (${compressed.original_input_tokens ?? "?"} -> ${compressed.output_tokens ?? "?"} tokens)...`));
|
|
322
334
|
if (resolvedOptions.dryRun) continue;
|
|
@@ -341,6 +353,9 @@ ${pc.dim("Examples:")}
|
|
|
341
353
|
${pc.cyan("npx @farming-labs/docs@latest agent compact --page installation --page configuration")}
|
|
342
354
|
${pc.cyan("npx @farming-labs/docs@latest agent compact --all")}
|
|
343
355
|
|
|
356
|
+
${pc.dim("Per-page override:")}
|
|
357
|
+
Add ${pc.cyan("agent.tokenBudget")} to a page frontmatter block to override the compact output target for that page.
|
|
358
|
+
|
|
344
359
|
${pc.dim("Options:")}
|
|
345
360
|
${pc.cyan("--all")} Compact every folder-based docs page under the configured contentDir
|
|
346
361
|
${pc.cyan("--page <slug|path>")} Add a page explicitly (repeatable); positional page args work too
|
package/dist/cli/index.mjs
CHANGED
|
@@ -75,7 +75,7 @@ async function main() {
|
|
|
75
75
|
const { runMcp } = await import("../mcp-CYpMeMfi.mjs");
|
|
76
76
|
await runMcp(mcpOptions);
|
|
77
77
|
} else if (parsedCommand.command === "agent" && subcommand === "compact") {
|
|
78
|
-
const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-
|
|
78
|
+
const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-CuYI2Nji.mjs");
|
|
79
79
|
const agentCompactOptions = parseAgentCompactArgs(args.slice(2));
|
|
80
80
|
if (agentCompactOptions.help) {
|
|
81
81
|
printAgentCompactHelp();
|
|
@@ -85,11 +85,11 @@ async function main() {
|
|
|
85
85
|
} else if (parsedCommand.command === "agent") {
|
|
86
86
|
console.error(pc.red(`Unknown agent subcommand: ${subcommand ?? "(missing)"}`));
|
|
87
87
|
console.error();
|
|
88
|
-
const { printAgentCompactHelp } = await import("../agent-
|
|
88
|
+
const { printAgentCompactHelp } = await import("../agent-CuYI2Nji.mjs");
|
|
89
89
|
printAgentCompactHelp();
|
|
90
90
|
process.exit(1);
|
|
91
91
|
} else if (parsedCommand.command === "doctor") {
|
|
92
|
-
const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-
|
|
92
|
+
const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-D0UYKEFN.mjs");
|
|
93
93
|
const doctorOptions = parseDoctorArgs(args.slice(1));
|
|
94
94
|
if (doctorOptions.help) {
|
|
95
95
|
printDoctorHelp();
|
|
@@ -129,7 +129,7 @@ ${pc.dim("Usage:")}
|
|
|
129
129
|
${pc.dim("Commands:")}
|
|
130
130
|
${pc.cyan("init")} Scaffold docs in your project (default)
|
|
131
131
|
${pc.cyan("agent")} Agent utilities (${pc.dim("compact")} to generate sibling agent.md files)
|
|
132
|
-
${pc.cyan("doctor")} Inspect and score agent
|
|
132
|
+
${pc.cyan("doctor")} Inspect and score agent or reader-facing docs quality
|
|
133
133
|
${pc.cyan("mcp")} Run the built-in docs MCP server over stdio
|
|
134
134
|
${pc.cyan("search")} Search utilities (${pc.dim("sync")} for external indexes)
|
|
135
135
|
${pc.cyan("upgrade")} Upgrade @farming-labs/* packages to latest (auto-detect or use --framework)
|
|
@@ -162,7 +162,11 @@ ${pc.dim("Options for agent compact:")}
|
|
|
162
162
|
${pc.dim("Options for doctor:")}
|
|
163
163
|
${pc.cyan("doctor")} Score the current docs app for agent-readiness
|
|
164
164
|
${pc.cyan("doctor --agent")} Same as ${pc.cyan("doctor")}; explicit agent scoring mode
|
|
165
|
+
${pc.cyan("doctor --site")} Score the current docs app for reader-facing docs quality
|
|
166
|
+
${pc.cyan("doctor --human")} Alias for ${pc.cyan("doctor --site")}
|
|
165
167
|
${pc.cyan("doctor agent")} Subcommand alias for agent scoring
|
|
168
|
+
${pc.cyan("doctor site")} Subcommand alias for reader-facing scoring
|
|
169
|
+
${pc.cyan("doctor human")} Legacy alias for reader-facing scoring
|
|
166
170
|
${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
|
|
167
171
|
|
|
168
172
|
${pc.dim("Options for search sync:")}
|
|
@@ -41,7 +41,11 @@ function parseDoctorArgs(argv) {
|
|
|
41
41
|
continue;
|
|
42
42
|
}
|
|
43
43
|
if (arg === "--agent" || arg === "agent") {
|
|
44
|
-
parsed.
|
|
44
|
+
parsed.mode = "agent";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === "--human" || arg === "human" || arg === "--site" || arg === "site") {
|
|
48
|
+
parsed.mode = "human";
|
|
45
49
|
continue;
|
|
46
50
|
}
|
|
47
51
|
if (arg.startsWith("--config=")) {
|
|
@@ -59,7 +63,7 @@ function parseDoctorArgs(argv) {
|
|
|
59
63
|
}
|
|
60
64
|
throw new Error(`Unknown doctor flag or subcommand: ${arg}.`);
|
|
61
65
|
}
|
|
62
|
-
if (!parsed.help && parsed.
|
|
66
|
+
if (!parsed.help && !parsed.mode) parsed.mode = "agent";
|
|
63
67
|
return parsed;
|
|
64
68
|
}
|
|
65
69
|
function printDoctorHelp() {
|
|
@@ -69,10 +73,14 @@ ${pc.bold("@farming-labs/docs doctor")}
|
|
|
69
73
|
${pc.dim("Usage:")}
|
|
70
74
|
pnpm exec docs doctor
|
|
71
75
|
pnpm exec docs doctor --agent
|
|
76
|
+
pnpm exec docs doctor --site
|
|
72
77
|
pnpm exec docs doctor agent
|
|
78
|
+
pnpm exec docs doctor site
|
|
73
79
|
|
|
74
80
|
${pc.dim("Options:")}
|
|
75
81
|
${pc.cyan("--agent")} Score agent-readiness for the current docs app (default)
|
|
82
|
+
${pc.cyan("--site")} Score reader-facing docs quality for the current docs app
|
|
83
|
+
${pc.cyan("--human")} Alias for ${pc.cyan("--site")}
|
|
76
84
|
${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
|
|
77
85
|
${pc.cyan("-h, --help")} Show this help message
|
|
78
86
|
`);
|
|
@@ -174,6 +182,46 @@ function resolveAgentFeedbackEnabled(config, content) {
|
|
|
174
182
|
if (nestedAgentBlock) return readBooleanProperty(nestedAgentBlock, "enabled") ?? true;
|
|
175
183
|
return readBooleanProperty(feedbackBlock, "agent") ?? false;
|
|
176
184
|
}
|
|
185
|
+
function resolveHumanFeedbackEnabled(config, content) {
|
|
186
|
+
const feedback = config?.feedback;
|
|
187
|
+
if (typeof feedback === "boolean") return feedback;
|
|
188
|
+
if (feedback && typeof feedback === "object") return feedback.enabled ?? true;
|
|
189
|
+
const topLevelBoolean = readTopLevelBooleanProperty(content, "feedback");
|
|
190
|
+
if (typeof topLevelBoolean === "boolean") return topLevelBoolean;
|
|
191
|
+
const feedbackBlock = extractNestedObjectLiteral(content, ["feedback"]);
|
|
192
|
+
if (!feedbackBlock) return false;
|
|
193
|
+
return readBooleanProperty(feedbackBlock, "enabled") ?? true;
|
|
194
|
+
}
|
|
195
|
+
function resolveLastUpdatedEnabled(config, content) {
|
|
196
|
+
const current = config?.lastUpdated;
|
|
197
|
+
if (typeof current === "boolean") return current;
|
|
198
|
+
if (current && typeof current === "object") return current.enabled ?? true;
|
|
199
|
+
const topLevelBoolean = readTopLevelBooleanProperty(content, "lastUpdated");
|
|
200
|
+
if (typeof topLevelBoolean === "boolean") return topLevelBoolean;
|
|
201
|
+
const block = extractNestedObjectLiteral(content, ["lastUpdated"]);
|
|
202
|
+
if (!block) return true;
|
|
203
|
+
return readBooleanProperty(block, "enabled") ?? true;
|
|
204
|
+
}
|
|
205
|
+
function hasGithubIntegration(config, content) {
|
|
206
|
+
if (typeof config?.github === "string") return config.github.trim().length > 0;
|
|
207
|
+
if (config?.github && typeof config.github === "object") return typeof config.github.url === "string" && config.github.url.trim().length > 0;
|
|
208
|
+
const topLevelString = readTopLevelStringProperty(content, "github");
|
|
209
|
+
if (typeof topLevelString === "string" && topLevelString.trim().length > 0) return true;
|
|
210
|
+
const githubBlock = extractNestedObjectLiteral(content, ["github"]);
|
|
211
|
+
if (!githubBlock) return false;
|
|
212
|
+
const urlMatch = githubBlock.match(/\burl\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
213
|
+
return typeof urlMatch?.[1] === "string" && urlMatch[1].trim().length > 0;
|
|
214
|
+
}
|
|
215
|
+
function hasReadingTimeSurface(config, content) {
|
|
216
|
+
const current = config?.readingTime;
|
|
217
|
+
if (current === true) return true;
|
|
218
|
+
if (current && typeof current === "object") return current.enabled !== false;
|
|
219
|
+
const topLevelBoolean = readTopLevelBooleanProperty(content, "readingTime");
|
|
220
|
+
if (typeof topLevelBoolean === "boolean") return topLevelBoolean;
|
|
221
|
+
const block = extractNestedObjectLiteral(content, ["readingTime"]);
|
|
222
|
+
if (!block) return false;
|
|
223
|
+
return readBooleanProperty(block, "enabled") ?? true;
|
|
224
|
+
}
|
|
177
225
|
function hasAgentCompactDefaults(config, content) {
|
|
178
226
|
if (config?.agent?.compact) return true;
|
|
179
227
|
return extractNestedObjectLiteral(content, ["agent", "compact"]) !== void 0;
|
|
@@ -332,12 +380,84 @@ function metadataScore(descriptionCoverage, relatedCoverage) {
|
|
|
332
380
|
score: 0
|
|
333
381
|
};
|
|
334
382
|
}
|
|
335
|
-
function
|
|
383
|
+
function descriptionScore(descriptionCoverage) {
|
|
384
|
+
if (descriptionCoverage >= 90) return {
|
|
385
|
+
status: "pass",
|
|
386
|
+
score: 15
|
|
387
|
+
};
|
|
388
|
+
if (descriptionCoverage >= 75) return {
|
|
389
|
+
status: "pass",
|
|
390
|
+
score: 12
|
|
391
|
+
};
|
|
392
|
+
if (descriptionCoverage >= 50) return {
|
|
393
|
+
status: "warn",
|
|
394
|
+
score: 8
|
|
395
|
+
};
|
|
396
|
+
if (descriptionCoverage > 0) return {
|
|
397
|
+
status: "warn",
|
|
398
|
+
score: 4
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
status: "warn",
|
|
402
|
+
score: 0
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function structureScore(structureCoverage) {
|
|
406
|
+
if (structureCoverage >= 90) return {
|
|
407
|
+
status: "pass",
|
|
408
|
+
score: 15
|
|
409
|
+
};
|
|
410
|
+
if (structureCoverage >= 75) return {
|
|
411
|
+
status: "pass",
|
|
412
|
+
score: 12
|
|
413
|
+
};
|
|
414
|
+
if (structureCoverage >= 50) return {
|
|
415
|
+
status: "warn",
|
|
416
|
+
score: 8
|
|
417
|
+
};
|
|
418
|
+
if (structureCoverage > 0) return {
|
|
419
|
+
status: "warn",
|
|
420
|
+
score: 4
|
|
421
|
+
};
|
|
422
|
+
return {
|
|
423
|
+
status: "warn",
|
|
424
|
+
score: 0
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function navigationScore(navigationCoverage) {
|
|
428
|
+
if (navigationCoverage >= 100) return {
|
|
429
|
+
status: "pass",
|
|
430
|
+
score: 15
|
|
431
|
+
};
|
|
432
|
+
if (navigationCoverage >= 80) return {
|
|
433
|
+
status: "pass",
|
|
434
|
+
score: 12
|
|
435
|
+
};
|
|
436
|
+
if (navigationCoverage >= 50) return {
|
|
437
|
+
status: "warn",
|
|
438
|
+
score: 8
|
|
439
|
+
};
|
|
440
|
+
if (navigationCoverage > 0) return {
|
|
441
|
+
status: "warn",
|
|
442
|
+
score: 4
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
status: "fail",
|
|
446
|
+
score: 0
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function gradeForAgentScore(score) {
|
|
336
450
|
if (score >= 90) return "Agent-optimized";
|
|
337
451
|
if (score >= 75) return "Agent-ready";
|
|
338
452
|
if (score >= 60) return "Promising";
|
|
339
453
|
return "Needs work";
|
|
340
454
|
}
|
|
455
|
+
function gradeForHumanScore(score) {
|
|
456
|
+
if (score >= 90) return "Human-optimized";
|
|
457
|
+
if (score >= 75) return "Reader-ready";
|
|
458
|
+
if (score >= 60) return "Promising";
|
|
459
|
+
return "Needs work";
|
|
460
|
+
}
|
|
341
461
|
function formatStatus(status) {
|
|
342
462
|
if (status === "pass") return pc.green("PASS");
|
|
343
463
|
if (status === "warn") return pc.yellow("WARN");
|
|
@@ -367,6 +487,47 @@ function buildMetadataCoverage(pages) {
|
|
|
367
487
|
relatedCoverage: totalPages === 0 ? 0 : Math.round(relatedPages / totalPages * 100)
|
|
368
488
|
};
|
|
369
489
|
}
|
|
490
|
+
function countNavigationPages(node) {
|
|
491
|
+
const urls = /* @__PURE__ */ new Set();
|
|
492
|
+
const visit = (current) => {
|
|
493
|
+
if (current.type === "page" && typeof current.url === "string") urls.add(current.url);
|
|
494
|
+
if (current.index && typeof current.index === "object") {
|
|
495
|
+
const indexNode = current.index;
|
|
496
|
+
if (typeof indexNode.url === "string") urls.add(indexNode.url);
|
|
497
|
+
}
|
|
498
|
+
const children = Array.isArray(current.children) ? current.children : [];
|
|
499
|
+
for (const child of children) {
|
|
500
|
+
if (!child || typeof child !== "object") continue;
|
|
501
|
+
visit(child);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
visit(node);
|
|
505
|
+
return urls.size;
|
|
506
|
+
}
|
|
507
|
+
function estimateWordCount(content) {
|
|
508
|
+
return content.match(/\b[\p{L}\p{N}][\p{L}\p{N}'’-]*\b/gu)?.length ?? 0;
|
|
509
|
+
}
|
|
510
|
+
function hasSectionHeadings(content) {
|
|
511
|
+
return /^###{0,1}\s+/m.test(content);
|
|
512
|
+
}
|
|
513
|
+
function buildHumanCoverage(pages, navigationPages) {
|
|
514
|
+
const totalPages = pages.length;
|
|
515
|
+
const describedPages = pages.filter((page) => typeof page.description === "string" && page.description.trim().length > 0).length;
|
|
516
|
+
const longPages = pages.filter((page) => estimateWordCount(page.rawContent ?? "") >= 120).length;
|
|
517
|
+
const structuredLongPages = pages.filter((page) => {
|
|
518
|
+
const content = page.rawContent ?? "";
|
|
519
|
+
return estimateWordCount(content) >= 120 && hasSectionHeadings(content);
|
|
520
|
+
}).length;
|
|
521
|
+
return {
|
|
522
|
+
totalPages,
|
|
523
|
+
describedPages,
|
|
524
|
+
descriptionCoverage: totalPages === 0 ? 0 : Math.round(describedPages / totalPages * 100),
|
|
525
|
+
longPages,
|
|
526
|
+
structuredLongPages,
|
|
527
|
+
structureCoverage: longPages === 0 ? 100 : Math.round(structuredLongPages / longPages * 100),
|
|
528
|
+
navigationPages
|
|
529
|
+
};
|
|
530
|
+
}
|
|
370
531
|
async function loadDocsConfigModuleWithProjectEnv(rootDir, explicitPath) {
|
|
371
532
|
const env = loadProjectEnv(rootDir);
|
|
372
533
|
const injectedKeys = Object.entries(env).filter(([key]) => process.env[key] === void 0).map(([key, value]) => {
|
|
@@ -405,7 +566,7 @@ async function inspectAgentReadiness(options = {}) {
|
|
|
405
566
|
framework,
|
|
406
567
|
score: 0,
|
|
407
568
|
maxScore: 100,
|
|
408
|
-
grade:
|
|
569
|
+
grade: gradeForAgentScore(0),
|
|
409
570
|
checks,
|
|
410
571
|
coverage: {
|
|
411
572
|
totalPages: 0,
|
|
@@ -472,7 +633,88 @@ async function inspectAgentReadiness(options = {}) {
|
|
|
472
633
|
contentDir,
|
|
473
634
|
score,
|
|
474
635
|
maxScore,
|
|
475
|
-
grade:
|
|
636
|
+
grade: gradeForAgentScore(score),
|
|
637
|
+
checks,
|
|
638
|
+
coverage,
|
|
639
|
+
recommendations: checks.map((check) => check.recommendation).filter((recommendation) => Boolean(recommendation)).slice(0, 3)
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
async function inspectHumanReadiness(options = {}) {
|
|
643
|
+
const rootDir = process.cwd();
|
|
644
|
+
const files = listProjectFiles(rootDir);
|
|
645
|
+
const framework = detectFramework(rootDir) ?? detectFrameworkFromFiles(files) ?? "unknown";
|
|
646
|
+
const configCheckMax = 10;
|
|
647
|
+
let configPath;
|
|
648
|
+
try {
|
|
649
|
+
configPath = resolveDocsConfigPath(rootDir, options.configPath);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const checks = [makeCheck("config", "Docs config", "fail", 0, configCheckMax, error instanceof Error ? error.message : String(error), "Add docs.config.ts[x] or pass --config so the doctor can inspect the docs app.")];
|
|
652
|
+
return {
|
|
653
|
+
mode: "human",
|
|
654
|
+
framework,
|
|
655
|
+
score: 0,
|
|
656
|
+
maxScore: 100,
|
|
657
|
+
grade: gradeForHumanScore(0),
|
|
658
|
+
checks,
|
|
659
|
+
coverage: {
|
|
660
|
+
totalPages: 0,
|
|
661
|
+
describedPages: 0,
|
|
662
|
+
descriptionCoverage: 0,
|
|
663
|
+
longPages: 0,
|
|
664
|
+
structuredLongPages: 0,
|
|
665
|
+
structureCoverage: 0,
|
|
666
|
+
navigationPages: 0
|
|
667
|
+
},
|
|
668
|
+
recommendations: checks.map((check) => check.recommendation).filter(Boolean)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const configContent = readFileSync(configPath, "utf-8");
|
|
672
|
+
const loadedConfig = await loadDocsConfigModuleWithProjectEnv(rootDir, options.configPath);
|
|
673
|
+
const config = loadedConfig?.config;
|
|
674
|
+
const entry = config?.entry ?? readTopLevelStringProperty(configContent, "entry") ?? "docs";
|
|
675
|
+
const contentDir = config?.contentDir ?? resolveDocsContentDir(rootDir, configContent, entry);
|
|
676
|
+
const ordering = config?.ordering === "alphabetical" || config?.ordering === "numeric" || Array.isArray(config?.ordering) ? config.ordering : void 0;
|
|
677
|
+
const siteTitle = typeof config?.nav?.title === "string" ? config.nav.title : readNavTitle(configContent) ?? "Documentation";
|
|
678
|
+
const searchEnabled = resolveFeatureEnabled(config, configContent, "search");
|
|
679
|
+
const humanFeedbackEnabled = resolveHumanFeedbackEnabled(config, configContent);
|
|
680
|
+
const lastUpdatedEnabled = resolveLastUpdatedEnabled(config, configContent);
|
|
681
|
+
const githubEnabled = hasGithubIntegration(config, configContent);
|
|
682
|
+
const readingTimeEnabled = hasReadingTimeSurface(config, configContent);
|
|
683
|
+
const source = createFilesystemDocsMcpSource({
|
|
684
|
+
rootDir,
|
|
685
|
+
entry,
|
|
686
|
+
contentDir,
|
|
687
|
+
siteTitle,
|
|
688
|
+
ordering
|
|
689
|
+
});
|
|
690
|
+
const coverage = buildHumanCoverage(await Promise.resolve(source.getPages()), countNavigationPages(await Promise.resolve(source.getNavigation())));
|
|
691
|
+
const descriptionResult = descriptionScore(coverage.descriptionCoverage);
|
|
692
|
+
const structureResult = structureScore(coverage.structureCoverage);
|
|
693
|
+
const navigationCoverage = coverage.totalPages === 0 ? 0 : Math.min(100, Math.round(coverage.navigationPages / coverage.totalPages * 100));
|
|
694
|
+
const navigationResult = navigationScore(navigationCoverage);
|
|
695
|
+
const checks = [];
|
|
696
|
+
checks.push(makeCheck("config", "Docs config", "pass", 10, 10, loadedConfig ? `Resolved ${path.relative(rootDir, loadedConfig.path).replace(/\\/g, "/")} and evaluated the config module.` : `Resolved ${path.relative(rootDir, configPath).replace(/\\/g, "/")} using static parsing fallback.`));
|
|
697
|
+
const contentDirAbs = path.resolve(rootDir, contentDir);
|
|
698
|
+
checks.push(coverage.totalPages > 0 ? makeCheck("content", "Docs content", "pass", 15, 15, `Found ${coverage.totalPages} docs page${coverage.totalPages === 1 ? "" : "s"} in ${path.relative(rootDir, contentDirAbs).replace(/\\/g, "/")}.`) : makeCheck("content", "Docs content", "fail", 0, 15, `No folder-based docs pages were found in ${path.relative(rootDir, contentDirAbs).replace(/\\/g, "/")}.`, "Add index/page MDX files under the configured contentDir so the human docs site has pages to render."));
|
|
699
|
+
checks.push(makeCheck("navigation", "Navigation coverage", navigationResult.status, navigationResult.score, 15, coverage.totalPages > 0 ? `The generated docs navigation exposes ${coverage.navigationPages}/${coverage.totalPages} page entries (${navigationCoverage}% coverage).` : "No docs pages were available to score navigation coverage.", navigationCoverage >= 100 ? void 0 : "Make sure every important docs page is reachable from the generated navigation tree and not stranded outside the main docs flow."));
|
|
700
|
+
checks.push(makeCheck("descriptions", "Page descriptions", descriptionResult.status, descriptionResult.score, 15, coverage.totalPages > 0 ? `${coverage.describedPages}/${coverage.totalPages} pages include a description (${coverage.descriptionCoverage}% coverage).` : "No docs pages were available to score descriptions.", coverage.descriptionCoverage >= 75 ? void 0 : "Add frontmatter descriptions to more pages so readers get better search snippets, summaries, and page introductions."));
|
|
701
|
+
checks.push(makeCheck("structure", "Page structure", structureResult.status, structureResult.score, 15, coverage.longPages > 0 ? `${coverage.structuredLongPages}/${coverage.longPages} longer pages include section headings (${coverage.structureCoverage}% coverage).` : "No longer docs pages required section-heading checks.", coverage.structureCoverage >= 75 ? void 0 : "Break longer pages into clearer sections with H2/H3 headings so readers can scan and navigate without hitting a wall of text."));
|
|
702
|
+
checks.push(searchEnabled ? makeCheck("search", "Search surface", "pass", 10, 10, "Search is enabled for the docs site.") : makeCheck("search", "Search surface", "warn", 0, 10, "Search is disabled in docs config.", "Enable search so readers can jump directly to the right page instead of relying only on sidebar browsing."));
|
|
703
|
+
const trustScore = (githubEnabled ? 5 : 0) + (lastUpdatedEnabled ? 5 : 0);
|
|
704
|
+
checks.push(makeCheck("trust", "Trust signals", trustScore === 10 ? "pass" : "warn", trustScore, 10, githubEnabled && lastUpdatedEnabled ? "Edit links and last-updated metadata are configured." : githubEnabled ? "Edit links are configured, but last-updated metadata is not enabled." : lastUpdatedEnabled ? "Last-updated metadata is enabled, but edit links are not configured." : "Edit links and last-updated metadata are not configured.", trustScore === 10 ? void 0 : "Configure GitHub edit links and/or lastUpdated so readers can trust freshness and find the source of truth faster."));
|
|
705
|
+
checks.push(humanFeedbackEnabled ? makeCheck("feedback", "Reader feedback", "pass", 5, 5, "Built-in page feedback is enabled for the docs site.") : makeCheck("feedback", "Reader feedback", "warn", 0, 5, "Built-in page feedback is not enabled.", "Enable feedback if you want readers to leave quick page-level quality signals without opening an issue."));
|
|
706
|
+
checks.push(readingTimeEnabled ? makeCheck("reading-time", "Reading-time cues", "pass", 5, 5, "Reading time is configured for the docs site.") : makeCheck("reading-time", "Reading-time cues", "warn", 0, 5, "Reading time is not enabled.", "Enable readingTime if you want readers to get a quick effort estimate before they dive into longer pages."));
|
|
707
|
+
const score = checks.reduce((total, check) => total + check.score, 0);
|
|
708
|
+
const maxScore = checks.reduce((total, check) => total + check.maxScore, 0);
|
|
709
|
+
return {
|
|
710
|
+
mode: "human",
|
|
711
|
+
framework,
|
|
712
|
+
configPath: path.relative(rootDir, configPath).replace(/\\/g, "/"),
|
|
713
|
+
entry,
|
|
714
|
+
contentDir,
|
|
715
|
+
score,
|
|
716
|
+
maxScore,
|
|
717
|
+
grade: gradeForHumanScore(score),
|
|
476
718
|
checks,
|
|
477
719
|
coverage,
|
|
478
720
|
recommendations: checks.map((check) => check.recommendation).filter((recommendation) => Boolean(recommendation)).slice(0, 3)
|
|
@@ -497,7 +739,29 @@ function printAgentDoctorReport(report) {
|
|
|
497
739
|
console.log();
|
|
498
740
|
console.log(pc.dim(`Expected public surfaces: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}, ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_TXT_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_ROUTE}, ${DEFAULT_SKILL_MD_ROUTE}, ${DEFAULT_MCP_PUBLIC_ROUTE}`));
|
|
499
741
|
}
|
|
742
|
+
function printHumanDoctorReport(report) {
|
|
743
|
+
console.log(`${pc.bold("@farming-labs/docs doctor")} ${pc.dim("—")} ${pc.bold("site")}`);
|
|
744
|
+
console.log();
|
|
745
|
+
console.log(`${pc.bold("Score:")} ${pc.cyan(`${report.score}/${report.maxScore}`)} ${pc.dim(`(${report.grade})`)}`);
|
|
746
|
+
console.log(`${pc.bold("Framework:")} ${report.framework} ${pc.dim("•")} ${pc.bold("Entry:")} ${report.entry ?? "docs"} ${pc.dim("•")} ${pc.bold("Content:")} ${report.contentDir ?? "-"}`);
|
|
747
|
+
console.log(`${pc.bold("Described pages:")} ${report.coverage.describedPages}/${report.coverage.totalPages} pages ${pc.dim(`(${report.coverage.descriptionCoverage}%)`)}`);
|
|
748
|
+
console.log();
|
|
749
|
+
for (const check of report.checks) {
|
|
750
|
+
console.log(`${formatStatus(check.status)} ${check.title} ${pc.dim(`(${check.score}/${check.maxScore})`)}`);
|
|
751
|
+
console.log(` ${check.detail}`);
|
|
752
|
+
}
|
|
753
|
+
if (report.recommendations.length > 0) {
|
|
754
|
+
console.log();
|
|
755
|
+
console.log(pc.bold("Next steps"));
|
|
756
|
+
for (const recommendation of report.recommendations) console.log(`- ${recommendation}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
500
759
|
async function runDoctor(options = {}) {
|
|
760
|
+
if (options.mode === "human") {
|
|
761
|
+
const report = await inspectHumanReadiness(options);
|
|
762
|
+
printHumanDoctorReport(report);
|
|
763
|
+
return report;
|
|
764
|
+
}
|
|
501
765
|
const report = await inspectAgentReadiness(options);
|
|
502
766
|
printAgentDoctorReport(report);
|
|
503
767
|
return report;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { $ as SidebarConfig, A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, F as FeedbackConfig, G as OrderingItem, H as OpenDocsConfig, I as FontStyle, J as PageOpenGraph, K as PageActionsConfig, L as GithubConfig, M as DocsSearchResultType, N as DocsSearchSourcePage, O as DocsSearchDocument, P as DocsTheme, Q as SidebarComponentProps, R as LastUpdatedConfig, S as DocsRelatedItem, T as DocsSearchAdapterFactory, U as OpenDocsProvider, V as OGConfig, W as OpenGraphImage, X as ReadingTimeConfig, Y as PageTwitter, Z as ResolvedDocsRelatedLink, _ as DocsI18nConfig, a as ApiReferenceRenderer, at as ThemeToggleConfig, b as DocsMetadata, c as ChangelogFrontmatter, ct as UIConfig, d as CustomDocsSearchConfig, et as SidebarFolderNode, f as DocsAgentFeedbackContext, g as DocsFeedbackValue, h as DocsFeedbackData, i as ApiReferenceConfig, it as SimpleDocsSearchConfig, j as DocsSearchResult, k as DocsSearchEmbeddingsConfig, l as CodeBlockCopyData, m as DocsConfig, n as AgentFeedbackConfig, nt as SidebarPageNode, o as BreadcrumbConfig, ot as TypesenseDocsSearchConfig, p as DocsAgentFeedbackData, q as PageFrontmatter, r as AlgoliaDocsSearchConfig, rt as SidebarTree, s as ChangelogConfig, st as TypographyConfig, t as AIConfig, tt as SidebarNode, u as CopyMarkdownConfig, v as DocsMcpConfig, w as DocsSearchAdapterContext, x as DocsNav, y as DocsMcpToolsConfig, z as LlmsTxtConfig } from "./types-
|
|
1
|
+
import { $ as SidebarConfig, A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, F as FeedbackConfig, G as OrderingItem, H as OpenDocsConfig, I as FontStyle, J as PageOpenGraph, K as PageActionsConfig, L as GithubConfig, M as DocsSearchResultType, N as DocsSearchSourcePage, O as DocsSearchDocument, P as DocsTheme, Q as SidebarComponentProps, R as LastUpdatedConfig, S as DocsRelatedItem, T as DocsSearchAdapterFactory, U as OpenDocsProvider, V as OGConfig, W as OpenGraphImage, X as ReadingTimeConfig, Y as PageTwitter, Z as ResolvedDocsRelatedLink, _ as DocsI18nConfig, a as ApiReferenceRenderer, at as ThemeToggleConfig, b as DocsMetadata, c as ChangelogFrontmatter, ct as UIConfig, d as CustomDocsSearchConfig, et as SidebarFolderNode, f as DocsAgentFeedbackContext, g as DocsFeedbackValue, h as DocsFeedbackData, i as ApiReferenceConfig, it as SimpleDocsSearchConfig, j as DocsSearchResult, k as DocsSearchEmbeddingsConfig, l as CodeBlockCopyData, m as DocsConfig, n as AgentFeedbackConfig, nt as SidebarPageNode, o as BreadcrumbConfig, ot as TypesenseDocsSearchConfig, p as DocsAgentFeedbackData, q as PageFrontmatter, r as AlgoliaDocsSearchConfig, rt as SidebarTree, s as ChangelogConfig, st as TypographyConfig, t as AIConfig, tt as SidebarNode, u as CopyMarkdownConfig, v as DocsMcpConfig, w as DocsSearchAdapterContext, x as DocsNav, y as DocsMcpToolsConfig, z as LlmsTxtConfig } from "./types-Agkn2EQE.mjs";
|
|
2
2
|
import { DocsMcpPage, DocsMcpResolvedConfig } from "./mcp.mjs";
|
|
3
|
-
import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-
|
|
3
|
+
import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-DcI00OQT.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/define-docs.d.ts
|
|
6
6
|
/**
|
package/dist/mcp.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DocsSearchConfig, G as OrderingItem, N as DocsSearchSourcePage, v as DocsMcpConfig } from "./types-
|
|
1
|
+
import { D as DocsSearchConfig, G as OrderingItem, N as DocsSearchSourcePage, v as DocsMcpConfig } from "./types-Agkn2EQE.mjs";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
|
|
4
4
|
//#region src/mcp.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, d as CustomDocsSearchConfig, j as DocsSearchResult, ot as TypesenseDocsSearchConfig, r as AlgoliaDocsSearchConfig } from "./types-
|
|
1
|
+
import { B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, d as CustomDocsSearchConfig, j as DocsSearchResult, ot as TypesenseDocsSearchConfig, r as AlgoliaDocsSearchConfig } from "./types-Agkn2EQE.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/search.d.ts
|
|
4
4
|
declare function buildDocsSearchDocuments(pages: DocsSearchSourcePage[], chunking?: DocsSearchChunkingConfig): DocsSearchDocument[];
|
package/dist/server.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, a as ApiReferenceRenderer, j as DocsSearchResult, m as DocsConfig, w as DocsSearchAdapterContext } from "./types-
|
|
1
|
+
import { A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, a as ApiReferenceRenderer, j as DocsSearchResult, m as DocsConfig, w as DocsSearchAdapterContext } from "./types-Agkn2EQE.mjs";
|
|
2
2
|
import { DocsMcpHttpHandlers, DocsMcpNavigationNode, DocsMcpNavigationTree, DocsMcpPage, DocsMcpResolvedConfig, DocsMcpSource, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio } from "./mcp.mjs";
|
|
3
|
-
import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-
|
|
3
|
+
import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-DcI00OQT.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/api-reference.d.ts
|
|
6
6
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
|
|
@@ -246,11 +246,20 @@ type DocsRelatedItem = string;
|
|
|
246
246
|
interface ResolvedDocsRelatedLink {
|
|
247
247
|
href: string;
|
|
248
248
|
}
|
|
249
|
+
interface PageAgentFrontmatter {
|
|
250
|
+
/**
|
|
251
|
+
* Approximate output token target for machine-readable compaction on this page.
|
|
252
|
+
* Used by `docs agent compact` as a per-page override.
|
|
253
|
+
*/
|
|
254
|
+
tokenBudget?: number;
|
|
255
|
+
}
|
|
249
256
|
interface PageFrontmatter {
|
|
250
257
|
title: string;
|
|
251
258
|
description?: string;
|
|
252
259
|
/** Related doc URLs rendered into machine-readable markdown routes and MCP page output. */
|
|
253
260
|
related?: DocsRelatedItem[];
|
|
261
|
+
/** Per-page agent-oriented metadata used by machine-readable docs features. */
|
|
262
|
+
agent?: PageAgentFrontmatter;
|
|
254
263
|
/** Override or disable the estimated reading time for this page. */
|
|
255
264
|
readingTime?: boolean | number;
|
|
256
265
|
tags?: string[];
|