@codeyam/codeyam-cli 0.1.21 → 0.1.22

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 (95) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +31 -8
  4. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
  5. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
  6. package/analyzer-template/packages/analyze/index.ts +4 -1
  7. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
  8. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
  9. package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
  10. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
  11. package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
  12. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +235 -58
  13. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
  14. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +63 -0
  15. package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
  16. package/codeyam-cli/src/commands/editor.js +412 -78
  17. package/codeyam-cli/src/commands/editor.js.map +1 -1
  18. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +354 -1
  19. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  20. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
  21. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  22. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
  23. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  24. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
  25. package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
  26. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
  27. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
  28. package/codeyam-cli/src/utils/analysisRunner.js +28 -1
  29. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  30. package/codeyam-cli/src/utils/analyzer.js +4 -2
  31. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  32. package/codeyam-cli/src/utils/editorAudit.js +98 -3
  33. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  34. package/codeyam-cli/src/utils/editorPreview.js +5 -3
  35. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  36. package/codeyam-cli/src/utils/entityChangeStatus.server.js +16 -0
  37. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  38. package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
  39. package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
  40. package/codeyam-cli/src/utils/queue/job.js +20 -2
  41. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  42. package/codeyam-cli/src/utils/testRunner.js +199 -1
  43. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  44. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +35 -0
  45. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  46. package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
  47. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
  48. package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CJzc4vOH.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
  49. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-aIHKLB-m.js +96 -0
  50. package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-NTuLi4Xg.js → editorPreview-CluPkvXJ.js} +6 -6
  51. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
  52. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BA5L8bU-.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
  53. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
  55. package/codeyam-cli/src/webserver/build/client/assets/{manifest-5025e428.js → manifest-bcbb3d49.js} +1 -1
  56. package/codeyam-cli/src/webserver/build/client/assets/{root-BCx1S8Z3.js → root-D2_tktnk.js} +6 -6
  57. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DjF-soOH.js +16 -0
  58. package/codeyam-cli/src/webserver/build/server/assets/{index-C91yWWCI.js → index-nAvHGWbz.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/server/assets/{init-Dkas-RUS.js → init-XhpIt-OT.js} +1 -1
  60. package/codeyam-cli/src/webserver/build/server/assets/server-build-DVwiibFu.js +644 -0
  61. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  62. package/codeyam-cli/src/webserver/build-info.json +5 -5
  63. package/codeyam-cli/src/webserver/idleDetector.js +15 -0
  64. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  65. package/codeyam-cli/src/webserver/terminalServer.js +8 -2
  66. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  67. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
  68. package/package.json +1 -1
  69. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +23 -9
  70. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  71. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
  72. package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
  73. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
  74. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  75. package/packages/analyze/index.js +1 -1
  76. package/packages/analyze/index.js.map +1 -1
  77. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
  78. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  79. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
  80. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  81. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
  82. package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
  83. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
  84. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  85. package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
  86. package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
  87. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -28
  88. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  89. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +139 -24
  90. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  91. package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +0 -1
  92. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +0 -130
  93. package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +0 -1
  94. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +0 -13
  95. package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +0 -640
@@ -25,6 +25,18 @@ function getTypeParameter(functionName) {
25
25
  }
26
26
  return null;
27
27
  }
28
+ /**
29
+ * Check if a schema path contains a Set/Map collection method call.
30
+ * Paths like `.has(articleId)`, `.delete(articleId)`, `.add(articleId)` represent
31
+ * membership checks on Sets/Maps, not meaningful data flow for schema generation.
32
+ * These create massive combinatorial explosions when every filter field (filterRatings,
33
+ * filterPublications, filterAuthors, etc.) × every method (has, delete, add) gets
34
+ * tracked as a separate equivalency.
35
+ */
36
+ const COLLECTION_METHOD_PATTERN = /\.(?:has|delete|add|clear|get|set)\(/;
37
+ function isCollectionMethodPath(path) {
38
+ return COLLECTION_METHOD_PATTERN.test(path);
39
+ }
28
40
  // Primitive types that should not have child paths
29
41
  const PRIMITIVE_TYPES = new Set([
30
42
  'number',
@@ -107,7 +119,27 @@ function bestValueFromOptions(options) {
107
119
  }
108
120
  return options[0] ?? 'unknown';
109
121
  }
110
- export default function mergeInDependentDataStructure({ importedExports, dependentAnalyses, rootScopeName, dataStructure, dependencySchemas, }) {
122
+ /** Timeout (ms) for the merge operation. Throws DataStructureTimeoutError if exceeded.
123
+ * All successful merges complete in <300ms. Anything exceeding 2s is pathological. */
124
+ const MERGE_TIMEOUT_MS = 2000;
125
+ export class DataStructureTimeoutError extends Error {
126
+ constructor(entityName, elapsedMs) {
127
+ super(`Data structure merge timed out for ${entityName} after ${Math.round(elapsedMs / 1000)}s (limit: ${MERGE_TIMEOUT_MS / 1000}s)`);
128
+ this.name = 'DataStructureTimeoutError';
129
+ }
130
+ }
131
+ export default function mergeInDependentDataStructure({ importedExports, dependentAnalyses, rootScopeName, dataStructure, dependencySchemas, timeoutMs = MERGE_TIMEOUT_MS, }) {
132
+ const mergeStartTime = Date.now();
133
+ const mergeDeadline = timeoutMs > 0 ? mergeStartTime + timeoutMs : 0;
134
+ /** Call in hot loops. Throws DataStructureTimeoutError if deadline exceeded.
135
+ * Date.now() is ~20ns — negligible vs the ms-scale string ops in each iteration. */
136
+ const checkDeadline = () => {
137
+ if (!mergeDeadline)
138
+ return;
139
+ if (Date.now() > mergeDeadline) {
140
+ throw new DataStructureTimeoutError(rootScopeName, Date.now() - mergeStartTime);
141
+ }
142
+ };
111
143
  const mergedDataStructure = {
112
144
  signatureSchema: { ...(dataStructure.signatureSchema ?? {}) },
113
145
  returnValueSchema: { ...(dataStructure.returnValueSchema ?? {}) },
@@ -205,7 +237,16 @@ export default function mergeInDependentDataStructure({ importedExports, depende
205
237
  const cleanSchema = (schema, context) => {
206
238
  transformationTracer.traceSchemaTransform(rootScopeName, 'cleanKnownObjectFunctionsFromMapping', schema, cleanKnownObjectFunctionsFromMapping, context);
207
239
  };
240
+ // Cache translatePath results — the same path is often translated multiple times
241
+ // (once per equivalency entry that references it). Avoids redundant
242
+ // splitOutsideParenthesesAndArrays calls on long paths.
243
+ const translatePathCache = new Map();
208
244
  const translatePath = (path, dependencyName) => {
245
+ const cacheKey = `${dependencyName}\0${path}`;
246
+ const cached = translatePathCache.get(cacheKey);
247
+ if (cached !== undefined)
248
+ return cached;
249
+ let result = path;
209
250
  if (path.startsWith(dependencyName)) {
210
251
  const pathParts = splitOutsideParenthesesAndArrays(path);
211
252
  if (pathParts.length > 1) {
@@ -213,34 +254,27 @@ export default function mergeInDependentDataStructure({ importedExports, depende
213
254
  // Check if this function has multiple DIFFERENT type parameters.
214
255
  // If so, DON'T normalize to returnValue - keep the full path to avoid
215
256
  // merging different type-parameterized variants together.
216
- // e.g., useFetcher<{ data: UserData }>().functionCallReturnValue.data
217
- // should NOT be merged with useFetcher<{ data: ConfigData }>().functionCallReturnValue.data
218
257
  const baseName = cleanFunctionName(pathParts[0]);
219
- if (functionsWithMultipleTypeParams.has(baseName)) {
220
- return path; // Keep the original path with type parameters
258
+ if (!functionsWithMultipleTypeParams.has(baseName)) {
259
+ // functionCallReturnValue immediately follows - normalize to returnValue
260
+ result = joinParenthesesAndArrays([
261
+ 'returnValue',
262
+ ...pathParts.slice(2),
263
+ ]);
221
264
  }
222
- // functionCallReturnValue immediately follows - normalize to returnValue
223
- // e.g., useAuth().functionCallReturnValue.user -> returnValue.user
224
- return joinParenthesesAndArrays([
225
- 'returnValue',
226
- ...pathParts.slice(2),
227
- ]);
228
265
  }
229
266
  else if (pathParts[0].endsWith(')') &&
230
267
  pathParts[1].startsWith('signature[')) {
231
- // Hook-style with signature access (e.g., BranchChangesTab().signature[0]...)
232
- // Strip the function name for signature equivalency matching
233
- return joinParenthesesAndArrays(pathParts.slice(1));
268
+ // Hook-style with signature access
269
+ result = joinParenthesesAndArrays(pathParts.slice(1));
234
270
  }
235
- // For all other cases (object-style APIs like getSupabase().auth and
236
- // direct object references like supabase.from), preserve the path as-is.
237
- // The prefix must be kept for proper schema lookups in constructMockCode
238
- // and gatherDataForMocks.
239
271
  }
240
272
  }
241
- return path;
273
+ translatePathCache.set(cacheKey, result);
274
+ return result;
242
275
  };
243
276
  const gatherAllEquivalentSchemaPaths = (functionName, sourceAndUsageEquivalencies, dataStructure) => {
277
+ checkDeadline();
244
278
  if (!sourceAndUsageEquivalencies)
245
279
  return;
246
280
  const normalizedSchemaCache = new Map();
@@ -252,6 +286,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
252
286
  return cached;
253
287
  const byFirstPart = new Map();
254
288
  for (const path in schema) {
289
+ checkDeadline();
255
290
  let parts = splitOutsideParenthesesAndArrays(path);
256
291
  if (parts[0].startsWith(functionName)) {
257
292
  const baseName = cleanFunctionName(parts[0]);
@@ -300,6 +335,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
300
335
  // Use espIndex Map for O(1) lookup instead of O(E) linear search.
301
336
  // Falls back to linear search only when Map hit has a signature index conflict.
302
337
  for (const pathInfo of allPaths) {
338
+ checkDeadline();
303
339
  if (equivalentSchemaPathsEntry)
304
340
  break;
305
341
  const candidate = espIndex.get(espIndexKey(pathInfo.path, pathInfo.functionName));
@@ -420,18 +456,40 @@ export default function mergeInDependentDataStructure({ importedExports, depende
420
456
  for (const equivalencies of allEquivalencies) {
421
457
  const schemaPathEntries = Object.entries(equivalencies);
422
458
  for (const [schemaPath, usages] of schemaPathEntries) {
459
+ checkDeadline();
460
+ // Skip equivalency entries whose source path is a Set/Map membership operation.
461
+ // Patterns like `.has(articleId)`, `.delete(articleId)`, `.add(articleId)` on
462
+ // Sets/Maps represent membership checks, not meaningful data flow for schema generation.
463
+ // In the Margo LibraryPage case, these account for 74% of all equivalency targets
464
+ // (19,444 of 26,340) and cause a combinatorial explosion in the merge.
465
+ if (isCollectionMethodPath(schemaPath))
466
+ continue;
423
467
  // First, check if the raw schemaPath starts with a function call to a dependency.
424
468
  // If so, use that dependency name for translation (so translatePath can strip the prefix).
425
469
  const extractedFuncName = extractFunctionNameFromPath(schemaPath);
426
470
  const effectiveFunctionName = extractedFuncName || functionName;
427
471
  const translatedPath = translatePath(schemaPath, effectiveFunctionName);
428
- const allPaths = [
472
+ const allPathsRaw = [
429
473
  { path: translatedPath, functionName: effectiveFunctionName },
430
- ...usages.map((u) => ({
474
+ ...usages
475
+ .filter((u) => !isCollectionMethodPath(u.schemaPath))
476
+ .map((u) => ({
431
477
  path: translatePath(u.schemaPath, u.scopeNodeName),
432
478
  functionName: u.scopeNodeName,
433
479
  })),
434
480
  ].filter((pathInfo) => !pathInfo.path.includes('.map('));
481
+ // Deduplicate by translated path + function name.
482
+ // Multiple call variants (e.g., loadView(viewKey(null,null)) vs loadView(viewKey(newTag,newCol)))
483
+ // translate to the same path after stripping arguments. Processing duplicates
484
+ // creates O(n²) work in the schema matching loops below.
485
+ const seenPathKeys = new Set();
486
+ const allPaths = allPathsRaw.filter((p) => {
487
+ const key = `${p.functionName ?? ''}::${p.path}`;
488
+ if (seenPathKeys.has(key))
489
+ return false;
490
+ seenPathKeys.add(key);
491
+ return true;
492
+ });
435
493
  // Fix 38: Derive base paths from property access paths.
436
494
  // When we have equivalent paths like:
437
495
  // Parent: signature[0].scenarios[].name
@@ -454,6 +512,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
454
512
  const derivedBasePathSet = new Set();
455
513
  // For each child path, find its equivalent parent path and derive bases
456
514
  for (const childPathInfo of childPaths) {
515
+ checkDeadline();
457
516
  const childParts = splitOutsideParenthesesAndArrays(childPathInfo.path);
458
517
  // Look for a parent path that shares a common suffix with this child path
459
518
  for (const parentPathInfo of parentPaths) {
@@ -545,6 +604,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
545
604
  });
546
605
  }
547
606
  for (const equivalentRoot of entry.equivalentRoots) {
607
+ checkDeadline();
548
608
  const dataStructures = equivalentRoot.function &&
549
609
  equivalentRoot.function.name !== rootScopeName
550
610
  ? [
@@ -582,6 +642,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
582
642
  : lookupPart;
583
643
  const candidates = schemaIndex.byFirstPart.get(lookupBase) || [];
584
644
  for (const { path: schemaPath, parts: schemaPathParts, } of candidates) {
645
+ checkDeadline();
585
646
  if (schemaPathParts.length < pathParts.length)
586
647
  continue;
587
648
  // Check if all path parts match (allowing function call variants)
@@ -676,6 +737,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
676
737
  // where we want both X().functionCallReturnValue and Y().functionCallReturnValue as bases
677
738
  const allBasePaths = new Set();
678
739
  for (const path of Object.keys(dataStructure.returnValueSchema)) {
740
+ checkDeadline();
679
741
  const parts = splitOutsideParenthesesAndArrays(path);
680
742
  // Find all positions of functionCallReturnValue and create base paths for each
681
743
  for (let i = 0; i < parts.length; i++) {
@@ -702,6 +764,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
702
764
  espIndex.set(espIndexKey(newRoot.schemaRootPath, newRootFuncName), entry);
703
765
  const basePathParts = splitOutsideParenthesesAndArrays(basePath);
704
766
  for (const schemaPath in dataStructure.returnValueSchema) {
767
+ checkDeadline();
705
768
  const schemaPathParts = splitOutsideParenthesesAndArrays(schemaPath);
706
769
  if (schemaPathParts.length < basePathParts.length)
707
770
  continue;
@@ -734,6 +797,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
734
797
  // We do this before the main merge to ensure the connection happens regardless
735
798
  // of processing order.
736
799
  for (const esp of equivalentSchemaPaths) {
800
+ checkDeadline();
737
801
  for (const root of esp.equivalentRoots) {
738
802
  if (root.schemaRootPath.endsWith('[]')) {
739
803
  // Find a matching parent entry with the base array path
@@ -746,6 +810,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
746
810
  // Add transformed postfixes from child (array element) to parent (array)
747
811
  // so they can be applied with [] prefix to parent paths
748
812
  for (const [postfixPath, postfixValue] of Object.entries(esp.equivalentPostfixes)) {
813
+ checkDeadline();
749
814
  const transformedPostfix = joinParenthesesAndArrays(['[]', postfixPath].filter(Boolean));
750
815
  if (!(transformedPostfix in parentEntry.equivalentPostfixes)) {
751
816
  parentEntry.equivalentPostfixes[transformedPostfix] =
@@ -798,6 +863,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
798
863
  const sortedEquivalentSchemaPaths = equivalentSchemaPaths.sort((a, b) => Math.max(...a.equivalentRoots.map((er) => splitOutsideParenthesesAndArrays(er.schemaRootPath).length)) -
799
864
  Math.max(...b.equivalentRoots.map((er) => splitOutsideParenthesesAndArrays(er.schemaRootPath).length)));
800
865
  for (const esp of sortedEquivalentSchemaPaths) {
866
+ checkDeadline();
801
867
  if (esp.equivalentRoots.length === 0)
802
868
  continue;
803
869
  let bestCandidateLength;
@@ -857,6 +923,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
857
923
  // dependencySchemas contains usage information (how dependencies are called),
858
924
  // not internal implementation, so we want this for mocked dependencies too
859
925
  for (const dependency of importedExports) {
926
+ checkDeadline();
860
927
  const dependentDataStructure = dependencySchemas?.[dependency.filePath]?.[dependency.name];
861
928
  if (!dependentDataStructure)
862
929
  continue;
@@ -872,15 +939,28 @@ export default function mergeInDependentDataStructure({ importedExports, depende
872
939
  gatherAllEquivalentSchemaPaths(name, childMergedDataStructure);
873
940
  }
874
941
  }
942
+ const gatherElapsed = Date.now() - mergeStartTime;
875
943
  equivalentSchemaPaths = mergeAllEquivalentSchemaPaths();
944
+ const mergeEspElapsed = Date.now() - mergeStartTime;
876
945
  // Collect schemas that need cleaning — batch the calls for the end instead of
877
946
  // calling cleanSchema inside the inner root loop (which was O(roots * schemaSize)).
878
947
  const schemasToClean = new Set();
879
948
  for (const esp of equivalentSchemaPaths) {
949
+ checkDeadline();
880
950
  // Pre-compute which postfixes have children to avoid O(n²) lookups in the inner loop.
881
951
  // A postfix "has children" if there are other postfixes that extend it.
882
952
  const postfixesWithChildren = new Set();
883
953
  const postfixKeys = Object.keys(esp.equivalentPostfixes);
954
+ // Pre-parse ALL postfix paths once. These parsed parts are reused in:
955
+ // 1. The children detection loop below
956
+ // 2. The inner postfix application loop (lines that split postfixPath and equivalentRoot.postfix)
957
+ // This eliminates thousands of redundant splitOutsideParenthesesAndArrays calls.
958
+ const postfixPartsCache = new Map();
959
+ for (const postfixPath of postfixKeys) {
960
+ if (!postfixPath)
961
+ continue;
962
+ postfixPartsCache.set(postfixPath, splitOutsideParenthesesAndArrays(postfixPath));
963
+ }
884
964
  // Check for empty postfix having children (any other postfixes exist)
885
965
  if (postfixKeys.length > 1 && '' in esp.equivalentPostfixes) {
886
966
  postfixesWithChildren.add('');
@@ -892,7 +972,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
892
972
  for (const postfixPath of postfixKeys) {
893
973
  if (!postfixPath)
894
974
  continue;
895
- const parts = splitOutsideParenthesesAndArrays(postfixPath);
975
+ const parts = postfixPartsCache.get(postfixPath);
896
976
  for (let i = 1; i < parts.length; i++) {
897
977
  postfixPrefixSet.add(joinParenthesesAndArrays(parts.slice(0, i)));
898
978
  }
@@ -912,7 +992,12 @@ export default function mergeInDependentDataStructure({ importedExports, depende
912
992
  seenRootKeys.add(key);
913
993
  return true;
914
994
  });
995
+ // Cap schema size to prevent combinatorial explosion.
996
+ // Successful merges produce <3K ret keys. Beyond 5K, further postfixes
997
+ // add noise but no useful data — they're cross-products of unrelated equivalencies.
998
+ const SCHEMA_KEY_CAP = 5000;
915
999
  for (const equivalentRoot of uniqueRoots) {
1000
+ checkDeadline();
916
1001
  let merged;
917
1002
  if (equivalentRoot.function) {
918
1003
  merged = findOrCreateDependentSchemas(equivalentRoot.function);
@@ -925,7 +1010,11 @@ export default function mergeInDependentDataStructure({ importedExports, depende
925
1010
  const schema = equivalentRoot.schemaRootPath.startsWith('signature[')
926
1011
  ? merged.signatureSchema
927
1012
  : merged.returnValueSchema;
1013
+ // Skip if this schema has already grown past the cap
1014
+ if (Object.keys(schema).length > SCHEMA_KEY_CAP)
1015
+ continue;
928
1016
  for (const [postfixPath, postfixValue] of Object.entries(esp.equivalentPostfixes)) {
1017
+ checkDeadline();
929
1018
  let relevantPostfix = postfixPath;
930
1019
  if (equivalentRoot.postfix) {
931
1020
  // Check if postfixPath starts with equivalentRoot.postfix at a path boundary.
@@ -943,8 +1032,13 @@ export default function mergeInDependentDataStructure({ importedExports, depende
943
1032
  // This means "entity" matched "entityCode" which is wrong - they're different properties.
944
1033
  continue;
945
1034
  }
946
- const postFixPathParts = splitOutsideParenthesesAndArrays(postfixPath);
947
- const equivalentRootPostFixParts = splitOutsideParenthesesAndArrays(equivalentRoot.postfix);
1035
+ const postFixPathParts = postfixPartsCache.get(postfixPath) ??
1036
+ splitOutsideParenthesesAndArrays(postfixPath);
1037
+ // Cache equivalentRoot.postfix parts — same root reused across all postfixes
1038
+ if (!postfixPartsCache.has(equivalentRoot.postfix)) {
1039
+ postfixPartsCache.set(equivalentRoot.postfix, splitOutsideParenthesesAndArrays(equivalentRoot.postfix));
1040
+ }
1041
+ const equivalentRootPostFixParts = postfixPartsCache.get(equivalentRoot.postfix);
948
1042
  relevantPostfix = joinParenthesesAndArrays(postFixPathParts.slice(equivalentRootPostFixParts.length));
949
1043
  }
950
1044
  const newSchemaPath = joinParenthesesAndArrays([
@@ -1028,10 +1122,12 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1028
1122
  schemasToClean.add(schema);
1029
1123
  }
1030
1124
  }
1125
+ const postfixElapsed = Date.now() - mergeStartTime;
1031
1126
  // Batch-clean all modified schemas once (instead of once per root per ESP entry)
1032
1127
  for (const schema of schemasToClean) {
1033
1128
  cleanSchema(schema, { stage: 'afterMergePostfix' });
1034
1129
  }
1130
+ const cleanElapsed = Date.now() - mergeStartTime;
1035
1131
  // Propagate equivalency-derived attributes to generic function call variants.
1036
1132
  // When attributes are traced via equivalencies (e.g., fileComparisons from buildDataMap.signature[2]),
1037
1133
  // they get written to non-generic paths (returnValue.data.x or funcName().functionCallReturnValue.data.x).
@@ -1050,6 +1146,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1050
1146
  const genericVariants = new Set();
1051
1147
  const genericRegex = new RegExp(`^${depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}<[^>]+>\\(\\)`);
1052
1148
  for (const path in schemaToSearchForGenericVariants) {
1149
+ checkDeadline();
1053
1150
  const match = path.match(genericRegex);
1054
1151
  if (match) {
1055
1152
  genericVariants.add(match[0]);
@@ -1061,6 +1158,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1061
1158
  // create corresponding paths for each generic variant
1062
1159
  const pathsToAdd = [];
1063
1160
  for (const path in returnValueSchema) {
1161
+ checkDeadline();
1064
1162
  const value = returnValueSchema[path];
1065
1163
  // Handle returnValue. paths
1066
1164
  if (path.startsWith('returnValue.')) {
@@ -1107,6 +1205,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1107
1205
  // This includes both returnValue. (dot) and returnValue[ (array) paths.
1108
1206
  const pathsToNormalize = [];
1109
1207
  for (const path in depSchema.returnValueSchema) {
1208
+ checkDeadline();
1110
1209
  if (path === 'returnValue' ||
1111
1210
  path.startsWith('returnValue.') ||
1112
1211
  path.startsWith('returnValue[')) {
@@ -1140,6 +1239,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1140
1239
  }
1141
1240
  // Now copy paths from the source schema (dependencySchemas)
1142
1241
  for (const path in srcSchema.returnValueSchema) {
1242
+ checkDeadline();
1143
1243
  const value = srcSchema.returnValueSchema[path];
1144
1244
  // Normalize paths starting with 'returnValue' to use the standard format:
1145
1245
  // 'returnValue.foo' -> 'dependencyName().functionCallReturnValue.foo'
@@ -1300,9 +1400,11 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1300
1400
  if (!existingSchema) {
1301
1401
  const depSchema = findOrCreateDependentSchemas({ filePath, name });
1302
1402
  for (const path in srcSchema.returnValueSchema) {
1403
+ checkDeadline();
1303
1404
  depSchema.returnValueSchema[path] = srcSchema.returnValueSchema[path];
1304
1405
  }
1305
1406
  for (const path in srcSchema.signatureSchema) {
1407
+ checkDeadline();
1306
1408
  depSchema.signatureSchema[path] = srcSchema.signatureSchema[path];
1307
1409
  }
1308
1410
  // Clean known object functions (like String.prototype.replace, Array.prototype.map)
@@ -1328,6 +1430,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1328
1430
  const childSignatureSchema = childAnalysis?.metadata?.mergedDataStructure?.signatureSchema;
1329
1431
  if (childSignatureSchema) {
1330
1432
  for (const path in depSchema.signatureSchema) {
1433
+ checkDeadline();
1331
1434
  const parentType = depSchema.signatureSchema[path];
1332
1435
  const childType = childSignatureSchema[path];
1333
1436
  if (parentType && childType) {
@@ -1367,6 +1470,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1367
1470
  });
1368
1471
  // Copy only paths that belong to this variant
1369
1472
  for (const path in srcSchema.returnValueSchema) {
1473
+ checkDeadline();
1370
1474
  if (path.startsWith(variant)) {
1371
1475
  variantSchema.returnValueSchema[path] =
1372
1476
  srcSchema.returnValueSchema[path];
@@ -1388,6 +1492,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1388
1492
  // EXCEPT: Skip mocked dependencies - we don't want their internal implementation details.
1389
1493
  for (const filePath in dependentAnalyses) {
1390
1494
  for (const name in dependentAnalyses[filePath]) {
1495
+ checkDeadline();
1391
1496
  const dependentMergedDataStructure = dependentAnalyses[filePath][name].metadata?.mergedDataStructure;
1392
1497
  if (!dependentMergedDataStructure)
1393
1498
  continue;
@@ -1400,6 +1505,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1400
1505
  // Copy over all paths from the dependent's returnValueSchema
1401
1506
  // Only add paths that don't already exist (don't overwrite values set by equivalencies)
1402
1507
  for (const path in dependentMergedDataStructure.returnValueSchema) {
1508
+ checkDeadline();
1403
1509
  const translatedPath = translatePath(path, name);
1404
1510
  if (!(translatedPath in depSchema.returnValueSchema)) {
1405
1511
  depSchema.returnValueSchema[translatedPath] =
@@ -1408,6 +1514,7 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1408
1514
  }
1409
1515
  // Copy over signature schema as well
1410
1516
  for (const path in dependentMergedDataStructure.signatureSchema) {
1517
+ checkDeadline();
1411
1518
  const translatedPath = translatePath(path, name);
1412
1519
  if (!(translatedPath in depSchema.signatureSchema)) {
1413
1520
  depSchema.signatureSchema[translatedPath] =
@@ -1427,12 +1534,14 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1427
1534
  });
1428
1535
  // Merge in the nested dependency schemas
1429
1536
  for (const path in nestedDepSchema.returnValueSchema) {
1537
+ checkDeadline();
1430
1538
  if (!(path in targetDepSchema.returnValueSchema)) {
1431
1539
  const value = nestedDepSchema.returnValueSchema[path];
1432
1540
  targetDepSchema.returnValueSchema[path] = value;
1433
1541
  }
1434
1542
  }
1435
1543
  for (const path in nestedDepSchema.signatureSchema) {
1544
+ checkDeadline();
1436
1545
  if (!(path in targetDepSchema.signatureSchema)) {
1437
1546
  targetDepSchema.signatureSchema[path] =
1438
1547
  nestedDepSchema.signatureSchema[path];
@@ -1443,6 +1552,12 @@ export default function mergeInDependentDataStructure({ importedExports, depende
1443
1552
  }
1444
1553
  }
1445
1554
  }
1555
+ const totalElapsed = Date.now() - mergeStartTime;
1556
+ const retKeys = Object.keys(mergedDataStructure.returnValueSchema).length;
1557
+ // Only log phase breakdown for slow merges (>2s)
1558
+ if (totalElapsed > 2000) {
1559
+ console.log(`CodeYam Log Level 2: ${rootScopeName} merge phases: gather=${gatherElapsed}ms mergeESP=${mergeEspElapsed - gatherElapsed}ms postfix=${postfixElapsed - mergeEspElapsed}ms clean=${cleanElapsed - postfixElapsed}ms depCopy=${totalElapsed - cleanElapsed}ms total=${totalElapsed}ms ret=${retKeys}`);
1560
+ }
1446
1561
  return mergedDataStructure;
1447
1562
  }
1448
1563
  //# sourceMappingURL=mergeInDependentDataStructure.js.map