@farming-labs/docs 0.2.114 → 0.2.116

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 (71) hide show
  1. package/dist/{agent-5jyEosML.mjs → agent-CEZCaYto.mjs} +52 -7
  2. package/dist/{agent-Dlswu0Mm.mjs → agent-DGNTD-N6.mjs} +3 -3
  3. package/dist/{agent-evals-CnQC3W2a.mjs → agent-evals-CmeTjDMC.mjs} +3 -3
  4. package/dist/{agent-export-yilcRMg6.mjs → agent-export-DIKwVWYN.mjs} +11 -10
  5. package/dist/agent-skills-bundle.d.mts +8 -2
  6. package/dist/agent-skills-bundle.mjs +7 -1
  7. package/dist/{agent-skills-progressive-disclosure-DL-q4JZo.mjs → agent-skills-progressive-disclosure-BDSjGGFd.mjs} +1 -1
  8. package/dist/{agent-skills-server-CF6iVTBR.d.mts → agent-skills-server-BWRJjIQG.d.mts} +2 -2
  9. package/dist/{agent-skills-server-Ddbf15pY.mjs → agent-skills-server-Ddm90WL6.mjs} +1 -1
  10. package/dist/agent-skills-vite.d.mts +4 -4
  11. package/dist/agent-skills-vite.mjs +7 -3
  12. package/dist/{agents-D5waM-jZ.mjs → agents-l8RtkFim.mjs} +10 -9
  13. package/dist/{analytics-CwXxM9oL.d.mts → analytics-R9c-Md4b.d.mts} +1 -1
  14. package/dist/api-reference-0XdEWya8.mjs +3033 -0
  15. package/dist/{authoring-CsR0sewh.mjs → authoring-CvsCmYuq.mjs} +4 -4
  16. package/dist/{authoring-mcp-BpwboE3k.mjs → authoring-mcp-C35lj3cI.mjs} +2 -2
  17. package/dist/browser.d.mts +3 -3
  18. package/dist/cli/index.mjs +75 -34
  19. package/dist/client/react.d.mts +1 -1
  20. package/dist/{cloud-Bvof6mET.mjs → cloud-DtmNQJg2.mjs} +3 -3
  21. package/dist/{cloud-ask-ai-C9dvEBpW.d.mts → cloud-ask-ai-DLyrydIR.d.mts} +1 -1
  22. package/dist/{codeblocks-C911CdyJ.mjs → codeblocks-DEcqNXve.mjs} +2 -2
  23. package/dist/{content-change-hydration-CDuohygf.d.mts → content-change-hydration-BWJNUIot.d.mts} +33 -5
  24. package/dist/{dev-Bx0JNglW.mjs → dev-Df77AKR1.mjs} +9 -24
  25. package/dist/docs-cloud-server.d.mts +2 -2
  26. package/dist/docs-cloud-server.mjs +1 -1
  27. package/dist/{doctor-DbsNvdb8.mjs → doctor-zNmt9s6j.mjs} +266 -17
  28. package/dist/{downgrade-BXmQMkMy.mjs → downgrade-C6DSsOO9.mjs} +2 -2
  29. package/dist/{feedback-DWVUMrxy.mjs → feedback-CNsQP2jY.mjs} +1 -1
  30. package/dist/{golden-evaluations-D49vhw5u.mjs → golden-evaluations-B1QDOzYj.mjs} +6 -2
  31. package/dist/index.d.mts +8 -8
  32. package/dist/index.mjs +5 -5
  33. package/dist/{init-CCV2eRNB.mjs → init-BXKzipSS.mjs} +3 -3
  34. package/dist/{mcp-D-1u1RBW.mjs → mcp-Bj3ZoEtt.mjs} +33 -19
  35. package/dist/mcp.d.mts +12 -4
  36. package/dist/mcp.mjs +50 -8
  37. package/dist/openapi-anOaQn9o.mjs +20 -0
  38. package/dist/openapi-diff-DKW3xkQU.mjs +461 -0
  39. package/dist/{openapi-mcp-D8NVpTW1.mjs → openapi-mcp-UF4Cx885.mjs} +43 -91
  40. package/dist/openapi-operations-DFpaqOqA.mjs +324 -0
  41. package/dist/openapi-sdk-DqRHPsCb.mjs +415 -0
  42. package/dist/openapi-source-CiKbUK71.mjs +37 -0
  43. package/dist/{package-version-Ci99L0yo.mjs → package-version-CTkii52k.mjs} +1 -1
  44. package/dist/{prompt-references-CV1zw72C.mjs → prompt-references-Cficetgk.mjs} +2 -2
  45. package/dist/{retrieval-digest-CBhsp9LK.d.mts → retrieval-digest-PD4fFy7P.d.mts} +2 -2
  46. package/dist/{review-BdM27yh7.mjs → review-CcplK9Hj.mjs} +9 -9
  47. package/dist/{robots-Bne9ODlC.mjs → robots-BPYWIGqD.mjs} +1 -1
  48. package/dist/{robots-D6VufwPn.mjs → robots-D4T3zZr_.mjs} +4 -4
  49. package/dist/{search-BCxmGlxh.mjs → search-BNmlDQJ2.mjs} +27 -11
  50. package/dist/server.d.mts +132 -9
  51. package/dist/server.mjs +12 -10
  52. package/dist/{sidebar-Bne72SU-.d.mts → sidebar-CGbvEPTX.d.mts} +1 -1
  53. package/dist/{sitemap-ChOEBnrk.mjs → sitemap-Bc4LkIcm.mjs} +10 -9
  54. package/dist/sitemap-server-C4bpuoLi.mjs +235 -0
  55. package/dist/{skills-qswruIKO.mjs → skills-B6wl1bUL.mjs} +10 -9
  56. package/dist/{standards-discovery-CjyUCxdy.d.mts → standards-discovery-C1RLKwV9.d.mts} +1 -1
  57. package/dist/{templates-DdB4BBGq.mjs → templates-BiORflC3.mjs} +1 -2
  58. package/dist/{types-BkAk6B2d.d.mts → types-Dkl2DUfp.d.mts} +39 -9
  59. package/dist/{upgrade-B92U83xo.mjs → upgrade-8zStRidw.mjs} +2 -2
  60. package/package.json +2 -1
  61. package/dist/sitemap-server-D3mFJkjE.mjs +0 -1166
  62. /package/dist/{agent-feedback-loop-vaC-M1sx.mjs → agent-feedback-loop-CcZL_sb1.mjs} +0 -0
  63. /package/dist/{audience-exposure-CkNZNeVX.d.mts → audience-exposure-BAjHjaTT.d.mts} +0 -0
  64. /package/dist/{cloud-ask-ai-CK2qlNns.mjs → cloud-ask-ai-BDtBbbZZ.mjs} +0 -0
  65. /package/dist/{code-blocks-D90CCJFs.mjs → code-blocks-GYdvS3BH.mjs} +0 -0
  66. /package/dist/{config-DrZ3fXgf.mjs → config-BnUOoj07.mjs} +0 -0
  67. /package/dist/{errors-BDmra6mz.mjs → errors-BkS29NUA.mjs} +0 -0
  68. /package/dist/{feedback-evals-k0sFDdms.mjs → feedback-evals-CYuS2wTu.mjs} +0 -0
  69. /package/dist/{openapi-mcp-node-vO3IXVGI.mjs → openapi-mcp-node-C4jUpZG6.mjs} +0 -0
  70. /package/dist/{propose-DJOiHu9Y.mjs → propose-ZH_mGMY_.mjs} +0 -0
  71. /package/dist/{utils-Cc1SrVza.mjs → utils-c9arebCi.mjs} +0 -0
@@ -0,0 +1,461 @@
1
+ import "./markdown-sections-BKy4labo.mjs";
2
+ import "./agent-CEZCaYto.mjs";
3
+ import "./standards-discovery-Bpz90VYq.mjs";
4
+ import { n as buildNormalizedOpenApiModel } from "./openapi-operations-DFpaqOqA.mjs";
5
+ import "./api-reference-0XdEWya8.mjs";
6
+ import { n as loadOpenApiCliSource, t as displayOpenApiCliSource } from "./openapi-source-CiKbUK71.mjs";
7
+ import path from "node:path";
8
+ import pc from "picocolors";
9
+
10
+ //#region src/cli/openapi-diff.ts
11
+ const SCHEMA_ANNOTATION_KEYS = new Set([
12
+ "$comment",
13
+ "deprecated",
14
+ "description",
15
+ "example",
16
+ "examples",
17
+ "externalDocs",
18
+ "title",
19
+ "xml"
20
+ ]);
21
+ function parseOpenApiDiffArgs(argv) {
22
+ const positionals = [];
23
+ let baseline;
24
+ let current;
25
+ let baseUrl;
26
+ let failOn = "breaking";
27
+ let json = false;
28
+ let help = false;
29
+ for (let index = 0; index < argv.length; index += 1) {
30
+ const argument = argv[index] ?? "";
31
+ if (argument === "--help" || argument === "-h") {
32
+ help = true;
33
+ continue;
34
+ }
35
+ if (argument === "--json") {
36
+ json = true;
37
+ continue;
38
+ }
39
+ const flagSeparator = argument.startsWith("--") ? argument.indexOf("=") : -1;
40
+ const rawFlag = argument.startsWith("--") ? argument.slice(2, flagSeparator === -1 ? void 0 : flagSeparator) : void 0;
41
+ const inlineValue = flagSeparator === -1 ? void 0 : argument.slice(flagSeparator + 1);
42
+ if (rawFlag) {
43
+ const value = inlineValue ?? argv[index + 1];
44
+ if (value === void 0 || value.startsWith("--")) throw new Error(`Missing value for --${rawFlag}.`);
45
+ if (inlineValue === void 0) index += 1;
46
+ if (rawFlag === "base" || rawFlag === "baseline") baseline = value;
47
+ else if (rawFlag === "head" || rawFlag === "current") current = value;
48
+ else if (rawFlag === "base-url") baseUrl = value;
49
+ else if (rawFlag === "fail-on") {
50
+ if (value !== "breaking" && value !== "any" && value !== "never") throw new Error("`--fail-on` must be \"breaking\", \"any\", or \"never\".");
51
+ failOn = value;
52
+ } else throw new Error(`Unknown OpenAPI diff option: --${rawFlag}.`);
53
+ continue;
54
+ }
55
+ positionals.push(argument);
56
+ }
57
+ for (const source of positionals) if (!baseline) baseline = source;
58
+ else if (!current) current = source;
59
+ else throw new Error("Pass exactly two OpenAPI sources: baseline and current.");
60
+ if (!help && (!baseline || !current)) throw new Error("Pass baseline and current OpenAPI sources as positional arguments or with --base and --head.");
61
+ return {
62
+ baseline,
63
+ current,
64
+ baseUrl,
65
+ failOn,
66
+ json,
67
+ help
68
+ };
69
+ }
70
+ async function runOpenApiDiff(options) {
71
+ if (!options.baseline || !options.current) throw new Error("Both baseline and current OpenAPI sources are required.");
72
+ const rootDir = path.resolve(options.rootDir ?? process.cwd());
73
+ const [baseline, current] = await Promise.all([loadOpenApiCliSource(options.baseline, {
74
+ rootDir,
75
+ baseUrl: options.baseUrl
76
+ }), loadOpenApiCliSource(options.current, {
77
+ rootDir,
78
+ baseUrl: options.baseUrl
79
+ })]);
80
+ const report = compareOpenApiDocuments(baseline, current, {
81
+ baselineSource: displayOpenApiCliSource(options.baseline),
82
+ currentSource: displayOpenApiCliSource(options.current)
83
+ });
84
+ if (options.json) console.log(JSON.stringify(report, null, 2));
85
+ else printOpenApiDiffReport(report);
86
+ if (options.failOn === "breaking" && report.breaking || options.failOn === "any" && report.changes.length > 0) process.exitCode = 1;
87
+ return report;
88
+ }
89
+ function compareOpenApiDocuments(baselineDocument, currentDocument, sources = {}) {
90
+ const baselineModel = buildNormalizedOpenApiModel(baselineDocument);
91
+ const currentModel = buildNormalizedOpenApiModel(currentDocument);
92
+ const changes = [];
93
+ const baselineOperations = new Map(baselineModel.operations.map((operation) => [operation.selector, operation]));
94
+ const currentOperations = new Map(currentModel.operations.map((operation) => [operation.selector, operation]));
95
+ for (const selector of Array.from(baselineOperations.keys()).sort()) {
96
+ const baseline = baselineOperations.get(selector);
97
+ const current = currentOperations.get(selector);
98
+ if (!current) {
99
+ changes.push(change("breaking", "operation-removed", selector, "Operation was removed."));
100
+ continue;
101
+ }
102
+ compareOperation(baseline, current, baselineDocument, currentDocument, changes);
103
+ }
104
+ for (const selector of Array.from(currentOperations.keys()).sort()) {
105
+ if (baselineOperations.has(selector)) continue;
106
+ changes.push(change("non-breaking", "operation-added", selector, "Operation was added."));
107
+ }
108
+ if (baselineModel.specificationVersion !== currentModel.specificationVersion) changes.push({
109
+ severity: "non-breaking",
110
+ kind: "specification-version-changed",
111
+ detail: `OpenAPI version changed from ${baselineModel.specificationVersion} to ${currentModel.specificationVersion}.`,
112
+ location: "/openapi"
113
+ });
114
+ changes.sort(compareChanges);
115
+ const breakingCount = changes.filter((entry) => entry.severity === "breaking").length;
116
+ return {
117
+ format: "farming-labs-openapi-diff.v1",
118
+ baseline: summarizeSource(sources.baselineSource ?? "baseline", baselineModel),
119
+ current: summarizeSource(sources.currentSource ?? "current", currentModel),
120
+ breaking: breakingCount > 0,
121
+ breakingCount,
122
+ nonBreakingCount: changes.length - breakingCount,
123
+ changes
124
+ };
125
+ }
126
+ function compareOperation(baseline, current, baselineDocument, currentDocument, changes) {
127
+ const selector = baseline.selector;
128
+ if (baseline.operationId !== current.operationId) changes.push(change("breaking", "operation-id-changed", selector, `operationId changed from ${baseline.operationId} to ${current.operationId}.`, `${selector} operationId`));
129
+ compareParameters(baseline.parameters, current.parameters, baselineDocument, currentDocument, selector, changes);
130
+ compareRequestBody(baseline, current, baselineDocument, currentDocument, selector, changes);
131
+ compareResponses(baseline.responses, current.responses, baselineDocument, currentDocument, selector, changes);
132
+ if (normalizeSecurityForDiff(baseline.security) !== normalizeSecurityForDiff(current.security)) changes.push(change(baseline.security.length === 0 && current.security.length > 0 ? "breaking" : baseline.security.length > 0 && current.security.length === 0 ? "non-breaking" : "breaking", "security-changed", selector, "Security requirements changed.", `${selector} security`));
133
+ if (stableJson(baseline.securitySchemes) !== stableJson(current.securitySchemes)) changes.push(change("breaking", "security-scheme-changed", selector, "A security scheme used by the operation changed.", `${selector} securitySchemes`));
134
+ if (baseline.servers.length > 0 && !baseline.servers.some((server) => current.servers.includes(server))) changes.push(change("breaking", "server-changed", selector, "The operation no longer exposes any previously declared server URL.", `${selector} servers`));
135
+ if (!baseline.deprecated && current.deprecated) changes.push(change("non-breaking", "operation-deprecated", selector, "Operation is now deprecated."));
136
+ }
137
+ function compareParameters(baseline, current, baselineDocument, currentDocument, selector, changes) {
138
+ const baselineMap = new Map(baseline.map((parameter) => [parameterKey(parameter), parameter]));
139
+ const currentMap = new Map(current.map((parameter) => [parameterKey(parameter), parameter]));
140
+ for (const key of Array.from(baselineMap.keys()).sort()) {
141
+ const before = baselineMap.get(key);
142
+ const after = currentMap.get(key);
143
+ const location = `${selector} parameter ${before.in}:${before.name}`;
144
+ if (!after) {
145
+ changes.push(change("breaking", "parameter-removed", selector, `${before.in} parameter ${before.name} was removed.`, location));
146
+ continue;
147
+ }
148
+ if (!before.required && after.required) changes.push(change("breaking", "parameter-required", selector, `${after.in} parameter ${after.name} is now required.`, location));
149
+ else if (before.required && !after.required) changes.push(change("non-breaking", "parameter-optional", selector, `${after.in} parameter ${after.name} is now optional.`, location));
150
+ compareSchema(before.schema, after.schema, baselineDocument, currentDocument, "request", selector, location, "parameter-schema-changed", changes);
151
+ }
152
+ for (const key of Array.from(currentMap.keys()).sort()) {
153
+ if (baselineMap.has(key)) continue;
154
+ const parameter = currentMap.get(key);
155
+ changes.push(change(parameter.required ? "breaking" : "non-breaking", parameter.required ? "required-parameter-added" : "optional-parameter-added", selector, `${parameter.required ? "Required" : "Optional"} ${parameter.in} parameter ${parameter.name} was added.`, `${selector} parameter ${parameter.in}:${parameter.name}`));
156
+ }
157
+ }
158
+ function compareRequestBody(baseline, current, baselineDocument, currentDocument, selector, changes) {
159
+ const before = baseline.requestBody;
160
+ const after = current.requestBody;
161
+ const location = `${selector} requestBody`;
162
+ if (before && !after) {
163
+ changes.push(change("breaking", "request-body-removed", selector, "Request body was removed.", location));
164
+ return;
165
+ }
166
+ if (!before && after) {
167
+ changes.push(change(after.required ? "breaking" : "non-breaking", after.required ? "required-request-body-added" : "optional-request-body-added", selector, `${after.required ? "Required" : "Optional"} request body was added.`, location));
168
+ return;
169
+ }
170
+ if (!before || !after) return;
171
+ if (!before.required && after.required) changes.push(change("breaking", "request-body-required", selector, "Request body is now required.", location));
172
+ else if (before.required && !after.required) changes.push(change("non-breaking", "request-body-optional", selector, "Request body is now optional.", location));
173
+ compareMediaTypes(before.content, after.content, baselineDocument, currentDocument, "request", selector, location, changes);
174
+ }
175
+ function compareResponses(baseline, current, baselineDocument, currentDocument, selector, changes) {
176
+ const baselineMap = new Map(baseline.map((response) => [response.status, response]));
177
+ const currentMap = new Map(current.map((response) => [response.status, response]));
178
+ for (const status of Array.from(baselineMap.keys()).sort()) {
179
+ const before = baselineMap.get(status);
180
+ const after = currentMap.get(status);
181
+ const location = `${selector} response ${status}`;
182
+ if (!after) {
183
+ changes.push(change("breaking", "response-removed", selector, `Response ${status} was removed.`, location));
184
+ continue;
185
+ }
186
+ compareMediaTypes(before.content, after.content, baselineDocument, currentDocument, "response", selector, location, changes);
187
+ }
188
+ for (const status of Array.from(currentMap.keys()).sort()) {
189
+ if (baselineMap.has(status)) continue;
190
+ changes.push(change("non-breaking", "response-added", selector, `Response ${status} was added.`, `${selector} response ${status}`));
191
+ }
192
+ }
193
+ function compareMediaTypes(baseline, current, baselineDocument, currentDocument, mode, selector, parentLocation, changes) {
194
+ const baselineMap = new Map(baseline.map((media) => [media.mediaType, media]));
195
+ const currentMap = new Map(current.map((media) => [media.mediaType, media]));
196
+ for (const mediaType of Array.from(baselineMap.keys()).sort()) {
197
+ const before = baselineMap.get(mediaType);
198
+ const after = currentMap.get(mediaType);
199
+ const location = `${parentLocation} ${mediaType}`;
200
+ if (!after) {
201
+ changes.push(change("breaking", "media-type-removed", selector, `${mediaType} was removed.`, location));
202
+ continue;
203
+ }
204
+ compareSchema(before.schema, after.schema, baselineDocument, currentDocument, mode, selector, location, `${mode}-schema-changed`, changes);
205
+ }
206
+ for (const mediaType of Array.from(currentMap.keys()).sort()) {
207
+ if (baselineMap.has(mediaType)) continue;
208
+ changes.push(change("non-breaking", "media-type-added", selector, `${mediaType} was added.`, `${parentLocation} ${mediaType}`));
209
+ }
210
+ }
211
+ function compareSchema(baseline, current, baselineDocument, currentDocument, mode, selector, location, kind, changes) {
212
+ const before = normalizeSchema(baselineDocument, baseline);
213
+ const after = normalizeSchema(currentDocument, current);
214
+ if (stableJson(before) === stableJson(after)) return;
215
+ const breakingReason = findBreakingSchemaReason(before, after, mode);
216
+ changes.push(change(breakingReason ? "breaking" : "non-breaking", kind, selector, breakingReason ?? (mode === "request" ? "Request schema was expanded compatibly." : "Response schema was narrowed compatibly."), location));
217
+ }
218
+ function findBreakingSchemaReason(baseline, current, mode, schemaPath = "schema") {
219
+ if (baseline === void 0 || current === void 0) {
220
+ if (baseline === current) return void 0;
221
+ const constrained = baseline === void 0 ? "current" : "baseline";
222
+ return (mode === "request" ? constrained === "current" : constrained === "baseline") ? `${schemaPath} changed incompatibly.` : void 0;
223
+ }
224
+ const before = asRecord(baseline);
225
+ const after = asRecord(current);
226
+ if (!before || !after) return stableJson(baseline) === stableJson(current) ? void 0 : `${schemaPath} changed incompatibly.`;
227
+ if (restrictionChangedIncompatibly(normalizeTypes(before.type), normalizeTypes(after.type), mode)) return `${schemaPath} type changed incompatibly.`;
228
+ if (restrictionChangedIncompatibly(Array.isArray(before.enum) ? before.enum.map(stableJson) : [], Array.isArray(after.enum) ? after.enum.map(stableJson) : [], mode)) return `${schemaPath} enum changed incompatibly.`;
229
+ const beforeConst = Object.hasOwn(before, "const") ? [stableJson(before.const)] : [];
230
+ const afterConst = Object.hasOwn(after, "const") ? [stableJson(after.const)] : [];
231
+ if (stableJson(beforeConst) !== stableJson(afterConst)) {
232
+ if (beforeConst.length > 0 && afterConst.length > 0 && beforeConst[0] !== afterConst[0]) return `${schemaPath} constant value changed.`;
233
+ if (restrictionChangedIncompatibly(beforeConst, afterConst, mode)) return `${schemaPath} constant constraint changed incompatibly.`;
234
+ }
235
+ const scalarRestrictionReason = compareScalarSchemaRestrictions(before, after, mode, schemaPath);
236
+ if (scalarRestrictionReason) return scalarRestrictionReason;
237
+ const limitReason = compareSchemaLimits(before, after, mode, schemaPath);
238
+ if (limitReason) return limitReason;
239
+ const beforeRequired = new Set(stringArray(before.required));
240
+ const afterRequired = new Set(stringArray(after.required));
241
+ if (mode === "request") {
242
+ const addedRequired = Array.from(afterRequired).find((name) => !beforeRequired.has(name));
243
+ if (addedRequired) return `${schemaPath}.${addedRequired} is newly required.`;
244
+ } else {
245
+ const relaxedRequired = Array.from(beforeRequired).find((name) => !afterRequired.has(name));
246
+ if (relaxedRequired) return `${schemaPath}.${relaxedRequired} is no longer guaranteed.`;
247
+ }
248
+ const beforeProperties = asRecord(before.properties) ?? {};
249
+ const afterProperties = asRecord(after.properties) ?? {};
250
+ if (mode === "response") {
251
+ const removed = Object.keys(beforeProperties).find((name) => !Object.hasOwn(afterProperties, name));
252
+ if (removed) return `${schemaPath}.${removed} was removed from the response.`;
253
+ } else if (after.additionalProperties === false) {
254
+ const rejected = Object.keys(beforeProperties).find((name) => !Object.hasOwn(afterProperties, name));
255
+ if (rejected) return `${schemaPath}.${rejected} is no longer accepted.`;
256
+ }
257
+ for (const name of Object.keys(beforeProperties).sort()) {
258
+ if (!Object.hasOwn(afterProperties, name)) continue;
259
+ const nested = findBreakingSchemaReason(beforeProperties[name], afterProperties[name], mode, `${schemaPath}.${name}`);
260
+ if (nested) return nested;
261
+ }
262
+ if (stableJson(before.items) !== stableJson(after.items)) {
263
+ const nested = findBreakingSchemaReason(before.items, after.items, mode, `${schemaPath}[]`);
264
+ if (nested) return nested;
265
+ }
266
+ const beforeAdditional = before.additionalProperties ?? true;
267
+ const afterAdditional = after.additionalProperties ?? true;
268
+ if (stableJson(beforeAdditional) !== stableJson(afterAdditional)) {
269
+ const beforeAdditionalSchema = asRecord(beforeAdditional);
270
+ const afterAdditionalSchema = asRecord(afterAdditional);
271
+ if (beforeAdditionalSchema && afterAdditionalSchema) {
272
+ const nested = findBreakingSchemaReason(beforeAdditionalSchema, afterAdditionalSchema, mode, `${schemaPath}.*`);
273
+ if (nested) return nested;
274
+ } else {
275
+ const becameRestricted = beforeAdditional !== false && afterAdditional === false;
276
+ const becameUnrestricted = beforeAdditional === false && afterAdditional !== false;
277
+ if (mode === "request" && becameRestricted || mode === "response" && becameUnrestricted || !becameRestricted && !becameUnrestricted) return `${schemaPath}.additionalProperties changed incompatibly.`;
278
+ }
279
+ }
280
+ for (const keyword of [
281
+ "allOf",
282
+ "anyOf",
283
+ "oneOf",
284
+ "not",
285
+ "discriminator"
286
+ ]) if (stableJson(before[keyword]) !== stableJson(after[keyword])) return `${schemaPath}.${keyword} changed; compatibility cannot be preserved safely.`;
287
+ }
288
+ function compareScalarSchemaRestrictions(baseline, current, mode, schemaPath) {
289
+ for (const keyword of [
290
+ "pattern",
291
+ "format",
292
+ "multipleOf"
293
+ ]) {
294
+ if (stableJson(baseline[keyword]) === stableJson(current[keyword])) continue;
295
+ const before = baseline[keyword] === void 0 ? [] : [stableJson(baseline[keyword])];
296
+ const after = current[keyword] === void 0 ? [] : [stableJson(current[keyword])];
297
+ if (before.length > 0 && after.length > 0) return `${schemaPath}.${keyword} changed; compatibility cannot be proven.`;
298
+ if (restrictionChangedIncompatibly(before, after, mode)) return `${schemaPath}.${keyword} changed incompatibly.`;
299
+ }
300
+ const beforeNullable = baseline.nullable === true;
301
+ const afterNullable = current.nullable === true;
302
+ if (mode === "request" && beforeNullable && !afterNullable || mode === "response" && !beforeNullable && afterNullable) return `${schemaPath}.nullable changed incompatibly.`;
303
+ if (mode === "request" && baseline.readOnly !== true && current.readOnly === true) return `${schemaPath}.readOnly now rejects request input.`;
304
+ if (mode === "response" && baseline.writeOnly !== true && current.writeOnly === true) return `${schemaPath}.writeOnly removes response output.`;
305
+ }
306
+ function compareSchemaLimits(baseline, current, mode, schemaPath) {
307
+ for (const [keyword, direction] of [
308
+ ["minimum", "min"],
309
+ ["exclusiveMinimum", "min"],
310
+ ["minLength", "min"],
311
+ ["minItems", "min"],
312
+ ["minProperties", "min"],
313
+ ["maximum", "max"],
314
+ ["exclusiveMaximum", "max"],
315
+ ["maxLength", "max"],
316
+ ["maxItems", "max"],
317
+ ["maxProperties", "max"]
318
+ ]) {
319
+ const before = numberValue(baseline[keyword]);
320
+ const after = numberValue(current[keyword]);
321
+ if (before === after) continue;
322
+ const narrowed = before === void 0 ? after !== void 0 : after === void 0 ? false : direction === "min" ? after > before : after < before;
323
+ if (mode === "request" ? narrowed : !narrowed) return `${schemaPath}.${keyword} changed incompatibly.`;
324
+ }
325
+ if (typeof baseline.pattern === "string" && typeof current.pattern === "string" && baseline.pattern !== current.pattern) return `${schemaPath}.pattern changed; compatibility cannot be proven.`;
326
+ }
327
+ function normalizeSchema(document, value) {
328
+ return normalizeSchemaValue(document, value, /* @__PURE__ */ new Set());
329
+ }
330
+ function normalizeSchemaValue(document, value, references) {
331
+ if (Array.isArray(value)) return value.map((entry) => normalizeSchemaValue(document, entry, references));
332
+ const record = asRecord(value);
333
+ if (!record) return value;
334
+ const reference = typeof record.$ref === "string" ? record.$ref : void 0;
335
+ if (reference?.startsWith("#/")) {
336
+ if (references.has(reference)) return { $ref: reference };
337
+ const target = resolvePointer(document, reference.slice(1));
338
+ if (target !== void 0) {
339
+ const nextReferences = new Set(references).add(reference);
340
+ const siblings = Object.fromEntries(Object.entries(record).filter(([key]) => key !== "$ref"));
341
+ const resolved = normalizeSchemaValue(document, target, nextReferences);
342
+ const resolvedRecord = asRecord(resolved);
343
+ return resolvedRecord ? normalizeSchemaValue(document, {
344
+ ...resolvedRecord,
345
+ ...siblings
346
+ }, nextReferences) : resolved;
347
+ }
348
+ }
349
+ return Object.fromEntries(Object.keys(record).filter((key) => !SCHEMA_ANNOTATION_KEYS.has(key)).sort().map((key) => [key, normalizeSchemaValue(document, record[key], references)]));
350
+ }
351
+ function summarizeSource(source, model) {
352
+ return {
353
+ source,
354
+ title: model.title,
355
+ version: model.version,
356
+ specificationVersion: model.specificationVersion,
357
+ operationCount: model.operationCount
358
+ };
359
+ }
360
+ function printOpenApiDiffReport(report) {
361
+ console.log(`${pc.bold("@farming-labs/docs")} ${pc.dim("—")} ${pc.bold("openapi diff")}`);
362
+ console.log();
363
+ console.log(`${pc.bold("Baseline:")} ${report.baseline.source} ${pc.dim(`(${report.baseline.version}, ${report.baseline.operationCount} operations)`)}`);
364
+ console.log(`${pc.bold("Current:")} ${report.current.source} ${pc.dim(`(${report.current.version}, ${report.current.operationCount} operations)`)}`);
365
+ console.log(`${pc.bold("Changes:")} ${report.breakingCount} breaking ${pc.dim("•")} ${report.nonBreakingCount} non-breaking`);
366
+ if (report.changes.length === 0) {
367
+ console.log();
368
+ console.log(pc.green("No contract changes found."));
369
+ return;
370
+ }
371
+ for (const severity of ["breaking", "non-breaking"]) {
372
+ const entries = report.changes.filter((entry) => entry.severity === severity);
373
+ if (entries.length === 0) continue;
374
+ console.log();
375
+ console.log(severity === "breaking" ? pc.red(pc.bold("BREAKING")) : pc.cyan(pc.bold("NON-BREAKING")));
376
+ for (const entry of entries) {
377
+ const target = entry.selector ? `${entry.selector}: ` : "";
378
+ console.log(`- ${pc.dim(`[${entry.kind}]`)} ${target}${entry.detail}`);
379
+ }
380
+ }
381
+ }
382
+ function printOpenApiDiffHelp() {
383
+ console.log(`${pc.bold("docs openapi diff")} — Compare two OpenAPI contracts.
384
+
385
+ Usage:
386
+ docs openapi diff <baseline> <current> [options]
387
+ docs api diff --base <baseline> --head <current> [options]
388
+
389
+ Sources:
390
+ Project-relative or absolute JSON/YAML files, file: URLs, and HTTP(S) URLs.
391
+
392
+ Options:
393
+ ${pc.cyan("--base, --baseline <source>")} Baseline contract
394
+ ${pc.cyan("--head, --current <source>")} Current contract
395
+ ${pc.cyan("--base-url <url>")} Resolve request-relative remote sources
396
+ ${pc.cyan("--fail-on <policy>")} breaking (default), any, or never
397
+ ${pc.cyan("--json")} Print ${pc.dim("farming-labs-openapi-diff.v1")} JSON
398
+ ${pc.cyan("--help")} Show this help
399
+ `);
400
+ }
401
+ function change(severity, kind, selector, detail, location) {
402
+ return {
403
+ severity,
404
+ kind,
405
+ selector,
406
+ detail,
407
+ ...location ? { location } : {}
408
+ };
409
+ }
410
+ function compareChanges(left, right) {
411
+ if (left.severity !== right.severity) return left.severity === "breaking" ? -1 : 1;
412
+ return `${left.selector ?? ""}\0${left.kind}\0${left.location ?? ""}`.localeCompare(`${right.selector ?? ""}\0${right.kind}\0${right.location ?? ""}`);
413
+ }
414
+ function parameterKey(parameter) {
415
+ return `${parameter.in}:${parameter.name}`;
416
+ }
417
+ function normalizeTypes(value) {
418
+ if (typeof value === "string") return [value];
419
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string").sort() : [];
420
+ }
421
+ function restrictionChangedIncompatibly(baseline, current, mode) {
422
+ if (baseline.length === 0 && current.length === 0) return false;
423
+ if (mode === "request") {
424
+ if (baseline.length === 0) return current.length > 0;
425
+ if (current.length === 0) return false;
426
+ return baseline.some((value) => !current.includes(value));
427
+ }
428
+ if (current.length === 0) return baseline.length > 0;
429
+ if (baseline.length === 0) return false;
430
+ return current.some((value) => !baseline.includes(value));
431
+ }
432
+ function normalizeSecurityForDiff(security) {
433
+ return security.map((requirement) => stableJson(Object.fromEntries(Object.keys(requirement).sort().map((name) => [name, [...requirement[name] ?? []].sort()])))).sort().join("|");
434
+ }
435
+ function stringArray(value) {
436
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
437
+ }
438
+ function numberValue(value) {
439
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
440
+ }
441
+ function stableJson(value) {
442
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
443
+ const record = asRecord(value);
444
+ if (record) return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
445
+ return JSON.stringify(value) ?? "undefined";
446
+ }
447
+ function asRecord(value) {
448
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
449
+ }
450
+ function resolvePointer(document, pointer) {
451
+ let current = document;
452
+ for (const token of pointer.slice(1).split("/")) {
453
+ const record = asRecord(current);
454
+ if (!record) return void 0;
455
+ current = record[token.replaceAll("~1", "/").replaceAll("~0", "~")];
456
+ }
457
+ return current;
458
+ }
459
+
460
+ //#endregion
461
+ export { parseOpenApiDiffArgs, printOpenApiDiffHelp, runOpenApiDiff };
@@ -1,6 +1,7 @@
1
1
  import { s as parseDocsMarkdownSections } from "./markdown-sections-BKy4labo.mjs";
2
- import { $ as isDocsSkillRequest, Fn as createDocsPaginationCursor, Jt as isDocsContentChangesRequest, K as isDocsAgentDiscoveryRequest, Ln as resolveDocsPaginationOffset, Pn as DocsPaginationCursorError, St as resolveDocsSkillFormat, Yt as resolveDocsContentChangesConfig, dt as resolveDocsAgentFeedbackRequest, en as buildDocsRetrievalDigestProjection, ft as resolveDocsAgentsFormat, mt as resolveDocsLlmsTxtRequest, q as isDocsAgentsRequest, ut as resolveDocsAgentFeedbackConfig, vt as resolveDocsMarkdownRequest } from "./agent-5jyEosML.mjs";
2
+ import { $ as isDocsSkillRequest, Fn as DocsPaginationCursorError, In as createDocsPaginationCursor, K as isDocsAgentDiscoveryRequest, Rn as resolveDocsPaginationOffset, St as resolveDocsSkillFormat, Xt as resolveDocsContentChangesConfig, Yt as isDocsContentChangesRequest, dt as resolveDocsAgentFeedbackRequest, ft as resolveDocsAgentsFormat, mt as resolveDocsLlmsTxtRequest, q as isDocsAgentsRequest, tn as buildDocsRetrievalDigestProjection, ut as resolveDocsAgentFeedbackConfig, vt as resolveDocsMarkdownRequest } from "./agent-CEZCaYto.mjs";
3
3
  import { n as digestDocsRetrievalContent } from "./retrieval-digest-B-SgtXKK.mjs";
4
+ import { n as buildNormalizedOpenApiModel } from "./openapi-operations-DFpaqOqA.mjs";
4
5
  import matter from "gray-matter";
5
6
 
6
7
  //#region src/telemetry.ts
@@ -606,15 +607,6 @@ function hydrateDocsContentChanges(options) {
606
607
 
607
608
  //#endregion
608
609
  //#region src/openapi-mcp.ts
609
- const HTTP_METHODS = [
610
- "get",
611
- "post",
612
- "put",
613
- "patch",
614
- "delete",
615
- "options",
616
- "head"
617
- ];
618
610
  const OPENAPI_RATE_WINDOW_MS = 6e4;
619
611
  const openApiRateWindows = /* @__PURE__ */ new Map();
620
612
  function acquireDocsOpenApiMcpBudget(key, config, now = Date.now()) {
@@ -682,104 +674,64 @@ async function readDocsOpenApiMcpResponse(response, maxBytes = 1e6) {
682
674
  function asRecord(value) {
683
675
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
684
676
  }
685
- function selectorFor(method, path) {
686
- return `${method.toUpperCase()} ${path}`;
687
- }
688
677
  function sanitizeToolName(value) {
689
678
  return `api_${value.trim().replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 56) || "operation"}`;
690
679
  }
691
680
  function operationExtensionEnabled(operation) {
692
- const extension = operation["x-farming-labs-mcp"];
681
+ const extension = operation.extensions["x-farming-labs-mcp"];
693
682
  return extension === true || asRecord(extension)?.enabled === true;
694
683
  }
695
- function normalizeSecurity(value) {
696
- if (!Array.isArray(value)) return [];
697
- return value.flatMap((requirement) => {
698
- const record = asRecord(requirement);
699
- if (!record) return [];
700
- const normalized = {};
701
- for (const [name, scopes] of Object.entries(record)) normalized[name] = Array.isArray(scopes) ? scopes.filter((scope) => typeof scope === "string") : [];
702
- return [normalized];
703
- });
704
- }
705
- function normalizeParameters(...values) {
706
- const parameters = values.flatMap((value) => Array.isArray(value) ? value : []);
707
- const seen = /* @__PURE__ */ new Set();
708
- return parameters.flatMap((parameter) => {
709
- const record = asRecord(parameter);
710
- if (!record) return [];
711
- const name = typeof record.name === "string" ? record.name.trim() : "";
712
- const location = record?.in;
713
- if (!name || location !== "path" && location !== "query" && location !== "header" && location !== "cookie") return [];
714
- const key = `${location}:${name}`;
715
- if (seen.has(key)) return [];
716
- seen.add(key);
717
- return [{
718
- name,
719
- in: location,
720
- required: record.required === true || location === "path"
721
- }];
722
- });
723
- }
724
684
  function resolveDocsOpenApiMcpOperations(document, config) {
725
685
  if (!config || config.enabled === false) return [];
726
686
  const allow = new Set((config.operations ?? []).map((value) => value.trim()).filter(Boolean));
727
- const paths = asRecord(document.paths) ?? {};
728
- const securitySchemes = asRecord(asRecord(document.components)?.securitySchemes) ?? {};
729
- const globalSecurity = normalizeSecurity(document.security);
687
+ const model = buildNormalizedOpenApiModel(document);
730
688
  const operations = [];
731
689
  const names = /* @__PURE__ */ new Set();
732
- for (const [path, rawPathItem] of Object.entries(paths)) {
733
- const pathItem = asRecord(rawPathItem);
734
- if (!pathItem) continue;
735
- for (const method of HTTP_METHODS) {
736
- const operation = asRecord(pathItem[method]);
737
- if (!operation) continue;
738
- const operationId = typeof operation.operationId === "string" && operation.operationId.trim() ? operation.operationId.trim() : `${method}_${path.replace(/[^a-zA-Z0-9]+/g, "_")}`;
739
- if (!(allow.has(operationId) || allow.has(selectorFor(method, path)) || operationExtensionEnabled(operation))) continue;
740
- if (![
741
- "get",
742
- "head",
743
- "options"
744
- ].includes(method) && config.allowMutations !== true) continue;
745
- const toolName = sanitizeToolName(operationId);
746
- if (names.has(toolName)) throw new Error(`OpenAPI MCP tool name collision: ${toolName}`);
747
- names.add(toolName);
748
- const security = operation.security === void 0 ? globalSecurity : normalizeSecurity(operation.security);
749
- const usedSchemeNames = new Set(security.flatMap((requirement) => Object.keys(requirement)));
750
- const usedSecuritySchemes = Object.fromEntries(Object.entries(securitySchemes).filter(([name]) => usedSchemeNames.has(name)));
751
- operations.push({
752
- toolName,
753
- operationId,
754
- method: method.toUpperCase(),
755
- path,
756
- title: typeof operation.summary === "string" && operation.summary.trim() || typeof operation.description === "string" && operation.description.trim() || selectorFor(method, path),
757
- ...typeof operation.description === "string" && operation.description.trim() ? { description: operation.description.trim() } : {},
758
- parameters: normalizeParameters(pathItem.parameters, operation.parameters),
759
- security,
760
- securitySchemes: usedSecuritySchemes,
761
- readOnly: method === "get" || method === "head",
762
- destructive: method === "delete",
763
- idempotent: [
764
- "GET",
765
- "HEAD",
766
- "PUT",
767
- "DELETE",
768
- "OPTIONS"
769
- ].includes(method.toUpperCase())
770
- });
771
- }
690
+ for (const operation of model.operations) {
691
+ if (operation.method === "TRACE") continue;
692
+ if (!(allow.has(operation.operationId) || allow.has(operation.selector) || operationExtensionEnabled(operation))) continue;
693
+ if (![
694
+ "GET",
695
+ "HEAD",
696
+ "OPTIONS"
697
+ ].includes(operation.method) && config.allowMutations !== true) continue;
698
+ const toolName = sanitizeToolName(operation.operationId);
699
+ if (names.has(toolName)) throw new Error(`OpenAPI MCP tool name collision: ${toolName}`);
700
+ names.add(toolName);
701
+ operations.push({
702
+ toolName,
703
+ operationId: operation.operationId,
704
+ method: operation.method,
705
+ path: operation.path,
706
+ title: operation.title,
707
+ ...operation.description ? { description: operation.description } : {},
708
+ parameters: operation.parameters.flatMap((parameter) => {
709
+ if (parameter.in !== "path" && parameter.in !== "query" && parameter.in !== "header" && parameter.in !== "cookie") return [];
710
+ return [{
711
+ name: parameter.name,
712
+ in: parameter.in,
713
+ required: parameter.required
714
+ }];
715
+ }),
716
+ security: operation.security,
717
+ securitySchemes: operation.securitySchemes,
718
+ readOnly: operation.method === "GET" || operation.method === "HEAD",
719
+ destructive: operation.method === "DELETE",
720
+ idempotent: [
721
+ "GET",
722
+ "HEAD",
723
+ "PUT",
724
+ "DELETE",
725
+ "OPTIONS"
726
+ ].includes(operation.method)
727
+ });
772
728
  }
773
729
  return operations.sort((left, right) => left.toolName.localeCompare(right.toolName));
774
730
  }
775
731
  function resolveDocsOpenApiMcpBaseUrl(document, config) {
776
732
  const configured = config.baseUrl?.trim();
777
733
  if (configured) return configured;
778
- const servers = Array.isArray(document.servers) ? document.servers : [];
779
- for (const server of servers) {
780
- const url = asRecord(server)?.url;
781
- if (typeof url === "string" && url.trim()) return url.trim();
782
- }
734
+ return buildNormalizedOpenApiModel(document).servers[0];
783
735
  }
784
736
 
785
737
  //#endregion