@farming-labs/docs 0.2.60 → 0.2.62

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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +11 -1
  3. package/dist/agent-BFqyqEnC.mjs +0 -4547
  4. package/dist/agent-DlxriaTs.mjs +0 -624
  5. package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
  6. package/dist/agent-export-BgUaiW8f.mjs +0 -869
  7. package/dist/agent-scope-CCaIY1aK.mjs +0 -283
  8. package/dist/agents-Djh-HXih.mjs +0 -219
  9. package/dist/analytics-Bx44lg6d.mjs +0 -177
  10. package/dist/cli/index.d.mts +0 -15
  11. package/dist/cli/index.mjs +0 -452
  12. package/dist/client/react.d.mts +0 -45
  13. package/dist/client/react.mjs +0 -223
  14. package/dist/cloud-BH_sHX64.mjs +0 -1615
  15. package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
  16. package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
  17. package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
  18. package/dist/code-blocks-qe0T8-xe.mjs +0 -871
  19. package/dist/codeblocks-Bq67u32v.mjs +0 -250
  20. package/dist/config-DASewQ0x.mjs +0 -363
  21. package/dist/dev-DgY5xGl9.mjs +0 -1333
  22. package/dist/docs-cloud-server.d.mts +0 -70
  23. package/dist/docs-cloud-server.mjs +0 -310
  24. package/dist/doctor-CO1VMcF_.mjs +0 -1906
  25. package/dist/downgrade-BZs86NVr.mjs +0 -184
  26. package/dist/errors-CVqZ3kOO.mjs +0 -20
  27. package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
  28. package/dist/i18n-CAlj1ADU.mjs +0 -40
  29. package/dist/index.d.mts +0 -1099
  30. package/dist/index.mjs +0 -9
  31. package/dist/init-Bd_k06bR.mjs +0 -1233
  32. package/dist/mcp-B_yXL5G5.mjs +0 -137
  33. package/dist/mcp.d.mts +0 -287
  34. package/dist/mcp.mjs +0 -4135
  35. package/dist/metadata-BDuewuzq.mjs +0 -237
  36. package/dist/package-version-qik_4J6C.mjs +0 -128
  37. package/dist/reading-time-BkEft6SD.mjs +0 -741
  38. package/dist/review-NC-sOdXn.mjs +0 -665
  39. package/dist/robots-DskPvGPw.mjs +0 -178
  40. package/dist/robots-ltltiLJF.mjs +0 -197
  41. package/dist/search-C1JitPwi.d.mts +0 -397
  42. package/dist/search-D57JXQLj.mjs +0 -1758
  43. package/dist/search-o4Ud6OXv.mjs +0 -102
  44. package/dist/server.d.mts +0 -341
  45. package/dist/server.mjs +0 -11
  46. package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
  47. package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
  48. package/dist/templates-DNw15P-x.mjs +0 -2373
  49. package/dist/types-XHABMh_f.d.mts +0 -3248
  50. package/dist/upgrade-BCJTCW3O.mjs +0 -56
  51. package/dist/utils-6UCLxv4B.mjs +0 -225
@@ -1,1483 +0,0 @@
1
- import { Ot as resolveDocsAudienceMdxContent, Tt as findDocsAudienceMdxTags, _n as hasStructuredPageAgentContract, vn as normalizePageAgentFrontmatter } from "./agent-BFqyqEnC.mjs";
2
- import { n as agentVersionConstraintsOverlap, o as normalizeAgentVersion, r as normalizeAgentFramework } from "./agent-scope-CCaIY1aK.mjs";
3
- import { t as extractCodeBlocksFromMarkdown } from "./code-blocks-qe0T8-xe.mjs";
4
- import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
5
- import path from "node:path";
6
-
7
- //#region src/agent-usefulness.ts
8
- /** Build analyzer inputs from the same filesystem MCP pages used by doctor and review. */
9
- function createAgentUsefulnessPagesFromMcp(rootDir, pages) {
10
- return pages.map((page) => {
11
- const sourcePath = page.sourcePath ?? page.url;
12
- const absoluteSourcePath = page.sourcePath ? path.isAbsolute(page.sourcePath) ? page.sourcePath : path.join(rootDir, page.sourcePath) : void 0;
13
- const source = absoluteSourcePath && existsSync(absoluteSourcePath) ? readFileSync(absoluteSourcePath, "utf-8") : page.rawContent ?? page.agentFallbackRawContent ?? page.content;
14
- const absoluteAgentPath = Boolean(absoluteSourcePath && /(?:^|\/)(?:page|index|\+page)\.(?:md|mdx|svx)$/i.test(absoluteSourcePath)) && absoluteSourcePath ? path.join(path.dirname(absoluteSourcePath), "agent.md") : void 0;
15
- const hasAgentSource = Boolean(absoluteAgentPath && existsSync(absoluteAgentPath));
16
- return {
17
- route: page.url,
18
- sourcePath,
19
- source,
20
- agentSourcePath: hasAgentSource && absoluteAgentPath ? path.relative(rootDir, absoluteAgentPath).replace(/\\/g, "/") : void 0,
21
- agentSource: hasAgentSource && absoluteAgentPath ? readFileSync(absoluteAgentPath, "utf-8") : void 0,
22
- title: page.title,
23
- description: page.description,
24
- related: page.related,
25
- framework: page.framework,
26
- version: page.version,
27
- agent: page.agent
28
- };
29
- });
30
- }
31
- const DEFAULT_DOCS_COMMANDS = [
32
- "init",
33
- "dev",
34
- "deploy",
35
- "preview",
36
- "cloud init",
37
- "cloud sync",
38
- "cloud check",
39
- "cloud deploy",
40
- "cloud preview",
41
- "mcp",
42
- "agent compact",
43
- "agent export",
44
- "agents generate",
45
- "doctor",
46
- "review",
47
- "codeblocks validate",
48
- "code-blocks validate",
49
- "search sync",
50
- "sitemap generate",
51
- "robots generate",
52
- "upgrade",
53
- "downgrade"
54
- ];
55
- const ACTIONABLE_PATTERN = /\b(?:add|authenticate|build|configure|create|debug|deploy|disable|enable|fetch|fix|implement|install|integrate|migrate|open|remove|run|set|set\s*up|test|troubleshoot|update|upgrade)\b/i;
56
- const PREREQUISITE_PATTERN = /(?:^|\n)\s{0,3}#{1,6}\s+(?:before you begin|prerequisites?|requirements?|setup)\b|\b(?:before (?:starting|you begin)|requires? (?:an?|the|you))\b/i;
57
- const EXPECTED_RESULT_PATTERN = /(?:^|\n)\s{0,3}#{1,6}\s+(?:expected (?:result|output)|outcome|result|success|verification)\b|\b(?:you should see|expected to (?:see|return|produce)|succeeds? when|tests? pass(?:es)?)\b/i;
58
- const RECOVERY_PATTERN = /(?:^|\n)\s{0,3}#{1,6}\s+(?:failure modes?|recovery|rollback|troubleshooting)\b|\b(?:if .{0,80} fails?|recover(?:y)?|restore|retry|revert|roll back|undo)\b/i;
59
- const STRONG_SPECIFIC_CONTEXT_PATTERN = /(?:@[a-z0-9_-]+\/[a-z0-9_.-]+|(?:^|\s)--[a-z][\w-]*|(?:^|\s)(?:\.\.?\/|\/)[\w./-]+|\b[\w-]+\.(?:[cm]?[jt]sx?|json|mdx?|vue|svelte|astro)\b|\bv?\d+(?:\.\d+)+\b)/im;
60
- const GENERIC_SENTENCE_PATTERN = /\b(?:follow|keep answers|refer to|read|use) (?:the |this )?(?:documentation|docs|instructions|page)\b|\bkeep answers grounded\b|\bpoint to (?:the )?(?:closest )?related docs\b|\bif the request moves beyond this page\b|\bdo not (?:guess|invent)\b/i;
61
- const FRAMEWORK_PATTERNS = [
62
- ["nextjs", /\bNext\.js\b|@farming-labs\/next\b|\bnext\.config\b|\bApp Router\b/i],
63
- ["tanstackstart", /\bTanStack Start\b|@farming-labs\/(?:tanstack|tanstack-start)\b|\bcreateFileRoute\b/i],
64
- ["sveltekit", /\bSvelteKit\b|@farming-labs\/svelte(?:kit)?\b|\bhooks\.server\b|\+server\.[cm]?[jt]s\b/i],
65
- ["astro", /\bAstro\b|@farming-labs\/astro\b|\bastro\.config\b/i],
66
- ["nuxt", /\bNuxt\b|@farming-labs\/nuxt\b|\bnuxt\.config\b/i]
67
- ];
68
- const PACKAGE_MANAGER_BUILT_INS = {
69
- npm: new Set([
70
- "access",
71
- "adduser",
72
- "audit",
73
- "cache",
74
- "ci",
75
- "config",
76
- "dedupe",
77
- "exec",
78
- "help",
79
- "init",
80
- "install",
81
- "link",
82
- "login",
83
- "logout",
84
- "org",
85
- "outdated",
86
- "owner",
87
- "pack",
88
- "ping",
89
- "prefix",
90
- "profile",
91
- "prune",
92
- "publish",
93
- "query",
94
- "rebuild",
95
- "repo",
96
- "restart",
97
- "root",
98
- "run",
99
- "run-script",
100
- "search",
101
- "shrinkwrap",
102
- "star",
103
- "stars",
104
- "start",
105
- "stop",
106
- "team",
107
- "test",
108
- "token",
109
- "uninstall",
110
- "unpublish",
111
- "unstar",
112
- "update",
113
- "version",
114
- "view",
115
- "whoami"
116
- ]),
117
- pnpm: new Set([
118
- "add",
119
- "approve-builds",
120
- "audit",
121
- "bin",
122
- "config",
123
- "create",
124
- "dedupe",
125
- "deploy",
126
- "dlx",
127
- "env",
128
- "exec",
129
- "fetch",
130
- "help",
131
- "import",
132
- "init",
133
- "install",
134
- "link",
135
- "list",
136
- "outdated",
137
- "pack",
138
- "patch",
139
- "patch-commit",
140
- "prune",
141
- "publish",
142
- "rebuild",
143
- "remove",
144
- "root",
145
- "run",
146
- "server",
147
- "setup",
148
- "store",
149
- "test",
150
- "unlink",
151
- "update",
152
- "view",
153
- "why"
154
- ]),
155
- yarn: new Set([
156
- "add",
157
- "bin",
158
- "cache",
159
- "config",
160
- "create",
161
- "dedupe",
162
- "dlx",
163
- "exec",
164
- "help",
165
- "info",
166
- "init",
167
- "install",
168
- "link",
169
- "npm",
170
- "pack",
171
- "plugin",
172
- "rebuild",
173
- "remove",
174
- "run",
175
- "set",
176
- "stage",
177
- "unlink",
178
- "up",
179
- "why",
180
- "workspace",
181
- "workspaces"
182
- ]),
183
- bun: new Set([
184
- "add",
185
- "build",
186
- "create",
187
- "dev",
188
- "help",
189
- "install",
190
- "link",
191
- "pm",
192
- "publish",
193
- "remove",
194
- "repl",
195
- "run",
196
- "test",
197
- "unlink",
198
- "update",
199
- "upgrade",
200
- "x"
201
- ])
202
- };
203
- const PACKAGE_MANAGER_OPTIONS_WITH_VALUES = new Set([
204
- "--cwd",
205
- "--dir",
206
- "--filter",
207
- "--prefix",
208
- "--workspace",
209
- "-C",
210
- "-F",
211
- "-w"
212
- ]);
213
- function isAgentContextVisible(scopes) {
214
- return scopes.every((scope) => scope.only === void 0 || scope.only === "agent");
215
- }
216
- /**
217
- * Extract actual agent-only context while ignoring examples in fenced and inline code.
218
- *
219
- * `<Agent>` is agent-only shorthand. `<Audience>` counts only when `only` is the
220
- * static value `"agent"`; human-only and dynamic/invalid audience declarations do
221
- * not contribute to agent-context usefulness metrics.
222
- */
223
- function extractAgentBlocks(source, options = {}) {
224
- const sourcePath = options.sourcePath ?? "unknown";
225
- const tags = findDocsAudienceMdxTags(source);
226
- const blocks = [];
227
- const scopes = [];
228
- let nextScopeId = 1;
229
- let cursor = 0;
230
- let active;
231
- const appendVisibleContent = (content) => {
232
- if (active && isAgentContextVisible(scopes)) active.content.push(content);
233
- };
234
- const finishActiveBlock = () => {
235
- if (!active) return;
236
- blocks.push({
237
- sourcePath,
238
- route: options.route,
239
- line: active.line,
240
- content: active.content.join("").trim()
241
- });
242
- active = void 0;
243
- };
244
- for (const tag of tags) {
245
- const activeScope = scopes.at(-1);
246
- if (tag.closing && activeScope?.name !== tag.name) continue;
247
- appendVisibleContent(source.slice(cursor, tag.index));
248
- cursor = tag.end;
249
- if (tag.closing) {
250
- const closedScope = scopes.pop();
251
- if (active && closedScope?.id === active.scopeId) finishActiveBlock();
252
- continue;
253
- }
254
- if (tag.selfClosing) continue;
255
- const scope = {
256
- id: nextScopeId,
257
- name: tag.name,
258
- only: tag.only
259
- };
260
- nextScopeId += 1;
261
- scopes.push(scope);
262
- const isAgentOnly = tag.name === "Agent" || tag.name === "Audience" && tag.only === "agent";
263
- if (!active && isAgentOnly && isAgentContextVisible(scopes)) active = {
264
- line: source.slice(0, tag.index).split(/\r?\n/).length,
265
- scopeId: scope.id,
266
- content: []
267
- };
268
- }
269
- appendVisibleContent(source.slice(cursor));
270
- return blocks;
271
- }
272
- /** Analyze page-level context quality without executing documented commands. */
273
- function analyzeAgentUsefulness(options) {
274
- const rootDir = path.resolve(options.rootDir);
275
- const packageManager = options.packageManager ?? detectPackageManager(rootDir);
276
- const knownDocsCommands = new Set([...DEFAULT_DOCS_COMMANDS, ...options.knownDocsCommands ?? []]);
277
- const packageManifests = readProjectPackageManifests(rootDir);
278
- const pages = options.pages.map((page) => analyzePage(page));
279
- const findings = [];
280
- const unhealthyCommandKeys = /* @__PURE__ */ new Set();
281
- const unverifiedCommandKeys = /* @__PURE__ */ new Set();
282
- const blockQuality = analyzeBlockQuality(pages, options.boilerplate);
283
- findings.push(...blockQuality.findings);
284
- let completeTaskPages = 0;
285
- let missingPrerequisites = 0;
286
- let missingExpectedResults = 0;
287
- let missingRecovery = 0;
288
- let conflictingPages = 0;
289
- let ambiguousPages = 0;
290
- let mismatchedPages = 0;
291
- let commandCount = 0;
292
- let coveredActionablePages = 0;
293
- let missingRelatedPages = 0;
294
- let brokenRelatedLinks = 0;
295
- const knownRoutes = new Set(options.pages.map((page) => normalizeRoute(page.route)));
296
- for (const analysis of pages) {
297
- const { page, agent, actionable, projectedSource } = analysis;
298
- if (actionable) {
299
- const guidance = [
300
- stripFencedContent(projectedSource),
301
- page.agentSource ? stripFencedContent(page.agentSource) : void 0,
302
- ...analysis.blocks.map((block) => stripFencedContent(block.content))
303
- ].filter((value) => Boolean(value)).join("\n\n");
304
- const taskIssuesBefore = findings.length;
305
- if (!agent?.prerequisites?.length && !PREREQUISITE_PATTERN.test(guidance)) {
306
- missingPrerequisites += 1;
307
- findings.push(makeFinding(page, {
308
- code: "task-missing-prerequisites",
309
- severity: "warning",
310
- category: "task",
311
- message: "Actionable agent guidance is missing prerequisites or an explicit before-you-begin condition."
312
- }));
313
- }
314
- if (!hasExpectedResult(agent) && !EXPECTED_RESULT_PATTERN.test(guidance)) {
315
- missingExpectedResults += 1;
316
- findings.push(makeFinding(page, {
317
- code: "task-missing-expected-result",
318
- severity: "warning",
319
- category: "task",
320
- message: "Actionable agent guidance is missing an observable outcome or expected verification result."
321
- }));
322
- }
323
- if (!hasRecovery(agent) && !RECOVERY_PATTERN.test(guidance)) {
324
- missingRecovery += 1;
325
- findings.push(makeFinding(page, {
326
- code: "task-missing-recovery",
327
- severity: "warning",
328
- category: "task",
329
- message: "Actionable agent guidance is missing rollback, recovery, or resolved failure-mode steps."
330
- }));
331
- }
332
- if (findings.length === taskIssuesBefore) completeTaskPages += 1;
333
- }
334
- const applicability = analyzeApplicability(page, agent, actionable, options.projectFramework);
335
- findings.push(...applicability.findings);
336
- if (applicability.conflicting) conflictingPages += 1;
337
- if (applicability.ambiguous) ambiguousPages += 1;
338
- if (applicability.mismatched) mismatchedPages += 1;
339
- for (const [commandIndex, command] of analysis.shellCommands.entries()) {
340
- commandCount += 1;
341
- const commandKey = `${page.sourcePath}:${command.line ?? 1}:${commandIndex}`;
342
- const commandAnalysis = analyzeCommand({
343
- page,
344
- command,
345
- rootDir,
346
- packageManager,
347
- knownDocsCommands,
348
- packageManifests
349
- });
350
- if (commandAnalysis.status === "unhealthy") unhealthyCommandKeys.add(commandKey);
351
- if (commandAnalysis.status === "unverified") unverifiedCommandKeys.add(commandKey);
352
- findings.push(...commandAnalysis.findings);
353
- }
354
- if (actionable) {
355
- const related = normalizeRelated(page.related);
356
- const validRelated = related.filter((href) => {
357
- const route = internalRelatedRoute(href);
358
- return route !== void 0 && knownRoutes.has(route);
359
- });
360
- for (const href of related) {
361
- const route = internalRelatedRoute(href);
362
- if (!route || knownRoutes.has(route)) continue;
363
- brokenRelatedLinks += 1;
364
- findings.push(makeFinding(page, {
365
- code: "related-broken",
366
- severity: "warning",
367
- category: "related",
368
- message: `Related page does not resolve to a known docs route: ${href}`
369
- }));
370
- }
371
- if (validRelated.length > 0) coveredActionablePages += 1;
372
- else {
373
- missingRelatedPages += 1;
374
- findings.push(makeFinding(page, {
375
- code: "related-missing",
376
- severity: "suggestion",
377
- category: "related",
378
- message: "Actionable agent guidance has no valid related-page route."
379
- }));
380
- }
381
- }
382
- }
383
- const actionablePages = pages.filter((page) => page.actionable).length;
384
- const usefulBlocks = Math.max(0, blockQuality.totalBlocks - blockQuality.lowQualityBlockKeys.size);
385
- return {
386
- findings: findings.sort(compareFindings),
387
- metrics: {
388
- totalPages: pages.length,
389
- actionablePages,
390
- agentBlocks: {
391
- total: blockQuality.totalBlocks,
392
- pages: pages.filter((page) => page.blocks.length > 0).length,
393
- duplicate: blockQuality.duplicateBlockKeys.size,
394
- boilerplate: blockQuality.boilerplateBlockKeys.size,
395
- generic: blockQuality.genericBlockKeys.size,
396
- useful: usefulBlocks
397
- },
398
- taskCompleteness: {
399
- completePages: completeTaskPages,
400
- missingPrerequisites,
401
- missingExpectedResults,
402
- missingRecovery,
403
- coverage: percentage(completeTaskPages, actionablePages)
404
- },
405
- applicability: {
406
- conflictingPages,
407
- ambiguousPages,
408
- mismatchedPages
409
- },
410
- commands: {
411
- total: commandCount,
412
- healthy: Math.max(0, commandCount - unhealthyCommandKeys.size - unverifiedCommandKeys.size),
413
- unhealthy: unhealthyCommandKeys.size,
414
- unverified: unverifiedCommandKeys.size
415
- },
416
- related: {
417
- coveredActionablePages,
418
- missingActionablePages: missingRelatedPages,
419
- brokenLinks: brokenRelatedLinks,
420
- coverage: percentage(coveredActionablePages, actionablePages)
421
- }
422
- }
423
- };
424
- }
425
- function analyzePage(page) {
426
- const agent = normalizePageAgentFrontmatter(page.agent);
427
- const projectedSource = resolveDocsAudienceMdxContent(page.source, "agent");
428
- const blocks = extractAgentBlocks(page.source, {
429
- sourcePath: page.sourcePath,
430
- route: page.route
431
- });
432
- const shellCommands = collectPageCommands(page, agent, projectedSource);
433
- return {
434
- page,
435
- agent,
436
- blocks,
437
- projectedSource,
438
- shellCommands,
439
- actionable: page.actionable ?? (hasStructuredPageAgentContract(agent) || shellCommands.length > 0 || blocks.some((block) => ACTIONABLE_PATTERN.test(block.content)) || ACTIONABLE_PATTERN.test(page.agentSource ?? ""))
440
- };
441
- }
442
- function analyzeBlockQuality(pages, config) {
443
- const findings = [];
444
- const allBlocks = pages.flatMap((analysis) => analysis.blocks.map((block, index) => ({
445
- analysis,
446
- block,
447
- index
448
- })));
449
- const signatureGroups = /* @__PURE__ */ new Map();
450
- const sentenceOccurrences = /* @__PURE__ */ new Map();
451
- const blockSentences = /* @__PURE__ */ new Map();
452
- for (const item of allBlocks) {
453
- const key = blockKey(item.block, item.index);
454
- const sentences = splitNormalizedSentences(item.block.content);
455
- blockSentences.set(key, sentences);
456
- const signature = normalizeBlockSignature(item.block.content);
457
- if (signature) {
458
- const group = signatureGroups.get(signature) ?? [];
459
- group.push(item);
460
- signatureGroups.set(signature, group);
461
- }
462
- for (const sentence of new Set(sentences)) {
463
- const occurrences = sentenceOccurrences.get(sentence) ?? /* @__PURE__ */ new Set();
464
- occurrences.add(key);
465
- sentenceOccurrences.set(sentence, occurrences);
466
- }
467
- }
468
- const minOccurrences = Math.max(2, Math.round(config?.minOccurrences ?? 3));
469
- const corpusRatio = clampRatio(config?.corpusRatio ?? .2);
470
- const repeatedThreshold = Math.min(minOccurrences, Math.max(2, Math.ceil(allBlocks.length * corpusRatio)));
471
- const blockRatio = clampRatio(config?.blockRatio ?? .5);
472
- const repeatedSentences = new Set([...sentenceOccurrences].filter(([, occurrences]) => occurrences.size >= repeatedThreshold).map(([sentence]) => sentence));
473
- const duplicateBlockKeys = /* @__PURE__ */ new Set();
474
- const boilerplateBlockKeys = /* @__PURE__ */ new Set();
475
- const genericBlockKeys = /* @__PURE__ */ new Set();
476
- const lowQualityBlockKeys = /* @__PURE__ */ new Set();
477
- for (const group of signatureGroups.values()) {
478
- if (group.length < 2) continue;
479
- const relatedFiles = Array.from(new Set(group.map((item) => item.block.sourcePath))).sort();
480
- for (const item of group) {
481
- const key = blockKey(item.block, item.index);
482
- duplicateBlockKeys.add(key);
483
- lowQualityBlockKeys.add(key);
484
- findings.push(makeFinding(item.analysis.page, {
485
- code: "agent-block-duplicate",
486
- severity: "warning",
487
- category: "context",
488
- line: item.block.line,
489
- relatedFiles: relatedFiles.filter((file) => file !== item.block.sourcePath),
490
- message: `Agent-only block duplicates context used on ${group.length - 1} other page${group.length === 2 ? "" : "s"}.`
491
- }));
492
- }
493
- }
494
- for (const item of allBlocks) {
495
- const key = blockKey(item.block, item.index);
496
- const sentences = blockSentences.get(key) ?? [];
497
- const totalChars = sentences.reduce((total, sentence) => total + sentence.length, 0);
498
- const repeatedChars = sentences.filter((sentence) => repeatedSentences.has(sentence)).reduce((total, sentence) => total + sentence.length, 0);
499
- const ratio = totalChars > 0 ? repeatedChars / totalChars : 0;
500
- if (ratio >= blockRatio) {
501
- boilerplateBlockKeys.add(key);
502
- lowQualityBlockKeys.add(key);
503
- if (!duplicateBlockKeys.has(key)) findings.push(makeFinding(item.analysis.page, {
504
- code: "agent-block-boilerplate",
505
- severity: "warning",
506
- category: "context",
507
- line: item.block.line,
508
- message: `${Math.round(ratio * 100)}% of this agent-only block repeats sentences used across the docs corpus.`
509
- }));
510
- }
511
- if (isGenericAgentBlock(item.block.content, sentences)) {
512
- genericBlockKeys.add(key);
513
- lowQualityBlockKeys.add(key);
514
- findings.push(makeFinding(item.analysis.page, {
515
- code: "agent-block-generic",
516
- severity: "suggestion",
517
- category: "context",
518
- line: item.block.line,
519
- message: "Agent-only block is generic and lacks a concrete command, path, identifier, version, or task-specific constraint."
520
- }));
521
- }
522
- }
523
- return {
524
- findings,
525
- totalBlocks: allBlocks.length,
526
- duplicateBlockKeys,
527
- boilerplateBlockKeys,
528
- genericBlockKeys,
529
- lowQualityBlockKeys
530
- };
531
- }
532
- function analyzeApplicability(page, agent, actionable, projectFramework) {
533
- const findings = [];
534
- const topFrameworks = normalizeFrameworks(page.framework);
535
- const contractFrameworks = normalizeFrameworks(agent?.appliesTo?.framework);
536
- const topVersions = normalizeValues(page.version);
537
- const contractVersions = normalizeValues(agent?.appliesTo?.version);
538
- let conflicting = false;
539
- let ambiguous = false;
540
- let mismatched = false;
541
- if (topFrameworks.length > 0 && contractFrameworks.length > 0 && !setsOverlap(topFrameworks, contractFrameworks)) {
542
- conflicting = true;
543
- findings.push(makeFinding(page, {
544
- code: "applicability-framework-conflict",
545
- severity: "warning",
546
- category: "applicability",
547
- message: `Top-level framework (${topFrameworks.join(", ")}) conflicts with agent.appliesTo.framework (${contractFrameworks.join(", ")}).`
548
- }));
549
- }
550
- if (topVersions.length > 0 && contractVersions.length > 0 && !versionSetsCompatible(topVersions, contractVersions)) {
551
- conflicting = true;
552
- findings.push(makeFinding(page, {
553
- code: "applicability-version-conflict",
554
- severity: "warning",
555
- category: "applicability",
556
- message: `Top-level version (${topVersions.join(", ")}) conflicts with agent.appliesTo.version (${contractVersions.join(", ")}).`
557
- }));
558
- }
559
- const declaredFrameworks = Array.from(new Set([...topFrameworks, ...contractFrameworks]));
560
- const inferredFrameworks = inferFrameworks(`${page.source}\n${page.agentSource ?? ""}`);
561
- if (actionable && declaredFrameworks.length === 0 && inferredFrameworks.length > 1) {
562
- ambiguous = true;
563
- findings.push(makeFinding(page, {
564
- code: "applicability-framework-ambiguous",
565
- severity: "warning",
566
- category: "applicability",
567
- message: `Actionable page references multiple frameworks (${inferredFrameworks.join(", ")}) without framework applicability metadata.`
568
- }));
569
- }
570
- const normalizedProjectFramework = projectFramework ? normalizeAgentFramework(projectFramework) : void 0;
571
- const effectiveFrameworks = declaredFrameworks.length > 0 ? declaredFrameworks : inferredFrameworks;
572
- if (actionable && normalizedProjectFramework && effectiveFrameworks.length > 0 && !effectiveFrameworks.includes(normalizedProjectFramework)) {
573
- mismatched = true;
574
- findings.push(makeFinding(page, {
575
- code: "applicability-framework-mismatch",
576
- severity: "warning",
577
- category: "applicability",
578
- message: `Page applicability (${effectiveFrameworks.join(", ")}) does not include the detected project framework (${normalizedProjectFramework}).`
579
- }));
580
- }
581
- if (actionable && topVersions.length === 0 && contractVersions.length === 0 && hasAmbiguousVersionSignals(`${page.source}\n${page.agentSource ?? ""}`)) {
582
- ambiguous = true;
583
- findings.push(makeFinding(page, {
584
- code: "applicability-version-ambiguous",
585
- severity: "warning",
586
- category: "applicability",
587
- message: "Actionable page discusses multiple current, legacy, or migrated versions without version applicability metadata."
588
- }));
589
- }
590
- return {
591
- findings,
592
- conflicting,
593
- ambiguous,
594
- mismatched
595
- };
596
- }
597
- function analyzeCommand(options) {
598
- const findings = [];
599
- let verificationEstablished = false;
600
- const commandText = normalizeShellCommand(options.command.run);
601
- if (!commandText) return commandAnalysis(findings);
602
- let commandCwd = options.command.cwd ?? ".";
603
- const inlineCwd = /^(?:cd)\s+([^;&|]+?)\s*&&\s*(.+)$/s.exec(commandText);
604
- const executableCommand = inlineCwd?.[2]?.trim() ?? commandText;
605
- if (inlineCwd?.[1]) commandCwd = stripShellQuotes(inlineCwd[1].trim());
606
- const resolvedCwd = path.resolve(options.rootDir, commandCwd);
607
- if (!isPathInside(options.rootDir, resolvedCwd)) {
608
- findings.push(commandFinding(options, {
609
- code: "command-cwd-outside-root",
610
- severity: "error",
611
- message: `Command working directory resolves outside the project root: ${commandCwd}`
612
- }));
613
- return commandAnalysis(findings);
614
- }
615
- if (!existsSync(resolvedCwd)) {
616
- findings.push(commandFinding(options, {
617
- code: "command-cwd-missing",
618
- severity: "error",
619
- message: `Command working directory does not exist: ${commandCwd}`
620
- }));
621
- return commandAnalysis(findings);
622
- }
623
- const tokens = tokenizeShellCommand(executableCommand);
624
- const commandPackageManager = readCommandPackageManager(tokens);
625
- const expectedPackageManager = options.command.packageManagerHint ?? (options.command.source === "contract" ? options.packageManager : void 0);
626
- if (commandPackageManager && expectedPackageManager && commandPackageManager !== expectedPackageManager) findings.push(commandFinding(options, {
627
- code: "command-package-manager-mismatch",
628
- severity: "warning",
629
- message: `Command uses ${commandPackageManager}, but ${expectedPackageManager} is expected for this project or code block.`
630
- }));
631
- const workspaceSelection = readWorkspaceSelection(tokens, commandPackageManager);
632
- const workspaceResolution = resolveWorkspaceSelection(workspaceSelection, options.packageManifests, options.rootDir);
633
- if (workspaceResolution.status === "unresolved") {
634
- const selectors = workspaceSelection.selectors.length ? workspaceSelection.selectors.join(", ") : "all workspaces";
635
- findings.push(commandFinding(options, {
636
- code: "command-unverified",
637
- severity: "suggestion",
638
- message: `Workspace selector could not be resolved statically (${selectors}); this command is unverified.`
639
- }));
640
- }
641
- const script = readPackageScript(tokens);
642
- if (script) {
643
- const packageJsons = workspaceResolution.status === "resolved" ? workspaceResolution.manifests : workspaceResolution.status === "none" ? [readNearestPackageJson(resolvedCwd, options.rootDir)].filter((manifest) => Boolean(manifest)) : [];
644
- if (workspaceResolution.status === "none" && packageJsons.length === 0) findings.push(commandFinding(options, {
645
- code: "command-unverified",
646
- severity: "suggestion",
647
- message: `No package.json could be found for package script "${script}"; this command is unverified.`
648
- }));
649
- for (const packageJson of packageJsons) if (packageJson.scripts.has(script)) verificationEstablished = true;
650
- else findings.push(commandFinding(options, {
651
- code: "command-script-missing",
652
- severity: "error",
653
- message: `Command references package script "${script}", but that script is not defined in ${packageJson.relativePath}.`
654
- }));
655
- }
656
- const docsCommand = readDocsCommand(tokens);
657
- if (docsCommand) if (matchesKnownDocsCommand(docsCommand, options.knownDocsCommands)) verificationEstablished = true;
658
- else findings.push(commandFinding(options, {
659
- code: "command-cli-unknown",
660
- severity: "error",
661
- message: `Command references an unknown docs CLI command: docs ${docsCommand}`
662
- }));
663
- if (isStaticallyKnownPackageManagerCommand(tokens) || isVersionProbe(tokens)) verificationEstablished = true;
664
- if (!verificationEstablished && findings.length === 0) findings.push(commandFinding(options, {
665
- code: "command-unverified",
666
- severity: "suggestion",
667
- message: "Command form could not be verified statically; this command is unverified."
668
- }));
669
- return commandAnalysis(findings);
670
- }
671
- function commandAnalysis(findings) {
672
- if (findings.some((finding) => finding.code !== "command-unverified")) return {
673
- findings,
674
- status: "unhealthy"
675
- };
676
- if (findings.length > 0) return {
677
- findings,
678
- status: "unverified"
679
- };
680
- return {
681
- findings,
682
- status: "healthy"
683
- };
684
- }
685
- function collectPageCommands(page, agent, projectedSource) {
686
- const commands = [];
687
- for (const command of agent?.commands ?? []) commands.push(normalizeAgentCommand(command, page.sourcePath));
688
- for (const verification of agent?.verification ?? []) if (typeof verification !== "string" && verification.run) commands.push({
689
- run: verification.run,
690
- sourcePath: page.sourcePath,
691
- source: "contract"
692
- });
693
- collectMarkdownCommands(commands, projectedSource, page.sourcePath);
694
- if (page.agentSource) collectMarkdownCommands(commands, page.agentSource, page.agentSourcePath ?? page.sourcePath);
695
- return dedupeCommands(commands);
696
- }
697
- function collectMarkdownCommands(commands, source, sourcePath) {
698
- const blocks = extractCodeBlocksFromMarkdown({
699
- source,
700
- filePath: sourcePath,
701
- relativePath: sourcePath
702
- });
703
- for (const block of blocks) {
704
- if (!/^(?:bash|console|sh|shell|zsh)$/i.test(block.language ?? "")) continue;
705
- for (const command of shellLines(block.code, block.language)) commands.push({
706
- run: command,
707
- line: block.lineStart,
708
- sourcePath,
709
- packageManagerHint: block.packageManager,
710
- source: "fence"
711
- });
712
- }
713
- }
714
- function normalizeAgentCommand(command, sourcePath) {
715
- return typeof command === "string" ? {
716
- run: command,
717
- sourcePath,
718
- source: "contract"
719
- } : {
720
- run: command.run,
721
- cwd: command.cwd,
722
- sourcePath,
723
- source: "contract"
724
- };
725
- }
726
- function shellLines(code, language) {
727
- const lines = code.replace(/\\\s*\r?\n\s*/g, " ").split(/\r?\n/);
728
- const promptedConsole = language?.toLowerCase() === "console" && lines.some((line) => /^(?:\$|>)\s+/.test(line.trim()));
729
- return lines.map((line) => line.trim()).filter((line) => !promptedConsole || /^(?:\$|>)\s+/.test(line)).map((line) => line.replace(/^(?:\$|>)\s+/, "")).filter((line) => Boolean(line) && !line.startsWith("#") && !/^\w+=\S+$/.test(line));
730
- }
731
- function dedupeCommands(commands) {
732
- const seen = /* @__PURE__ */ new Set();
733
- return commands.filter((command) => {
734
- const key = `${command.sourcePath ?? ""}\0${command.cwd ?? ""}\0${command.run}\0${command.packageManagerHint ?? ""}`;
735
- if (seen.has(key)) return false;
736
- seen.add(key);
737
- return true;
738
- });
739
- }
740
- function hasExpectedResult(agent) {
741
- if (agent?.outcome) return true;
742
- return Boolean(agent?.verification?.some((step) => {
743
- if (typeof step === "string") return EXPECTED_RESULT_PATTERN.test(step);
744
- return Boolean(step.expect?.trim() || EXPECTED_RESULT_PATTERN.test(step.description ?? ""));
745
- }));
746
- }
747
- function hasRecovery(agent) {
748
- if (agent?.rollback?.length) return true;
749
- return Boolean(agent?.failureModes?.some((mode) => {
750
- if (typeof mode === "string") return RECOVERY_PATTERN.test(mode);
751
- return Boolean(mode.resolution?.trim());
752
- }));
753
- }
754
- function splitNormalizedSentences(content) {
755
- const withoutFences = stripFencedContent(content);
756
- const sentences = [];
757
- for (const line of withoutFences.split(/\r?\n/)) {
758
- const cleanedLine = line.replace(/^\s*(?:[-*+] |\d+[.)] )/, "").replace(/^\s{0,3}#{1,6}\s+/, "").trim();
759
- if (!cleanedLine) continue;
760
- const parts = cleanedLine.match(/[^.!?]+[.!?]?/g) ?? [cleanedLine];
761
- for (const part of parts) {
762
- const normalized = normalizeSentence(part);
763
- if (normalized) sentences.push(normalized);
764
- }
765
- }
766
- return sentences;
767
- }
768
- function normalizeSentence(value) {
769
- return value.replace(/!?(?:\[([^\]]*)\])\([^)]+\)/g, "$1").replace(/[`*_~]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
770
- }
771
- function normalizeBlockSignature(content) {
772
- const signature = [];
773
- let fence;
774
- for (const line of content.replace(/\r\n?/g, "\n").split("\n")) {
775
- const trimmed = line.trim();
776
- const nextFence = advanceFence(trimmed, fence);
777
- if (fence) {
778
- if (nextFence) signature.push(`code:${line}`);
779
- else signature.push("fence:end");
780
- fence = nextFence;
781
- continue;
782
- }
783
- if (nextFence) {
784
- signature.push(`fence:start:${trimmed}`);
785
- fence = nextFence;
786
- continue;
787
- }
788
- const prose = normalizeSentence(line);
789
- if (prose) signature.push(`prose:${prose}`);
790
- }
791
- return signature.join("\n");
792
- }
793
- function stripFencedContent(source) {
794
- const output = [];
795
- let fence;
796
- for (const line of source.split(/\r?\n/)) {
797
- const nextFence = advanceFence(line.trim(), fence);
798
- if (!fence && !nextFence) output.push(line);
799
- fence = nextFence;
800
- }
801
- return output.join("\n");
802
- }
803
- function advanceFence(line, fence) {
804
- if (fence) {
805
- const closing = /^(`+|~+)\s*$/.exec(line)?.[1];
806
- if (closing?.[0] === fence.character && closing.length >= fence.length) return void 0;
807
- return fence;
808
- }
809
- const opening = /^(`{3,}|~{3,})(.*)$/.exec(line)?.[1];
810
- if (!opening) return void 0;
811
- return {
812
- character: opening[0],
813
- length: opening.length
814
- };
815
- }
816
- function isGenericAgentBlock(content, sentences) {
817
- const words = normalizeSentence(content).match(/[a-z0-9][a-z0-9_-]*/g) ?? [];
818
- const hasSpecificContext = hasMeaningfulSpecificContext(content);
819
- const genericSentences = sentences.filter((sentence) => GENERIC_SENTENCE_PATTERN.test(sentence));
820
- const totalSentenceChars = sentences.reduce((total, sentence) => total + sentence.length, 0);
821
- const genericSentenceChars = genericSentences.reduce((total, sentence) => total + sentence.length, 0);
822
- const genericRatio = totalSentenceChars > 0 ? genericSentenceChars / totalSentenceChars : 0;
823
- return !hasSpecificContext && words.length < 12 || !hasSpecificContext && sentences.length > 0 && genericRatio >= .67;
824
- }
825
- function hasMeaningfulSpecificContext(content) {
826
- if (STRONG_SPECIFIC_CONTEXT_PATTERN.test(content)) return true;
827
- if (!ACTIONABLE_PATTERN.test(content)) return false;
828
- if (/https?:\/\/[^\s)]+/i.test(content)) return true;
829
- const trivialInlineValues = new Set([
830
- "config",
831
- "documentation",
832
- "docs",
833
- "page",
834
- "settings"
835
- ]);
836
- return [...content.matchAll(/`([^`\n]+)`/g)].some((match) => {
837
- const value = (match[1] ?? "").trim().toLowerCase();
838
- return value.length >= 3 && !trivialInlineValues.has(value);
839
- });
840
- }
841
- function normalizeFrameworks(value) {
842
- return normalizeValues(value).map(normalizeAgentFramework);
843
- }
844
- function normalizeValues(value) {
845
- const values = Array.isArray(value) ? value : value ? [value] : [];
846
- return Array.from(new Set(values.map((item) => item.trim().toLowerCase()).filter((item) => item.length > 0)));
847
- }
848
- function inferFrameworks(source) {
849
- return FRAMEWORK_PATTERNS.filter(([, pattern]) => pattern.test(source)).map(([name]) => name);
850
- }
851
- function hasAmbiguousVersionSignals(source) {
852
- if (!/\b(?:current|deprecated|latest|legacy|migrat(?:e|ion)|version)\b/i.test(source)) return false;
853
- return new Set([...source.matchAll(/\b(?:v|version\s*)(\d+(?:\.\d+){0,2})\b/gi)].map((match) => match[1])).size > 1;
854
- }
855
- function versionSetsCompatible(left, right) {
856
- return left.some((leftVersion) => right.some((rightVersion) => versionsCompatible(leftVersion, rightVersion)));
857
- }
858
- function versionsCompatible(left, right) {
859
- const normalizedLeft = normalizeAgentVersion(left);
860
- const normalizedRight = normalizeAgentVersion(right);
861
- if (normalizedLeft === normalizedRight) return true;
862
- if (isVersionConstraint(normalizedLeft) && isVersionConstraint(normalizedRight)) return agentVersionConstraintsOverlap(normalizedLeft, normalizedRight);
863
- return true;
864
- }
865
- function isExactVersion(value) {
866
- return /^\d+(?:\.\d+){0,2}(?:-[0-9a-z.-]+)?(?:\+[0-9a-z.-]+)?$/i.test(value);
867
- }
868
- function isVersionConstraint(value) {
869
- return isExactVersion(value) || /(?:[<>=~^*x]|\|\||\s+-\s+)/i.test(value);
870
- }
871
- function setsOverlap(left, right) {
872
- return left.some((value) => right.includes(value));
873
- }
874
- function normalizeRelated(related) {
875
- if (!related) return [];
876
- return Array.from(new Set(related.map((item) => typeof item === "string" ? item : item.href).map((item) => item.trim()).filter(Boolean)));
877
- }
878
- function internalRelatedRoute(href) {
879
- if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("#")) return void 0;
880
- const pathname = href.split(/[?#]/, 1)[0];
881
- if (!pathname) return void 0;
882
- return normalizeRoute(pathname);
883
- }
884
- function normalizeRoute(route) {
885
- const normalized = `/${route}`.replace(/\/+/g, "/").replace(/\.md$/i, "");
886
- return normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
887
- }
888
- function detectPackageManager(rootDir) {
889
- if (existsSync(path.join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
890
- if (existsSync(path.join(rootDir, "yarn.lock"))) return "yarn";
891
- if (existsSync(path.join(rootDir, "bun.lock")) || existsSync(path.join(rootDir, "bun.lockb"))) return "bun";
892
- if (existsSync(path.join(rootDir, "package-lock.json"))) return "npm";
893
- const packageJson = readJsonFile(path.join(rootDir, "package.json"));
894
- const declared = typeof packageJson?.packageManager === "string" ? packageJson.packageManager : "";
895
- return /^(npm|pnpm|yarn|bun)@/.exec(declared)?.[1];
896
- }
897
- function normalizeShellCommand(command) {
898
- return command.trim().replace(/^\$\s*/, "");
899
- }
900
- function tokenizeShellCommand(command) {
901
- return command.match(/"[^"]*"|'[^']*'|[^\s]+/g)?.map(stripShellQuotes) ?? [];
902
- }
903
- function stripShellQuotes(value) {
904
- if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
905
- return value;
906
- }
907
- function readCommandPackageManager(tokens) {
908
- const command = tokens[0];
909
- if (command === "npx") return "npm";
910
- if (command === "pnpx") return "pnpm";
911
- if (command === "bunx") return "bun";
912
- return command && command in PACKAGE_MANAGER_BUILT_INS ? command : void 0;
913
- }
914
- function readPackageScript(tokens) {
915
- const manager = readCommandPackageManager(tokens);
916
- if (!manager) return void 0;
917
- if ([
918
- "npx",
919
- "pnpx",
920
- "bunx"
921
- ].includes(tokens[0] ?? "")) return void 0;
922
- if (manager === "yarn") {
923
- const workspaceIndex = findPackageManagerCommandIndex(tokens);
924
- if (workspaceIndex >= 0 && tokens[workspaceIndex] === "workspace") return readScriptFromArguments(manager, tokens.slice(workspaceIndex + 2));
925
- }
926
- return readScriptFromArguments(manager, readPackageManagerArguments(tokens));
927
- }
928
- function readScriptFromArguments(manager, commandArguments) {
929
- if ([
930
- "dlx",
931
- "exec",
932
- "x"
933
- ].includes(commandArguments[0] ?? "")) return void 0;
934
- if (["run", "run-script"].includes(commandArguments[0] ?? "")) return cleanShellToken(commandArguments[1]);
935
- const candidate = cleanShellToken(commandArguments[0]);
936
- if (!candidate) return void 0;
937
- if ([
938
- "start",
939
- "stop",
940
- "restart",
941
- "test"
942
- ].includes(candidate)) return candidate;
943
- return PACKAGE_MANAGER_BUILT_INS[manager]?.has(candidate) ? void 0 : candidate;
944
- }
945
- function readWorkspaceSelection(tokens, manager) {
946
- if (!manager) return {
947
- requested: false,
948
- selectors: []
949
- };
950
- if (manager === "pnpm" || manager === "bun") {
951
- const selectors = readOptionValues(tokens, ["--filter"], manager === "pnpm" ? ["-F"] : []);
952
- const recursive = manager === "pnpm" && hasOptionBeforeCommand(tokens, ["-r", "--recursive"]);
953
- return {
954
- requested: selectors.length > 0 || recursive,
955
- selectors
956
- };
957
- }
958
- if (manager === "npm") {
959
- const selectors = readOptionValues(tokens, ["--workspace"], ["-w"]);
960
- const allWorkspaces = hasOptionBeforeCommand(tokens, ["--workspaces"]);
961
- return {
962
- requested: selectors.length > 0 || allWorkspaces,
963
- selectors
964
- };
965
- }
966
- if (manager === "yarn") {
967
- const workspaceIndex = findPackageManagerCommandIndex(tokens);
968
- if (workspaceIndex >= 0 && tokens[workspaceIndex] === "workspace") {
969
- const selector = cleanShellToken(tokens[workspaceIndex + 1]);
970
- return {
971
- requested: true,
972
- selectors: selector ? [selector] : []
973
- };
974
- }
975
- if (workspaceIndex >= 0 && tokens[workspaceIndex] === "workspaces") return {
976
- requested: true,
977
- selectors: []
978
- };
979
- }
980
- return {
981
- requested: false,
982
- selectors: []
983
- };
984
- }
985
- function readOptionValues(tokens, longOptions, shortOptions) {
986
- const values = [];
987
- for (let index = 1; index < tokens.length; index += 1) {
988
- const token = tokens[index] ?? "";
989
- if (token === "--" || !token.startsWith("-")) break;
990
- if ([...longOptions, ...shortOptions].includes(token)) {
991
- const value = cleanShellToken(tokens[index + 1]);
992
- if (value) values.push(value);
993
- index += 1;
994
- continue;
995
- }
996
- const longOption = longOptions.find((option) => token.startsWith(`${option}=`));
997
- if (longOption) {
998
- const value = cleanShellToken(token.slice(longOption.length + 1));
999
- if (value) values.push(value);
1000
- continue;
1001
- }
1002
- if (PACKAGE_MANAGER_OPTIONS_WITH_VALUES.has(token)) {
1003
- index += 1;
1004
- continue;
1005
- }
1006
- const shortOption = shortOptions.find((option) => token.startsWith(option) && token.length > option.length);
1007
- if (shortOption) {
1008
- const value = cleanShellToken(token.slice(shortOption.length).replace(/^=/, ""));
1009
- if (value) values.push(value);
1010
- }
1011
- }
1012
- return values;
1013
- }
1014
- function readPackageManagerArguments(tokens) {
1015
- const commandArguments = [];
1016
- for (let index = 1; index < tokens.length; index += 1) {
1017
- const token = tokens[index] ?? "";
1018
- if (token === "--") break;
1019
- if (PACKAGE_MANAGER_OPTIONS_WITH_VALUES.has(token)) {
1020
- index += 1;
1021
- continue;
1022
- }
1023
- if (token.startsWith("-")) continue;
1024
- commandArguments.push(token);
1025
- }
1026
- return commandArguments;
1027
- }
1028
- function findPackageManagerCommandIndex(tokens) {
1029
- for (let index = 1; index < tokens.length; index += 1) {
1030
- const token = tokens[index] ?? "";
1031
- if (token === "--") return -1;
1032
- if (PACKAGE_MANAGER_OPTIONS_WITH_VALUES.has(token)) {
1033
- index += 1;
1034
- continue;
1035
- }
1036
- if (!token.startsWith("-")) return index;
1037
- }
1038
- return -1;
1039
- }
1040
- function hasOptionBeforeCommand(tokens, options) {
1041
- for (let index = 1; index < tokens.length; index += 1) {
1042
- const token = tokens[index] ?? "";
1043
- if (token === "--" || !token.startsWith("-")) return false;
1044
- if (options.includes(token)) return true;
1045
- if (PACKAGE_MANAGER_OPTIONS_WITH_VALUES.has(token)) index += 1;
1046
- }
1047
- return false;
1048
- }
1049
- function isStaticallyKnownPackageManagerCommand(tokens) {
1050
- const manager = readCommandPackageManager(tokens);
1051
- if (!manager || [
1052
- "npx",
1053
- "pnpx",
1054
- "bunx"
1055
- ].includes(tokens[0] ?? "")) return false;
1056
- const command = readPackageManagerArguments(tokens)[0];
1057
- if (!command || [
1058
- "dlx",
1059
- "exec",
1060
- "x"
1061
- ].includes(command)) return false;
1062
- return Boolean(PACKAGE_MANAGER_BUILT_INS[manager]?.has(command));
1063
- }
1064
- function isVersionProbe(tokens) {
1065
- return tokens.length === 2 && [
1066
- "node",
1067
- "deno",
1068
- "bun",
1069
- "npm",
1070
- "pnpm",
1071
- "yarn"
1072
- ].includes(tokens[0] ?? "") && ["--version", "-v"].includes(tokens[1] ?? "");
1073
- }
1074
- function resolveWorkspaceSelection(selection, manifests, rootDir) {
1075
- if (!selection.requested) return {
1076
- status: "none",
1077
- manifests: []
1078
- };
1079
- if (selection.selectors.length === 0) return {
1080
- status: "unresolved",
1081
- manifests: []
1082
- };
1083
- const resolved = [];
1084
- for (const rawSelector of selection.selectors) {
1085
- const selector = rawSelector.trim();
1086
- if (!selector || selector.startsWith("!") || /(?:\.\.\.|[?*[\]{}<>])/.test(selector)) return {
1087
- status: "unresolved",
1088
- manifests: []
1089
- };
1090
- const namedManifest = manifests.get(selector);
1091
- if (namedManifest) {
1092
- resolved.push(namedManifest);
1093
- continue;
1094
- }
1095
- const selectorPath = path.resolve(rootDir, selector);
1096
- const pathManifest = [...manifests.values()].find((manifest) => path.resolve(manifest.directory) === selectorPath);
1097
- if (!pathManifest) return {
1098
- status: "unresolved",
1099
- manifests: []
1100
- };
1101
- resolved.push(pathManifest);
1102
- }
1103
- return {
1104
- status: "resolved",
1105
- manifests: Array.from(new Set(resolved))
1106
- };
1107
- }
1108
- function cleanShellToken(value) {
1109
- return value?.replace(/[;&|]+$/, "").trim() || void 0;
1110
- }
1111
- function readDocsCommand(tokens) {
1112
- const first = tokens[0] ?? "";
1113
- const isDocsBinary = (token) => token === "docs" || !token.startsWith("@") && /(?:^|\/)\.?(?:bin\/)?docs$/.test(token);
1114
- let commandStart = -1;
1115
- if (isDocsBinary(first)) commandStart = 1;
1116
- else if ([
1117
- "npx",
1118
- "pnpx",
1119
- "bunx"
1120
- ].includes(first) && tokens[1] === "@farming-labs/docs") commandStart = 2;
1121
- else if ([
1122
- "npm",
1123
- "pnpm",
1124
- "yarn",
1125
- "bun"
1126
- ].includes(first)) {
1127
- const launcherIndex = tokens.findIndex((token, index) => index > 0 && [
1128
- "dlx",
1129
- "exec",
1130
- "x"
1131
- ].includes(token));
1132
- if (launcherIndex >= 0) {
1133
- const binaryIndex = tokens.findIndex((token, index) => index > launcherIndex && (token === "@farming-labs/docs" || isDocsBinary(token)));
1134
- if (binaryIndex >= 0) commandStart = binaryIndex + 1;
1135
- }
1136
- }
1137
- if (commandStart < 0) return void 0;
1138
- return tokens.slice(commandStart).filter((token) => !token.startsWith("-")).slice(0, 2).map((token) => cleanShellToken(token)).filter((token) => Boolean(token)).join(" ");
1139
- }
1140
- function matchesKnownDocsCommand(command, known) {
1141
- if (!command) return true;
1142
- if (known.has(command)) return true;
1143
- const first = command.split(" ")[0];
1144
- return known.has(first);
1145
- }
1146
- function readNearestPackageJson(startDir, rootDir) {
1147
- let current = startDir;
1148
- while (isPathInside(rootDir, current)) {
1149
- const packagePath = path.join(current, "package.json");
1150
- const contents = readJsonFile(packagePath);
1151
- if (contents) {
1152
- const scripts = contents.scripts && typeof contents.scripts === "object" && !Array.isArray(contents.scripts) ? new Set(Object.keys(contents.scripts)) : /* @__PURE__ */ new Set();
1153
- return {
1154
- directory: current,
1155
- scripts,
1156
- relativePath: path.relative(rootDir, packagePath).replace(/\\/g, "/") || "package.json"
1157
- };
1158
- }
1159
- if (current === rootDir) break;
1160
- current = path.dirname(current);
1161
- }
1162
- }
1163
- function readProjectPackageManifests(rootDir) {
1164
- const manifests = /* @__PURE__ */ new Map();
1165
- const ignored = new Set([
1166
- ".git",
1167
- ".next",
1168
- ".nuxt",
1169
- ".output",
1170
- ".svelte-kit",
1171
- "build",
1172
- "coverage",
1173
- "dist",
1174
- "node_modules",
1175
- "out"
1176
- ]);
1177
- const visit = (directory) => {
1178
- const packagePath = path.join(directory, "package.json");
1179
- const contents = readJsonFile(packagePath);
1180
- if (contents) {
1181
- const name = typeof contents.name === "string" ? contents.name.trim() : "";
1182
- const scripts = contents.scripts && typeof contents.scripts === "object" && !Array.isArray(contents.scripts) ? new Set(Object.keys(contents.scripts)) : /* @__PURE__ */ new Set();
1183
- if (name) manifests.set(name, {
1184
- directory,
1185
- scripts,
1186
- relativePath: path.relative(rootDir, packagePath).replace(/\\/g, "/") || "package.json"
1187
- });
1188
- }
1189
- let names;
1190
- try {
1191
- names = readdirSync(directory);
1192
- } catch {
1193
- return;
1194
- }
1195
- for (const name of names) {
1196
- if (ignored.has(name)) continue;
1197
- const child = path.join(directory, name);
1198
- try {
1199
- if (lstatSync(child).isDirectory()) visit(child);
1200
- } catch {}
1201
- }
1202
- };
1203
- visit(rootDir);
1204
- return manifests;
1205
- }
1206
- function readJsonFile(filePath) {
1207
- if (!existsSync(filePath)) return void 0;
1208
- try {
1209
- const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
1210
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
1211
- } catch {
1212
- return;
1213
- }
1214
- }
1215
- function isPathInside(rootDir, candidate) {
1216
- const relative = path.relative(rootDir, candidate);
1217
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
1218
- }
1219
- function commandFinding(options, finding) {
1220
- return {
1221
- ...makeFinding(options.page, {
1222
- ...finding,
1223
- category: "command",
1224
- line: options.command.line,
1225
- command: options.command.run
1226
- }),
1227
- file: options.command.sourcePath ?? options.page.sourcePath
1228
- };
1229
- }
1230
- function makeFinding(page, finding) {
1231
- return {
1232
- ...finding,
1233
- file: page.sourcePath,
1234
- route: page.route
1235
- };
1236
- }
1237
- function blockKey(block, index) {
1238
- return `${block.sourcePath}:${block.line}:${index}`;
1239
- }
1240
- function compareFindings(left, right) {
1241
- return left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message);
1242
- }
1243
- function percentage(numerator, denominator) {
1244
- if (denominator === 0) return 0;
1245
- return Math.round(numerator / denominator * 100);
1246
- }
1247
- function clampRatio(value) {
1248
- if (!Number.isFinite(value)) return 0;
1249
- return Math.min(1, Math.max(0, value));
1250
- }
1251
-
1252
- //#endregion
1253
- //#region src/agent-surface-drift.ts
1254
- function isRecord(value) {
1255
- return typeof value === "object" && value !== null && !Array.isArray(value);
1256
- }
1257
- function normalizeDotPath(value) {
1258
- const segments = value.trim().replace(/\[\d+\]/gu, "[]").replace(/^\/+|\/+$/g, "").replaceAll("/", ".").split(".").map((segment) => segment.trim()).filter(Boolean);
1259
- const normalized = [];
1260
- for (const segment of segments) {
1261
- if (/^\d+$/u.test(segment) && normalized.length > 0) {
1262
- normalized[normalized.length - 1] = `${normalized.at(-1)}[]`;
1263
- continue;
1264
- }
1265
- normalized.push(segment);
1266
- }
1267
- return normalized.join(".");
1268
- }
1269
- function collectSchemaPaths(options) {
1270
- const paths = /* @__PURE__ */ new Set();
1271
- const visited = /* @__PURE__ */ new Set();
1272
- const visit = (option) => {
1273
- if (visited.has(option)) return;
1274
- visited.add(option);
1275
- const optionPath = normalizeDotPath(option.path);
1276
- if (optionPath) paths.add(optionPath);
1277
- for (const child of option.children ?? []) visit(child);
1278
- };
1279
- for (const option of options) visit(option);
1280
- return paths;
1281
- }
1282
- function schemaCoversConfigPath(schemaPaths, configPath) {
1283
- if (schemaPaths.has(configPath)) return true;
1284
- if (configPath.endsWith("[]") && schemaPaths.has(configPath.slice(0, -2))) return true;
1285
- for (const schemaPath of schemaPaths) {
1286
- if (!schemaPath.endsWith(".*")) continue;
1287
- const prefix = schemaPath.slice(0, -1);
1288
- if (configPath.startsWith(prefix) && configPath.length > prefix.length) return true;
1289
- }
1290
- return false;
1291
- }
1292
- function readPath(root, dotPath) {
1293
- const normalized = normalizeDotPath(dotPath);
1294
- if (!normalized) return {
1295
- present: true,
1296
- value: root
1297
- };
1298
- let current = root;
1299
- for (const segment of normalized.split(".")) {
1300
- if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, segment)) return {
1301
- present: false,
1302
- value: void 0
1303
- };
1304
- current = current[segment];
1305
- }
1306
- return {
1307
- present: true,
1308
- value: current
1309
- };
1310
- }
1311
- function stableJsonValue(value) {
1312
- if (Array.isArray(value)) return value.map(stableJsonValue);
1313
- if (!isRecord(value)) return value;
1314
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableJsonValue(value[key])]));
1315
- }
1316
- function displayValue(value, present = true) {
1317
- if (!present || value === void 0) return "<missing>";
1318
- if (typeof value === "string") return JSON.stringify(value);
1319
- if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value);
1320
- try {
1321
- return JSON.stringify(stableJsonValue(value));
1322
- } catch {
1323
- return "<unserializable>";
1324
- }
1325
- }
1326
- function valuesMatch(actual, expected) {
1327
- return actual.present && Object.is(actual.value, expected);
1328
- }
1329
- function mismatchIssue(code, path, expected, actual, label) {
1330
- const expectedDisplay = displayValue(expected);
1331
- const actualDisplay = displayValue(actual.value, actual.present);
1332
- return {
1333
- code,
1334
- path,
1335
- expected: expectedDisplay,
1336
- actual: actualDisplay,
1337
- message: `${label} is ${actualDisplay}; expected ${expectedDisplay}.`
1338
- };
1339
- }
1340
- function compareExpectedValue(issues, discovery, code, path, expected, label) {
1341
- const actual = readPath(discovery, path);
1342
- if (!valuesMatch(actual, expected)) issues.push(mismatchIssue(code, path, expected, actual, label));
1343
- }
1344
- /**
1345
- * Compare independently resolved docs values with the public agent discovery and
1346
- * config-schema surfaces. The helper is intentionally pure so doctor, review,
1347
- * and golden evaluations can use the same deterministic drift rules.
1348
- */
1349
- function analyzeAgentSurfaceDrift(options) {
1350
- const issues = [];
1351
- const schemaPaths = collectSchemaPaths(options.schemaOptions);
1352
- const configPaths = Array.from(new Set(options.configOptionPaths.map(normalizeDotPath).filter(Boolean))).sort();
1353
- for (const configPath of configPaths) {
1354
- if (schemaCoversConfigPath(schemaPaths, configPath)) continue;
1355
- issues.push({
1356
- code: "config-schema-omission",
1357
- path: configPath,
1358
- expected: "documented schema option",
1359
- actual: "<missing>",
1360
- message: `Config option ${JSON.stringify(configPath)} is present but missing from the published config schema.`
1361
- });
1362
- }
1363
- const canonicalFields = Array.from(new Set(options.agentContractFields.map((field) => field.trim()).filter(Boolean))).sort();
1364
- const discoveredFieldsValue = readPath(options.discovery, "agentContract.fields");
1365
- const discoveredFields = isRecord(discoveredFieldsValue.value) ? Object.keys(discoveredFieldsValue.value).sort() : [];
1366
- const canonicalFieldSet = new Set(canonicalFields);
1367
- const discoveredFieldSet = new Set(discoveredFields);
1368
- for (const field of canonicalFields) {
1369
- if (discoveredFieldSet.has(field)) continue;
1370
- const issuePath = `agentContract.fields.${field}`;
1371
- issues.push({
1372
- code: "agent-contract-field-missing",
1373
- path: issuePath,
1374
- expected: "canonical field",
1375
- actual: "<missing>",
1376
- message: `Discovery agent contract is missing canonical field ${JSON.stringify(field)}.`
1377
- });
1378
- }
1379
- for (const field of discoveredFields) {
1380
- if (canonicalFieldSet.has(field)) continue;
1381
- const issuePath = `agentContract.fields.${field}`;
1382
- issues.push({
1383
- code: "agent-contract-field-unexpected",
1384
- path: issuePath,
1385
- expected: "<absent>",
1386
- actual: "declared field",
1387
- message: `Discovery agent contract advertises non-canonical field ${JSON.stringify(field)}.`
1388
- });
1389
- }
1390
- compareExpectedValue(issues, options.discovery, "entry-mismatch", "site.entry", options.expected.entry, "Discovery site.entry");
1391
- compareExpectedValue(issues, options.discovery, "search-enabled-mismatch", "search.enabled", options.expected.search.enabled, "Discovery search.enabled");
1392
- compareExpectedValue(issues, options.discovery, "search-capability-mismatch", "capabilities.search", options.expected.search.enabled, "Discovery capabilities.search");
1393
- compareExpectedValue(issues, options.discovery, "search-route-mismatch", "search.endpoint", options.expected.search.endpoint, "Discovery search.endpoint");
1394
- compareExpectedValue(issues, options.discovery, "mcp-enabled-mismatch", "mcp.enabled", options.expected.mcp.enabled, "Discovery mcp.enabled");
1395
- compareExpectedValue(issues, options.discovery, "mcp-capability-mismatch", "capabilities.mcp", options.expected.mcp.enabled, "Discovery capabilities.mcp");
1396
- compareExpectedValue(issues, options.discovery, "mcp-route-mismatch", "mcp.endpoint", options.expected.mcp.endpoint, "Discovery mcp.endpoint");
1397
- const expectedToolNames = Object.keys(options.expected.mcp.tools).sort();
1398
- const expectedToolSet = new Set(expectedToolNames);
1399
- const discoveredToolsValue = readPath(options.discovery, "mcp.tools");
1400
- const discoveredTools = isRecord(discoveredToolsValue.value) ? discoveredToolsValue.value : {};
1401
- for (const toolName of expectedToolNames) {
1402
- const expected = options.expected.mcp.tools[toolName];
1403
- const path = `mcp.tools.${toolName}`;
1404
- const actual = Object.prototype.hasOwnProperty.call(discoveredTools, toolName) ? {
1405
- present: true,
1406
- value: discoveredTools[toolName]
1407
- } : {
1408
- present: false,
1409
- value: void 0
1410
- };
1411
- if (!valuesMatch(actual, expected)) issues.push(mismatchIssue("mcp-tool-mismatch", path, expected, actual, `Discovery ${path}`));
1412
- }
1413
- for (const toolName of Object.keys(discoveredTools).sort()) {
1414
- if (expectedToolSet.has(toolName)) continue;
1415
- const path = `mcp.tools.${toolName}`;
1416
- issues.push({
1417
- code: "mcp-tool-unexpected",
1418
- path,
1419
- expected: "<absent>",
1420
- actual: displayValue(discoveredTools[toolName]),
1421
- message: `Discovery MCP tools advertise unexpected tool flag ${JSON.stringify(toolName)}.`
1422
- });
1423
- }
1424
- for (const routePath of Object.keys(options.expected.routes).sort()) {
1425
- const expected = options.expected.routes[routePath];
1426
- compareExpectedValue(issues, options.discovery, "route-mismatch", routePath, expected, `Discovery ${routePath}`);
1427
- }
1428
- return issues.sort((left, right) => left.path.localeCompare(right.path) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message));
1429
- }
1430
-
1431
- //#endregion
1432
- //#region src/cli/golden-evaluations.ts
1433
- function isPlainRecord(value) {
1434
- return typeof value === "object" && value !== null && !Array.isArray(value);
1435
- }
1436
- /** Normalize the runtime docs.config evaluation shape shared by doctor and review. */
1437
- function resolveGoldenEvaluationInput(evaluationInput) {
1438
- if (evaluationInput === void 0 || typeof evaluationInput === "boolean") return {
1439
- tasks: void 0,
1440
- options: {}
1441
- };
1442
- if (!isPlainRecord(evaluationInput)) return {
1443
- tasks: { evaluationConfig: evaluationInput },
1444
- options: {}
1445
- };
1446
- const options = {
1447
- surface: evaluationInput.surface,
1448
- allowNetwork: evaluationInput.allowNetwork,
1449
- searchTimeoutMs: evaluationInput.searchTimeoutMs,
1450
- answer: evaluationInput.answer
1451
- };
1452
- if (evaluationInput.enabled === false) return {
1453
- tasks: void 0,
1454
- options
1455
- };
1456
- if ("enabled" in evaluationInput && typeof evaluationInput.enabled !== "boolean") return {
1457
- tasks: evaluationInput,
1458
- options
1459
- };
1460
- if (!("tasks" in evaluationInput)) return {
1461
- tasks: void 0,
1462
- options
1463
- };
1464
- const runtimeTasks = evaluationInput.tasks;
1465
- if (!Array.isArray(runtimeTasks)) return {
1466
- tasks: runtimeTasks,
1467
- options
1468
- };
1469
- return {
1470
- tasks: runtimeTasks.map((task) => {
1471
- if (!isPlainRecord(task)) return task;
1472
- return {
1473
- ...task,
1474
- tokenBudget: task.tokenBudget ?? evaluationInput.tokenBudget,
1475
- topK: task.topK ?? evaluationInput.topK
1476
- };
1477
- }),
1478
- options
1479
- };
1480
- }
1481
-
1482
- //#endregion
1483
- export { extractAgentBlocks as a, createAgentUsefulnessPagesFromMcp as i, analyzeAgentSurfaceDrift as n, analyzeAgentUsefulness as r, resolveGoldenEvaluationInput as t };