@workos/oagen 0.1.1 → 0.2.0

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 (47) hide show
  1. package/dist/cli/index.d.mts +2 -0
  2. package/dist/cli/index.mjs +6018 -0
  3. package/dist/cli/index.mjs.map +1 -0
  4. package/dist/compat/{index.d.ts → index.d.mts} +43 -114
  5. package/dist/compat/index.d.mts.map +1 -0
  6. package/dist/compat/index.mjs +4562 -0
  7. package/dist/compat/index.mjs.map +1 -0
  8. package/dist/diff-DyQF4sel.mjs +2250 -0
  9. package/dist/diff-DyQF4sel.mjs.map +1 -0
  10. package/dist/differ-BwelBJN0.mjs +1100 -0
  11. package/dist/differ-BwelBJN0.mjs.map +1 -0
  12. package/dist/index.d.mts +295 -0
  13. package/dist/index.d.mts.map +1 -0
  14. package/dist/index.mjs +1553 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/scripts/smoke/baseline.d.mts +1 -0
  17. package/dist/scripts/smoke/baseline.mjs +1916 -0
  18. package/dist/scripts/smoke/baseline.mjs.map +1 -0
  19. package/dist/scripts/smoke/sdk-test.d.mts +1 -0
  20. package/dist/scripts/smoke/sdk-test.mjs +111 -0
  21. package/dist/scripts/smoke/sdk-test.mjs.map +1 -0
  22. package/dist/smoke.d.mts +331 -0
  23. package/dist/smoke.d.mts.map +1 -0
  24. package/dist/smoke.mjs +2084 -0
  25. package/dist/smoke.mjs.map +1 -0
  26. package/dist/tree-sitter-BPNfbEzY.mjs +359 -0
  27. package/dist/tree-sitter-BPNfbEzY.mjs.map +1 -0
  28. package/dist/types-O25qZxMl.d.mts +397 -0
  29. package/dist/types-O25qZxMl.d.mts.map +1 -0
  30. package/dist/verify/index.d.mts +82 -0
  31. package/dist/verify/index.d.mts.map +1 -0
  32. package/dist/verify/index.mjs +327 -0
  33. package/dist/verify/index.mjs.map +1 -0
  34. package/package.json +19 -16
  35. package/dist/chunk-2JXFTDTO.js +0 -1669
  36. package/dist/chunk-CIX5NOBF.js +0 -263
  37. package/dist/chunk-IJ5E5AE7.js +0 -1040
  38. package/dist/cli/index.js +0 -5322
  39. package/dist/compat/index.js +0 -4491
  40. package/dist/index.d.ts +0 -277
  41. package/dist/index.js +0 -1565
  42. package/dist/scripts/smoke/baseline.js +0 -1813
  43. package/dist/scripts/smoke/sdk-test.js +0 -93
  44. package/dist/smoke.js +0 -2025
  45. package/dist/types-B5t9O9Wm.d.ts +0 -367
  46. package/dist/verify/index.d.ts +0 -76
  47. package/dist/verify/index.js +0 -336
@@ -0,0 +1,1100 @@
1
+ import { A as walkTypeRef, f as toSnakeCase, o as splitWords, x as RegistryError } from "./tree-sitter-BPNfbEzY.mjs";
2
+ //#region src/compat/extractor-registry.ts
3
+ const extractors = /* @__PURE__ */ new Map();
4
+ function registerExtractor(extractor) {
5
+ extractors.set(extractor.language, extractor);
6
+ }
7
+ function getExtractor(language) {
8
+ const extractor = extractors.get(language);
9
+ if (!extractor) throw new RegistryError(`No extractor registered for language: ${language}. Available: ${[...extractors.keys()].join(", ") || "(none)"}`, `Register an extractor for "${language}" via registerExtractor() or add one in your oagen.config.ts plugin.`);
10
+ return extractor;
11
+ }
12
+ //#endregion
13
+ //#region src/compat/language-hints.ts
14
+ /** Untyped map patterns — hoisted to avoid allocation on every isTypeEquivalent call. */
15
+ const UNTYPED_MAP_PATTERNS = new Set([
16
+ "Record<string, unknown>",
17
+ "Record<string, any>",
18
+ "{ [key: string]: any; }",
19
+ "{ [key: string]: unknown; }",
20
+ "any"
21
+ ]);
22
+ const NAMED_TYPE_RE = /^[A-Z][a-zA-Z0-9]*$/;
23
+ /** Check whether a type name exists as an interface, class, or enum in a surface. */
24
+ function typeExistsInSurface(name, surface) {
25
+ return !!(surface.interfaces[name] || surface.classes[name] || surface.enums[name]);
26
+ }
27
+ /** Split a PascalCase string into words, keeping only words > 2 chars. */
28
+ function splitPascalWords(s) {
29
+ return splitWords(s).filter((w) => w.length > 2);
30
+ }
31
+ /**
32
+ * Check whether two named types share enough PascalCase word components to
33
+ * be considered equivalent (handles name reordering from Json merges, etc.).
34
+ * Both names have `Response$` stripped before comparison.
35
+ */
36
+ function namedTypeWordsOverlap(a, b, minOverlap = 1) {
37
+ const aNoResp = a.replace(/Response$/, "");
38
+ const bNoResp = b.replace(/Response$/, "");
39
+ const aWords = new Set(splitPascalWords(aNoResp).map((w) => w.toLowerCase()));
40
+ const bWords = new Set(splitPascalWords(bNoResp).map((w) => w.toLowerCase()));
41
+ const overlap = [...aWords].filter((w) => bWords.has(w));
42
+ return overlap.length >= minOverlap && overlap.length >= Math.min(aWords.size, bWords.size) - 1;
43
+ }
44
+ /** Split a pipe-delimited union string into trimmed, non-empty members. */
45
+ function parseUnionMembers(s) {
46
+ return s.split("|").map((p) => p.trim()).filter(Boolean);
47
+ }
48
+ /**
49
+ * Default implementation of isNullableOnlyDifference — shared across extractors.
50
+ * Returns true when stripping nullable wrappers from both sides yields the same
51
+ * type, but the original types differ (i.e., the only difference is nullability).
52
+ */
53
+ function defaultIsNullableOnlyDifference(hints, a, b) {
54
+ return (hints.stripNullable(a) ?? a) === (hints.stripNullable(b) ?? b) && a !== b;
55
+ }
56
+ /**
57
+ * Node/TypeScript language hints — reference implementation.
58
+ * Extracted from the hardcoded logic previously in differ.ts and overlay.ts.
59
+ */
60
+ const nodeHints = {
61
+ stripNullable(type) {
62
+ const members = parseUnionMembers(type);
63
+ const stripped = members.filter((p) => p !== "null");
64
+ if (stripped.length === members.length) return null;
65
+ return stripped.join(" | ");
66
+ },
67
+ isNullableOnlyDifference(a, b) {
68
+ return (nodeHints.stripNullable(a) ?? a) === (nodeHints.stripNullable(b) ?? b) && a !== b;
69
+ },
70
+ isUnionReorder(a, b) {
71
+ const membersA = parseUnionMembers(a).sort();
72
+ const membersB = parseUnionMembers(b).sort();
73
+ if (membersA.length !== membersB.length || membersA.length < 2) return false;
74
+ return membersA.every((m, i) => m === membersB[i]);
75
+ },
76
+ isGenericTypeParam(type) {
77
+ if (/^[A-Z]$/.test(type)) return true;
78
+ if (/^T[A-Z][a-zA-Z]*$/.test(type)) return true;
79
+ if (/^[A-Z]\[\]$/.test(type) || /^T[A-Z][a-zA-Z]*\[\]$/.test(type)) return true;
80
+ return false;
81
+ },
82
+ isExtractionArtifact(type) {
83
+ return type === "any" || type === "Record<string, unknown>";
84
+ },
85
+ tolerateCategoryMismatch: true,
86
+ extractReturnTypeName(returnType) {
87
+ let inner = returnType;
88
+ while (inner.startsWith("Promise<") && inner.endsWith(">")) inner = inner.slice(8, -1);
89
+ const genericMatch = inner.match(/^[A-Za-z]+<(.+)>$/);
90
+ if (genericMatch) inner = genericMatch[1];
91
+ inner = inner.replace(/\[\]$/, "");
92
+ if ([
93
+ "void",
94
+ "string",
95
+ "number",
96
+ "boolean",
97
+ "any",
98
+ "unknown",
99
+ "null",
100
+ "undefined"
101
+ ].includes(inner)) return null;
102
+ return inner;
103
+ },
104
+ extractParamTypeName(paramType) {
105
+ if ([
106
+ "string",
107
+ "number",
108
+ "boolean",
109
+ "any",
110
+ "unknown"
111
+ ].includes(paramType)) return null;
112
+ return paramType;
113
+ },
114
+ propertyMatchesClass(propertyName, className) {
115
+ return propertyName.replace(/[-_]/g, "").toLowerCase() === className.toLowerCase();
116
+ },
117
+ derivedModelNames(modelName) {
118
+ return [`${modelName}Response`, `Serialized${modelName}`];
119
+ },
120
+ isTypeEquivalent(baselineType, candidateType, candidateSurface) {
121
+ const candEnum = candidateSurface.enums[candidateType];
122
+ if (candEnum) {
123
+ const enumValuesSet = new Set(Object.values(candEnum.members).flatMap((v) => [`"${v}"`, `'${v}'`]));
124
+ const baseMembers = parseUnionMembers(baselineType);
125
+ if (baseMembers.length > 0 && baseMembers.every((m) => enumValuesSet.has(m))) return true;
126
+ }
127
+ if (UNTYPED_MAP_PATTERNS.has(baselineType) && UNTYPED_MAP_PATTERNS.has(candidateType)) return true;
128
+ if (baselineType.startsWith("{") && baselineType.endsWith("}")) {
129
+ if (typeExistsInSurface(candidateType, candidateSurface)) return true;
130
+ }
131
+ const baseClean = baselineType.replace(/\[\]$/, "");
132
+ const candClean = candidateType.replace(/\[\]$/, "");
133
+ const sameArrayness = baselineType.endsWith("[]") === candidateType.endsWith("[]");
134
+ if (sameArrayness) {
135
+ if (candClean === baseClean + "Response" || baseClean === candClean + "Response") return true;
136
+ }
137
+ if (sameArrayness && NAMED_TYPE_RE.test(baseClean) && NAMED_TYPE_RE.test(candClean)) {
138
+ if (typeExistsInSurface(candClean, candidateSurface)) {
139
+ if (candClean.includes(baseClean) || baseClean.includes(candClean)) return true;
140
+ const baseNoResp = baseClean.replace(/Response$/, "");
141
+ const candNoResp = candClean.replace(/Response$/, "");
142
+ if (candNoResp.includes(baseNoResp) || baseNoResp.includes(candNoResp)) return true;
143
+ if (namedTypeWordsOverlap(baseClean, candClean, 2)) return true;
144
+ }
145
+ }
146
+ return false;
147
+ }
148
+ };
149
+ /**
150
+ * Merge partial language hint overrides over nodeHints defaults.
151
+ * Any hint not provided falls back to the Node/TypeScript implementation.
152
+ */
153
+ function resolveHints(overrides) {
154
+ return {
155
+ ...nodeHints,
156
+ ...overrides
157
+ };
158
+ }
159
+ //#endregion
160
+ //#region src/compat/overlay.ts
161
+ /**
162
+ * Find the class name that owns a given SDK resource property.
163
+ * E.g. if the surface has a class "Organizations" with property "organizations",
164
+ * and sdkResourceProperty is "organizations", return "Organizations".
165
+ */
166
+ function findClassForProperty(surface, sdkResourceProperty, hints) {
167
+ for (const [className, cls] of Object.entries(surface.classes)) {
168
+ if (hints.propertyMatchesClass(sdkResourceProperty, className)) return className;
169
+ for (const [propName, prop] of Object.entries(cls.properties)) if (propName === sdkResourceProperty) {
170
+ const propType = prop.type;
171
+ if (propType && surface.classes[propType]) return propType;
172
+ return;
173
+ }
174
+ }
175
+ const propWords = splitWords(sdkResourceProperty).map((w) => w.toLowerCase());
176
+ if (propWords.length > 1) for (const [className] of Object.entries(surface.classes)) {
177
+ const classWords = splitWords(className).map((w) => w.toLowerCase());
178
+ if (classWords.length > 0 && classWords.length < propWords.length) {
179
+ const suffix = propWords.slice(propWords.length - classWords.length);
180
+ if (classWords.every((w, i) => w === suffix[i])) return className;
181
+ }
182
+ }
183
+ }
184
+ function buildOverlayLookup(surface, manifest, spec, hints, options) {
185
+ const resolvedHints = hints ?? nodeHints;
186
+ const lookup = {
187
+ methodByOperation: /* @__PURE__ */ new Map(),
188
+ httpKeyByMethod: /* @__PURE__ */ new Map(),
189
+ interfaceByName: /* @__PURE__ */ new Map(),
190
+ typeAliasByName: /* @__PURE__ */ new Map(),
191
+ requiredExports: /* @__PURE__ */ new Map(),
192
+ modelNameByIR: /* @__PURE__ */ new Map(),
193
+ fileBySymbol: /* @__PURE__ */ new Map()
194
+ };
195
+ if (manifest) for (const entry of manifest) {
196
+ const key = `${entry.httpMethod.toUpperCase()} ${entry.path}`;
197
+ const className = findClassForProperty(surface, entry.sdkResourceProperty, resolvedHints);
198
+ if (className) {
199
+ let resolvedMethodName = entry.sdkMethodName;
200
+ let methods = surface.classes[className]?.methods[resolvedMethodName];
201
+ if (!methods) {
202
+ const classMethods = surface.classes[className]?.methods ?? {};
203
+ const prefix = resolvedMethodName.toLowerCase();
204
+ const candidates = [];
205
+ for (const [name, overloads] of Object.entries(classMethods)) if (name.toLowerCase().startsWith(prefix) && name !== resolvedMethodName) candidates.push([name, overloads]);
206
+ if (candidates.length === 1) {
207
+ methods = candidates[0][1];
208
+ resolvedMethodName = candidates[0][0];
209
+ } else if (candidates.length > 1) {
210
+ const firstSegment = entry.path.split("/").filter(Boolean)[0]?.replace(/[{}_-]/g, "") ?? "";
211
+ if (firstSegment) {
212
+ const segLower = firstSegment.toLowerCase();
213
+ const segSingular = segLower.endsWith("s") ? segLower.slice(0, -1) : segLower;
214
+ const best = candidates.find(([name]) => {
215
+ const lower = name.toLowerCase();
216
+ return lower === prefix + segLower || lower === prefix + segSingular;
217
+ });
218
+ if (best) {
219
+ methods = best[1];
220
+ resolvedMethodName = best[0];
221
+ }
222
+ }
223
+ if (!methods) {
224
+ const verbMatch = disambiguateByHttpVerb(candidates, entry.httpMethod);
225
+ if (verbMatch) {
226
+ methods = verbMatch[1];
227
+ resolvedMethodName = verbMatch[0];
228
+ }
229
+ }
230
+ }
231
+ }
232
+ if (!methods) {
233
+ const classMethods = surface.classes[className]?.methods ?? {};
234
+ const suffix = resolvedMethodName.toLowerCase();
235
+ const candidates = [];
236
+ for (const [name, overloads] of Object.entries(classMethods)) if (name.toLowerCase().endsWith(suffix) && name !== resolvedMethodName) candidates.push([name, overloads]);
237
+ if (candidates.length === 1) {
238
+ methods = candidates[0][1];
239
+ resolvedMethodName = candidates[0][0];
240
+ }
241
+ }
242
+ if (!methods) {
243
+ const manifestWords = splitCamelCase(resolvedMethodName);
244
+ if (manifestWords.length >= 2) {
245
+ const classMethods = surface.classes[className]?.methods ?? {};
246
+ const candidates = [];
247
+ for (const [name, overloads] of Object.entries(classMethods)) {
248
+ if (name === resolvedMethodName) continue;
249
+ const surfaceWords = splitCamelCase(name);
250
+ if (surfaceWords.length > manifestWords.length && isSubsequence(manifestWords, surfaceWords)) candidates.push([name, overloads]);
251
+ }
252
+ if (candidates.length === 1) {
253
+ methods = candidates[0][1];
254
+ resolvedMethodName = candidates[0][0];
255
+ } else if (candidates.length > 1) {
256
+ const verbMatch = disambiguateByHttpVerb(candidates, entry.httpMethod);
257
+ if (verbMatch) {
258
+ methods = verbMatch[1];
259
+ resolvedMethodName = verbMatch[0];
260
+ }
261
+ }
262
+ }
263
+ }
264
+ if (!methods) {
265
+ const classMethods = surface.classes[className]?.methods ?? {};
266
+ const manifestLower = resolvedMethodName.toLowerCase();
267
+ const candidates = [];
268
+ for (const [name, overloads] of Object.entries(classMethods)) {
269
+ if (name === resolvedMethodName) continue;
270
+ if (manifestLower.startsWith(name.toLowerCase()) && name.length >= 3) candidates.push([name, overloads]);
271
+ }
272
+ if (candidates.length === 1) {
273
+ methods = candidates[0][1];
274
+ resolvedMethodName = candidates[0][0];
275
+ } else if (candidates.length > 1) {
276
+ const verbMatch = disambiguateByHttpVerb(candidates, entry.httpMethod);
277
+ if (verbMatch) {
278
+ methods = verbMatch[1];
279
+ resolvedMethodName = verbMatch[0];
280
+ }
281
+ }
282
+ }
283
+ const method = methods?.[0];
284
+ if (method) {
285
+ lookup.methodByOperation.set(key, {
286
+ className,
287
+ methodName: resolvedMethodName,
288
+ params: method.params,
289
+ returnType: method.returnType
290
+ });
291
+ lookup.httpKeyByMethod.set(`${className}.${resolvedMethodName}`, key);
292
+ }
293
+ }
294
+ }
295
+ for (const name of Object.keys(surface.interfaces)) lookup.interfaceByName.set(name, name);
296
+ for (const name of Object.keys(surface.typeAliases)) lookup.typeAliasByName.set(name, name);
297
+ for (const [path, symbols] of Object.entries(surface.exports)) lookup.requiredExports.set(path, new Set(symbols));
298
+ for (const record of [
299
+ surface.classes,
300
+ surface.interfaces,
301
+ surface.typeAliases,
302
+ surface.enums
303
+ ]) for (const [name, item] of Object.entries(record)) if (item.sourceFile) lookup.fileBySymbol.set(name, item.sourceFile);
304
+ if (spec) {
305
+ buildModelNameMap(surface, spec, lookup, resolvedHints, options);
306
+ for (const [irName, sdkName] of lookup.modelNameByIR) {
307
+ const filePath = lookup.fileBySymbol.get(sdkName);
308
+ if (filePath) lookup.fileBySymbol.set(irName, filePath);
309
+ }
310
+ }
311
+ return lookup;
312
+ }
313
+ /** Map HTTP methods to preferred method name prefixes for disambiguation. */
314
+ const HTTP_VERB_PREFIXES = {
315
+ GET: [
316
+ "list",
317
+ "get",
318
+ "fetch",
319
+ "retrieve",
320
+ "find"
321
+ ],
322
+ POST: [
323
+ "create",
324
+ "add",
325
+ "insert",
326
+ "send"
327
+ ],
328
+ PUT: [
329
+ "set",
330
+ "update",
331
+ "replace",
332
+ "put"
333
+ ],
334
+ PATCH: [
335
+ "update",
336
+ "patch",
337
+ "modify"
338
+ ],
339
+ DELETE: [
340
+ "delete",
341
+ "remove",
342
+ "revoke"
343
+ ]
344
+ };
345
+ /**
346
+ * Disambiguate multiple method candidates using HTTP verb semantics.
347
+ * Returns the best match or undefined if no unique match is found.
348
+ */
349
+ function disambiguateByHttpVerb(candidates, httpMethod) {
350
+ const prefixes = HTTP_VERB_PREFIXES[httpMethod.toUpperCase()];
351
+ if (!prefixes) return void 0;
352
+ const verbCandidates = candidates.filter(([name]) => {
353
+ const lower = name.toLowerCase();
354
+ return prefixes.some((p) => lower.startsWith(p));
355
+ });
356
+ if (verbCandidates.length === 1) return verbCandidates[0];
357
+ }
358
+ /** Split a camelCase identifier into its word parts (e.g., "getByExternalId" → ["get","By","External","Id"]). */
359
+ function splitCamelCase(name) {
360
+ const parts = [];
361
+ let start = 0;
362
+ for (let i = 1; i < name.length; i++) if (name[i] >= "A" && name[i] <= "Z") {
363
+ parts.push(name.slice(start, i));
364
+ start = i;
365
+ }
366
+ parts.push(name.slice(start));
367
+ return parts;
368
+ }
369
+ /** Check if `needle` words form a subsequence of `haystack` words (case-insensitive). */
370
+ function isSubsequence(needle, haystack) {
371
+ let ni = 0;
372
+ for (let hi = 0; hi < haystack.length && ni < needle.length; hi++) if (needle[ni].toLowerCase() === haystack[hi].toLowerCase()) ni++;
373
+ return ni === needle.length;
374
+ }
375
+ /**
376
+ * Normalize a field name to a canonical form for comparison.
377
+ * Handles camelCase → snake_case so "createdAt" and "created_at" match.
378
+ */
379
+ function normalizeFieldName(name) {
380
+ return toSnakeCase(name);
381
+ }
382
+ /**
383
+ * Compute the Jaccard similarity between two sets of normalized field names.
384
+ * Returns score (0–1) and raw intersection count.
385
+ */
386
+ function jaccardSimilarity(a, b) {
387
+ if (a.size === 0 && b.size === 0) return {
388
+ score: 0,
389
+ intersection: 0
390
+ };
391
+ let intersection = 0;
392
+ for (const item of a) if (b.has(item)) intersection++;
393
+ const union = a.size + b.size - intersection;
394
+ return {
395
+ score: union === 0 ? 0 : intersection / union,
396
+ intersection
397
+ };
398
+ }
399
+ /**
400
+ * Build a set of normalized field names from an IR model.
401
+ */
402
+ function irModelFieldSignature(model) {
403
+ return new Set(model.fields.map((f) => normalizeFieldName(f.name)));
404
+ }
405
+ /**
406
+ * Build a set of normalized field names from an SDK interface.
407
+ */
408
+ function sdkInterfaceFieldSignature(iface) {
409
+ return new Set(Object.keys(iface.fields).map(normalizeFieldName));
410
+ }
411
+ /**
412
+ * Populate the modelNameByIR map using two strategies:
413
+ *
414
+ * 1. Operation-based matching (high confidence):
415
+ * When a manifest maps an HTTP operation to an SDK method, extract the
416
+ * method's return type and parameter types. Match them against the IR
417
+ * operation's response model and request body model.
418
+ *
419
+ * 2. Field-structure matching (fallback):
420
+ * For IR models not matched by operations, compare their field names
421
+ * against all SDK interfaces using Jaccard similarity. A match requires
422
+ * ≥60% field overlap with ≥3 fields in common.
423
+ */
424
+ function buildModelNameMap(surface, spec, lookup, hints, options) {
425
+ const mapped = /* @__PURE__ */ new Set();
426
+ const usedSdkNames = /* @__PURE__ */ new Set();
427
+ if (lookup.methodByOperation.size > 0) for (const service of spec.services) for (const op of service.operations) {
428
+ const httpKey = `${op.httpMethod.toUpperCase()} ${op.path}`;
429
+ const methodOverlay = lookup.methodByOperation.get(httpKey);
430
+ if (!methodOverlay) continue;
431
+ if (op.response.kind === "model" && !mapped.has(op.response.name)) {
432
+ const sdkTypeName = hints.extractReturnTypeName(methodOverlay.returnType);
433
+ if (sdkTypeName && surface.interfaces[sdkTypeName]) {
434
+ lookup.modelNameByIR.set(op.response.name, sdkTypeName);
435
+ mapped.add(op.response.name);
436
+ usedSdkNames.add(sdkTypeName);
437
+ }
438
+ }
439
+ if (op.requestBody?.kind === "model" && !mapped.has(op.requestBody.name)) for (const param of methodOverlay.params) {
440
+ const sdkTypeName = hints.extractParamTypeName(param.type);
441
+ if (sdkTypeName && surface.interfaces[sdkTypeName]) {
442
+ lookup.modelNameByIR.set(op.requestBody.name, sdkTypeName);
443
+ mapped.add(op.requestBody.name);
444
+ usedSdkNames.add(sdkTypeName);
445
+ break;
446
+ }
447
+ }
448
+ }
449
+ const sdkSignatures = [];
450
+ for (const [name, iface] of Object.entries(surface.interfaces)) {
451
+ if (usedSdkNames.has(name)) continue;
452
+ const fields = sdkInterfaceFieldSignature(iface);
453
+ if (fields.size >= 2) sdkSignatures.push({
454
+ name,
455
+ fields,
456
+ fieldCount: fields.size
457
+ });
458
+ }
459
+ const sdkSignatureNames = new Set(sdkSignatures.map((s) => s.name));
460
+ for (const model of spec.models) {
461
+ if (mapped.has(model.name)) continue;
462
+ if (model.fields.length < 2) continue;
463
+ if (sdkSignatureNames.has(model.name) && !usedSdkNames.has(model.name)) {
464
+ lookup.modelNameByIR.set(model.name, model.name);
465
+ mapped.add(model.name);
466
+ usedSdkNames.add(model.name);
467
+ continue;
468
+ }
469
+ const derivedNames = hints.derivedModelNames(model.name);
470
+ for (const derived of derivedNames) if (sdkSignatureNames.has(derived) && !usedSdkNames.has(derived)) {
471
+ lookup.modelNameByIR.set(model.name, derived);
472
+ mapped.add(model.name);
473
+ usedSdkNames.add(derived);
474
+ break;
475
+ }
476
+ if (mapped.has(model.name)) continue;
477
+ if (options?.strictModelMatch) continue;
478
+ const irFields = irModelFieldSignature(model);
479
+ let bestMatch = null;
480
+ let bestScore = 0;
481
+ for (const sdk of sdkSignatures) {
482
+ if (usedSdkNames.has(sdk.name)) continue;
483
+ const { score, intersection } = jaccardSimilarity(irFields, sdk.fields);
484
+ const minIntersection = Math.max(3, Math.ceil(irFields.size * .5));
485
+ if (score > bestScore && score >= .6 && intersection >= minIntersection) {
486
+ bestScore = score;
487
+ bestMatch = sdk.name;
488
+ }
489
+ }
490
+ if (bestMatch) {
491
+ lookup.modelNameByIR.set(model.name, bestMatch);
492
+ mapped.add(model.name);
493
+ usedSdkNames.add(bestMatch);
494
+ }
495
+ }
496
+ }
497
+ /**
498
+ * Patch overlay with violations from a failed verification.
499
+ * Adds explicit name mappings for symbols that were generated with wrong names.
500
+ * Returns a new OverlayLookup (immutable).
501
+ */
502
+ function patchOverlay(overlay, violations, baseline) {
503
+ const patched = {
504
+ methodByOperation: new Map(overlay.methodByOperation),
505
+ httpKeyByMethod: new Map(overlay.httpKeyByMethod),
506
+ interfaceByName: new Map(overlay.interfaceByName),
507
+ typeAliasByName: new Map(overlay.typeAliasByName),
508
+ requiredExports: new Map(Array.from(overlay.requiredExports.entries()).map(([k, v]) => [k, new Set(v)])),
509
+ modelNameByIR: new Map(overlay.modelNameByIR),
510
+ fileBySymbol: new Map(overlay.fileBySymbol)
511
+ };
512
+ let warnedManifest = false;
513
+ for (const v of violations) {
514
+ if (v.category === "public-api") {
515
+ const parts = v.symbolPath.split(".");
516
+ if (parts.length === 2) {
517
+ const [className, methodName] = parts;
518
+ const baseClass = baseline.classes[className];
519
+ if (baseClass && baseClass.methods[methodName]?.length > 0) {
520
+ const httpKey = overlay.httpKeyByMethod.get(`${className}.${methodName}`);
521
+ if (!httpKey && overlay.httpKeyByMethod.size === 0 && !warnedManifest) {
522
+ console.warn("Warning: No smoke-manifest.json available. Method-level violations cannot be auto-patched. Implement generateManifest in your emitter for the self-correcting loop to resolve these.");
523
+ warnedManifest = true;
524
+ }
525
+ if (httpKey) {
526
+ const method = baseClass.methods[methodName][0];
527
+ patched.methodByOperation.set(httpKey, {
528
+ className,
529
+ methodName,
530
+ params: method.params,
531
+ returnType: method.returnType
532
+ });
533
+ }
534
+ }
535
+ if (baseline.interfaces[className]) patched.interfaceByName.set(className, className);
536
+ } else if (parts.length === 1) {
537
+ const name = parts[0];
538
+ if (baseline.interfaces[name]) patched.interfaceByName.set(name, name);
539
+ if (baseline.typeAliases[name]) patched.typeAliasByName.set(name, name);
540
+ }
541
+ }
542
+ if (v.category === "export-structure") {
543
+ const match = v.symbolPath.match(/^exports\[(.+?)\]\.(.+)$/);
544
+ if (match) {
545
+ const [, path, symbol] = match;
546
+ if (!patched.requiredExports.has(path)) patched.requiredExports.set(path, /* @__PURE__ */ new Set());
547
+ patched.requiredExports.get(path).add(symbol);
548
+ }
549
+ }
550
+ }
551
+ return patched;
552
+ }
553
+ //#endregion
554
+ //#region src/compat/spec-filter.ts
555
+ /**
556
+ * Compute the set of symbol names that are derivable from the OpenAPI spec.
557
+ * Only these names should be compared during compat verification — everything
558
+ * else in the baseline is hand-written and out of scope for generation.
559
+ */
560
+ function specDerivedNames(spec, hints) {
561
+ const names = /* @__PURE__ */ new Set();
562
+ const topLevelEnumNames = new Set(spec.enums.map((e) => e.name));
563
+ for (const service of spec.services) {
564
+ names.add(service.name);
565
+ for (const op of service.operations) {
566
+ collectTypeRefNames(op.response, names, hints, topLevelEnumNames);
567
+ if (op.requestBody) collectTypeRefNames(op.requestBody, names, hints, topLevelEnumNames);
568
+ for (const p of [
569
+ ...op.pathParams,
570
+ ...op.queryParams,
571
+ ...op.headerParams,
572
+ ...op.cookieParams ?? []
573
+ ]) collectTypeRefNames(p.type, names, hints, topLevelEnumNames);
574
+ }
575
+ }
576
+ for (const model of spec.models) {
577
+ names.add(model.name);
578
+ for (const derived of hints.derivedModelNames(model.name)) names.add(derived);
579
+ for (const field of model.fields) collectTypeRefNames(field.type, names, hints, topLevelEnumNames);
580
+ }
581
+ for (const e of spec.enums) names.add(e.name);
582
+ return names;
583
+ }
584
+ /**
585
+ * Compute a map of enum name → set of wire values that appear in the spec.
586
+ * Used by filterSurface to exclude hand-added enum members that aren't in the spec.
587
+ */
588
+ function specDerivedEnumValues(spec) {
589
+ const result = /* @__PURE__ */ new Map();
590
+ for (const e of spec.enums) result.set(e.name, new Set(e.values.map((v) => v.value)));
591
+ return result;
592
+ }
593
+ /**
594
+ * Compute the set of field paths (e.g., "Organization.name") that are defined
595
+ * in the OpenAPI spec's model schemas. Used by filterSurface to exclude
596
+ * hand-added SDK fields that reference spec-derived types.
597
+ */
598
+ function specDerivedFieldPaths(spec, hints) {
599
+ const paths = /* @__PURE__ */ new Set();
600
+ for (const model of spec.models) {
601
+ for (const field of model.fields) paths.add(`${model.name}.${field.name}`);
602
+ for (const derived of hints.derivedModelNames(model.name)) for (const field of model.fields) paths.add(`${derived}.${field.name}`);
603
+ }
604
+ return paths;
605
+ }
606
+ /**
607
+ * Compute the set of method paths (e.g., "SSO.authorize") that are defined
608
+ * as operations in the OpenAPI spec's services. Used by filterSurface to
609
+ * exclude hand-written SDK methods that don't correspond to spec operations.
610
+ */
611
+ function specDerivedMethodPaths(spec) {
612
+ const paths = /* @__PURE__ */ new Set();
613
+ for (const service of spec.services) for (const op of service.operations) paths.add(`${service.name}.${op.name}`);
614
+ return paths;
615
+ }
616
+ /**
617
+ * Compute the set of HTTP operation keys (e.g., "GET /sso/authorize") that
618
+ * exist in the spec. Used for overlay-based method matching.
619
+ */
620
+ function specDerivedHttpKeys(spec) {
621
+ const keys = /* @__PURE__ */ new Set();
622
+ for (const service of spec.services) for (const op of service.operations) keys.add(`${op.httpMethod.toUpperCase()} ${op.path}`);
623
+ return keys;
624
+ }
625
+ function collectTypeRefNames(ref, out, hints, topLevelEnums) {
626
+ walkTypeRef(ref, {
627
+ model: (r) => {
628
+ out.add(r.name);
629
+ for (const derived of hints.derivedModelNames(r.name)) out.add(derived);
630
+ },
631
+ enum: (r) => {
632
+ if (!topLevelEnums || topLevelEnums.has(r.name)) out.add(r.name);
633
+ }
634
+ });
635
+ }
636
+ function filterRecord(record, allowed) {
637
+ const result = {};
638
+ for (const [name, value] of Object.entries(record)) if (allowed.has(name)) result[name] = value;
639
+ return result;
640
+ }
641
+ /**
642
+ * Filter an ApiSurface to only include symbols whose names appear in the
643
+ * allowed set. Symbols not in the set are dropped entirely — they won't
644
+ * count toward the total or produce violations.
645
+ *
646
+ * For interfaces: when fieldPaths is provided, only keeps fields that appear
647
+ * in the spec-derived field paths. This prevents false positives from hand-added
648
+ * SDK fields that reference spec-derived types but aren't defined in the spec.
649
+ *
650
+ * For classes: when methodPaths is provided, only keeps methods whose names
651
+ * appear in the spec-derived method paths. This prevents false positives from
652
+ * hand-written SDK methods (e.g., SSO.authorization_url, Webhooks.construct_event)
653
+ * that don't correspond to spec operations. Properties on service classes are
654
+ * also filtered: only UPPER_CASE constants that are spec-derivable are kept.
655
+ */
656
+ function filterSurface(surface, allowedNames, opts) {
657
+ const { fieldPaths, methodPaths, enumValues } = opts ?? {};
658
+ const filteredInterfaces = filterRecord(surface.interfaces, allowedNames);
659
+ if (fieldPaths) for (const [name, iface] of Object.entries(filteredInterfaces)) {
660
+ const filteredFields = {};
661
+ for (const [fieldName, field] of Object.entries(iface.fields)) if (fieldPaths.has(`${name}.${fieldName}`)) filteredFields[fieldName] = field;
662
+ filteredInterfaces[name] = {
663
+ ...iface,
664
+ fields: filteredFields
665
+ };
666
+ }
667
+ let filteredClasses = filterRecord(surface.classes, allowedNames);
668
+ if (methodPaths) {
669
+ const serviceClassNames = /* @__PURE__ */ new Set();
670
+ for (const path of methodPaths) {
671
+ const dot = path.indexOf(".");
672
+ if (dot !== -1) serviceClassNames.add(path.slice(0, dot));
673
+ }
674
+ const newClasses = {};
675
+ for (const [name, cls] of Object.entries(filteredClasses)) if (serviceClassNames.has(name)) {
676
+ const filteredMethods = {};
677
+ for (const [methodName, overloads] of Object.entries(cls.methods)) if (methodPaths.has(`${name}.${methodName}`)) filteredMethods[methodName] = overloads;
678
+ const filteredProperties = {};
679
+ for (const [propName, prop] of Object.entries(cls.properties)) if (methodPaths.has(`${name}.${propName}`)) filteredProperties[propName] = prop;
680
+ newClasses[name] = {
681
+ ...cls,
682
+ methods: filteredMethods,
683
+ properties: filteredProperties
684
+ };
685
+ } else if (fieldPaths) {
686
+ const filteredProperties = {};
687
+ for (const [propName, prop] of Object.entries(cls.properties)) if (fieldPaths.has(`${name}.${propName}`)) filteredProperties[propName] = prop;
688
+ const filteredMethods = {};
689
+ for (const [methodName, overloads] of Object.entries(cls.methods)) if (fieldPaths.has(`${name}.${methodName}`) || methodPaths.has(`${name}.${methodName}`)) filteredMethods[methodName] = overloads;
690
+ newClasses[name] = {
691
+ ...cls,
692
+ methods: filteredMethods,
693
+ properties: filteredProperties
694
+ };
695
+ } else newClasses[name] = cls;
696
+ filteredClasses = newClasses;
697
+ }
698
+ let filteredEnums = filterRecord(surface.enums, allowedNames);
699
+ if (enumValues) {
700
+ const newEnums = {};
701
+ for (const [name, enumDef] of Object.entries(filteredEnums)) {
702
+ const specValues = enumValues.get(name);
703
+ if (!specValues) {
704
+ newEnums[name] = enumDef;
705
+ continue;
706
+ }
707
+ const specValuesLower = new Set([...specValues].map((v) => String(v).toLowerCase()));
708
+ const filteredMembers = {};
709
+ for (const [member, value] of Object.entries(enumDef.members)) if (specValues.has(value) || specValuesLower.has(String(value).toLowerCase())) filteredMembers[member] = value;
710
+ newEnums[name] = {
711
+ ...enumDef,
712
+ members: filteredMembers
713
+ };
714
+ }
715
+ filteredEnums = newEnums;
716
+ }
717
+ return {
718
+ ...surface,
719
+ classes: filteredClasses,
720
+ interfaces: filteredInterfaces,
721
+ typeAliases: filterRecord(surface.typeAliases, allowedNames),
722
+ enums: filteredEnums,
723
+ exports: {}
724
+ };
725
+ }
726
+ //#endregion
727
+ //#region src/compat/differ.ts
728
+ function diffSurfaces(baseline, candidate, hints) {
729
+ const violations = [];
730
+ const additions = [];
731
+ let totalBaseline = 0;
732
+ let preserved = 0;
733
+ for (const [name, baseClass] of Object.entries(baseline.classes)) {
734
+ totalBaseline++;
735
+ const candClass = candidate.classes[name];
736
+ if (!candClass) {
737
+ violations.push({
738
+ category: "public-api",
739
+ severity: "breaking",
740
+ symbolPath: name,
741
+ baseline: name,
742
+ candidate: "(missing)",
743
+ message: `Class "${name}" exists in baseline but not in generated output`
744
+ });
745
+ totalBaseline += Object.values(baseClass.methods).reduce((sum, overloads) => sum + overloads.length, 0);
746
+ totalBaseline += Object.keys(baseClass.properties).length;
747
+ continue;
748
+ }
749
+ preserved++;
750
+ for (const [methodName, baseOverloads] of Object.entries(baseClass.methods)) {
751
+ const candOverloads = candClass.methods[methodName];
752
+ for (const baseMethod of baseOverloads) {
753
+ totalBaseline++;
754
+ if (!candOverloads || candOverloads.length === 0) {
755
+ violations.push({
756
+ category: "public-api",
757
+ severity: "breaking",
758
+ symbolPath: `${name}.${methodName}`,
759
+ baseline: methodName,
760
+ candidate: "(missing)",
761
+ message: `Method "${name}.${methodName}" exists in baseline but not in generated output`
762
+ });
763
+ continue;
764
+ }
765
+ if (!candOverloads.find((c) => signaturesMatch(baseMethod, c))) {
766
+ if (hints.isSignatureEquivalent ? candOverloads.find((c) => hints.isSignatureEquivalent(baseMethod, c, candidate)) : void 0) {
767
+ preserved++;
768
+ continue;
769
+ }
770
+ violations.push({
771
+ category: "signature",
772
+ severity: "breaking",
773
+ symbolPath: `${name}.${methodName}`,
774
+ baseline: formatSignature(baseMethod),
775
+ candidate: formatSignature(candOverloads[0]),
776
+ message: `Signature mismatch for "${name}.${methodName}"`
777
+ });
778
+ continue;
779
+ }
780
+ preserved++;
781
+ }
782
+ }
783
+ for (const methodName of Object.keys(candClass.methods)) if (!baseClass.methods[methodName]) additions.push({
784
+ symbolPath: `${name}.${methodName}`,
785
+ symbolType: "method"
786
+ });
787
+ for (const [propName, baseProp] of Object.entries(baseClass.properties)) {
788
+ totalBaseline++;
789
+ const candProp = candClass.properties[propName];
790
+ if (!candProp) {
791
+ violations.push({
792
+ category: "public-api",
793
+ severity: "breaking",
794
+ symbolPath: `${name}.${propName}`,
795
+ baseline: baseProp.type,
796
+ candidate: "(missing)",
797
+ message: `Property "${name}.${propName}" exists in baseline but not in generated output`
798
+ });
799
+ continue;
800
+ }
801
+ if (baseProp.type !== candProp.type) {
802
+ const nullableOnly = hints.isNullableOnlyDifference(baseProp.type, candProp.type);
803
+ violations.push({
804
+ category: "signature",
805
+ severity: nullableOnly ? "warning" : "breaking",
806
+ symbolPath: `${name}.${propName}`,
807
+ baseline: baseProp.type,
808
+ candidate: candProp.type,
809
+ message: `Property type mismatch for "${name}.${propName}"`
810
+ });
811
+ if (nullableOnly) preserved++;
812
+ continue;
813
+ }
814
+ preserved++;
815
+ }
816
+ for (const propName of Object.keys(candClass.properties)) if (!baseClass.properties[propName]) additions.push({
817
+ symbolPath: `${name}.${propName}`,
818
+ symbolType: "property"
819
+ });
820
+ }
821
+ for (const name of Object.keys(candidate.classes)) if (!baseline.classes[name]) additions.push({
822
+ symbolPath: name,
823
+ symbolType: "class"
824
+ });
825
+ const candIfaceFieldSets = /* @__PURE__ */ new Map();
826
+ for (const [n, iface] of Object.entries(candidate.interfaces)) candIfaceFieldSets.set(n, new Set(Object.keys(iface.fields).map((f) => f.toLowerCase())));
827
+ const candClassPropSets = /* @__PURE__ */ new Map();
828
+ for (const [n, cls] of Object.entries(candidate.classes)) candClassPropSets.set(n, new Set(Object.keys(cls.properties).map((f) => f.toLowerCase())));
829
+ for (const [name, baseIface] of Object.entries(baseline.interfaces)) {
830
+ totalBaseline++;
831
+ const candIface = candidate.interfaces[name];
832
+ if (!candIface) {
833
+ let tolerated = false;
834
+ if (hints.tolerateCategoryMismatch && name.startsWith("Serialized")) {
835
+ const baseName = name.slice(10);
836
+ if (candidate.interfaces[baseName] || candidate.classes[baseName]) tolerated = true;
837
+ }
838
+ if (tolerated) {
839
+ preserved++;
840
+ totalBaseline += Object.keys(baseIface.fields).length;
841
+ preserved += Object.keys(baseIface.fields).length;
842
+ continue;
843
+ }
844
+ if (!tolerated) {
845
+ const baseFieldNamesLower = new Set(Object.keys(baseIface.fields).map((f) => f.toLowerCase()));
846
+ if (baseFieldNamesLower.size > 0) {
847
+ for (const [, candFieldNamesLower] of candIfaceFieldSets) if (candFieldNamesLower.size === baseFieldNamesLower.size && [...baseFieldNamesLower].every((f) => candFieldNamesLower.has(f))) {
848
+ tolerated = true;
849
+ break;
850
+ }
851
+ if (!tolerated) {
852
+ for (const [, candPropNamesLower] of candClassPropSets) if (candPropNamesLower.size === baseFieldNamesLower.size && [...baseFieldNamesLower].every((f) => candPropNamesLower.has(f))) {
853
+ tolerated = true;
854
+ break;
855
+ }
856
+ }
857
+ } else tolerated = true;
858
+ }
859
+ if (tolerated) {
860
+ preserved++;
861
+ totalBaseline += Object.keys(baseIface.fields).length;
862
+ preserved += Object.keys(baseIface.fields).length;
863
+ continue;
864
+ }
865
+ violations.push({
866
+ category: "public-api",
867
+ severity: "breaking",
868
+ symbolPath: name,
869
+ baseline: name,
870
+ candidate: "(missing)",
871
+ message: `Interface "${name}" exists in baseline but not in generated output`
872
+ });
873
+ totalBaseline += Object.keys(baseIface.fields).length;
874
+ continue;
875
+ }
876
+ preserved++;
877
+ for (const [fieldName, baseField] of Object.entries(baseIface.fields)) {
878
+ totalBaseline++;
879
+ const candField = candIface.fields[fieldName];
880
+ if (!candField) {
881
+ const baseTypeClean = baseField.type.replace(/\[\]$/, "").replace(/ \| null$/, "");
882
+ const typeIsUnresolvable = NAMED_TYPE_RE.test(baseTypeClean) && !typeExistsInSurface(baseTypeClean, candidate);
883
+ violations.push({
884
+ category: "public-api",
885
+ severity: typeIsUnresolvable ? "warning" : "breaking",
886
+ symbolPath: `${name}.${fieldName}`,
887
+ baseline: baseField.type,
888
+ candidate: "(missing)",
889
+ message: `Field "${name}.${fieldName}" exists in baseline but not in generated output`
890
+ });
891
+ if (typeIsUnresolvable) preserved++;
892
+ continue;
893
+ }
894
+ if (baseField.type !== candField.type) {
895
+ if (hints.isUnionReorder(baseField.type, candField.type)) {
896
+ preserved++;
897
+ continue;
898
+ }
899
+ if (hints.isTypeEquivalent?.(baseField.type, candField.type, candidate)) {
900
+ preserved++;
901
+ continue;
902
+ }
903
+ const nullableOnly = hints.isNullableOnlyDifference(baseField.type, candField.type);
904
+ const genericParam = hints.isGenericTypeParam(baseField.type);
905
+ const extractionArtifact = hints.isExtractionArtifact(candField.type);
906
+ const isWarning = nullableOnly || genericParam || extractionArtifact;
907
+ violations.push({
908
+ category: "signature",
909
+ severity: isWarning ? "warning" : "breaking",
910
+ symbolPath: `${name}.${fieldName}`,
911
+ baseline: baseField.type,
912
+ candidate: candField.type,
913
+ message: `Field type mismatch for "${name}.${fieldName}"`
914
+ });
915
+ if (isWarning) preserved++;
916
+ continue;
917
+ }
918
+ preserved++;
919
+ }
920
+ for (const fieldName of Object.keys(candIface.fields)) if (!baseIface.fields[fieldName]) additions.push({
921
+ symbolPath: `${name}.${fieldName}`,
922
+ symbolType: "property"
923
+ });
924
+ }
925
+ for (const name of Object.keys(candidate.interfaces)) if (!baseline.interfaces[name]) additions.push({
926
+ symbolPath: name,
927
+ symbolType: "interface"
928
+ });
929
+ for (const [name, baseAlias] of Object.entries(baseline.typeAliases)) {
930
+ totalBaseline++;
931
+ const candAlias = candidate.typeAliases[name];
932
+ if (!candAlias) {
933
+ if (hints.tolerateCategoryMismatch && typeExistsInSurface(name, candidate)) {
934
+ preserved++;
935
+ continue;
936
+ }
937
+ violations.push({
938
+ category: "public-api",
939
+ severity: "breaking",
940
+ symbolPath: name,
941
+ baseline: baseAlias.value,
942
+ candidate: "(missing)",
943
+ message: `Type alias "${name}" exists in baseline but not in generated output`
944
+ });
945
+ continue;
946
+ }
947
+ if (baseAlias.value !== candAlias.value) {
948
+ if (hints.isUnionReorder(baseAlias.value, candAlias.value)) {
949
+ preserved++;
950
+ continue;
951
+ }
952
+ const nullableOnly = hints.isNullableOnlyDifference(baseAlias.value, candAlias.value);
953
+ violations.push({
954
+ category: "signature",
955
+ severity: nullableOnly ? "warning" : "breaking",
956
+ symbolPath: name,
957
+ baseline: baseAlias.value,
958
+ candidate: candAlias.value,
959
+ message: `Type alias value mismatch for "${name}"`
960
+ });
961
+ if (nullableOnly) preserved++;
962
+ continue;
963
+ }
964
+ preserved++;
965
+ }
966
+ for (const name of Object.keys(candidate.typeAliases)) if (!baseline.typeAliases[name]) additions.push({
967
+ symbolPath: name,
968
+ symbolType: "type-alias"
969
+ });
970
+ for (const [name, baseEnum] of Object.entries(baseline.enums)) {
971
+ totalBaseline++;
972
+ const candEnum = candidate.enums[name];
973
+ if (!candEnum) {
974
+ violations.push({
975
+ category: "public-api",
976
+ severity: "breaking",
977
+ symbolPath: name,
978
+ baseline: name,
979
+ candidate: "(missing)",
980
+ message: `Enum "${name}" exists in baseline but not in generated output`
981
+ });
982
+ continue;
983
+ }
984
+ const candValueToMembers = /* @__PURE__ */ new Map();
985
+ for (const [candMember, candValue] of Object.entries(candEnum.members)) {
986
+ const existing = candValueToMembers.get(candValue);
987
+ if (existing) existing.push(candMember);
988
+ else candValueToMembers.set(candValue, [candMember]);
989
+ }
990
+ let enumMatch = true;
991
+ for (const [member, value] of Object.entries(baseEnum.members)) {
992
+ if (candEnum.members[member] === value) continue;
993
+ const valueMatches = candValueToMembers.get(value);
994
+ if (valueMatches && valueMatches.length > 0) {
995
+ violations.push({
996
+ category: "signature",
997
+ severity: "warning",
998
+ symbolPath: `${name}.${member}`,
999
+ baseline: `${member}=${String(value)}`,
1000
+ candidate: `${valueMatches[0]}=${String(value)}`,
1001
+ message: `Enum member name differs for "${name}.${member}" (value "${value}" preserved as "${valueMatches[0]}")`
1002
+ });
1003
+ continue;
1004
+ }
1005
+ const lowerValue = String(value).toLowerCase();
1006
+ const caseInsensitiveMatch = [...candValueToMembers.entries()].find(([candVal]) => String(candVal).toLowerCase() === lowerValue);
1007
+ if (caseInsensitiveMatch) {
1008
+ violations.push({
1009
+ category: "signature",
1010
+ severity: "warning",
1011
+ symbolPath: `${name}.${member}`,
1012
+ baseline: `${member}=${String(value)}`,
1013
+ candidate: `${caseInsensitiveMatch[1][0]}=${String(caseInsensitiveMatch[0])}`,
1014
+ message: `Enum member value case differs for "${name}.${member}" (baseline "${value}" vs candidate "${caseInsensitiveMatch[0]}")`
1015
+ });
1016
+ continue;
1017
+ }
1018
+ if (String(value) === member || member === "JsonEnumDefaultValue" || member === "JsonProperty") {
1019
+ violations.push({
1020
+ category: "signature",
1021
+ severity: "warning",
1022
+ symbolPath: `${name}.${member}`,
1023
+ baseline: String(value),
1024
+ candidate: "(extraction artifact)",
1025
+ message: `Enum member "${name}.${member}" appears to be an extraction artifact`
1026
+ });
1027
+ continue;
1028
+ }
1029
+ violations.push({
1030
+ category: "signature",
1031
+ severity: "breaking",
1032
+ symbolPath: `${name}.${member}`,
1033
+ baseline: String(value),
1034
+ candidate: member in candEnum.members ? String(candEnum.members[member]) : "(missing)",
1035
+ message: `Enum member mismatch for "${name}.${member}"`
1036
+ });
1037
+ enumMatch = false;
1038
+ }
1039
+ if (enumMatch) preserved++;
1040
+ }
1041
+ for (const name of Object.keys(candidate.enums)) if (!baseline.enums[name]) additions.push({
1042
+ symbolPath: name,
1043
+ symbolType: "enum"
1044
+ });
1045
+ for (const [path, baseExports] of Object.entries(baseline.exports)) {
1046
+ const candExports = candidate.exports[path];
1047
+ if (!candExports) {
1048
+ for (const exp of baseExports) violations.push({
1049
+ category: "export-structure",
1050
+ severity: "warning",
1051
+ symbolPath: `exports[${path}].${exp}`,
1052
+ baseline: exp,
1053
+ candidate: "(missing)",
1054
+ message: `Export "${exp}" from "${path}" not found in generated output`
1055
+ });
1056
+ continue;
1057
+ }
1058
+ const candSet = new Set(candExports);
1059
+ for (const exp of baseExports) if (!candSet.has(exp)) violations.push({
1060
+ category: "export-structure",
1061
+ severity: "warning",
1062
+ symbolPath: `exports[${path}].${exp}`,
1063
+ baseline: exp,
1064
+ candidate: "(missing)",
1065
+ message: `Export "${exp}" from "${path}" not found in generated output`
1066
+ });
1067
+ }
1068
+ return {
1069
+ preservationScore: totalBaseline > 0 ? Math.round(preserved / totalBaseline * 100) : 100,
1070
+ totalBaselineSymbols: totalBaseline,
1071
+ preservedSymbols: preserved,
1072
+ violations,
1073
+ additions
1074
+ };
1075
+ }
1076
+ /**
1077
+ * Compare method signatures using raw type strings.
1078
+ * NOTE: This only produces meaningful results when both surfaces were extracted
1079
+ * by the same extractor for the same language. Cross-language or cross-extractor
1080
+ * comparisons will always fail because type representations differ.
1081
+ */
1082
+ function signaturesMatch(baseline, candidate) {
1083
+ if (baseline.returnType !== candidate.returnType) return false;
1084
+ for (let i = 0; i < baseline.params.length; i++) {
1085
+ const baseParam = baseline.params[i];
1086
+ const candParam = candidate.params[i];
1087
+ if (!candParam) return false;
1088
+ if (baseParam.type !== candParam.type) return false;
1089
+ if (baseParam.name !== candParam.name) return false;
1090
+ }
1091
+ for (let i = baseline.params.length; i < candidate.params.length; i++) if (!candidate.params[i].optional) return false;
1092
+ return true;
1093
+ }
1094
+ function formatSignature(method) {
1095
+ return `(${method.params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ")}) => ${method.returnType}`;
1096
+ }
1097
+ //#endregion
1098
+ export { registerExtractor as _, specDerivedHttpKeys as a, buildOverlayLookup as c, defaultIsNullableOnlyDifference as d, namedTypeWordsOverlap as f, getExtractor as g, typeExistsInSurface as h, specDerivedFieldPaths as i, patchOverlay as l, resolveHints as m, filterSurface as n, specDerivedMethodPaths as o, nodeHints as p, specDerivedEnumValues as r, specDerivedNames as s, diffSurfaces as t, NAMED_TYPE_RE as u };
1099
+
1100
+ //# sourceMappingURL=differ-BwelBJN0.mjs.map