@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,2250 @@
1
+ import { C as assertNever, f as toSnakeCase, i as normalizeJsExtension, t as safeParse, x as RegistryError } from "./tree-sitter-BPNfbEzY.mjs";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path$1 from "node:path";
4
+ import Parser from "tree-sitter";
5
+ //#region src/engine/merge-adapters/url-fingerprint.ts
6
+ /**
7
+ * Extract a URL path fingerprint from a method body using tree-sitter AST traversal.
8
+ *
9
+ * Walks the AST subtree of the member node looking for the first string literal
10
+ * that starts with '/'. Normalizes interpolation expressions to '{}' and format
11
+ * specifiers (%s, %d, etc.) to '{}' for cross-language comparison.
12
+ *
13
+ * @returns A normalized URL fingerprint like "/organizations/{}" or undefined if none found.
14
+ */
15
+ function extractUrlFingerprint(memberNode, config) {
16
+ const stringTypes = new Set(config.stringNodeTypes);
17
+ const contentTypes = new Set(config.contentNodeTypes);
18
+ const interpolationTypes = new Set(config.interpolationNodeTypes);
19
+ const formatFns = config.formatFunctionNames ? new Set(config.formatFunctionNames) : null;
20
+ const stack = [memberNode];
21
+ while (stack.length > 0) {
22
+ const node = stack.pop();
23
+ if (stringTypes.has(node.type)) {
24
+ const fp = resolveFingerprint(node, contentTypes, interpolationTypes);
25
+ if (fp?.startsWith("/")) return fp;
26
+ }
27
+ if (formatFns && isFormatCall(node, formatFns)) {
28
+ const fp = extractFromFormatCall(node, stringTypes, contentTypes, interpolationTypes);
29
+ if (fp?.startsWith("/")) return fp;
30
+ }
31
+ for (let i = node.childCount - 1; i >= 0; i--) {
32
+ const child = node.child(i);
33
+ if (child) stack.push(child);
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Resolve the text content of a string node, normalizing interpolations to '{}'.
39
+ */
40
+ function resolveFingerprint(stringNode, contentTypes, interpolationTypes) {
41
+ if (stringNode.namedChildCount === 0) return normalizeFormatSpecifiers(stripQuotes(stringNode.text));
42
+ let result = "";
43
+ for (let i = 0; i < stringNode.childCount; i++) {
44
+ const child = stringNode.child(i);
45
+ if (contentTypes.has(child.type)) result += child.text;
46
+ else if (interpolationTypes.has(child.type)) result += "{}";
47
+ }
48
+ return result ? normalizeFormatSpecifiers(result) : normalizeFormatSpecifiers(stripQuotes(stringNode.text));
49
+ }
50
+ /** Check if a node is a call to one of the format functions (e.g., fmt.Sprintf, format!). */
51
+ function isFormatCall(node, formatFns) {
52
+ if (node.type === "macro_invocation") {
53
+ const macroName = node.childForFieldName("macro")?.text;
54
+ return !!(macroName && formatFns.has(macroName.replace(/!$/, "")));
55
+ }
56
+ if (node.type !== "call_expression") return false;
57
+ const fnNode = node.childForFieldName("function");
58
+ if (!fnNode) return false;
59
+ if (formatFns.has(fnNode.text)) return true;
60
+ if (fnNode.type === "selector_expression" || fnNode.type === "member_expression") {
61
+ const prop = fnNode.childForFieldName("field") ?? fnNode.childForFieldName("property");
62
+ if (prop && formatFns.has(prop.text)) return true;
63
+ }
64
+ return false;
65
+ }
66
+ /** Extract the first string argument from a format function call. */
67
+ function extractFromFormatCall(callNode, stringTypes, contentTypes, interpolationTypes) {
68
+ const args = callNode.childForFieldName("arguments");
69
+ if (!args) return void 0;
70
+ for (let i = 0; i < args.childCount; i++) {
71
+ const arg = args.child(i);
72
+ if (stringTypes.has(arg.type)) return resolveFingerprint(arg, contentTypes, interpolationTypes);
73
+ }
74
+ }
75
+ /** Strip surrounding quote characters from a string literal. */
76
+ function stripQuotes(text) {
77
+ const match = text.match(/^(?:f|r|b|@|\$)?(?:"""([\s\S]*)"""|'''([\s\S]*)'''|"(.*)"|'(.*)'|`(.*)`)$/s);
78
+ if (match) return match[1] ?? match[2] ?? match[3] ?? match[4] ?? match[5] ?? text;
79
+ return text;
80
+ }
81
+ /** Replace printf-style format specifiers (%s, %d, %v, %f) with {}. */
82
+ function normalizeFormatSpecifiers(s) {
83
+ return s.replace(/%[sdvf]/g, "{}");
84
+ }
85
+ //#endregion
86
+ //#region src/engine/merge-adapters/dotnet.ts
87
+ const DOTNET_URL_FINGERPRINT_CONFIG = {
88
+ stringNodeTypes: ["string_literal", "interpolated_string_expression"],
89
+ contentNodeTypes: ["string_content", "string_literal_content"],
90
+ interpolationNodeTypes: ["interpolation"]
91
+ };
92
+ const DECLARATION_TYPES = new Set([
93
+ "class_declaration",
94
+ "struct_declaration",
95
+ "interface_declaration",
96
+ "enum_declaration",
97
+ "record_declaration"
98
+ ]);
99
+ function extractDeclarationName$1(node) {
100
+ if (DECLARATION_TYPES.has(node.type)) return (node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier"))?.text ?? null;
101
+ if (node.type === "namespace_declaration" || node.type === "file_scoped_namespace_declaration") {
102
+ const nameNode = node.children.find((c) => c.type === "qualified_name") ?? node.children.find((c) => c.type === "identifier");
103
+ return nameNode ? `__namespace:${nameNode.text}` : null;
104
+ }
105
+ return null;
106
+ }
107
+ function collectPrecedingXmlDocComments(children, index, source) {
108
+ let lastIdx = -1;
109
+ let firstIdx = -1;
110
+ for (let k = index - 1; k >= 0; k--) {
111
+ const prev = children[k];
112
+ if (prev.type === "comment") if (source.slice(prev.startIndex, prev.endIndex).startsWith("///")) {
113
+ if (lastIdx === -1) lastIdx = k;
114
+ firstIdx = k;
115
+ } else break;
116
+ else break;
117
+ }
118
+ if (firstIdx === -1) return null;
119
+ const first = children[firstIdx];
120
+ const last = children[lastIdx];
121
+ return {
122
+ text: source.slice(first.startIndex, last.endIndex),
123
+ startIndex: first.startIndex,
124
+ endIndex: last.endIndex
125
+ };
126
+ }
127
+ /**
128
+ * Collect declarations from a list of sibling nodes (root level or namespace body).
129
+ */
130
+ function collectDeclarations(children, source, statements, imports, importAnchors, importInsertionAnchor) {
131
+ for (const child of children) {
132
+ if (child.type === "comment") continue;
133
+ if (child.type === "using_directive") {
134
+ const text = source.slice(child.startIndex, child.endIndex);
135
+ imports.push({
136
+ key: text.trim(),
137
+ text
138
+ });
139
+ importAnchors.push(text);
140
+ continue;
141
+ }
142
+ if (child.type === "file_scoped_namespace_declaration") {
143
+ importInsertionAnchor.value = source.slice(child.startIndex, child.endIndex);
144
+ continue;
145
+ }
146
+ if (child.type === "namespace_declaration") {
147
+ importInsertionAnchor.value = source.slice(child.startIndex, child.endIndex);
148
+ const body = child.children.find((c) => c.type === "declaration_list");
149
+ if (body) collectDeclarations(body.namedChildren, source, statements, imports, importAnchors, importInsertionAnchor);
150
+ continue;
151
+ }
152
+ const name = extractDeclarationName$1(child);
153
+ statements.push({
154
+ key: name,
155
+ kind: name ? "declaration" : "other",
156
+ text: source.slice(child.startIndex, child.endIndex)
157
+ });
158
+ }
159
+ }
160
+ const dotnetMergeAdapter = {
161
+ language: "dotnet",
162
+ grammarModule: "tree-sitter-c-sharp",
163
+ testFilePatterns: [/Tests?\.cs$/],
164
+ urlFingerprintConfig: DOTNET_URL_FINGERPRINT_CONFIG,
165
+ parseStatements(tree, source) {
166
+ const imports = [];
167
+ const importAnchors = [];
168
+ const statements = [];
169
+ const importInsertionAnchor = {};
170
+ collectDeclarations(tree.rootNode.children, source, statements, imports, importAnchors, importInsertionAnchor);
171
+ return {
172
+ imports,
173
+ importAnchors,
174
+ importInsertionAnchor: importInsertionAnchor.value,
175
+ statements
176
+ };
177
+ },
178
+ renderImports(imports) {
179
+ return imports.map((entry) => entry.text);
180
+ },
181
+ extractDocstrings(tree, source) {
182
+ const result = /* @__PURE__ */ new Map();
183
+ function processChildren(children) {
184
+ for (let i = 0; i < children.length; i++) {
185
+ const child = children[i];
186
+ if (child.type === "namespace_declaration") {
187
+ const body = child.children.find((c) => c.type === "declaration_list");
188
+ if (body) processChildren(body.children);
189
+ continue;
190
+ }
191
+ const name = extractDeclarationName$1(child);
192
+ if (!name || name.startsWith("__namespace:")) continue;
193
+ const docstring = collectPrecedingXmlDocComments(children, i, source);
194
+ const members = /* @__PURE__ */ new Map();
195
+ const body = child.children.find((c) => c.type === "declaration_list");
196
+ if (body) {
197
+ const bodyChildren = body.children;
198
+ for (let j = 0; j < bodyChildren.length; j++) {
199
+ const member = bodyChildren[j];
200
+ if (member.type !== "method_declaration" && member.type !== "property_declaration") continue;
201
+ const identifiers = member.children.filter((c) => c.type === "identifier");
202
+ const memberName = member.type === "method_declaration" ? identifiers.length >= 2 ? identifiers[1].text : identifiers[0]?.text : identifiers[0]?.text;
203
+ if (!memberName) continue;
204
+ const memberDoc = collectPrecedingXmlDocComments(bodyChildren, j, source);
205
+ members.set(memberName, {
206
+ docstring: memberDoc,
207
+ declStartIndex: member.startIndex,
208
+ declColumn: member.startPosition.column,
209
+ urlFingerprint: extractUrlFingerprint(member, DOTNET_URL_FINGERPRINT_CONFIG)
210
+ });
211
+ }
212
+ }
213
+ result.set(name, {
214
+ docstring,
215
+ declStartIndex: child.startIndex,
216
+ declColumn: child.startPosition.column,
217
+ members
218
+ });
219
+ }
220
+ }
221
+ processChildren(tree.rootNode.children);
222
+ return result;
223
+ }
224
+ };
225
+ //#endregion
226
+ //#region src/engine/merge-adapters/elixir.ts
227
+ const ELIXIR_URL_FINGERPRINT_CONFIG = {
228
+ stringNodeTypes: ["string"],
229
+ contentNodeTypes: ["quoted_content"],
230
+ interpolationNodeTypes: ["interpolation"]
231
+ };
232
+ function isDefmodule(node) {
233
+ return node.type === "call" && node.children[0]?.text === "defmodule";
234
+ }
235
+ function getModuleName(node) {
236
+ if (!isDefmodule(node)) return null;
237
+ const args = node.children.find((c) => c.type === "arguments");
238
+ if (!args) return null;
239
+ return args.children.find((c) => c.type === "alias")?.text ?? null;
240
+ }
241
+ function getDoBlock(node) {
242
+ return node.children.find((c) => c.type === "do_block") ?? null;
243
+ }
244
+ /** Check if a node is an @moduledoc or @doc attribute. */
245
+ function isDocAttribute(node) {
246
+ if (node.type !== "unary_operator") return null;
247
+ const call = node.children.find((c) => c.type === "call");
248
+ if (!call) return null;
249
+ const id = call.children.find((c) => c.type === "identifier");
250
+ if (id?.text === "moduledoc" || id?.text === "doc") return id.text;
251
+ return null;
252
+ }
253
+ /** Check if a node is a def/defp call. */
254
+ function getDefName(node) {
255
+ if (node.type !== "call") return null;
256
+ const id = node.children[0];
257
+ if (!id || id.text !== "def" && id.text !== "defp") return null;
258
+ const args = node.children.find((c) => c.type === "arguments");
259
+ if (!args) return null;
260
+ for (const arg of args.namedChildren) {
261
+ if (arg.type === "identifier") return arg.text;
262
+ if (arg.type === "call") return arg.children.find((c) => c.type === "identifier")?.text ?? null;
263
+ }
264
+ return null;
265
+ }
266
+ /** Check if a node is an alias/import/require/use call. */
267
+ function isImportLike(node) {
268
+ if (node.type !== "call") return false;
269
+ const id = node.children[0];
270
+ return id?.type === "identifier" && [
271
+ "alias",
272
+ "import",
273
+ "require",
274
+ "use"
275
+ ].includes(id.text);
276
+ }
277
+ const elixirMergeAdapter = {
278
+ language: "elixir",
279
+ grammarModule: "tree-sitter-elixir",
280
+ testFilePatterns: [/_test\.exs$/],
281
+ urlFingerprintConfig: ELIXIR_URL_FINGERPRINT_CONFIG,
282
+ parseStatements(tree, source) {
283
+ const imports = [];
284
+ const importAnchors = [];
285
+ const statements = [];
286
+ for (const child of tree.rootNode.children) {
287
+ if (child.type === "comment") continue;
288
+ if (isImportLike(child)) {
289
+ const text = source.slice(child.startIndex, child.endIndex);
290
+ imports.push({
291
+ key: text.trim(),
292
+ text
293
+ });
294
+ importAnchors.push(text);
295
+ continue;
296
+ }
297
+ const moduleName = getModuleName(child);
298
+ statements.push({
299
+ key: moduleName,
300
+ kind: moduleName ? "declaration" : "other",
301
+ text: source.slice(child.startIndex, child.endIndex)
302
+ });
303
+ }
304
+ return {
305
+ imports,
306
+ importAnchors,
307
+ statements
308
+ };
309
+ },
310
+ renderImports(imports) {
311
+ return imports.map((entry) => entry.text);
312
+ },
313
+ extractDocstrings(tree, source) {
314
+ const result = /* @__PURE__ */ new Map();
315
+ const rootChildren = tree.rootNode.children;
316
+ for (let i = 0; i < rootChildren.length; i++) {
317
+ const child = rootChildren[i];
318
+ const moduleName = getModuleName(child);
319
+ if (!moduleName) continue;
320
+ let docstring = null;
321
+ const doBlock = getDoBlock(child);
322
+ const members = /* @__PURE__ */ new Map();
323
+ if (doBlock) {
324
+ const bodyChildren = doBlock.children;
325
+ for (const bodyChild of bodyChildren) if (isDocAttribute(bodyChild) === "moduledoc") {
326
+ docstring = {
327
+ text: source.slice(bodyChild.startIndex, bodyChild.endIndex),
328
+ startIndex: bodyChild.startIndex,
329
+ endIndex: bodyChild.endIndex
330
+ };
331
+ break;
332
+ }
333
+ for (let j = 0; j < bodyChildren.length; j++) {
334
+ const bodyChild = bodyChildren[j];
335
+ const defName = getDefName(bodyChild);
336
+ if (!defName) continue;
337
+ let memberDoc = null;
338
+ for (let k = j - 1; k >= 0; k--) {
339
+ const prev = bodyChildren[k];
340
+ if (isDocAttribute(prev) === "doc") {
341
+ memberDoc = {
342
+ text: source.slice(prev.startIndex, prev.endIndex),
343
+ startIndex: prev.startIndex,
344
+ endIndex: prev.endIndex
345
+ };
346
+ break;
347
+ }
348
+ if (prev.type === "comment") continue;
349
+ break;
350
+ }
351
+ members.set(defName, {
352
+ docstring: memberDoc,
353
+ declStartIndex: bodyChild.startIndex,
354
+ declColumn: bodyChild.startPosition.column,
355
+ urlFingerprint: extractUrlFingerprint(bodyChild, ELIXIR_URL_FINGERPRINT_CONFIG)
356
+ });
357
+ }
358
+ }
359
+ result.set(moduleName, {
360
+ docstring,
361
+ declStartIndex: child.startIndex,
362
+ declColumn: child.startPosition.column,
363
+ members
364
+ });
365
+ }
366
+ return result;
367
+ }
368
+ };
369
+ //#endregion
370
+ //#region src/engine/merge-adapters/go.ts
371
+ const GO_URL_FINGERPRINT_CONFIG = {
372
+ stringNodeTypes: ["interpreted_string_literal"],
373
+ contentNodeTypes: ["interpreted_string_literal_content"],
374
+ interpolationNodeTypes: [],
375
+ formatFunctionNames: ["Sprintf"]
376
+ };
377
+ function lastIdentifier(text) {
378
+ const matches = [...text.matchAll(/[A-Za-z_][A-Za-z0-9_]*/g)];
379
+ return matches.length > 0 ? matches[matches.length - 1][0] : null;
380
+ }
381
+ function receiverKey(node) {
382
+ const receiver = node.childForFieldName("receiver");
383
+ if (!receiver) return null;
384
+ return lastIdentifier(receiver.text);
385
+ }
386
+ function declarationKey$1(node) {
387
+ switch (node.type) {
388
+ case "package_clause": {
389
+ const pkg = lastIdentifier(node.text);
390
+ return pkg ? `__package:${pkg}` : "__package:";
391
+ }
392
+ case "type_declaration": return ((node.firstNamedChild?.type === "type_spec" ? node.firstNamedChild : node.namedChildren[0])?.childForFieldName("name"))?.text ?? null;
393
+ case "function_declaration": return node.childForFieldName("name")?.text ?? null;
394
+ case "method_declaration": {
395
+ const name = node.childForFieldName("name")?.text ?? null;
396
+ const receiver = receiverKey(node);
397
+ return name && receiver ? `method:${receiver}.${name}` : name;
398
+ }
399
+ case "const_declaration":
400
+ case "var_declaration": return node.text.split(/\s+/).slice(0, 3).join(" ");
401
+ default: return null;
402
+ }
403
+ }
404
+ function importEntries(node, source) {
405
+ const entries = [];
406
+ const collect = (spec) => {
407
+ const path = spec.childForFieldName("path")?.text ?? spec.text;
408
+ const name = spec.childForFieldName("name")?.text ?? "";
409
+ const text = source.slice(spec.startIndex, spec.endIndex);
410
+ entries.push({
411
+ key: `${name}:${path.replace(/^"|"$/g, "")}`,
412
+ text
413
+ });
414
+ };
415
+ for (const child of node.namedChildren) {
416
+ if (child.type === "import_spec") {
417
+ collect(child);
418
+ continue;
419
+ }
420
+ if (child.type === "import_spec_list") {
421
+ for (const spec of child.namedChildren) if (spec.type === "import_spec") collect(spec);
422
+ }
423
+ }
424
+ return entries;
425
+ }
426
+ function collectPrecedingGoComments(children, index, source) {
427
+ let lastIdx = -1;
428
+ let firstIdx = -1;
429
+ for (let k = index - 1; k >= 0; k--) {
430
+ const prev = children[k];
431
+ if (prev.type === "comment") if (source.slice(prev.startIndex, prev.endIndex).startsWith("//")) {
432
+ if (lastIdx === -1) lastIdx = k;
433
+ firstIdx = k;
434
+ } else break;
435
+ else break;
436
+ }
437
+ if (firstIdx === -1) return null;
438
+ const first = children[firstIdx];
439
+ const last = children[lastIdx];
440
+ return {
441
+ text: source.slice(first.startIndex, last.endIndex),
442
+ startIndex: first.startIndex,
443
+ endIndex: last.endIndex
444
+ };
445
+ }
446
+ const goMergeAdapter = {
447
+ language: "go",
448
+ grammarModule: "tree-sitter-go",
449
+ testFilePatterns: [/_test\.go$/],
450
+ urlFingerprintConfig: GO_URL_FINGERPRINT_CONFIG,
451
+ parseStatements(tree, source) {
452
+ const imports = [];
453
+ const importAnchors = [];
454
+ let importInsertionAnchor;
455
+ const statements = [];
456
+ for (const child of tree.rootNode.children) {
457
+ if (child.type === "comment") continue;
458
+ if (child.type === "package_clause") importInsertionAnchor = source.slice(child.startIndex, child.endIndex);
459
+ if (child.type === "import_declaration") {
460
+ imports.push(...importEntries(child, source));
461
+ importAnchors.push(source.slice(child.startIndex, child.endIndex));
462
+ continue;
463
+ }
464
+ const key = declarationKey$1(child);
465
+ statements.push({
466
+ key,
467
+ kind: key ? "declaration" : "other",
468
+ text: source.slice(child.startIndex, child.endIndex)
469
+ });
470
+ }
471
+ return {
472
+ imports,
473
+ importAnchors,
474
+ importInsertionAnchor,
475
+ statements
476
+ };
477
+ },
478
+ renderImports(imports) {
479
+ if (imports.length === 0) return [];
480
+ if (imports.length === 1) return [`import ${imports[0].text}`];
481
+ return [`import (\n${imports.map((entry) => ` ${entry.text}`).join("\n")}\n)`];
482
+ },
483
+ extractDocstrings(tree, source) {
484
+ const result = /* @__PURE__ */ new Map();
485
+ const rootChildren = tree.rootNode.children;
486
+ for (let i = 0; i < rootChildren.length; i++) {
487
+ const child = rootChildren[i];
488
+ const key = declarationKey$1(child);
489
+ if (!key || key.startsWith("__package:")) continue;
490
+ const docstring = collectPrecedingGoComments(rootChildren, i, source);
491
+ result.set(key, {
492
+ docstring,
493
+ declStartIndex: child.startIndex,
494
+ declColumn: child.startPosition.column,
495
+ members: /* @__PURE__ */ new Map()
496
+ });
497
+ }
498
+ return result;
499
+ }
500
+ };
501
+ //#endregion
502
+ //#region src/engine/merge-adapters/kotlin.ts
503
+ const KOTLIN_URL_FINGERPRINT_CONFIG = {
504
+ stringNodeTypes: ["string_literal"],
505
+ contentNodeTypes: ["string_content"],
506
+ interpolationNodeTypes: ["interpolated_expression"]
507
+ };
508
+ function extractDeclarationName(node) {
509
+ if (node.type === "class_declaration") return node.children.find((c) => c.type === "type_identifier")?.text ?? null;
510
+ if (node.type === "function_declaration") return node.children.find((c) => c.type === "simple_identifier")?.text ?? null;
511
+ if (node.type === "object_declaration") return node.children.find((c) => c.type === "type_identifier")?.text ?? null;
512
+ if (node.type === "type_alias") return node.children.find((c) => c.type === "type_identifier")?.text ?? null;
513
+ if (node.type === "property_declaration") {
514
+ const nameNode = node.children.find((c) => c.type === "variable_declaration");
515
+ if (nameNode) return nameNode.children.find((c) => c.type === "simple_identifier")?.text ?? null;
516
+ }
517
+ return null;
518
+ }
519
+ function findPrecedingKdoc(children, index, source) {
520
+ for (let k = index - 1; k >= 0; k--) {
521
+ const prev = children[k];
522
+ if (prev.type === "multiline_comment") {
523
+ const text = source.slice(prev.startIndex, prev.endIndex);
524
+ if (text.startsWith("/**")) return {
525
+ text,
526
+ startIndex: prev.startIndex,
527
+ endIndex: prev.endIndex
528
+ };
529
+ return null;
530
+ }
531
+ if (prev.type === "line_comment") continue;
532
+ return null;
533
+ }
534
+ return null;
535
+ }
536
+ const kotlinMergeAdapter = {
537
+ language: "kotlin",
538
+ grammarModule: "tree-sitter-kotlin",
539
+ testFilePatterns: [/Test\.kt$/],
540
+ urlFingerprintConfig: KOTLIN_URL_FINGERPRINT_CONFIG,
541
+ parseStatements(tree, source) {
542
+ const imports = [];
543
+ const importAnchors = [];
544
+ const statements = [];
545
+ for (const child of tree.rootNode.children) {
546
+ if (child.type === "multiline_comment" || child.type === "line_comment") continue;
547
+ if (child.type === "import_list") {
548
+ const fullText = source.slice(child.startIndex, child.endIndex);
549
+ importAnchors.push(fullText);
550
+ for (const imp of child.namedChildren) if (imp.type === "import_header") {
551
+ const text = source.slice(imp.startIndex, imp.endIndex).trim();
552
+ const key = imp.children.find((c) => c.type === "identifier")?.text ?? text;
553
+ imports.push({
554
+ key,
555
+ text
556
+ });
557
+ }
558
+ continue;
559
+ }
560
+ const name = extractDeclarationName(child);
561
+ statements.push({
562
+ key: name,
563
+ kind: name ? "declaration" : "other",
564
+ text: source.slice(child.startIndex, child.endIndex)
565
+ });
566
+ }
567
+ return {
568
+ imports,
569
+ importAnchors,
570
+ statements
571
+ };
572
+ },
573
+ renderImports(imports) {
574
+ return imports.map((entry) => entry.text);
575
+ },
576
+ extractDocstrings(tree, source) {
577
+ const result = /* @__PURE__ */ new Map();
578
+ const rootChildren = tree.rootNode.children;
579
+ for (let i = 0; i < rootChildren.length; i++) {
580
+ const child = rootChildren[i];
581
+ const name = extractDeclarationName(child);
582
+ if (!name) continue;
583
+ const docstring = findPrecedingKdoc(rootChildren, i, source);
584
+ const members = /* @__PURE__ */ new Map();
585
+ if (child.type === "class_declaration") {
586
+ const body = child.children.find((c) => c.type === "class_body" || c.type === "enum_class_body");
587
+ if (body) {
588
+ const bodyChildren = body.children;
589
+ for (let j = 0; j < bodyChildren.length; j++) {
590
+ const member = bodyChildren[j];
591
+ const memberName = extractDeclarationName(member);
592
+ if (!memberName) continue;
593
+ const memberDoc = findPrecedingKdoc(bodyChildren, j, source);
594
+ members.set(memberName, {
595
+ docstring: memberDoc,
596
+ declStartIndex: member.startIndex,
597
+ declColumn: member.startPosition.column,
598
+ urlFingerprint: extractUrlFingerprint(member, KOTLIN_URL_FINGERPRINT_CONFIG)
599
+ });
600
+ }
601
+ }
602
+ }
603
+ result.set(name, {
604
+ docstring,
605
+ declStartIndex: child.startIndex,
606
+ declColumn: child.startPosition.column,
607
+ members
608
+ });
609
+ }
610
+ return result;
611
+ }
612
+ };
613
+ //#endregion
614
+ //#region src/engine/merge-adapters/node.ts
615
+ const REEXPORT_PREFIX = "__export:";
616
+ function extractDeclName(node) {
617
+ const nameNode = node.childForFieldName("name");
618
+ if (nameNode) return nameNode.text;
619
+ if (node.type === "lexical_declaration") {
620
+ const declarator = node.firstNamedChild;
621
+ if (declarator?.type === "variable_declarator") {
622
+ const name = declarator.childForFieldName("name");
623
+ if (name) return name.text;
624
+ }
625
+ }
626
+ return null;
627
+ }
628
+ function extractNodeKey(node) {
629
+ if (node.type === "export_statement") {
630
+ const decl = node.childForFieldName("declaration");
631
+ if (decl) return {
632
+ key: extractDeclName(decl),
633
+ kind: "declaration"
634
+ };
635
+ const source = node.childForFieldName("source");
636
+ if (source) return {
637
+ key: `${REEXPORT_PREFIX}${normalizeJsExtension(source.text)}`,
638
+ kind: "reexport"
639
+ };
640
+ return {
641
+ key: null,
642
+ kind: "other"
643
+ };
644
+ }
645
+ return {
646
+ key: extractDeclName(node),
647
+ kind: extractDeclName(node) ? "declaration" : "other"
648
+ };
649
+ }
650
+ function extractClassMembers(classBody, source) {
651
+ const members = [];
652
+ for (const child of classBody.namedChildren) if (child.type === "method_definition") {
653
+ const nameNode = child.childForFieldName("name");
654
+ if (nameNode && nameNode.text !== "constructor") members.push({
655
+ key: nameNode.text,
656
+ text: source.slice(child.startIndex, child.endIndex)
657
+ });
658
+ } else if (child.type === "public_field_definition") {
659
+ const nameNode = child.childForFieldName("name");
660
+ if (nameNode) {
661
+ let endIdx = child.endIndex;
662
+ if (source[endIdx] === ";") endIdx += 1;
663
+ members.push({
664
+ key: nameNode.text,
665
+ text: source.slice(child.startIndex, endIdx)
666
+ });
667
+ }
668
+ }
669
+ return members;
670
+ }
671
+ function extractInterfaceMembers(body, source) {
672
+ const members = [];
673
+ for (const child of body.namedChildren) if (child.type === "property_signature" || child.type === "method_signature") {
674
+ const nameNode = child.childForFieldName("name");
675
+ if (nameNode) members.push({
676
+ key: nameNode.text,
677
+ text: source.slice(child.startIndex, child.endIndex)
678
+ });
679
+ }
680
+ return members;
681
+ }
682
+ function extractEnumMembers(body, source) {
683
+ const members = [];
684
+ for (const child of body.namedChildren) if (child.type === "enum_assignment") {
685
+ const nameNode = child.childForFieldName("name");
686
+ if (nameNode) members.push({
687
+ key: nameNode.text,
688
+ text: source.slice(child.startIndex, child.endIndex)
689
+ });
690
+ } else if (child.type === "property_identifier") members.push({
691
+ key: child.text,
692
+ text: source.slice(child.startIndex, child.endIndex)
693
+ });
694
+ return members;
695
+ }
696
+ function isCommentNode(node) {
697
+ return node.type === "comment" || node.grammarType === "comment";
698
+ }
699
+ function findPrecedingDocstring(children, index, source) {
700
+ for (let k = index - 1; k >= 0; k--) {
701
+ const prev = children[k];
702
+ if (isCommentNode(prev)) {
703
+ const text = source.slice(prev.startIndex, prev.endIndex);
704
+ if (text.startsWith("/**")) return {
705
+ text,
706
+ startIndex: prev.startIndex,
707
+ endIndex: prev.endIndex
708
+ };
709
+ return null;
710
+ }
711
+ if (prev.type === "," || prev.type === ";" || prev.type === "{") continue;
712
+ return null;
713
+ }
714
+ return null;
715
+ }
716
+ const CLASS_MEMBER_TYPES = new Set(["method_definition", "public_field_definition"]);
717
+ const INTERFACE_MEMBER_TYPES = new Set(["property_signature", "method_signature"]);
718
+ const ENUM_MEMBER_TYPES = new Set(["enum_assignment", "property_identifier"]);
719
+ const NODE_URL_FINGERPRINT_CONFIG = {
720
+ stringNodeTypes: ["string", "template_string"],
721
+ contentNodeTypes: ["string_fragment"],
722
+ interpolationNodeTypes: ["template_substitution"]
723
+ };
724
+ function extractBodyMemberDocstrings(body, source, memberTypes) {
725
+ const result = /* @__PURE__ */ new Map();
726
+ const allChildren = body.children;
727
+ for (let i = 0; i < allChildren.length; i++) {
728
+ const child = allChildren[i];
729
+ if (!memberTypes.has(child.type)) continue;
730
+ let memberName = null;
731
+ if (child.type === "property_identifier") memberName = child.text;
732
+ else {
733
+ const nameNode = child.childForFieldName("name");
734
+ if (nameNode) memberName = nameNode.text;
735
+ }
736
+ if (!memberName || memberName === "constructor") continue;
737
+ const docstring = findPrecedingDocstring(allChildren, i, source);
738
+ const fp = extractUrlFingerprint(child, NODE_URL_FINGERPRINT_CONFIG);
739
+ result.set(memberName, {
740
+ docstring,
741
+ declStartIndex: child.startIndex,
742
+ declColumn: child.startPosition.column,
743
+ urlFingerprint: fp
744
+ });
745
+ }
746
+ return result;
747
+ }
748
+ const nodeMergeAdapter = {
749
+ language: "node",
750
+ grammarModule: "tree-sitter-typescript/bindings/node/typescript.js",
751
+ normalizeReexport: normalizeJsExtension,
752
+ testFilePatterns: [/\.(spec|test)\.[jt]sx?$/],
753
+ urlFingerprintConfig: NODE_URL_FINGERPRINT_CONFIG,
754
+ shouldSkipDeepMerge(_symbolName, existingMemberKeys, newMembers) {
755
+ const newText = newMembers.map((m) => m.text).join("\n");
756
+ for (const match of newText.matchAll(/this\.(\w+)/g)) {
757
+ const propName = match[1];
758
+ if (propName && !existingMemberKeys.has(propName)) return true;
759
+ }
760
+ return false;
761
+ },
762
+ parseStatements(tree, source) {
763
+ const imports = [];
764
+ const importAnchors = [];
765
+ const statements = [];
766
+ for (const child of tree.rootNode.children) {
767
+ if (child.type === "comment") continue;
768
+ if (child.type === "import_statement") {
769
+ const text = source.slice(child.startIndex, child.endIndex);
770
+ const sourceNode = child.childForFieldName("source");
771
+ const modulePath = sourceNode ? normalizeJsExtension(sourceNode.text) : normalizeJsExtension(text.trim());
772
+ imports.push({
773
+ key: modulePath,
774
+ text
775
+ });
776
+ importAnchors.push(text);
777
+ continue;
778
+ }
779
+ const { key, kind } = extractNodeKey(child);
780
+ statements.push({
781
+ key,
782
+ kind,
783
+ text: source.slice(child.startIndex, child.endIndex)
784
+ });
785
+ }
786
+ return {
787
+ imports,
788
+ importAnchors,
789
+ statements
790
+ };
791
+ },
792
+ renderImports(imports) {
793
+ return imports.map((entry) => entry.text);
794
+ },
795
+ extractMembers(tree, source) {
796
+ const result = /* @__PURE__ */ new Map();
797
+ for (const child of tree.rootNode.children) {
798
+ if (child.type !== "export_statement") continue;
799
+ const decl = child.childForFieldName("declaration");
800
+ if (!decl) continue;
801
+ const nameNode = decl.childForFieldName("name");
802
+ if (!nameNode) continue;
803
+ const symbolName = nameNode.text;
804
+ if (decl.type === "class_declaration") {
805
+ const body = decl.childForFieldName("body");
806
+ if (!body) continue;
807
+ const members = extractClassMembers(body, source);
808
+ const firstMember = body.firstNamedChild;
809
+ const memberIndent = firstMember ? " ".repeat(firstMember.startPosition.column) : void 0;
810
+ result.set(symbolName, {
811
+ members,
812
+ bodyEndLine: body.endPosition.row,
813
+ memberIndent
814
+ });
815
+ } else if (decl.type === "interface_declaration") {
816
+ const body = decl.childForFieldName("body");
817
+ if (!body) continue;
818
+ const members = extractInterfaceMembers(body, source);
819
+ const firstMember = body.firstNamedChild;
820
+ const memberIndent = firstMember ? " ".repeat(firstMember.startPosition.column) : void 0;
821
+ result.set(symbolName, {
822
+ members,
823
+ bodyEndLine: body.endPosition.row,
824
+ memberIndent
825
+ });
826
+ } else if (decl.type === "enum_declaration") {
827
+ const body = decl.childForFieldName("body");
828
+ if (!body) continue;
829
+ const members = extractEnumMembers(body, source);
830
+ const firstMember = body.firstNamedChild;
831
+ const memberIndent = firstMember ? " ".repeat(firstMember.startPosition.column) : void 0;
832
+ result.set(symbolName, {
833
+ members,
834
+ bodyEndLine: body.endPosition.row,
835
+ memberIndent
836
+ });
837
+ }
838
+ }
839
+ return result;
840
+ },
841
+ extractDocstrings(tree, source) {
842
+ const result = /* @__PURE__ */ new Map();
843
+ const rootChildren = tree.rootNode.children;
844
+ for (let i = 0; i < rootChildren.length; i++) {
845
+ const child = rootChildren[i];
846
+ if (child.type !== "export_statement") continue;
847
+ const decl = child.childForFieldName("declaration");
848
+ if (!decl) continue;
849
+ const nameNode = decl.childForFieldName("name");
850
+ if (!nameNode) continue;
851
+ const docstring = findPrecedingDocstring(rootChildren, i, source);
852
+ let members = /* @__PURE__ */ new Map();
853
+ const body = decl.childForFieldName("body");
854
+ if (body) {
855
+ if (decl.type === "class_declaration") members = extractBodyMemberDocstrings(body, source, CLASS_MEMBER_TYPES);
856
+ else if (decl.type === "interface_declaration") members = extractBodyMemberDocstrings(body, source, INTERFACE_MEMBER_TYPES);
857
+ else if (decl.type === "enum_declaration") members = extractBodyMemberDocstrings(body, source, ENUM_MEMBER_TYPES);
858
+ }
859
+ result.set(nameNode.text, {
860
+ docstring,
861
+ declStartIndex: child.startIndex,
862
+ declColumn: child.startPosition.column,
863
+ members
864
+ });
865
+ }
866
+ return result;
867
+ }
868
+ };
869
+ //#endregion
870
+ //#region src/engine/merge-adapters/php.ts
871
+ const PHP_URL_FINGERPRINT_CONFIG = {
872
+ stringNodeTypes: ["encapsed_string", "string"],
873
+ contentNodeTypes: ["string_content"],
874
+ interpolationNodeTypes: ["variable_name"]
875
+ };
876
+ function extractPhpDeclarationName(node) {
877
+ switch (node.type) {
878
+ case "class_declaration":
879
+ case "interface_declaration":
880
+ case "trait_declaration":
881
+ case "enum_declaration":
882
+ case "function_definition": return node.childForFieldName("name")?.text ?? null;
883
+ case "namespace_definition": {
884
+ const nameNode = node.childForFieldName("name");
885
+ return nameNode ? `__namespace:${nameNode.text}` : "__namespace:";
886
+ }
887
+ default: return null;
888
+ }
889
+ }
890
+ function findPhpDocstring(children, index, source) {
891
+ for (let k = index - 1; k >= 0; k--) {
892
+ const prev = children[k];
893
+ if (prev.type === "comment") {
894
+ const text = source.slice(prev.startIndex, prev.endIndex);
895
+ if (text.startsWith("/**")) return {
896
+ text,
897
+ startIndex: prev.startIndex,
898
+ endIndex: prev.endIndex
899
+ };
900
+ return null;
901
+ }
902
+ if (prev.type === ";" || prev.type === "{") continue;
903
+ return null;
904
+ }
905
+ return null;
906
+ }
907
+ const phpMergeAdapter = {
908
+ language: "php",
909
+ grammarModule: "tree-sitter-php",
910
+ testFilePatterns: [/Test\.php$/],
911
+ urlFingerprintConfig: PHP_URL_FINGERPRINT_CONFIG,
912
+ resolveGrammar: (mod) => {
913
+ if (typeof mod === "object" && mod !== null && "php" in mod) return mod.php;
914
+ return mod;
915
+ },
916
+ parseStatements(tree, source) {
917
+ const imports = [];
918
+ const importAnchors = [];
919
+ let importInsertionAnchor;
920
+ const statements = [];
921
+ for (const child of tree.rootNode.children) {
922
+ if (child.type === "comment") continue;
923
+ if (child.type === "namespace_definition") importInsertionAnchor = source.slice(child.startIndex, child.endIndex);
924
+ if (child.type === "namespace_use_declaration") {
925
+ const text = source.slice(child.startIndex, child.endIndex);
926
+ imports.push({
927
+ key: text.trim(),
928
+ text
929
+ });
930
+ importAnchors.push(text);
931
+ continue;
932
+ }
933
+ const declarationName = extractPhpDeclarationName(child);
934
+ const kind = declarationName ? "declaration" : "other";
935
+ statements.push({
936
+ key: declarationName,
937
+ kind,
938
+ text: source.slice(child.startIndex, child.endIndex)
939
+ });
940
+ }
941
+ return {
942
+ imports,
943
+ importAnchors,
944
+ importInsertionAnchor,
945
+ statements
946
+ };
947
+ },
948
+ renderImports(imports) {
949
+ return imports.map((entry) => entry.text);
950
+ },
951
+ extractDocstrings(tree, source) {
952
+ const result = /* @__PURE__ */ new Map();
953
+ const rootChildren = tree.rootNode.children;
954
+ for (let i = 0; i < rootChildren.length; i++) {
955
+ const child = rootChildren[i];
956
+ const name = extractPhpDeclarationName(child);
957
+ if (!name || name.startsWith("__namespace:")) continue;
958
+ const docstring = findPhpDocstring(rootChildren, i, source);
959
+ const members = /* @__PURE__ */ new Map();
960
+ const body = child.childForFieldName("body");
961
+ if (body) {
962
+ const bodyChildren = body.children;
963
+ for (let j = 0; j < bodyChildren.length; j++) {
964
+ const member = bodyChildren[j];
965
+ if (member.type !== "method_declaration") continue;
966
+ const memberName = member.childForFieldName("name")?.text;
967
+ if (!memberName) continue;
968
+ const memberDoc = findPhpDocstring(bodyChildren, j, source);
969
+ members.set(memberName, {
970
+ docstring: memberDoc,
971
+ declStartIndex: member.startIndex,
972
+ declColumn: member.startPosition.column,
973
+ urlFingerprint: extractUrlFingerprint(member, PHP_URL_FINGERPRINT_CONFIG)
974
+ });
975
+ }
976
+ }
977
+ result.set(name, {
978
+ docstring,
979
+ declStartIndex: child.startIndex,
980
+ declColumn: child.startPosition.column,
981
+ members
982
+ });
983
+ }
984
+ return result;
985
+ }
986
+ };
987
+ //#endregion
988
+ //#region src/engine/merge-adapters/python.ts
989
+ const PYTHON_URL_FINGERPRINT_CONFIG = {
990
+ stringNodeTypes: ["string"],
991
+ contentNodeTypes: ["string_content"],
992
+ interpolationNodeTypes: ["interpolation"]
993
+ };
994
+ function extractPythonDeclarationName(node) {
995
+ if (node.type === "class_definition" || node.type === "function_definition") return node.childForFieldName("name")?.text ?? null;
996
+ if (node.type === "expression_statement") {
997
+ const firstChild = node.firstNamedChild;
998
+ if (firstChild?.type === "assignment") {
999
+ const left = firstChild.childForFieldName("left");
1000
+ if (left?.type === "identifier" && left.text === "__all__") return "__all__";
1001
+ }
1002
+ }
1003
+ return null;
1004
+ }
1005
+ function extractPythonDocstring(bodyNode, source) {
1006
+ for (const child of bodyNode.namedChildren) {
1007
+ if (child.type === "expression_statement") {
1008
+ if (child.firstNamedChild?.type === "string") return {
1009
+ text: source.slice(child.startIndex, child.endIndex),
1010
+ startIndex: child.startIndex,
1011
+ endIndex: child.endIndex
1012
+ };
1013
+ }
1014
+ break;
1015
+ }
1016
+ return null;
1017
+ }
1018
+ function collectPrecedingPythonComments(children, index, source) {
1019
+ let lastIdx = -1;
1020
+ let firstIdx = -1;
1021
+ for (let k = index - 1; k >= 0; k--) if (children[k].type === "comment") {
1022
+ if (lastIdx === -1) lastIdx = k;
1023
+ firstIdx = k;
1024
+ } else break;
1025
+ if (firstIdx === -1) return null;
1026
+ const first = children[firstIdx];
1027
+ const last = children[lastIdx];
1028
+ return {
1029
+ text: source.slice(first.startIndex, last.endIndex),
1030
+ startIndex: first.startIndex,
1031
+ endIndex: last.endIndex
1032
+ };
1033
+ }
1034
+ const pythonMergeAdapter = {
1035
+ language: "python",
1036
+ grammarModule: "tree-sitter-python",
1037
+ testFilePatterns: [/(?:^|\/)test_.*\.py$/, /_test\.py$/],
1038
+ urlFingerprintConfig: PYTHON_URL_FINGERPRINT_CONFIG,
1039
+ parseStatements(tree, source) {
1040
+ const imports = [];
1041
+ const importAnchors = [];
1042
+ const statements = [];
1043
+ for (const child of tree.rootNode.children) {
1044
+ if (child.type === "comment") continue;
1045
+ if (child.type === "import_statement" || child.type === "import_from_statement") {
1046
+ const text = source.slice(child.startIndex, child.endIndex);
1047
+ imports.push({
1048
+ key: text.trim(),
1049
+ text
1050
+ });
1051
+ importAnchors.push(text);
1052
+ continue;
1053
+ }
1054
+ const declarationName = extractPythonDeclarationName(child);
1055
+ const kind = declarationName ? "declaration" : "other";
1056
+ statements.push({
1057
+ key: declarationName,
1058
+ kind,
1059
+ text: source.slice(child.startIndex, child.endIndex)
1060
+ });
1061
+ }
1062
+ return {
1063
+ imports,
1064
+ importAnchors,
1065
+ statements
1066
+ };
1067
+ },
1068
+ renderImports(imports) {
1069
+ return imports.map((entry) => entry.text);
1070
+ },
1071
+ extractDocstrings(tree, source) {
1072
+ const result = /* @__PURE__ */ new Map();
1073
+ const rootChildren = tree.rootNode.children;
1074
+ for (let i = 0; i < rootChildren.length; i++) {
1075
+ const child = rootChildren[i];
1076
+ const name = extractPythonDeclarationName(child);
1077
+ if (!name) continue;
1078
+ let docstring = null;
1079
+ const body = child.childForFieldName("body");
1080
+ if (body) docstring = extractPythonDocstring(body, source);
1081
+ if (!docstring) docstring = collectPrecedingPythonComments(rootChildren, i, source);
1082
+ const members = /* @__PURE__ */ new Map();
1083
+ if (child.type === "class_definition" && body) for (const member of body.namedChildren) {
1084
+ if (member.type !== "function_definition") continue;
1085
+ const memberName = member.childForFieldName("name")?.text;
1086
+ if (!memberName) continue;
1087
+ let memberDoc = null;
1088
+ const memberBody = member.childForFieldName("body");
1089
+ if (memberBody) memberDoc = extractPythonDocstring(memberBody, source);
1090
+ members.set(memberName, {
1091
+ docstring: memberDoc,
1092
+ declStartIndex: member.startIndex,
1093
+ declColumn: member.startPosition.column,
1094
+ urlFingerprint: extractUrlFingerprint(member, PYTHON_URL_FINGERPRINT_CONFIG)
1095
+ });
1096
+ }
1097
+ result.set(name, {
1098
+ docstring,
1099
+ declStartIndex: child.startIndex,
1100
+ declColumn: child.startPosition.column,
1101
+ members
1102
+ });
1103
+ }
1104
+ return result;
1105
+ }
1106
+ };
1107
+ //#endregion
1108
+ //#region src/engine/merge-adapters/ruby.ts
1109
+ const RUBY_URL_FINGERPRINT_CONFIG = {
1110
+ stringNodeTypes: ["string"],
1111
+ contentNodeTypes: ["string_content"],
1112
+ interpolationNodeTypes: ["interpolation"]
1113
+ };
1114
+ function isRequireCall(node) {
1115
+ if (node.type !== "call") return false;
1116
+ const method = node.childForFieldName("method");
1117
+ if (!method) return false;
1118
+ return method.text === "require" || method.text === "require_relative";
1119
+ }
1120
+ function extractRubyDeclarationName(node) {
1121
+ if (node.type !== "class" && node.type !== "module") return null;
1122
+ return node.childForFieldName("name")?.text ?? null;
1123
+ }
1124
+ function collectPrecedingRubyComments(children, index, source) {
1125
+ let lastIdx = -1;
1126
+ let firstIdx = -1;
1127
+ for (let k = index - 1; k >= 0; k--) {
1128
+ const prev = children[k];
1129
+ if ((prev.grammarType ?? prev.type) === "comment") if (source.slice(prev.startIndex, prev.endIndex).startsWith("#")) {
1130
+ if (lastIdx === -1) lastIdx = k;
1131
+ firstIdx = k;
1132
+ } else break;
1133
+ else break;
1134
+ }
1135
+ if (firstIdx === -1) return null;
1136
+ const first = children[firstIdx];
1137
+ const last = children[lastIdx];
1138
+ return {
1139
+ text: source.slice(first.startIndex, last.endIndex),
1140
+ startIndex: first.startIndex,
1141
+ endIndex: last.endIndex
1142
+ };
1143
+ }
1144
+ const rubyMergeAdapter = {
1145
+ language: "ruby",
1146
+ grammarModule: "tree-sitter-ruby",
1147
+ testFilePatterns: [/_test\.rb$/, /_spec\.rb$/],
1148
+ urlFingerprintConfig: RUBY_URL_FINGERPRINT_CONFIG,
1149
+ parseStatements(tree, source) {
1150
+ const imports = [];
1151
+ const importAnchors = [];
1152
+ const statements = [];
1153
+ for (const child of tree.rootNode.children) {
1154
+ if (child.type === "comment") continue;
1155
+ const declarationName = extractRubyDeclarationName(child);
1156
+ if (isRequireCall(child)) {
1157
+ const text = source.slice(child.startIndex, child.endIndex);
1158
+ imports.push({
1159
+ key: text.trim(),
1160
+ text
1161
+ });
1162
+ importAnchors.push(text);
1163
+ continue;
1164
+ }
1165
+ const kind = declarationName ? "declaration" : "other";
1166
+ statements.push({
1167
+ key: declarationName,
1168
+ kind,
1169
+ text: source.slice(child.startIndex, child.endIndex)
1170
+ });
1171
+ }
1172
+ return {
1173
+ imports,
1174
+ importAnchors,
1175
+ statements
1176
+ };
1177
+ },
1178
+ renderImports(imports) {
1179
+ return imports.map((entry) => entry.text);
1180
+ },
1181
+ extractDocstrings(tree, source) {
1182
+ const result = /* @__PURE__ */ new Map();
1183
+ const rootChildren = tree.rootNode.children;
1184
+ for (let i = 0; i < rootChildren.length; i++) {
1185
+ const child = rootChildren[i];
1186
+ const name = extractRubyDeclarationName(child);
1187
+ if (!name) continue;
1188
+ const docstring = collectPrecedingRubyComments(rootChildren, i, source);
1189
+ const members = /* @__PURE__ */ new Map();
1190
+ if (child.type === "class" || child.type === "module") {
1191
+ const allClassChildren = child.children;
1192
+ const body = child.childForFieldName("body");
1193
+ let firstMethodDocFromClass = null;
1194
+ for (let j = 0; j < allClassChildren.length; j++) if (allClassChildren[j] === body || allClassChildren[j].type === "body_statement") {
1195
+ firstMethodDocFromClass = collectPrecedingRubyComments(allClassChildren, j, source);
1196
+ break;
1197
+ }
1198
+ if (body) {
1199
+ const bodyChildren = body.children;
1200
+ let firstMethodHandled = false;
1201
+ for (let j = 0; j < bodyChildren.length; j++) {
1202
+ const member = bodyChildren[j];
1203
+ if (member.type !== "method") continue;
1204
+ const memberName = member.childForFieldName("name")?.text;
1205
+ if (!memberName) continue;
1206
+ let memberDoc;
1207
+ if (!firstMethodHandled && firstMethodDocFromClass) {
1208
+ memberDoc = firstMethodDocFromClass;
1209
+ firstMethodHandled = true;
1210
+ } else {
1211
+ memberDoc = collectPrecedingRubyComments(bodyChildren, j, source);
1212
+ firstMethodHandled = true;
1213
+ }
1214
+ members.set(memberName, {
1215
+ docstring: memberDoc,
1216
+ declStartIndex: member.startIndex,
1217
+ declColumn: member.startPosition.column,
1218
+ urlFingerprint: extractUrlFingerprint(member, RUBY_URL_FINGERPRINT_CONFIG)
1219
+ });
1220
+ }
1221
+ }
1222
+ }
1223
+ result.set(name, {
1224
+ docstring,
1225
+ declStartIndex: child.startIndex,
1226
+ declColumn: child.startPosition.column,
1227
+ members
1228
+ });
1229
+ }
1230
+ return result;
1231
+ }
1232
+ };
1233
+ //#endregion
1234
+ //#region src/engine/merge-adapters/rust.ts
1235
+ const RUST_URL_FINGERPRINT_CONFIG = {
1236
+ stringNodeTypes: ["string_literal"],
1237
+ contentNodeTypes: ["string_content"],
1238
+ interpolationNodeTypes: []
1239
+ };
1240
+ function declarationKey(node) {
1241
+ switch (node.type) {
1242
+ case "struct_item":
1243
+ case "enum_item":
1244
+ case "function_item":
1245
+ case "trait_item":
1246
+ case "type_item":
1247
+ case "const_item": return node.childForFieldName("name")?.text ?? null;
1248
+ case "impl_item": {
1249
+ const typeName = node.childForFieldName("type")?.text ?? "unknown";
1250
+ const traitName = node.childForFieldName("trait")?.text;
1251
+ return traitName ? `impl:${traitName}->${typeName}` : `impl:${typeName}`;
1252
+ }
1253
+ default: return null;
1254
+ }
1255
+ }
1256
+ function collectPrecedingRustDocComments(children, index, source) {
1257
+ let lastIdx = -1;
1258
+ let firstIdx = -1;
1259
+ for (let k = index - 1; k >= 0; k--) {
1260
+ const prev = children[k];
1261
+ if (prev.type === "line_comment") if (source.slice(prev.startIndex, prev.endIndex).startsWith("///")) {
1262
+ if (lastIdx === -1) lastIdx = k;
1263
+ firstIdx = k;
1264
+ } else break;
1265
+ else break;
1266
+ }
1267
+ if (firstIdx === -1) return null;
1268
+ const first = children[firstIdx];
1269
+ const last = children[lastIdx];
1270
+ return {
1271
+ text: source.slice(first.startIndex, last.endIndex),
1272
+ startIndex: first.startIndex,
1273
+ endIndex: last.endIndex
1274
+ };
1275
+ }
1276
+ //#endregion
1277
+ //#region src/engine/merge-adapters/index.ts
1278
+ const adapters = new Map([
1279
+ ["dotnet", dotnetMergeAdapter],
1280
+ ["elixir", elixirMergeAdapter],
1281
+ ["go", goMergeAdapter],
1282
+ ["kotlin", kotlinMergeAdapter],
1283
+ ["node", nodeMergeAdapter],
1284
+ ["php", phpMergeAdapter],
1285
+ ["python", pythonMergeAdapter],
1286
+ ["ruby", rubyMergeAdapter],
1287
+ ["rust", {
1288
+ language: "rust",
1289
+ grammarModule: "tree-sitter-rust",
1290
+ testFilePatterns: [/_test\.rs$/, /(?:^|\/)tests\/.*\.rs$/],
1291
+ urlFingerprintConfig: RUST_URL_FINGERPRINT_CONFIG,
1292
+ parseStatements(tree, source) {
1293
+ const imports = [];
1294
+ const importAnchors = [];
1295
+ const statements = [];
1296
+ for (const child of tree.rootNode.children) {
1297
+ if (child.type === "comment") continue;
1298
+ if (child.type === "use_declaration") {
1299
+ const text = source.slice(child.startIndex, child.endIndex);
1300
+ imports.push({
1301
+ key: text.trim(),
1302
+ text
1303
+ });
1304
+ importAnchors.push(text);
1305
+ continue;
1306
+ }
1307
+ const key = declarationKey(child);
1308
+ statements.push({
1309
+ key,
1310
+ kind: key ? "declaration" : "other",
1311
+ text: source.slice(child.startIndex, child.endIndex)
1312
+ });
1313
+ }
1314
+ return {
1315
+ imports,
1316
+ importAnchors,
1317
+ statements
1318
+ };
1319
+ },
1320
+ renderImports(imports) {
1321
+ return imports.map((entry) => entry.text);
1322
+ },
1323
+ extractDocstrings(tree, source) {
1324
+ const result = /* @__PURE__ */ new Map();
1325
+ const rootChildren = tree.rootNode.children;
1326
+ for (let i = 0; i < rootChildren.length; i++) {
1327
+ const child = rootChildren[i];
1328
+ const key = declarationKey(child);
1329
+ if (!key) continue;
1330
+ const docstring = collectPrecedingRustDocComments(rootChildren, i, source);
1331
+ result.set(key, {
1332
+ docstring,
1333
+ declStartIndex: child.startIndex,
1334
+ declColumn: child.startPosition.column,
1335
+ members: /* @__PURE__ */ new Map()
1336
+ });
1337
+ }
1338
+ return result;
1339
+ }
1340
+ }]
1341
+ ]);
1342
+ function getMergeAdapter(language) {
1343
+ return adapters.get(language);
1344
+ }
1345
+ //#endregion
1346
+ //#region src/engine/merger.ts
1347
+ /**
1348
+ * AST-level file merger using tree-sitter.
1349
+ *
1350
+ * When generating into a directory that already contains files, the merger
1351
+ * parses both the existing and generated content, detects top-level symbols,
1352
+ * and only appends symbols that don't already exist. Hand-written code is
1353
+ * never modified or removed.
1354
+ *
1355
+ * Supports any language with a tree-sitter grammar package.
1356
+ */
1357
+ const parserCache = /* @__PURE__ */ new Map();
1358
+ function findIgnoredRegions(source) {
1359
+ const regions = [];
1360
+ const startTag = "@oagen-ignore-start";
1361
+ const endTag = "@oagen-ignore-end";
1362
+ let searchFrom = 0;
1363
+ while (true) {
1364
+ const startIdx = source.indexOf(startTag, searchFrom);
1365
+ if (startIdx === -1) break;
1366
+ const endIdx = source.indexOf(endTag, startIdx + 19);
1367
+ if (endIdx === -1) break;
1368
+ regions.push({
1369
+ startIndex: startIdx,
1370
+ endIndex: endIdx + 17
1371
+ });
1372
+ searchFrom = endIdx + 17;
1373
+ }
1374
+ return regions;
1375
+ }
1376
+ function buildIgnoredSymbolNames(docstrings, regions) {
1377
+ if (regions.length === 0) return /* @__PURE__ */ new Set();
1378
+ const ignored = /* @__PURE__ */ new Set();
1379
+ for (const [name, info] of docstrings) if (regions.some((r) => info.declStartIndex >= r.startIndex && info.declStartIndex <= r.endIndex)) ignored.add(name);
1380
+ return ignored;
1381
+ }
1382
+ /**
1383
+ * Check if a tree-sitter grammar is configured for the given language.
1384
+ */
1385
+ function hasGrammar(language) {
1386
+ return getMergeAdapter(language) !== void 0;
1387
+ }
1388
+ async function getParser(language) {
1389
+ const cached = parserCache.get(language);
1390
+ if (cached) return cached;
1391
+ const adapter = getMergeAdapter(language);
1392
+ if (!adapter) throw new Error(`No tree-sitter grammar configured for language "${language}". Add a merge adapter and install the corresponding tree-sitter grammar package.`);
1393
+ const mod = await import(adapter.grammarModule);
1394
+ const exported = adapter.resolveGrammar ? adapter.resolveGrammar(mod) : mod;
1395
+ const grammar = exported.default ?? exported;
1396
+ const parser = new Parser();
1397
+ parser.setLanguage(grammar);
1398
+ parserCache.set(language, parser);
1399
+ return parser;
1400
+ }
1401
+ /**
1402
+ * Extract top-level statements from generated source with their names
1403
+ * and exact text span.
1404
+ */
1405
+ async function extractStatements(source, language) {
1406
+ const parser = await getParser(language);
1407
+ const adapter = getMergeAdapter(language);
1408
+ if (!adapter) throw new Error(`No merge adapter configured for language "${language}"`);
1409
+ const tree = safeParse(parser, source);
1410
+ return adapter.parseStatements(tree, source);
1411
+ }
1412
+ /**
1413
+ * Merge generated content into an existing file.
1414
+ *
1415
+ * - Parses both files with tree-sitter
1416
+ * - Detects which top-level symbols already exist
1417
+ * - Appends only new symbols
1418
+ * - Never modifies or removes existing code
1419
+ * - Adds auto-generated header if not present
1420
+ */
1421
+ async function mergeIntoExisting(existingContent, generatedContent, language, header) {
1422
+ const adapter = getMergeAdapter(language);
1423
+ if (!adapter) throw new Error(`No merge adapter configured for language "${language}"`);
1424
+ const existingStatements = await extractStatements(existingContent, language);
1425
+ const generatedStatements = await extractStatements(generatedContent, language);
1426
+ const existingKeys = /* @__PURE__ */ new Set();
1427
+ const existingUnnamedTexts = /* @__PURE__ */ new Set();
1428
+ const existingImportKeys = /* @__PURE__ */ new Set();
1429
+ const existingReexports = /* @__PURE__ */ new Set();
1430
+ let lastImportEndIndex = -1;
1431
+ const existingImportedNames = /* @__PURE__ */ new Set();
1432
+ for (const imp of existingStatements.imports) {
1433
+ existingImportKeys.add(imp.key);
1434
+ const braceMatch = imp.text.match(/\{([^}]+)\}/);
1435
+ if (braceMatch) for (const name of braceMatch[1].split(",")) {
1436
+ const trimmed = name.replace(/\btype\b/, "").trim();
1437
+ if (trimmed) existingImportedNames.add(trimmed);
1438
+ }
1439
+ }
1440
+ for (const anchor of existingStatements.importAnchors) lastImportEndIndex = existingContent.slice(0, existingContent.indexOf(anchor)).split("\n").length - 1 + anchor.split("\n").length - 1;
1441
+ if (lastImportEndIndex === -1 && existingStatements.importInsertionAnchor) lastImportEndIndex = existingContent.slice(0, existingContent.indexOf(existingStatements.importInsertionAnchor)).split("\n").length - 1 + existingStatements.importInsertionAnchor.split("\n").length - 1;
1442
+ for (const stmt of existingStatements.statements) {
1443
+ if (stmt.kind === "reexport") existingReexports.add(adapter.normalizeReexport ? adapter.normalizeReexport(stmt.text.trim()) : stmt.text.trim());
1444
+ if (stmt.key) existingKeys.add(stmt.key);
1445
+ else existingUnnamedTexts.add(stmt.text.trim());
1446
+ }
1447
+ const headerLine = header.trim();
1448
+ const newImports = [];
1449
+ const toAppend = [];
1450
+ let preserved = 0;
1451
+ for (const imp of generatedStatements.imports) {
1452
+ if (imp.text.trim() === headerLine) continue;
1453
+ if (existingImportKeys.has(imp.key)) {
1454
+ const braceMatch = imp.text.match(/\{([^}]+)\}/);
1455
+ if (braceMatch) {
1456
+ const newNames = braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean).filter((n) => !existingImportedNames.has(n) && !existingKeys.has(n));
1457
+ if (newNames.length > 0) {
1458
+ const prefix = imp.text.trimStart().startsWith("import type") ? "import type" : "import";
1459
+ const sourceMatch = imp.text.match(/from\s+(['"][^'"]+['"]);?/);
1460
+ if (sourceMatch) newImports.push({
1461
+ key: imp.key + "#supplemental",
1462
+ text: `${prefix} { ${newNames.join(", ")} } from ${sourceMatch[1]};`
1463
+ });
1464
+ }
1465
+ }
1466
+ preserved++;
1467
+ continue;
1468
+ }
1469
+ const braceMatch = imp.text.match(/\{([^}]+)\}/);
1470
+ if (braceMatch) {
1471
+ const names = braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean);
1472
+ const isAlreadyDefined = (n) => existingImportedNames.has(n) || existingKeys.has(n);
1473
+ if (names.length > 0 && names.every(isAlreadyDefined)) {
1474
+ preserved++;
1475
+ continue;
1476
+ }
1477
+ if (names.some(isAlreadyDefined)) {
1478
+ const newNames = names.filter((n) => !isAlreadyDefined(n));
1479
+ if (newNames.length === 0) {
1480
+ preserved++;
1481
+ continue;
1482
+ }
1483
+ const prefix = imp.text.trimStart().startsWith("import type") ? "import type" : "import";
1484
+ const sourceMatch = imp.text.match(/from\s+(['"][^'"]+['"]);?/);
1485
+ if (sourceMatch) {
1486
+ const newText = `${prefix} { ${newNames.join(", ")} } from ${sourceMatch[1]};`;
1487
+ newImports.push({
1488
+ key: imp.key,
1489
+ text: newText
1490
+ });
1491
+ continue;
1492
+ }
1493
+ }
1494
+ }
1495
+ newImports.push(imp);
1496
+ }
1497
+ for (const stmt of generatedStatements.statements) {
1498
+ if (stmt.text.trim() === headerLine) continue;
1499
+ if (stmt.kind === "reexport") {
1500
+ const normalizedText = adapter.normalizeReexport ? adapter.normalizeReexport(stmt.text.trim()) : stmt.text.trim();
1501
+ if (existingReexports.has(normalizedText) || stmt.key !== null && existingKeys.has(stmt.key)) {
1502
+ preserved++;
1503
+ continue;
1504
+ }
1505
+ }
1506
+ if (stmt.key && (existingKeys.has(stmt.key) || existingImportedNames.has(stmt.key))) {
1507
+ preserved++;
1508
+ continue;
1509
+ }
1510
+ if (!stmt.key) {
1511
+ if (existingUnnamedTexts.has(stmt.text.trim())) {
1512
+ preserved++;
1513
+ continue;
1514
+ }
1515
+ }
1516
+ toAppend.push(stmt.text);
1517
+ }
1518
+ if (newImports.length === 0 && toAppend.length === 0) {
1519
+ if (!adapter.extractMembers) {}
1520
+ }
1521
+ let result = existingContent;
1522
+ if (toAppend.length > 0) result = result.trimEnd() + "\n\n" + toAppend.join("\n\n") + "\n";
1523
+ let deepAdded = 0;
1524
+ const insertions = [];
1525
+ if (adapter.extractMembers) {
1526
+ const parser = await getParser(language);
1527
+ const resultTree = safeParse(parser, result);
1528
+ const generatedTree = safeParse(parser, generatedContent);
1529
+ const resultSymbols = adapter.extractMembers(resultTree, result);
1530
+ const generatedSymbols = adapter.extractMembers(generatedTree, generatedContent);
1531
+ const existingTree = safeParse(parser, existingContent);
1532
+ const deepIgnoredSymbols = buildIgnoredSymbolNames(adapter.extractDocstrings(existingTree, existingContent), findIgnoredRegions(existingContent));
1533
+ for (const [symbolName, genSymbol] of generatedSymbols) {
1534
+ if (deepIgnoredSymbols.has(symbolName)) continue;
1535
+ const existSymbol = resultSymbols.get(symbolName);
1536
+ if (!existSymbol) continue;
1537
+ const existingMemberKeys = new Set(existSymbol.members.map((m) => m.key));
1538
+ const newMembers = genSymbol.members.filter((m) => !existingMemberKeys.has(m.key));
1539
+ if (newMembers.length > 0) {
1540
+ if (adapter.shouldSkipDeepMerge?.(symbolName, existingMemberKeys, newMembers)) continue;
1541
+ const indent = existSymbol.memberIndent ?? " ";
1542
+ const insertText = newMembers.map((m) => indent + m.text).join("\n");
1543
+ insertions.push({
1544
+ line: existSymbol.bodyEndLine,
1545
+ text: insertText
1546
+ });
1547
+ deepAdded += newMembers.length;
1548
+ }
1549
+ }
1550
+ if (insertions.length > 0) {
1551
+ insertions.sort((a, b) => b.line - a.line);
1552
+ const resultLines = result.split("\n");
1553
+ for (const ins of insertions) resultLines.splice(ins.line, 0, ins.text);
1554
+ result = resultLines.join("\n");
1555
+ }
1556
+ }
1557
+ if (newImports.length > 0 && (toAppend.length > 0 || deepAdded > 0)) {
1558
+ const addedParts = [...toAppend];
1559
+ if (insertions) for (const ins of insertions) addedParts.push(ins.text);
1560
+ const addedCodeText = addedParts.join("\n");
1561
+ const filteredImports = newImports.filter((imp) => {
1562
+ const braceMatch = imp.text.match(/\{([^}]+)\}/);
1563
+ if (!braceMatch) return true;
1564
+ return braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean).some((name) => addedCodeText.includes(name));
1565
+ });
1566
+ if (filteredImports.length > 0) {
1567
+ const renderedImports = adapter.renderImports ? adapter.renderImports(filteredImports) : filteredImports.map((entry) => entry.text);
1568
+ const lines = result.split("\n");
1569
+ const insertIdx = lastImportEndIndex + 1;
1570
+ lines.splice(insertIdx, 0, ...renderedImports);
1571
+ result = lines.join("\n");
1572
+ }
1573
+ }
1574
+ let docstringUpdates = 0;
1575
+ {
1576
+ const parser = await getParser(language);
1577
+ const resultTree = safeParse(parser, result);
1578
+ const generatedTree = safeParse(parser, generatedContent);
1579
+ const resultDocs = adapter.extractDocstrings(resultTree, result);
1580
+ const generatedDocs = adapter.extractDocstrings(generatedTree, generatedContent);
1581
+ const docIgnoredSymbols = buildIgnoredSymbolNames(resultDocs, findIgnoredRegions(result));
1582
+ const edits = [];
1583
+ for (const [symbolName, genInfo] of generatedDocs) {
1584
+ const existInfo = resultDocs.get(symbolName);
1585
+ if (!existInfo) continue;
1586
+ if (docIgnoredSymbols.has(symbolName)) continue;
1587
+ const genDoc = genInfo.docstring && genInfo.docstring.text.trim() !== headerLine ? genInfo.docstring : null;
1588
+ if (genDoc) if (existInfo.docstring) {
1589
+ if (!existInfo.docstring.text.includes("@oagen-ignore") && existInfo.docstring.text !== genDoc.text) {
1590
+ edits.push({
1591
+ start: existInfo.docstring.startIndex,
1592
+ end: existInfo.docstring.endIndex,
1593
+ newText: genDoc.text
1594
+ });
1595
+ docstringUpdates++;
1596
+ }
1597
+ } else {
1598
+ const lineStart = existInfo.declStartIndex - existInfo.declColumn;
1599
+ const indent = " ".repeat(existInfo.declColumn);
1600
+ edits.push({
1601
+ start: lineStart,
1602
+ end: lineStart,
1603
+ newText: indent + genDoc.text + "\n"
1604
+ });
1605
+ docstringUpdates++;
1606
+ }
1607
+ const matchedExistMembers = /* @__PURE__ */ new Set();
1608
+ for (const [memberName, genMember] of genInfo.members) {
1609
+ const existMember = existInfo.members.get(memberName);
1610
+ if (!existMember || !genMember.docstring) continue;
1611
+ matchedExistMembers.add(memberName);
1612
+ if (existMember.docstring) {
1613
+ if (!existMember.docstring.text.includes("@oagen-ignore") && existMember.docstring.text !== genMember.docstring.text) {
1614
+ edits.push({
1615
+ start: existMember.docstring.startIndex,
1616
+ end: existMember.docstring.endIndex,
1617
+ newText: genMember.docstring.text
1618
+ });
1619
+ docstringUpdates++;
1620
+ }
1621
+ } else {
1622
+ const lineStart = existMember.declStartIndex - existMember.declColumn;
1623
+ const indent = " ".repeat(existMember.declColumn);
1624
+ edits.push({
1625
+ start: lineStart,
1626
+ end: lineStart,
1627
+ newText: indent + genMember.docstring.text + "\n"
1628
+ });
1629
+ docstringUpdates++;
1630
+ }
1631
+ }
1632
+ for (const [_genName, genMember] of genInfo.members) {
1633
+ if (!genMember.docstring || !genMember.urlFingerprint) continue;
1634
+ for (const [existName, existMember] of existInfo.members) {
1635
+ if (matchedExistMembers.has(existName)) continue;
1636
+ if (!existMember.urlFingerprint || existMember.urlFingerprint !== genMember.urlFingerprint) continue;
1637
+ if (existMember.docstring) {
1638
+ if (existMember.docstring.text.includes("@oagen-ignore")) continue;
1639
+ if (existMember.docstring.text !== genMember.docstring.text) {
1640
+ edits.push({
1641
+ start: existMember.docstring.startIndex,
1642
+ end: existMember.docstring.endIndex,
1643
+ newText: genMember.docstring.text
1644
+ });
1645
+ docstringUpdates++;
1646
+ }
1647
+ } else {
1648
+ const lineStart = existMember.declStartIndex - existMember.declColumn;
1649
+ const indent = " ".repeat(existMember.declColumn);
1650
+ edits.push({
1651
+ start: lineStart,
1652
+ end: lineStart,
1653
+ newText: indent + genMember.docstring.text + "\n"
1654
+ });
1655
+ docstringUpdates++;
1656
+ }
1657
+ matchedExistMembers.add(existName);
1658
+ break;
1659
+ }
1660
+ }
1661
+ }
1662
+ if (edits.length > 0) {
1663
+ edits.sort((a, b) => b.start - a.start);
1664
+ for (const edit of edits) result = result.slice(0, edit.start) + edit.newText + result.slice(edit.end);
1665
+ }
1666
+ }
1667
+ const totalAdded = (toAppend.length > 0 || deepAdded > 0 ? adapter.renderImports ? adapter.renderImports(newImports).length : newImports.length : 0) + toAppend.length + deepAdded;
1668
+ if (totalAdded === 0 && docstringUpdates === 0) return {
1669
+ content: existingContent,
1670
+ added: 0,
1671
+ preserved,
1672
+ changed: false
1673
+ };
1674
+ return {
1675
+ content: result,
1676
+ added: totalAdded,
1677
+ preserved,
1678
+ changed: true
1679
+ };
1680
+ }
1681
+ //#endregion
1682
+ //#region src/engine/json-merge.ts
1683
+ /**
1684
+ * Deep merge two parsed JSON values.
1685
+ *
1686
+ * - Objects: recursively merge, preserving existing keys not in generated
1687
+ * - Arrays: generated replaces existing entirely (generated owns array contents)
1688
+ * - Primitives: generated wins
1689
+ */
1690
+ function deepMergeJson(existing, generated) {
1691
+ if (typeof existing === "object" && existing !== null && !Array.isArray(existing) && typeof generated === "object" && generated !== null && !Array.isArray(generated)) {
1692
+ const merged = { ...existing };
1693
+ for (const [key, value] of Object.entries(generated)) if (key in merged) merged[key] = deepMergeJson(merged[key], value);
1694
+ else merged[key] = value;
1695
+ return merged;
1696
+ }
1697
+ return generated;
1698
+ }
1699
+ //#endregion
1700
+ //#region src/engine/writer.ts
1701
+ /**
1702
+ * Write generated files to disk with AST-level merging.
1703
+ *
1704
+ * When a file already exists:
1705
+ * - skipIfExists → leave it alone entirely
1706
+ * - AST merge available → parse both, append only new symbols
1707
+ * - No grammar → skip (never clobber hand-written code)
1708
+ *
1709
+ * New files are always written in full.
1710
+ */
1711
+ async function writeFiles(files, outputDir, options) {
1712
+ const result = {
1713
+ written: [],
1714
+ merged: [],
1715
+ skipped: [],
1716
+ identical: [],
1717
+ ignored: []
1718
+ };
1719
+ const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
1720
+ const language = options?.language;
1721
+ const header = options?.header ?? "";
1722
+ for (const file of sorted) {
1723
+ const fullPath = path$1.join(outputDir, file.path);
1724
+ let existingContent = null;
1725
+ try {
1726
+ existingContent = await fs.readFile(fullPath, "utf-8");
1727
+ } catch {}
1728
+ if (existingContent === null) {
1729
+ await fs.mkdir(path$1.dirname(fullPath), { recursive: true });
1730
+ await fs.writeFile(fullPath, file.content, "utf-8");
1731
+ result.written.push(file.path);
1732
+ continue;
1733
+ }
1734
+ if (file.skipIfExists) {
1735
+ if (header && !existingContent.startsWith(header)) await fs.writeFile(fullPath, header + "\n\n" + existingContent, "utf-8");
1736
+ result.skipped.push(file.path);
1737
+ continue;
1738
+ }
1739
+ if (existingContent.includes("@oagen-ignore-file")) {
1740
+ result.ignored.push(file.path);
1741
+ continue;
1742
+ }
1743
+ const adapter = language ? getMergeAdapter(language) : void 0;
1744
+ if (adapter?.testFilePatterns ? adapter.testFilePatterns.some((p) => p.test(file.path)) : isDefaultTestFile(file.path)) {
1745
+ result.skipped.push(file.path);
1746
+ continue;
1747
+ }
1748
+ if (existingContent === file.content) {
1749
+ result.identical.push(file.path);
1750
+ continue;
1751
+ }
1752
+ if (file.overwriteExisting) {
1753
+ await fs.writeFile(fullPath, file.content, "utf-8");
1754
+ result.written.push(file.path);
1755
+ continue;
1756
+ }
1757
+ if (file.path.endsWith(".json")) {
1758
+ try {
1759
+ const merged = deepMergeJson(JSON.parse(existingContent), JSON.parse(file.content));
1760
+ const mergedContent = JSON.stringify(merged, null, 2) + "\n";
1761
+ if (mergedContent === existingContent) result.identical.push(file.path);
1762
+ else {
1763
+ await fs.writeFile(fullPath, mergedContent, "utf-8");
1764
+ result.merged.push(file.path);
1765
+ }
1766
+ } catch {
1767
+ await fs.writeFile(fullPath, file.content, "utf-8");
1768
+ result.written.push(file.path);
1769
+ }
1770
+ continue;
1771
+ }
1772
+ if (language && hasGrammar(language)) try {
1773
+ const mergeResult = await mergeIntoExisting(existingContent, file.content, language, header);
1774
+ let finalContent = mergeResult.content;
1775
+ if (header && !finalContent.startsWith(header)) finalContent = header + "\n\n" + finalContent;
1776
+ if (!mergeResult.changed && finalContent === existingContent) {
1777
+ result.identical.push(file.path);
1778
+ continue;
1779
+ }
1780
+ await fs.writeFile(fullPath, finalContent, "utf-8");
1781
+ result.merged.push(file.path);
1782
+ continue;
1783
+ } catch (err) {
1784
+ console.warn(`[oagen] AST merge failed for ${file.path}, falling back to overwrite.${err instanceof Error ? ` ${err.message}` : ""}`);
1785
+ await fs.writeFile(fullPath, file.content, "utf-8");
1786
+ result.written.push(file.path);
1787
+ continue;
1788
+ }
1789
+ result.skipped.push(file.path);
1790
+ }
1791
+ return result;
1792
+ }
1793
+ /** Comprehensive fallback when no adapter is available. */
1794
+ function isDefaultTestFile(filePath) {
1795
+ return /\.(spec|test)\.[jt]sx?$/.test(filePath) || filePath.endsWith("_test.go") || filePath.endsWith("_test.rb") || filePath.endsWith("_spec.rb") || /(?:^|\/)test_.*\.py$/.test(filePath) || filePath.endsWith("_test.py") || filePath.endsWith("Test.php") || filePath.endsWith("_test.rs") || /(?:^|\/)tests\/.*\.rs$/.test(filePath);
1796
+ }
1797
+ //#endregion
1798
+ //#region src/engine/generate-files.ts
1799
+ function buildEmitterContext(spec, options) {
1800
+ return {
1801
+ namespace: toSnakeCase(options.namespace),
1802
+ namespacePascal: options.namespace,
1803
+ spec,
1804
+ outputDir: options.outputDir,
1805
+ apiSurface: options.apiSurface,
1806
+ overlayLookup: options.overlayLookup
1807
+ };
1808
+ }
1809
+ /** Collect all generated files from an emitter (no headers, no path prefixes). */
1810
+ function generateAllFiles(spec, emitter, ctx) {
1811
+ return [
1812
+ ...emitter.generateModels(spec.models, ctx),
1813
+ ...emitter.generateEnums(spec.enums, ctx),
1814
+ ...emitter.generateResources(spec.services, ctx),
1815
+ ...emitter.generateClient(spec, ctx),
1816
+ ...emitter.generateErrors(ctx),
1817
+ ...emitter.generateConfig(ctx),
1818
+ ...emitter.generateTypeSignatures?.(spec, ctx) ?? [],
1819
+ ...emitter.generateTests(spec, ctx),
1820
+ ...emitter.generateManifest?.(spec, ctx) ?? []
1821
+ ];
1822
+ }
1823
+ /** Apply file header to generated files, respecting headerPlacement and JSON files. */
1824
+ function applyFileHeaders(files, header) {
1825
+ return files.map((f) => ({
1826
+ ...f,
1827
+ content: !header || f.path.endsWith(".json") || f.headerPlacement === "skip" ? f.content : header + "\n\n" + f.content,
1828
+ skipIfExists: f.skipIfExists ?? false
1829
+ }));
1830
+ }
1831
+ function generateFiles(spec, emitter, options) {
1832
+ const ctx = buildEmitterContext(spec, options);
1833
+ const files = generateAllFiles(spec, emitter, ctx);
1834
+ const header = emitter.fileHeader();
1835
+ return {
1836
+ files: applyFileHeaders(files, header),
1837
+ ctx,
1838
+ header
1839
+ };
1840
+ }
1841
+ //#endregion
1842
+ //#region src/engine/integrate.ts
1843
+ function mapFilesForTargetIntegration(files, language) {
1844
+ const langPrefix = `${language}/`;
1845
+ return files.filter((f) => f.integrateTarget !== false).map((f) => {
1846
+ const stripped = f.path.startsWith(langPrefix) ? f.path.replace(langPrefix, "") : f.path;
1847
+ return {
1848
+ ...f,
1849
+ skipIfExists: false,
1850
+ path: stripped
1851
+ };
1852
+ });
1853
+ }
1854
+ async function integrateGeneratedFiles(opts) {
1855
+ return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.targetDir, {
1856
+ language: opts.language,
1857
+ header: opts.header
1858
+ });
1859
+ }
1860
+ //#endregion
1861
+ //#region src/engine/orchestrator.ts
1862
+ async function generate(spec, emitter, options) {
1863
+ const { files: withHeaders, header } = generateFiles(spec, emitter, options);
1864
+ if (options.dryRun) {
1865
+ if (options.target) {
1866
+ console.log(`\nTarget integration (${options.target}):`);
1867
+ for (const f of withHeaders) console.log(` ${f.path}`);
1868
+ }
1869
+ return withHeaders;
1870
+ }
1871
+ const writeResult = await writeFiles(withHeaders, options.outputDir, {
1872
+ language: emitter.language,
1873
+ header
1874
+ });
1875
+ if (writeResult.merged.length > 0) console.log(`Merged into ${writeResult.merged.length} existing files (additive only)`);
1876
+ if (writeResult.ignored.length > 0) console.log(`Ignored ${writeResult.ignored.length} files (@oagen-ignore-file)`);
1877
+ if (options.target) {
1878
+ const targetResult = await integrateGeneratedFiles({
1879
+ files: withHeaders,
1880
+ language: emitter.language,
1881
+ targetDir: options.target,
1882
+ header
1883
+ });
1884
+ if (targetResult.written.length > 0) console.log(`Target: created ${targetResult.written.length} new files`);
1885
+ if (targetResult.merged.length > 0) console.log(`Target: merged into ${targetResult.merged.length} existing files (additive only)`);
1886
+ if (targetResult.skipped.length > 0) console.log(`Target: skipped ${targetResult.skipped.length} files (excluded or no grammar)`);
1887
+ }
1888
+ return withHeaders;
1889
+ }
1890
+ //#endregion
1891
+ //#region src/engine/registry.ts
1892
+ const emitters = /* @__PURE__ */ new Map();
1893
+ function registerEmitter(emitter) {
1894
+ emitters.set(emitter.language, emitter);
1895
+ }
1896
+ function getEmitter(language) {
1897
+ const emitter = emitters.get(language);
1898
+ if (!emitter) throw new RegistryError(`Unknown language: ${language}. Available: ${[...emitters.keys()].join(", ") || "(none)"}`, `Register an emitter for "${language}" via registerEmitter() or check your oagen.config.ts plugin configuration.`);
1899
+ return emitter;
1900
+ }
1901
+ //#endregion
1902
+ //#region src/differ/classify.ts
1903
+ function classifyFieldChange(kind, fieldName, isRequired) {
1904
+ switch (kind) {
1905
+ case "field-added": return {
1906
+ kind,
1907
+ fieldName,
1908
+ classification: isRequired ? "breaking" : "additive"
1909
+ };
1910
+ case "field-removed": return {
1911
+ kind,
1912
+ fieldName,
1913
+ classification: "breaking"
1914
+ };
1915
+ case "field-type-changed": return {
1916
+ kind,
1917
+ fieldName,
1918
+ classification: "breaking"
1919
+ };
1920
+ case "field-required-changed": return {
1921
+ kind,
1922
+ fieldName,
1923
+ classification: isRequired ? "breaking" : "additive",
1924
+ details: isRequired ? "optional → required" : "required → optional"
1925
+ };
1926
+ case "field-access-changed": return {
1927
+ kind,
1928
+ fieldName,
1929
+ classification: "breaking"
1930
+ };
1931
+ }
1932
+ }
1933
+ function classifyParamChange(kind, paramName, isRequired) {
1934
+ switch (kind) {
1935
+ case "param-added": return {
1936
+ kind,
1937
+ paramName,
1938
+ classification: isRequired ? "breaking" : "additive"
1939
+ };
1940
+ case "param-removed": return {
1941
+ kind,
1942
+ paramName,
1943
+ classification: "breaking"
1944
+ };
1945
+ case "param-type-changed": return {
1946
+ kind,
1947
+ paramName,
1948
+ classification: "breaking"
1949
+ };
1950
+ case "param-required-changed": return {
1951
+ kind,
1952
+ paramName,
1953
+ classification: isRequired ? "breaking" : "additive"
1954
+ };
1955
+ }
1956
+ }
1957
+ //#endregion
1958
+ //#region src/differ/models.ts
1959
+ function diffModels(oldModels, newModels) {
1960
+ const changes = [];
1961
+ const oldByName = new Map(oldModels.map((m) => [m.name, m]));
1962
+ const newByName = new Map(newModels.map((m) => [m.name, m]));
1963
+ for (const [name] of newByName) if (!oldByName.has(name)) changes.push({
1964
+ kind: "model-added",
1965
+ name,
1966
+ classification: "additive"
1967
+ });
1968
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push({
1969
+ kind: "model-removed",
1970
+ name,
1971
+ classification: "breaking"
1972
+ });
1973
+ for (const [name, newModel] of newByName) {
1974
+ const oldModel = oldByName.get(name);
1975
+ if (!oldModel) continue;
1976
+ const fieldChanges = diffFields(oldModel, newModel);
1977
+ if (fieldChanges.length > 0) {
1978
+ const hasBreaking = fieldChanges.some((fc) => fc.classification === "breaking");
1979
+ changes.push({
1980
+ kind: "model-modified",
1981
+ name,
1982
+ fieldChanges,
1983
+ classification: hasBreaking ? "breaking" : "additive"
1984
+ });
1985
+ }
1986
+ }
1987
+ return changes;
1988
+ }
1989
+ function diffFields(oldModel, newModel) {
1990
+ const changes = [];
1991
+ const oldByName = new Map(oldModel.fields.map((f) => [f.name, f]));
1992
+ const newByName = new Map(newModel.fields.map((f) => [f.name, f]));
1993
+ for (const [name, field] of newByName) if (!oldByName.has(name)) changes.push(classifyFieldChange("field-added", name, field.required));
1994
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push(classifyFieldChange("field-removed", name));
1995
+ for (const [name, newField] of newByName) {
1996
+ const oldField = oldByName.get(name);
1997
+ if (!oldField) continue;
1998
+ if (!typeRefsEqual(oldField.type, newField.type)) changes.push({
1999
+ kind: "field-type-changed",
2000
+ fieldName: name,
2001
+ classification: "breaking",
2002
+ details: `type changed`
2003
+ });
2004
+ if (oldField.required !== newField.required) changes.push(classifyFieldChange("field-required-changed", name, newField.required));
2005
+ if (!!oldField.readOnly !== !!newField.readOnly || !!oldField.writeOnly !== !!newField.writeOnly) changes.push(classifyFieldChange("field-access-changed", name));
2006
+ }
2007
+ return changes;
2008
+ }
2009
+ function typeRefsEqual(a, b) {
2010
+ if (a.kind !== b.kind) return false;
2011
+ switch (a.kind) {
2012
+ case "primitive": return b.kind === "primitive" && a.type === b.type && (a.format ?? "") === (b.format ?? "");
2013
+ case "array": return b.kind === "array" && typeRefsEqual(a.items, b.items);
2014
+ case "model": return b.kind === "model" && a.name === b.name;
2015
+ case "enum": return b.kind === "enum" && a.name === b.name;
2016
+ case "literal": return b.kind === "literal" && a.value === b.value;
2017
+ case "nullable": return b.kind === "nullable" && typeRefsEqual(a.inner, b.inner);
2018
+ case "union": {
2019
+ if (b.kind !== "union") return false;
2020
+ if (a.variants.length !== b.variants.length) return false;
2021
+ if (!a.variants.every((v, i) => typeRefsEqual(v, b.variants[i]))) return false;
2022
+ if (!a.discriminator && !b.discriminator) return true;
2023
+ if (!a.discriminator || !b.discriminator) return false;
2024
+ if (a.discriminator.property !== b.discriminator.property) return false;
2025
+ const aKeys = Object.keys(a.discriminator.mapping).sort();
2026
+ const bKeys = Object.keys(b.discriminator.mapping).sort();
2027
+ if (aKeys.length !== bKeys.length) return false;
2028
+ return aKeys.every((k, i) => k === bKeys[i] && a.discriminator.mapping[k] === b.discriminator.mapping[k]);
2029
+ }
2030
+ case "map": return b.kind === "map" && typeRefsEqual(a.valueType, b.valueType);
2031
+ }
2032
+ return assertNever(a);
2033
+ }
2034
+ //#endregion
2035
+ //#region src/differ/operations.ts
2036
+ function diffOperations(serviceName, oldOps, newOps) {
2037
+ const changes = [];
2038
+ const oldByName = new Map(oldOps.map((o) => [o.name, o]));
2039
+ const newByName = new Map(newOps.map((o) => [o.name, o]));
2040
+ for (const [name] of newByName) if (!oldByName.has(name)) changes.push({
2041
+ kind: "operation-added",
2042
+ serviceName,
2043
+ operationName: name,
2044
+ classification: "additive"
2045
+ });
2046
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push({
2047
+ kind: "operation-removed",
2048
+ serviceName,
2049
+ operationName: name,
2050
+ classification: "breaking"
2051
+ });
2052
+ for (const [name, newOp] of newByName) {
2053
+ const oldOp = oldByName.get(name);
2054
+ if (!oldOp) continue;
2055
+ const paramChanges = diffParams(oldOp, newOp);
2056
+ const responseChanged = !typeRefsEqual(oldOp.response, newOp.response);
2057
+ const requestBodyChanged = !requestBodiesEqual(oldOp.requestBody, newOp.requestBody);
2058
+ const httpMethodChanged = oldOp.httpMethod !== newOp.httpMethod;
2059
+ const pathChanged = oldOp.path !== newOp.path;
2060
+ const paginatedChanged = !!oldOp.pagination !== !!newOp.pagination;
2061
+ const injectIdempotencyKeyChanged = oldOp.injectIdempotencyKey !== newOp.injectIdempotencyKey;
2062
+ const errorsDiff = classifyErrorsChange(oldOp.errors, newOp.errors);
2063
+ const errorsChanged = errorsDiff !== "none";
2064
+ if (paramChanges.length > 0 || responseChanged || requestBodyChanged || httpMethodChanged || pathChanged || paginatedChanged || injectIdempotencyKeyChanged || errorsChanged) {
2065
+ const paginatedBreaking = paginatedChanged && !newOp.pagination;
2066
+ const injectIdempotencyKeyBreaking = injectIdempotencyKeyChanged && !newOp.injectIdempotencyKey;
2067
+ const hasBreaking = responseChanged || requestBodyChanged || httpMethodChanged || pathChanged || paginatedBreaking || injectIdempotencyKeyBreaking || errorsDiff === "breaking" || paramChanges.some((pc) => pc.classification === "breaking");
2068
+ changes.push({
2069
+ kind: "operation-modified",
2070
+ serviceName,
2071
+ operationName: name,
2072
+ paramChanges,
2073
+ responseChanged,
2074
+ requestBodyChanged,
2075
+ httpMethodChanged,
2076
+ pathChanged,
2077
+ paginatedChanged,
2078
+ injectIdempotencyKeyChanged,
2079
+ errorsChanged,
2080
+ classification: hasBreaking ? "breaking" : "additive"
2081
+ });
2082
+ }
2083
+ }
2084
+ return changes;
2085
+ }
2086
+ function requestBodiesEqual(a, b) {
2087
+ if (!a && !b) return true;
2088
+ if (!a || !b) return false;
2089
+ return typeRefsEqual(a, b);
2090
+ }
2091
+ function classifyErrorsChange(oldErrors, newErrors) {
2092
+ const oldByCode = new Map(oldErrors.map((e) => [e.statusCode, e]));
2093
+ const newByCode = new Map(newErrors.map((e) => [e.statusCode, e]));
2094
+ let hasAdditive = false;
2095
+ let hasBreaking = false;
2096
+ for (const code of newByCode.keys()) if (!oldByCode.has(code)) hasAdditive = true;
2097
+ for (const code of oldByCode.keys()) if (!newByCode.has(code)) hasBreaking = true;
2098
+ for (const [code, newErr] of newByCode) {
2099
+ const oldErr = oldByCode.get(code);
2100
+ if (!oldErr) continue;
2101
+ if (!oldErr.type && !newErr.type) continue;
2102
+ if (!oldErr.type || !newErr.type || !typeRefsEqual(oldErr.type, newErr.type)) hasBreaking = true;
2103
+ }
2104
+ if (hasBreaking) return "breaking";
2105
+ if (hasAdditive) return "additive";
2106
+ return "none";
2107
+ }
2108
+ function diffParams(oldOp, newOp) {
2109
+ const changes = [];
2110
+ const allOldParams = [
2111
+ ...oldOp.pathParams,
2112
+ ...oldOp.queryParams,
2113
+ ...oldOp.headerParams
2114
+ ];
2115
+ const allNewParams = [
2116
+ ...newOp.pathParams,
2117
+ ...newOp.queryParams,
2118
+ ...newOp.headerParams
2119
+ ];
2120
+ const oldByName = new Map(allOldParams.map((p) => [p.name, p]));
2121
+ const newByName = new Map(allNewParams.map((p) => [p.name, p]));
2122
+ for (const [name, param] of newByName) if (!oldByName.has(name)) changes.push(classifyParamChange("param-added", name, param.required));
2123
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push(classifyParamChange("param-removed", name));
2124
+ for (const [name, newParam] of newByName) {
2125
+ const oldParam = oldByName.get(name);
2126
+ if (!oldParam) continue;
2127
+ if (!typeRefsEqual(oldParam.type, newParam.type)) changes.push(classifyParamChange("param-type-changed", name));
2128
+ if (oldParam.required !== newParam.required) changes.push(classifyParamChange("param-required-changed", name, newParam.required));
2129
+ }
2130
+ return changes;
2131
+ }
2132
+ //#endregion
2133
+ //#region src/differ/services.ts
2134
+ function diffServices(oldServices, newServices) {
2135
+ const changes = [];
2136
+ const oldByName = new Map(oldServices.map((s) => [s.name, s]));
2137
+ const newByName = new Map(newServices.map((s) => [s.name, s]));
2138
+ for (const [name] of newByName) if (!oldByName.has(name)) changes.push({
2139
+ kind: "service-added",
2140
+ name,
2141
+ classification: "additive"
2142
+ });
2143
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push({
2144
+ kind: "service-removed",
2145
+ name,
2146
+ classification: "breaking"
2147
+ });
2148
+ for (const [name, newService] of newByName) {
2149
+ const oldService = oldByName.get(name);
2150
+ if (!oldService) continue;
2151
+ changes.push(...diffOperations(name, oldService.operations, newService.operations));
2152
+ }
2153
+ return changes;
2154
+ }
2155
+ //#endregion
2156
+ //#region src/differ/diff.ts
2157
+ function diffSpecs(oldSpec, newSpec) {
2158
+ const changes = [
2159
+ ...diffModels(oldSpec.models, newSpec.models),
2160
+ ...diffEnums(oldSpec.enums, newSpec.enums),
2161
+ ...diffServices(oldSpec.services, newSpec.services)
2162
+ ];
2163
+ return {
2164
+ oldVersion: oldSpec.version,
2165
+ newVersion: newSpec.version,
2166
+ changes,
2167
+ summary: summarize(changes)
2168
+ };
2169
+ }
2170
+ function diffEnums(oldEnums, newEnums) {
2171
+ const changes = [];
2172
+ const oldByName = new Map(oldEnums.map((e) => [e.name, e]));
2173
+ const newByName = new Map(newEnums.map((e) => [e.name, e]));
2174
+ for (const [name] of newByName) if (!oldByName.has(name)) changes.push({
2175
+ kind: "enum-added",
2176
+ name,
2177
+ classification: "additive"
2178
+ });
2179
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push({
2180
+ kind: "enum-removed",
2181
+ name,
2182
+ classification: "breaking"
2183
+ });
2184
+ for (const [name, newEnum] of newByName) {
2185
+ const oldEnum = oldByName.get(name);
2186
+ if (!oldEnum) continue;
2187
+ const valueChanges = diffEnumValues(oldEnum, newEnum);
2188
+ if (valueChanges.length > 0) {
2189
+ const hasBreaking = valueChanges.some((vc) => vc.classification === "breaking");
2190
+ changes.push({
2191
+ kind: "enum-modified",
2192
+ name,
2193
+ valueChanges,
2194
+ classification: hasBreaking ? "breaking" : "additive"
2195
+ });
2196
+ }
2197
+ }
2198
+ return changes;
2199
+ }
2200
+ function diffEnumValues(oldEnum, newEnum) {
2201
+ const changes = [];
2202
+ const oldByName = new Map(oldEnum.values.map((v) => [v.name, v]));
2203
+ const newByName = new Map(newEnum.values.map((v) => [v.name, v]));
2204
+ for (const [name] of newByName) if (!oldByName.has(name)) changes.push({
2205
+ kind: "value-added",
2206
+ valueName: name,
2207
+ classification: "additive"
2208
+ });
2209
+ for (const [name] of oldByName) if (!newByName.has(name)) changes.push({
2210
+ kind: "value-removed",
2211
+ valueName: name,
2212
+ classification: "breaking"
2213
+ });
2214
+ for (const [name, newVal] of newByName) {
2215
+ const oldVal = oldByName.get(name);
2216
+ if (!oldVal) continue;
2217
+ if (oldVal.value !== newVal.value) changes.push({
2218
+ kind: "value-changed",
2219
+ valueName: name,
2220
+ classification: "breaking",
2221
+ details: `value changed from '${oldVal.value}' to '${newVal.value}'`
2222
+ });
2223
+ }
2224
+ return changes;
2225
+ }
2226
+ function summarize(changes) {
2227
+ let added = 0;
2228
+ let removed = 0;
2229
+ let modified = 0;
2230
+ let breaking = 0;
2231
+ let additive = 0;
2232
+ for (const c of changes) {
2233
+ if (c.kind.endsWith("-added")) added++;
2234
+ else if (c.kind.endsWith("-removed")) removed++;
2235
+ else if (c.kind.endsWith("-modified")) modified++;
2236
+ if (c.classification === "breaking") breaking++;
2237
+ else additive++;
2238
+ }
2239
+ return {
2240
+ added,
2241
+ removed,
2242
+ modified,
2243
+ breaking,
2244
+ additive
2245
+ };
2246
+ }
2247
+ //#endregion
2248
+ export { integrateGeneratedFiles as a, buildEmitterContext as c, generate as i, generateAllFiles as l, getEmitter as n, mapFilesForTargetIntegration as o, registerEmitter as r, applyFileHeaders as s, diffSpecs as t, generateFiles as u };
2249
+
2250
+ //# sourceMappingURL=diff-DyQF4sel.mjs.map