@codeyam/codeyam-cli 0.1.0-staging.e090cb3 → 0.1.0-staging.e2d4438

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 (125) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  4. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +29 -18
  5. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +15 -6
  6. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +96 -0
  7. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +50 -25
  8. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +153 -76
  9. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  10. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +93 -2
  11. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  12. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +108 -2
  13. package/analyzer-template/project/constructMockCode.ts +2 -2
  14. package/analyzer-template/project/writeScenarioComponents.ts +14 -0
  15. package/background/src/lib/virtualized/project/constructMockCode.js +2 -2
  16. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  17. package/background/src/lib/virtualized/project/writeScenarioComponents.js +10 -0
  18. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  19. package/codeyam-cli/src/commands/analyze.js +2 -2
  20. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  21. package/codeyam-cli/src/commands/default.js +1 -1
  22. package/codeyam-cli/src/commands/default.js.map +1 -1
  23. package/codeyam-cli/src/commands/memory.js +63 -20
  24. package/codeyam-cli/src/commands/memory.js.map +1 -1
  25. package/codeyam-cli/src/commands/verify.js +2 -2
  26. package/codeyam-cli/src/commands/verify.js.map +1 -1
  27. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js +179 -0
  28. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js.map +1 -0
  29. package/codeyam-cli/src/utils/backgroundServer.js +90 -23
  30. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  31. package/codeyam-cli/src/utils/npmVersionCheck.js +76 -0
  32. package/codeyam-cli/src/utils/npmVersionCheck.js.map +1 -0
  33. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +1 -1
  34. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -1
  35. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +0 -1
  36. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -1
  37. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +2 -4
  38. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  39. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +2 -1
  40. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  41. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +1 -1
  42. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  43. package/codeyam-cli/src/utils/versionInfo.js +25 -0
  44. package/codeyam-cli/src/utils/versionInfo.js.map +1 -1
  45. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js +66 -0
  46. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js.map +1 -0
  47. package/codeyam-cli/src/webserver/app/lib/dbNotifier.js.map +1 -1
  48. package/codeyam-cli/src/webserver/backgroundServer.js +26 -0
  49. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  50. package/codeyam-cli/src/webserver/bootstrap.js +11 -0
  51. package/codeyam-cli/src/webserver/bootstrap.js.map +1 -1
  52. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CA3JxPb7.js → CopyButton-jNYXRRNI.js} +1 -1
  53. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-B86KKU7e.js → EntityItem-bwuHPyTa.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-B5ctlSYt.js → EntityTypeBadge-CvzqMxcu.js} +1 -1
  55. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BqY8gDAW.js → EntityTypeIcon-BH0XDim7.js} +1 -1
  56. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-ClaLpuOo.js → InlineSpinner-EhOseatT.js} +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-BDhPilK7.js → InteractivePreview-yjIHlOGa.js} +2 -2
  58. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-VeqEBv9v.js → LibraryFunctionPreview-Cq5o8jL4.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-Bs7Nn1Jr.js → LoadingDots-BvMu2i-g.js} +1 -1
  60. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-Bm3PmcCz.js → LogViewer-kgBTLoJD.js} +1 -1
  61. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CgMEzchJ.js → ReportIssueModal-BzPgx-xO.js} +1 -1
  62. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-Gq3Ocjo6.js → SafeScreenshot-CwZrv-Ok.js} +1 -1
  63. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-CBui0id_.js → ScenarioViewer-BX2Ny2Qj.js} +1 -1
  64. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-CiwXDxLh.js → TruncatedFilePath-CDpEprKa.js} +1 -1
  65. package/codeyam-cli/src/webserver/build/client/assets/{_index-B3TDXxnk.js → _index-BRx8ZGZo.js} +1 -1
  66. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BtBFH820.js → activity.(_tab)-4S4yPfFw.js} +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DHKuQSmR.js +17 -0
  68. package/codeyam-cli/src/webserver/build/client/assets/{book-open-PttOB2SF.js → book-open-D4IPYH_y.js} +1 -1
  69. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-TJp6ofnp.js → chevron-down-CG65viiV.js} +1 -1
  70. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-JE9ZIoBl.js → chunk-JZWAC4HX-DB3aFuEO.js} +9 -9
  71. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CXhHQYrI.js → circle-check-igfMr5DY.js} +1 -1
  72. package/codeyam-cli/src/webserver/build/client/assets/{copy-6y9ALfGT.js → copy-Coc4o_8c.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Ca9fAY46.js → createLucideIcon-D1zB-pYc.js} +1 -1
  74. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C0epRiVn.js → dev.empty-JTAjQ54M.js} +1 -1
  75. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BVnB8a9L.js → entity._sha._-B0h9AqE6.js} +2 -2
  76. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js → entity._sha.scenarios._scenarioId.fullscreen-DjLxr2JB.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DGgZjdFg.js → entity._sha_.create-scenario-CtYowLOt.js} +1 -1
  78. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-38yPijoD.js → entity._sha_.edit._scenarioId-PePWg17F.js} +1 -1
  79. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-BSHEfydn.js → entry.client-I-Wo99C_.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DCPhhSMo.js → fileTableUtils-9sMMAiWJ.js} +1 -1
  81. package/codeyam-cli/src/webserver/build/client/assets/{files-0N0YJQv7.js → files-Co65J0s3.js} +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/{git-DXnyr8uP.js → git-BdHOxVfg.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/client/assets/globals-BSZfYCkU.css +1 -0
  84. package/codeyam-cli/src/webserver/build/client/assets/{index-ChN9-fAY.js → index-CUM5iXwc.js} +1 -1
  85. package/codeyam-cli/src/webserver/build/client/assets/{index-CcsFv748.js → index-_417gcQW.js} +1 -1
  86. package/codeyam-cli/src/webserver/build/client/assets/{labs-BLJ7HxOC.js → labs-BK0C1H1T.js} +1 -1
  87. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-CTqLEAGU.js → loader-circle-TzRHMVog.js} +1 -1
  88. package/codeyam-cli/src/webserver/build/client/assets/{manifest-b171b9d3.js → manifest-040dab1c.js} +1 -1
  89. package/codeyam-cli/src/webserver/build/client/assets/memory-UIDVz141.js +92 -0
  90. package/codeyam-cli/src/webserver/build/client/assets/{pause-D6vreykR.js → pause-hjzB7t2z.js} +1 -1
  91. package/codeyam-cli/src/webserver/build/client/assets/root-D1WadSdf.js +62 -0
  92. package/codeyam-cli/src/webserver/build/client/assets/{search-B8VUL8nl.js → search-DcAwD_Ln.js} +1 -1
  93. package/codeyam-cli/src/webserver/build/client/assets/{settings-BejnUJ6R.js → settings-CclxrcPK.js} +1 -1
  94. package/codeyam-cli/src/webserver/build/client/assets/{simulations-CPoAg7Zo.js → simulations-DVNJVQgD.js} +1 -1
  95. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BrCP7uQo.js → terminal-DbEAHMbA.js} +1 -1
  96. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BZz2NjYa.js → triangle-alert-CAD5b1o_.js} +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DNwUduNu.js → useCustomSizes-BqgrAzs3.js} +1 -1
  98. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-COky1GVF.js → useLastLogLine-DAFqfEDH.js} +1 -1
  99. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CpZgwliL.js → useReportContext-DZlYx2c4.js} +1 -1
  100. package/codeyam-cli/src/webserver/build/client/assets/{useToast-Bv9JFvUO.js → useToast-ihdMtlf6.js} +1 -1
  101. package/codeyam-cli/src/webserver/build/server/assets/{index-8Fv-lH1-.js → index-B3dE0r28.js} +1 -1
  102. package/codeyam-cli/src/webserver/build/server/assets/server-build-DYbfdxa3.js +273 -0
  103. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  104. package/codeyam-cli/src/webserver/build-info.json +5 -5
  105. package/codeyam-cli/templates/codeyam-memory.md +5 -1
  106. package/codeyam-cli/templates/rule-reflection-hook.py +1 -1
  107. package/codeyam-cli/templates/rules-instructions.md +1 -1
  108. package/package.json +8 -8
  109. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +24 -16
  110. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  111. package/packages/ai/src/lib/dataStructureChunking.js +9 -5
  112. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  113. package/packages/ai/src/lib/generateExecutionFlows.js +81 -0
  114. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  115. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +42 -13
  116. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  117. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +123 -67
  118. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  119. package/packages/utils/src/lib/fs/rsyncCopy.js +93 -2
  120. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  121. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +0 -11
  122. package/codeyam-cli/src/webserver/build/client/assets/globals-CKT08Djd.css +0 -1
  123. package/codeyam-cli/src/webserver/build/client/assets/memory-CCQd4aZA.js +0 -78
  124. package/codeyam-cli/src/webserver/build/client/assets/root-CHhiHoo_.js +0 -62
  125. package/codeyam-cli/src/webserver/build/server/assets/server-build-Akn3iYFP.js +0 -257
@@ -210,6 +210,23 @@ export default function mergeInDependentDataStructure({
210
210
  equivalentPostfixes: Record<string, string>;
211
211
  }[] = [];
212
212
 
213
+ // O(1) index for findOrCreateEquivalentSchemaPathsEntry.
214
+ // Maps "(rootPath)::(normalizedFuncName)" → the entry containing that root.
215
+ // This replaces the O(E) linear search that was causing O(E²) gather performance.
216
+ const espIndex = new Map<string, (typeof equivalentSchemaPaths)[0]>();
217
+ const espIndexKey = (path: string, functionName: string | undefined) => {
218
+ const normalized = cleanFunctionName(functionName);
219
+ const funcKey =
220
+ normalized === rootScopeName ? '__self__' : normalized || '__self__';
221
+ return `${path}::${funcKey}`;
222
+ };
223
+ const updateEspIndex = (entry: (typeof equivalentSchemaPaths)[0]) => {
224
+ for (const root of entry.equivalentRoots) {
225
+ const funcName = root.function?.name ?? rootScopeName;
226
+ espIndex.set(espIndexKey(root.schemaRootPath, funcName), entry);
227
+ }
228
+ };
229
+
213
230
  // Pre-build a lookup map from cleaned function name to dependency for O(1) lookups.
214
231
  // This avoids O(n) linear search in findRelevantDependency which was causing O(n²) performance.
215
232
  const dependencyByCleanedName = new Map<
@@ -324,6 +341,54 @@ export default function mergeInDependentDataStructure({
324
341
  ) => {
325
342
  if (!sourceAndUsageEquivalencies) return;
326
343
 
344
+ // Pre-computed normalized schema index cache.
345
+ // Avoids repeated splitOutsideParenthesesAndArrays calls and function-name
346
+ // normalization for the same schema paths across multiple equivalency iterations.
347
+ // The normalization depends on `functionName` (constant per gatherAllEquivalentSchemaPaths call),
348
+ // so this cache is scoped to this call.
349
+ type NormalizedEntry = { path: string; parts: string[] };
350
+ const normalizedSchemaCache = new Map<
351
+ object,
352
+ {
353
+ byFirstPart: Map<string, NormalizedEntry[]>;
354
+ }
355
+ >();
356
+ const getSchemaIndex = (
357
+ schema: Record<string, string> | undefined,
358
+ ): { byFirstPart: Map<string, NormalizedEntry[]> } => {
359
+ if (!schema) return { byFirstPart: new Map() };
360
+ const cached = normalizedSchemaCache.get(schema);
361
+ if (cached) return cached;
362
+ const byFirstPart = new Map<string, NormalizedEntry[]>();
363
+ for (const path in schema) {
364
+ let parts = splitOutsideParenthesesAndArrays(path);
365
+ if (parts[0].startsWith(functionName)) {
366
+ const baseName = cleanFunctionName(parts[0]);
367
+ if (!functionsWithMultipleTypeParams.has(baseName)) {
368
+ parts =
369
+ parts[1] === 'functionCallReturnValue'
370
+ ? ['returnValue', ...parts.slice(2)]
371
+ : parts.slice(1);
372
+ }
373
+ }
374
+ const entry: NormalizedEntry = { path, parts };
375
+ // Index by the base of the first part (before any function call args)
376
+ const firstPart = parts[0] ?? '';
377
+ const parenIdx = firstPart.indexOf('(');
378
+ const firstPartBase =
379
+ parenIdx >= 0 ? firstPart.slice(0, parenIdx) : firstPart;
380
+ let bucket = byFirstPart.get(firstPartBase);
381
+ if (!bucket) {
382
+ bucket = [];
383
+ byFirstPart.set(firstPartBase, bucket);
384
+ }
385
+ bucket.push(entry);
386
+ }
387
+ const result = { byFirstPart };
388
+ normalizedSchemaCache.set(schema, result);
389
+ return result;
390
+ };
391
+
327
392
  const findOrCreateEquivalentSchemaPathsEntry = (
328
393
  allPaths: { path: string; functionName?: string }[],
329
394
  ) => {
@@ -354,62 +419,52 @@ export default function mergeInDependentDataStructure({
354
419
  }
355
420
  }
356
421
 
422
+ // Use espIndex Map for O(1) lookup instead of O(E) linear search.
423
+ // Falls back to linear search only when Map hit has a signature index conflict.
357
424
  for (const pathInfo of allPaths) {
358
- if (!equivalentSchemaPathsEntry) {
359
- equivalentSchemaPathsEntry = equivalentSchemaPaths.find((esp) => {
360
- // First check: does this entry have a matching root?
361
- const hasMatchingRoot = esp.equivalentRoots.some(
362
- (er) =>
363
- er.schemaRootPath === pathInfo.path &&
364
- (er.function?.name ===
365
- cleanFunctionName(pathInfo.functionName) ||
366
- (!er.function &&
367
- cleanFunctionName(pathInfo.functionName) ===
368
- rootScopeName)),
369
- );
370
- if (!hasMatchingRoot) return false;
371
-
372
- // Second check: would adding our new roots create a signature index conflict?
373
- // An entry should NOT contain roots with different signature indices from the same function.
374
- if (newRootSignatureIndices.size > 0) {
375
- // Get all signature indices in the existing entry (grouped by function)
376
- const existingIndicesByFunction = new Map<string, Set<number>>();
377
- for (const er of esp.equivalentRoots) {
378
- const funcKey = er.function
379
- ? `${er.function.name}::${er.function.filePath}`
380
- : '__self__';
381
- const idx = extractSignatureIndex(er.schemaRootPath);
382
- if (idx !== undefined) {
383
- if (!existingIndicesByFunction.has(funcKey)) {
384
- existingIndicesByFunction.set(funcKey, new Set());
385
- }
386
- existingIndicesByFunction.get(funcKey)!.add(idx);
387
- }
425
+ if (equivalentSchemaPathsEntry) break;
426
+ const candidate = espIndex.get(
427
+ espIndexKey(pathInfo.path, pathInfo.functionName),
428
+ );
429
+ if (!candidate) continue;
430
+
431
+ // Verify no signature index conflict with the candidate entry
432
+ if (newRootSignatureIndices.size > 0) {
433
+ const existingIndicesByFunction = new Map<string, Set<number>>();
434
+ for (const er of candidate.equivalentRoots) {
435
+ const funcKey = er.function
436
+ ? `${er.function.name}::${er.function.filePath}`
437
+ : '__self__';
438
+ const idx = extractSignatureIndex(er.schemaRootPath);
439
+ if (idx !== undefined) {
440
+ if (!existingIndicesByFunction.has(funcKey)) {
441
+ existingIndicesByFunction.set(funcKey, new Set());
388
442
  }
443
+ existingIndicesByFunction.get(funcKey)!.add(idx);
444
+ }
445
+ }
389
446
 
390
- // Check if adding our new roots would create a conflict
391
- for (const newRoot of equivalentRoots) {
392
- const funcKey = newRoot.function
393
- ? `${newRoot.function.name}::${newRoot.function.filePath}`
394
- : '__self__';
395
- const newIdx = extractSignatureIndex(newRoot.schemaRootPath);
396
- if (newIdx !== undefined) {
397
- const existingIndices =
398
- existingIndicesByFunction.get(funcKey);
399
- if (existingIndices && existingIndices.size > 0) {
400
- // If this function already has signature indices, check for conflict
401
- if (!existingIndices.has(newIdx)) {
402
- // Conflict: entry has indices like [1] but we want to add [2]
403
- return false;
404
- }
405
- }
447
+ let hasConflict = false;
448
+ for (const newRoot of equivalentRoots) {
449
+ const funcKey = newRoot.function
450
+ ? `${newRoot.function.name}::${newRoot.function.filePath}`
451
+ : '__self__';
452
+ const newIdx = extractSignatureIndex(newRoot.schemaRootPath);
453
+ if (newIdx !== undefined) {
454
+ const existingIndices = existingIndicesByFunction.get(funcKey);
455
+ if (existingIndices && existingIndices.size > 0) {
456
+ if (!existingIndices.has(newIdx)) {
457
+ hasConflict = true;
458
+ break;
406
459
  }
407
460
  }
408
461
  }
462
+ }
409
463
 
410
- return true;
411
- });
464
+ if (hasConflict) continue;
412
465
  }
466
+
467
+ equivalentSchemaPathsEntry = candidate;
413
468
  }
414
469
 
415
470
  if (!equivalentSchemaPathsEntry) {
@@ -467,6 +522,9 @@ export default function mergeInDependentDataStructure({
467
522
  return true;
468
523
  });
469
524
 
525
+ // Keep the espIndex in sync after adding/deduplicating roots
526
+ updateEspIndex(equivalentSchemaPathsEntry);
527
+
470
528
  return equivalentSchemaPathsEntry;
471
529
  };
472
530
 
@@ -535,6 +593,8 @@ export default function mergeInDependentDataStructure({
535
593
  );
536
594
 
537
595
  const derivedBasePaths: { path: string; functionName?: string }[] = [];
596
+ const allPathSet = new Set(allPaths.map((p) => p.path));
597
+ const derivedBasePathSet = new Set<string>();
538
598
 
539
599
  // For each child path, find its equivalent parent path and derive bases
540
600
  for (const childPathInfo of childPaths) {
@@ -611,26 +671,28 @@ export default function mergeInDependentDataStructure({
611
671
  !childHasArrayIterator &&
612
672
  !childBaseIsGenericSignature
613
673
  ) {
614
- // Add child base if not already present
615
- const childBaseExists =
616
- allPaths.some((p) => p.path === childBase) ||
617
- derivedBasePaths.some((p) => p.path === childBase);
618
- if (!childBaseExists) {
674
+ // Add child base if not already present (O(1) Set lookup)
675
+ if (
676
+ !allPathSet.has(childBase) &&
677
+ !derivedBasePathSet.has(childBase)
678
+ ) {
619
679
  derivedBasePaths.push({
620
680
  path: childBase,
621
681
  functionName: childPathInfo.functionName,
622
682
  });
683
+ derivedBasePathSet.add(childBase);
623
684
  }
624
685
 
625
- // Add parent base if not already present
626
- const parentBaseExists =
627
- allPaths.some((p) => p.path === parentBase) ||
628
- derivedBasePaths.some((p) => p.path === parentBase);
629
- if (!parentBaseExists) {
686
+ // Add parent base if not already present (O(1) Set lookup)
687
+ if (
688
+ !allPathSet.has(parentBase) &&
689
+ !derivedBasePathSet.has(parentBase)
690
+ ) {
630
691
  derivedBasePaths.push({
631
692
  path: parentBase,
632
693
  functionName: parentPathInfo.functionName,
633
694
  });
695
+ derivedBasePathSet.add(parentBase);
634
696
  }
635
697
  }
636
698
  }
@@ -708,21 +770,20 @@ export default function mergeInDependentDataStructure({
708
770
  }
709
771
 
710
772
  for (const schema of schemas) {
711
- for (const schemaPath in schema) {
712
- let schemaPathParts =
713
- splitOutsideParenthesesAndArrays(schemaPath);
714
-
715
- if (schemaPathParts[0].startsWith(functionName)) {
716
- // Only normalize if the function doesn't have multiple different type parameters
717
- const baseName = cleanFunctionName(schemaPathParts[0]);
718
- if (!functionsWithMultipleTypeParams.has(baseName)) {
719
- schemaPathParts =
720
- schemaPathParts[1] === 'functionCallReturnValue'
721
- ? ['returnValue', ...schemaPathParts.slice(2)]
722
- : schemaPathParts.slice(1);
723
- }
724
- }
725
-
773
+ // Use pre-computed index to only iterate schema entries whose
774
+ // normalized first part matches pathParts[0], instead of all entries.
775
+ const schemaIndex = getSchemaIndex(schema);
776
+ const lookupPart = pathParts[0] ?? '';
777
+ const lookupParenIdx = lookupPart.indexOf('(');
778
+ const lookupBase =
779
+ lookupParenIdx >= 0
780
+ ? lookupPart.slice(0, lookupParenIdx)
781
+ : lookupPart;
782
+ const candidates = schemaIndex.byFirstPart.get(lookupBase) || [];
783
+ for (const {
784
+ path: schemaPath,
785
+ parts: schemaPathParts,
786
+ } of candidates) {
726
787
  if (schemaPathParts.length < pathParts.length) continue;
727
788
 
728
789
  // Check if all path parts match (allowing function call variants)
@@ -850,10 +911,17 @@ export default function mergeInDependentDataStructure({
850
911
  const entry = findOrCreateEquivalentSchemaPathsEntry([
851
912
  { path: translatedBasePath, functionName: functionName },
852
913
  ]);
853
- entry.equivalentRoots.push({
914
+ const newRoot = {
854
915
  schemaRootPath: translatedBasePath,
855
916
  function: findRelevantDependency(functionName),
856
- });
917
+ };
918
+ entry.equivalentRoots.push(newRoot);
919
+ // Update index for the newly added root
920
+ const newRootFuncName = newRoot.function?.name ?? rootScopeName;
921
+ espIndex.set(
922
+ espIndexKey(newRoot.schemaRootPath, newRootFuncName),
923
+ entry,
924
+ );
857
925
 
858
926
  const basePathParts = splitOutsideParenthesesAndArrays(basePath);
859
927
  for (const schemaPath in dataStructure.returnValueSchema) {
@@ -1113,6 +1181,10 @@ export default function mergeInDependentDataStructure({
1113
1181
 
1114
1182
  equivalentSchemaPaths = mergeAllEquivalentSchemaPaths();
1115
1183
 
1184
+ // Collect schemas that need cleaning — batch the calls for the end instead of
1185
+ // calling cleanSchema inside the inner root loop (which was O(roots * schemaSize)).
1186
+ const schemasToClean = new Set<{ [key: string]: string }>();
1187
+
1116
1188
  for (const esp of equivalentSchemaPaths) {
1117
1189
  // Pre-compute which postfixes have children to avoid O(n²) lookups in the inner loop.
1118
1190
  // A postfix "has children" if there are other postfixes that extend it.
@@ -1290,10 +1362,15 @@ export default function mergeInDependentDataStructure({
1290
1362
  schema[newSchemaPath] = postfixValue;
1291
1363
  }
1292
1364
 
1293
- cleanSchema(schema, { stage: 'afterMergePostfix' });
1365
+ schemasToClean.add(schema);
1294
1366
  }
1295
1367
  }
1296
1368
 
1369
+ // Batch-clean all modified schemas once (instead of once per root per ESP entry)
1370
+ for (const schema of schemasToClean) {
1371
+ cleanSchema(schema, { stage: 'afterMergePostfix' });
1372
+ }
1373
+
1297
1374
  // Propagate equivalency-derived attributes to generic function call variants.
1298
1375
  // When attributes are traced via equivalencies (e.g., fileComparisons from buildDataMap.signature[2]),
1299
1376
  // they get written to non-generic paths (returnValue.data.x or funcName().functionCallReturnValue.data.x).
@@ -1 +1 @@
1
- {"version":3,"file":"rsyncCopy.d.ts","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"AAEA,wBAA8B,SAAS,CAAC,EACtC,UAAU,EACV,eAAe,EACf,QAAa,EACb,YAAoB,EACpB,MAAc,EACd,SAAc,GACf,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ChB"}
1
+ {"version":3,"file":"rsyncCopy.d.ts","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"AAqFA,wBAA8B,SAAS,CAAC,EACtC,UAAU,EACV,eAAe,EACf,QAAa,EACb,YAAoB,EACpB,MAAc,EACd,SAAc,GACf,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEhB"}
@@ -1,5 +1,97 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, execSync } from 'child_process';
2
+ import { existsSync, readdirSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ /**
5
+ * Try to use APFS copy-on-write clones on macOS for near-instant directory copies.
6
+ * Falls back to rsync on non-macOS or if the clone fails.
7
+ *
8
+ * Returns true if the clone succeeded (caller can skip rsync).
9
+ */
10
+ function tryApfsClone({ sourcePath, destinationPath, excludes, silent, }) {
11
+ if (process.platform !== 'darwin')
12
+ return false;
13
+ // APFS clone requires the destination to not exist.
14
+ // If it exists and is empty, remove it so we can clone into it.
15
+ if (existsSync(destinationPath)) {
16
+ try {
17
+ const contents = readdirSync(destinationPath);
18
+ if (contents.length > 0) {
19
+ // Destination is non-empty — can't use clone, fall back to rsync
20
+ return false;
21
+ }
22
+ rmSync(destinationPath, { recursive: true });
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ try {
29
+ // cp -c -R: APFS copy-on-write clone (faster than rsync, avoids data copy)
30
+ execSync(`cp -c -R "${sourcePath}" "${destinationPath}"`, {
31
+ stdio: 'pipe',
32
+ timeout: 300000, // 5 min safety timeout
33
+ });
34
+ // Remove excluded items from the clone
35
+ for (const exclude of excludes) {
36
+ if (exclude.includes('*')) {
37
+ // Glob pattern — use shell expansion
38
+ try {
39
+ execSync(`rm -rf "${join(destinationPath, exclude)}"`, {
40
+ stdio: 'pipe',
41
+ shell: '/bin/sh',
42
+ });
43
+ }
44
+ catch {
45
+ // Glob matched nothing — fine
46
+ }
47
+ }
48
+ else {
49
+ const excludePath = join(destinationPath, exclude);
50
+ if (existsSync(excludePath)) {
51
+ rmSync(excludePath, { recursive: true, force: true });
52
+ }
53
+ }
54
+ }
55
+ if (!silent) {
56
+ console.log(`Directory cloned (APFS CoW) from ${sourcePath} to ${destinationPath}`);
57
+ }
58
+ return true;
59
+ }
60
+ catch {
61
+ // Clone failed (cross-volume, non-APFS, etc.) — fall back to rsync
62
+ // Clean up any partial clone
63
+ if (existsSync(destinationPath)) {
64
+ try {
65
+ rmSync(destinationPath, { recursive: true });
66
+ }
67
+ catch {
68
+ // Best effort cleanup
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ }
2
74
  export default async function rsyncCopy({ sourcePath, destinationPath, excludes = [], keepExisting = false, silent = false, extraArgs = [], }) {
75
+ const startTime = Date.now();
76
+ // On macOS, try APFS copy-on-write clone first (near-instant).
77
+ // Skip when extraArgs are provided since those are rsync-specific flags
78
+ // that the clone path can't honor.
79
+ if (!keepExisting && extraArgs.length === 0) {
80
+ const cloned = tryApfsClone({
81
+ sourcePath,
82
+ destinationPath,
83
+ excludes,
84
+ silent,
85
+ });
86
+ if (cloned) {
87
+ if (!silent) {
88
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
89
+ console.log(`Directory synced from ${sourcePath} to ${destinationPath} [Time: ${duration}s]`);
90
+ }
91
+ return;
92
+ }
93
+ }
94
+ // Fall back to rsync
3
95
  return new Promise((resolve, reject) => {
4
96
  const source = sourcePath.endsWith('/') ? sourcePath : `${sourcePath}/`;
5
97
  const dest = destinationPath.endsWith('/')
@@ -16,7 +108,6 @@ export default async function rsyncCopy({ sourcePath, destinationPath, excludes
16
108
  rsyncArgs.push(`--exclude=${exclude}`);
17
109
  }
18
110
  rsyncArgs.push(source, dest);
19
- const startTime = Date.now();
20
111
  const rsyncProcess = spawn('rsync', rsyncArgs);
21
112
  rsyncProcess.on('exit', (code) => {
22
113
  if (code === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"rsyncCopy.js","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CAAC,EACtC,UAAU,EACV,eAAe,EACf,QAAQ,GAAG,EAAE,EACb,YAAY,GAAG,KAAK,EACpB,MAAM,GAAG,KAAK,EACd,SAAS,GAAG,EAAE,GAQf;IACC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,CAAC;QACxE,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC;YACxC,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,GAAG,eAAe,GAAG,CAAC;QAE1B,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,CAAC;QAEzB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QACxC,CAAC;QAED,wCAAwC;QACxC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;QAE7B,8BAA8B;QAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,SAAS,CAAC,IAAI,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;QACzC,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAE/C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC/B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBAC9D,OAAO,CAAC,GAAG,CACT,yBAAyB,UAAU,OAAO,eAAe,WAAW,QAAQ,IAAI,CACjF,CAAC;gBACJ,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,+BAA+B,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YACxC,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"rsyncCopy.js","sourceRoot":"","sources":["../../../../../src/lib/fs/rsyncCopy.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,87 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, execSync } from 'child_process';
2
+ import { existsSync, readdirSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ /**
6
+ * Try to use APFS copy-on-write clones on macOS for near-instant directory copies.
7
+ * Falls back to rsync on non-macOS or if the clone fails.
8
+ *
9
+ * Returns true if the clone succeeded (caller can skip rsync).
10
+ */
11
+ function tryApfsClone({
12
+ sourcePath,
13
+ destinationPath,
14
+ excludes,
15
+ silent,
16
+ }: {
17
+ sourcePath: string;
18
+ destinationPath: string;
19
+ excludes: string[];
20
+ silent: boolean;
21
+ }): boolean {
22
+ if (process.platform !== 'darwin') return false;
23
+
24
+ // APFS clone requires the destination to not exist.
25
+ // If it exists and is empty, remove it so we can clone into it.
26
+ if (existsSync(destinationPath)) {
27
+ try {
28
+ const contents = readdirSync(destinationPath);
29
+ if (contents.length > 0) {
30
+ // Destination is non-empty — can't use clone, fall back to rsync
31
+ return false;
32
+ }
33
+ rmSync(destinationPath, { recursive: true });
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ try {
40
+ // cp -c -R: APFS copy-on-write clone (faster than rsync, avoids data copy)
41
+ execSync(`cp -c -R "${sourcePath}" "${destinationPath}"`, {
42
+ stdio: 'pipe',
43
+ timeout: 300_000, // 5 min safety timeout
44
+ });
45
+
46
+ // Remove excluded items from the clone
47
+ for (const exclude of excludes) {
48
+ if (exclude.includes('*')) {
49
+ // Glob pattern — use shell expansion
50
+ try {
51
+ execSync(`rm -rf "${join(destinationPath, exclude)}"`, {
52
+ stdio: 'pipe',
53
+ shell: '/bin/sh',
54
+ });
55
+ } catch {
56
+ // Glob matched nothing — fine
57
+ }
58
+ } else {
59
+ const excludePath = join(destinationPath, exclude);
60
+ if (existsSync(excludePath)) {
61
+ rmSync(excludePath, { recursive: true, force: true });
62
+ }
63
+ }
64
+ }
65
+
66
+ if (!silent) {
67
+ console.log(
68
+ `Directory cloned (APFS CoW) from ${sourcePath} to ${destinationPath}`,
69
+ );
70
+ }
71
+ return true;
72
+ } catch {
73
+ // Clone failed (cross-volume, non-APFS, etc.) — fall back to rsync
74
+ // Clean up any partial clone
75
+ if (existsSync(destinationPath)) {
76
+ try {
77
+ rmSync(destinationPath, { recursive: true });
78
+ } catch {
79
+ // Best effort cleanup
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ }
2
85
 
3
86
  export default async function rsyncCopy({
4
87
  sourcePath,
@@ -15,6 +98,30 @@ export default async function rsyncCopy({
15
98
  silent?: boolean;
16
99
  extraArgs?: string[];
17
100
  }): Promise<void> {
101
+ const startTime = Date.now();
102
+
103
+ // On macOS, try APFS copy-on-write clone first (near-instant).
104
+ // Skip when extraArgs are provided since those are rsync-specific flags
105
+ // that the clone path can't honor.
106
+ if (!keepExisting && extraArgs.length === 0) {
107
+ const cloned = tryApfsClone({
108
+ sourcePath,
109
+ destinationPath,
110
+ excludes,
111
+ silent,
112
+ });
113
+ if (cloned) {
114
+ if (!silent) {
115
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
116
+ console.log(
117
+ `Directory synced from ${sourcePath} to ${destinationPath} [Time: ${duration}s]`,
118
+ );
119
+ }
120
+ return;
121
+ }
122
+ }
123
+
124
+ // Fall back to rsync
18
125
  return new Promise((resolve, reject) => {
19
126
  const source = sourcePath.endsWith('/') ? sourcePath : `${sourcePath}/`;
20
127
  const dest = destinationPath.endsWith('/')
@@ -37,7 +144,6 @@ export default async function rsyncCopy({
37
144
 
38
145
  rsyncArgs.push(source, dest);
39
146
 
40
- const startTime = Date.now();
41
147
  const rsyncProcess = spawn('rsync', rsyncArgs);
42
148
 
43
149
  rsyncProcess.on('exit', (code) => {
@@ -342,7 +342,7 @@ export default function constructMockCode(
342
342
  let foundEntityWithSignature = false;
343
343
  let signatureSchema: DataStructure['signatureSchema'] | undefined;
344
344
 
345
- for (const filePath in dependencySchemas) {
345
+ entitySearch: for (const filePath in dependencySchemas) {
346
346
  for (const entityName in dependencySchemas[filePath]) {
347
347
  // Match entity by base name (without generics/args)
348
348
  const entityBaseName = entityName.split(/[<(]/)[0];
@@ -386,7 +386,7 @@ export default function constructMockCode(
386
386
  // However, we still need to remove duplicate function calls that create invalid syntax
387
387
  removeDuplicateFunctionCalls(relevantReturnValueSchema);
388
388
  dataStructureValue = relevantReturnValueSchema?.[dataStructurePath];
389
- break;
389
+ break entitySearch;
390
390
  }
391
391
  }
392
392
  }
@@ -2733,6 +2733,20 @@ ${exportKeyword}const ${functionName} = new Proxy(() => scenarios().data()?.["${
2733
2733
  debugLog(
2734
2734
  `[REMAINING LOOP] import ${remainingImportIndex}/${remainingImportPaths.length}: ${importPath}`,
2735
2735
  );
2736
+
2737
+ // Skip imports that point to generated CodeYam files (same skip logic as
2738
+ // rewriteRelativeModuleImports). Without this, MockData files from earlier
2739
+ // captures that get discovered by the TypeScript compiler would be treated as
2740
+ // regular imports, creating transitive copies with stale content.
2741
+ const scenarioFilePattern = /[a-f0-9]{64}_\w+_[A-Z]\w*$/;
2742
+ const mockDataPattern = /__codeyamMocks__\//;
2743
+ if (
2744
+ scenarioFilePattern.test(importPath) ||
2745
+ mockDataPattern.test(importPath)
2746
+ ) {
2747
+ continue;
2748
+ }
2749
+
2736
2750
  // Resolve the import path to a project file path
2737
2751
  const resolvedFilePath = resolveImportPath(importPath, file.path, project);
2738
2752
 
@@ -257,7 +257,7 @@ options) {
257
257
  let dataStructureValue;
258
258
  let foundEntityWithSignature = false;
259
259
  let signatureSchema;
260
- for (const filePath in dependencySchemas) {
260
+ entitySearch: for (const filePath in dependencySchemas) {
261
261
  for (const entityName in dependencySchemas[filePath]) {
262
262
  // Match entity by base name (without generics/args)
263
263
  const entityBaseName = entityName.split(/[<(]/)[0];
@@ -289,7 +289,7 @@ options) {
289
289
  // However, we still need to remove duplicate function calls that create invalid syntax
290
290
  removeDuplicateFunctionCalls(relevantReturnValueSchema);
291
291
  dataStructureValue = relevantReturnValueSchema?.[dataStructurePath];
292
- break;
292
+ break entitySearch;
293
293
  }
294
294
  }
295
295
  }