@farming-labs/docs 0.1.48 → 0.1.49
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/cli/index.mjs
CHANGED
|
@@ -89,7 +89,7 @@ async function main() {
|
|
|
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;
|