@codeyam/codeyam-cli 0.1.0-staging.323686 → 0.1.0-staging.483fdc2

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 (171) 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 +2 -2
  4. package/analyzer-template/packages/ai/index.ts +6 -1
  5. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +39 -17
  6. package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +67 -9
  7. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +308 -50
  8. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +15 -6
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +664 -242
  10. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
  11. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
  12. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +20 -1
  13. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +35 -13
  14. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +160 -0
  15. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
  16. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +289 -83
  17. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +269 -1
  18. package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +9 -5
  19. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +11 -3
  20. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
  21. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +297 -7
  22. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +1 -1
  23. package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
  24. package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
  25. package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +25 -13
  26. package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +4 -3
  27. package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
  28. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
  29. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
  30. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
  31. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
  32. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
  33. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
  34. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
  35. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +20 -6
  36. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +14 -4
  37. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
  38. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +0 -3
  39. package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
  40. package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
  41. package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +57 -13
  42. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +29 -0
  43. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +35 -4
  44. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +117 -9
  45. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +199 -17
  46. package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
  47. package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
  48. package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
  49. package/analyzer-template/packages/aws/package.json +1 -1
  50. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  51. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  52. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  53. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  54. package/analyzer-template/packages/github/package.json +1 -1
  55. package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +6 -5
  56. package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
  57. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
  58. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  59. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  60. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  61. package/analyzer-template/project/constructMockCode.ts +54 -9
  62. package/analyzer-template/project/writeMockDataTsx.ts +73 -2
  63. package/background/src/lib/virtualized/project/constructMockCode.js +45 -3
  64. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  65. package/background/src/lib/virtualized/project/writeMockDataTsx.js +71 -2
  66. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  67. package/codeyam-cli/scripts/apply-setup.js +146 -0
  68. package/codeyam-cli/scripts/apply-setup.js.map +1 -1
  69. package/codeyam-cli/src/commands/debug.js +7 -5
  70. package/codeyam-cli/src/commands/debug.js.map +1 -1
  71. package/codeyam-cli/src/utils/install-skills.js +22 -0
  72. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  73. package/codeyam-cli/src/utils/reviewedRules.js +92 -0
  74. package/codeyam-cli/src/utils/reviewedRules.js.map +1 -0
  75. package/codeyam-cli/src/webserver/build/client/assets/globals-CX9f-5xM.css +1 -0
  76. package/codeyam-cli/src/webserver/build/client/assets/{manifest-7522edd4.js → manifest-bba56ec1.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/memory-DuTFSyJ2.js +92 -0
  78. package/codeyam-cli/src/webserver/build/client/assets/{root-eVAaavTS.js → root-DTfSQARG.js} +6 -6
  79. package/codeyam-cli/src/webserver/build/server/assets/{index-DVzYx8PN.js → index-TD1f-DHV.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/server/assets/server-build-BQ-1XyEa.js +258 -0
  81. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  82. package/codeyam-cli/src/webserver/build-info.json +5 -5
  83. package/codeyam-cli/templates/codeyam:memory.md +174 -233
  84. package/codeyam-cli/templates/codeyam:new-rule.md +41 -2
  85. package/codeyam-cli/templates/rule-reflection-hook.py +161 -0
  86. package/codeyam-cli/templates/rules-instructions.md +126 -0
  87. package/package.json +1 -1
  88. package/packages/ai/index.js +2 -1
  89. package/packages/ai/index.js.map +1 -1
  90. package/packages/ai/src/lib/analyzeScope.js +29 -12
  91. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  92. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +54 -8
  93. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
  94. package/packages/ai/src/lib/astScopes/processExpression.js +239 -43
  95. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  96. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +503 -165
  97. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  98. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
  99. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
  100. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
  101. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
  102. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +22 -1
  103. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  104. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +34 -9
  105. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
  106. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +159 -0
  107. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
  108. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
  109. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
  110. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +237 -73
  111. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
  112. package/packages/ai/src/lib/generateEntityScenarioData.js +195 -1
  113. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  114. package/packages/ai/src/lib/generateEntityScenarios.js +7 -1
  115. package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
  116. package/packages/ai/src/lib/generateExecutionFlows.js +10 -2
  117. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  118. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +209 -3
  119. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  120. package/packages/ai/src/lib/mergeStatements.js +70 -51
  121. package/packages/ai/src/lib/mergeStatements.js.map +1 -1
  122. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
  123. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
  124. package/packages/ai/src/lib/resolvePathToControllable.js +24 -14
  125. package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
  126. package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
  127. package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
  128. package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
  129. package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
  130. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  131. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
  132. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
  133. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
  134. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
  135. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
  136. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
  137. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
  138. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
  139. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
  140. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
  141. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
  142. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
  143. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +13 -5
  144. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  145. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +14 -4
  146. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  147. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
  148. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  149. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +0 -3
  150. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  151. package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
  152. package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
  153. package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
  154. package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
  155. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +52 -10
  156. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
  157. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +25 -8
  158. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  159. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +34 -4
  160. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  161. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +56 -8
  162. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  163. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +168 -9
  164. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  165. package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
  166. package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
  167. package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
  168. package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
  169. package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +0 -1
  170. package/codeyam-cli/src/webserver/build/client/assets/memory-yxFcrxBX.js +0 -92
  171. package/codeyam-cli/src/webserver/build/server/assets/server-build-4Cr0uToj.js +0 -257
@@ -6,7 +6,14 @@ type ImportsAnalysis = {
6
6
  [importedFilePath: string]: ExportInfo[];
7
7
  };
8
8
 
9
+ const importsAnalysisCache = new WeakMap<ts.SourceFile, ImportsAnalysis>();
10
+
9
11
  export function getImportsAnalysis(sourceFile: ts.SourceFile): ImportsAnalysis {
12
+ const cached = importsAnalysisCache.get(sourceFile);
13
+ if (cached) {
14
+ return cached;
15
+ }
16
+
10
17
  const importsMapEntries: [string, ExportInfo[]][] = [];
11
18
 
12
19
  const importsVisitor = (node: ts.Node) => {
@@ -35,6 +42,7 @@ export function getImportsAnalysis(sourceFile: ts.SourceFile): ImportsAnalysis {
35
42
  {} as ImportsAnalysis,
36
43
  );
37
44
 
45
+ importsAnalysisCache.set(sourceFile, consolidatedImportsMapEntries);
38
46
  return consolidatedImportsMapEntries;
39
47
  }
40
48
 
@@ -2,21 +2,44 @@ import ts from 'typescript';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
4
 
5
+ /**
6
+ * Cache for nearest tsconfig lookups (keyed by directory).
7
+ * Speeds up repeated lookups during module resolution.
8
+ */
9
+ const nearestTsConfigCache = new Map<string, string | undefined>();
10
+
5
11
  /**
6
12
  * Find the nearest tsconfig.json by walking up the directory tree from the source file
7
13
  */
8
14
  function findNearestTsConfig(sourceFileName: string): string | undefined {
9
15
  let currentDir = path.dirname(sourceFileName);
10
16
  const root = path.parse(currentDir).root;
17
+ const visited: string[] = [];
11
18
 
12
19
  while (currentDir !== root) {
20
+ const cached = nearestTsConfigCache.get(currentDir);
21
+ if (cached !== undefined || nearestTsConfigCache.has(currentDir)) {
22
+ for (const dir of visited) {
23
+ nearestTsConfigCache.set(dir, cached);
24
+ }
25
+ return cached;
26
+ }
27
+
28
+ visited.push(currentDir);
13
29
  const tsConfigPath = path.join(currentDir, 'tsconfig.json');
14
30
  if (fs.existsSync(tsConfigPath)) {
31
+ for (const dir of visited) {
32
+ nearestTsConfigCache.set(dir, tsConfigPath);
33
+ }
15
34
  return tsConfigPath;
16
35
  }
17
36
  currentDir = path.dirname(currentDir);
18
37
  }
19
38
 
39
+ for (const dir of visited) {
40
+ nearestTsConfigCache.set(dir, undefined);
41
+ }
42
+
20
43
  return undefined;
21
44
  }
22
45
 
@@ -25,6 +48,14 @@ function findNearestTsConfig(sourceFileName: string): string | undefined {
25
48
  */
26
49
  const tsConfigCache = new Map<string, ts.CompilerOptions>();
27
50
 
51
+ /**
52
+ * Cache resolved module lookups by program for fast repeat access.
53
+ */
54
+ const resolvedModuleCacheByProgram = new WeakMap<
55
+ ts.Program,
56
+ Map<string, ts.ResolvedModuleFull | null>
57
+ >();
58
+
28
59
  /**
29
60
  * Get compiler options from a tsconfig file
30
61
  */
@@ -62,6 +93,19 @@ export function getResolvedModule(
62
93
  // Try to find the nearest tsconfig for this source file
63
94
  const nearestTsConfig = findNearestTsConfig(sourceFile.fileName);
64
95
 
96
+ const cacheForProgram =
97
+ resolvedModuleCacheByProgram.get(program) ??
98
+ new Map<string, ts.ResolvedModuleFull | null>();
99
+ if (!resolvedModuleCacheByProgram.has(program)) {
100
+ resolvedModuleCacheByProgram.set(program, cacheForProgram);
101
+ }
102
+
103
+ const cacheKey = `${nearestTsConfig ?? ''}|${sourceFile.fileName}|${moduleName}`;
104
+ if (cacheForProgram.has(cacheKey)) {
105
+ const cached = cacheForProgram.get(cacheKey);
106
+ return cached ?? undefined;
107
+ }
108
+
65
109
  // Start with the program's compiler options
66
110
  let compilerOptions = program.getCompilerOptions();
67
111
 
@@ -90,6 +134,7 @@ export function getResolvedModule(
90
134
 
91
135
  // If TypeScript's resolution succeeded, return it
92
136
  if (result.resolvedModule) {
137
+ cacheForProgram.set(cacheKey, result.resolvedModule);
93
138
  return result.resolvedModule;
94
139
  }
95
140
 
@@ -105,14 +150,17 @@ export function getResolvedModule(
105
150
  for (const ext of extensions) {
106
151
  const testPath = absolutePath + ext;
107
152
  if (fs.existsSync(testPath)) {
108
- return {
153
+ const resolved = {
109
154
  resolvedFileName: testPath,
110
155
  extension: ext.includes('/') ? path.extname(ext) : ext,
111
156
  isExternalLibraryImport: false,
112
157
  } as ts.ResolvedModuleFull;
158
+ cacheForProgram.set(cacheKey, resolved);
159
+ return resolved;
113
160
  }
114
161
  }
115
162
  }
116
163
 
164
+ cacheForProgram.set(cacheKey, null);
117
165
  return undefined;
118
166
  }
@@ -21,5 +21,6 @@ export function getSourceFilesForAllImports(
21
21
 
22
22
  visit(sourceFile); // Start visiting nodes from the root source file
23
23
 
24
- return getSourceFilesForImports(moduleNames, sourceFile, program);
24
+ const uniqueModuleNames = Array.from(new Set(moduleNames));
25
+ return getSourceFilesForImports(uniqueModuleNames, sourceFile, program);
25
26
  }
@@ -336,11 +336,23 @@ async function getEntityDataStructureAndFunctionCalls({
336
336
  functionName: importedExport.name,
337
337
  });
338
338
 
339
- // If neither signature nor return value found, this function doesn't exist in the analysis.
340
- // We need to check BOTH because hooks like useParams() have no signature (no arguments)
341
- // but DO have a return value that needs to be mocked.
339
+ // If neither signature nor return value found, check if this function has matching
340
+ // external function calls. External functions (like useLoaderData from react-router)
341
+ // don't have entries in functionResults, so getReturnValue returns null for them.
342
+ // But their return values ARE traced in externalFunctionCalls via perVariableSchemas.
343
+ // We need to check for matching calls before skipping.
342
344
  if (!signatureSchema && !returnValueSchema) {
343
- return acc;
345
+ const hasMatchingExternalCall =
346
+ dataStructure.externalFunctionCalls?.some(
347
+ (fc) =>
348
+ fc.name === importedExport.name ||
349
+ fc.name.startsWith(importedExport.name + '<'),
350
+ );
351
+
352
+ // Only skip if there are no matching external function calls
353
+ if (!hasMatchingExternalCall) {
354
+ return acc;
355
+ }
344
356
  }
345
357
 
346
358
  // Clone schemas before deduplication to avoid mutating shared references
@@ -570,11 +582,13 @@ async function getEntityDataStructureAndFunctionCalls({
570
582
  // Get JSX rendering usages for array size and text length flow generation
571
583
  const jsxRenderingUsages = getJsxRenderingUsages(dataStructure);
572
584
 
585
+ const equivalentSignatureVariables =
586
+ getEquivalentSignatureVariables(dataStructure);
587
+
573
588
  const isolatedDataStructure = {
574
589
  signatureSchema: deduplicateFunctionSchemas(clonedRawSignature),
575
590
  returnValueSchema: deduplicateFunctionSchemas(clonedRawReturnValue),
576
- equivalentSignatureVariables:
577
- getEquivalentSignatureVariables(dataStructure),
591
+ equivalentSignatureVariables,
578
592
  dependencySchemas,
579
593
  environmentVariables: dataStructure.environmentVariables,
580
594
  conditionalUsages:
@@ -5,6 +5,7 @@ import {
5
5
  measureAndReportExecutionTime,
6
6
  ProjectAnalyzer,
7
7
  } from '~codeyam/analyze';
8
+ import { getFileByPathSafe } from '../../utils/getFileByPath';
8
9
  import trackEntityCircularDependencies from './trackEntityCircularDependencies';
9
10
  import validateDependencyAnalyses from './validateDependencyAnalyses';
10
11
  import extractClassMethods from './analyzeEntities/extractClassMethods';
@@ -388,10 +389,19 @@ export default async function analyzeEntities({
388
389
  // Circular dependency detected in path - skip
389
390
  continue;
390
391
  }
391
- // If already visited via another traversal path, skip to avoid deadlock.
392
- // Its subtree has been fully explored; any leaves are already in leafKeys.
393
- // Blocking on it would just propagate the block and cause stuck analysis.
392
+ // If already visited via another traversal path, check if it's a leaf.
393
+ // If it's a leaf, we should still wait for it to complete.
394
+ // Only skip if we've already explored it and it's NOT a leaf (meaning it's
395
+ // blocked on something else - continuing to traverse would cause a deadlock).
394
396
  if (visited.has(dependentEntityKey)) {
397
+ // If the visited entity is a leaf, we should wait for it to complete
398
+ // before processing this entity. Don't skip it!
399
+ if (leafKeys.has(dependentEntityKey)) {
400
+ // It's a leaf that hasn't completed yet - wait for it
401
+ isLeaf = false;
402
+ continue;
403
+ }
404
+ // If it's not a leaf, it's blocked on something else - skip to avoid deadlock
395
405
  continue;
396
406
  }
397
407
  isLeaf = false;
@@ -426,7 +436,7 @@ export default async function analyzeEntities({
426
436
  }
427
437
 
428
438
  const file =
429
- entity.file || project.files.find((f) => f.path === entity.filePath);
439
+ entity.file || getFileByPathSafe(projectAnalyzer, entity.filePath);
430
440
  if (!file) {
431
441
  awsLog(
432
442
  `CodeYam: analyzeEntities: File not found for entity: ${entityKey}`,
@@ -1,6 +1,7 @@
1
1
  import { Entity } from '~codeyam/types';
2
2
  import findOrCreateEntity from './findOrCreateEntity';
3
3
  import { ProjectAnalyzer } from '../../ProjectAnalyzer';
4
+ import { getFileByPathSafe } from '../../utils/getFileByPath';
4
5
  import { AnalysisContext } from '../../analysisContext';
5
6
 
6
7
  // Track files we've already warned about to avoid duplicate warnings
@@ -71,8 +72,9 @@ export default async function gatherEntityMap({
71
72
  // ` → imported export ${importedExportFilePath}:${importedExportName}`,
72
73
  // );
73
74
 
74
- const importedFile = project.files.find(
75
- (file) => file.path === importedExportFilePath,
75
+ const importedFile = getFileByPathSafe(
76
+ projectAnalyzer,
77
+ importedExportFilePath,
76
78
  );
77
79
 
78
80
  if (!importedFile) {
@@ -69,9 +69,6 @@ export default function validateDependencyAnalyses({
69
69
 
70
70
  // if the entity has duplicate dependencies, handle gracefully
71
71
  if (dependentAnalyses[dependencyFilePath]?.[dependencyName]) {
72
- // console.log(
73
- // `CodeYam: validateDependencyAnalyses: Entity ${entity.filePath}:${entity.name} has duplicate dependency ${dependencyFilePath}:${dependencyName}. Skipping...`,
74
- // );
75
72
  continue;
76
73
  }
77
74
 
@@ -1,5 +1,6 @@
1
1
  import { Entity, EntityType, File, ProjectFramework } from '~codeyam/types';
2
2
  import { ProjectAnalyzer } from '../ProjectAnalyzer';
3
+ import { getFileByPathSafe } from '../utils/getFileByPath';
3
4
  import { generateSha } from '~codeyam/database';
4
5
  import { isRemixRoute } from '~codeyam/utils';
5
6
 
@@ -88,11 +89,9 @@ export default function analyzeRemixRoute(
88
89
  // Find root.tsx for this route's webapp and add as implicit dependency
89
90
  // This ensures root.tsx dependencies (like useLoaderData in AppContent) get analyzed for mocking
90
91
  const routeAppDir = file.path.split('/routes/')[0];
91
- const rootFile = projectAnalyzer.project.files.find(
92
- (f) =>
93
- f.path === `${routeAppDir}/root.tsx` ||
94
- f.path === `${routeAppDir}/root.jsx`,
95
- );
92
+ const rootFile =
93
+ getFileByPathSafe(projectAnalyzer, `${routeAppDir}/root.tsx`) ??
94
+ getFileByPathSafe(projectAnalyzer, `${routeAppDir}/root.jsx`);
96
95
 
97
96
  const rootDependencies: Entity['metadata']['importedExports'] = [];
98
97
  if (rootFile) {
@@ -1,6 +1,7 @@
1
1
  import { Entity, File, Project } from '~codeyam/types';
2
2
  import { FileAnalyzer } from '../FileAnalyzer';
3
3
  import { ProjectAnalyzer } from '../ProjectAnalyzer';
4
+ import { getFileByPathSafe } from '../utils/getFileByPath';
4
5
  import type { FunctionDependenciesMap } from '../asts/sourceFiles/getPseudoFile';
5
6
  // import { generateSha } from '~codeyam/database';
6
7
 
@@ -63,9 +64,7 @@ export default function getImportedExports(
63
64
  imports[filePath].resolvedPath ??
64
65
  filePath;
65
66
 
66
- let importedExportFile = project.files.find(
67
- (f) => f.path === searchPath,
68
- );
67
+ let importedExportFile = getFileByPathSafe(projectAnalyzer, searchPath);
69
68
 
70
69
  // Track the actual path we found (may differ from searchPath after dist->src mapping)
71
70
  let actualResolvedPath = imports[filePath].resolvedPath;
@@ -84,7 +83,7 @@ export default function getImportedExports(
84
83
  const srcPath = `${packagePath}/src/${relativePath}`
85
84
  .replace(/\.d\.ts$/, '.ts')
86
85
  .replace(/\.js$/, '.ts');
87
- importedExportFile = project.files.find((f) => f.path === srcPath);
86
+ importedExportFile = getFileByPathSafe(projectAnalyzer, srcPath);
88
87
  if (importedExportFile) {
89
88
  actualResolvedPath = srcPath;
90
89
  console.log(
@@ -97,7 +96,7 @@ export default function getImportedExports(
97
96
  if (!importedExportFile) {
98
97
  const srcPath = searchPath.replace('/dist/', '/src/');
99
98
  // Try exact match first
100
- importedExportFile = project.files.find((f) => f.path === srcPath);
99
+ importedExportFile = getFileByPathSafe(projectAnalyzer, srcPath);
101
100
  if (importedExportFile) {
102
101
  actualResolvedPath = srcPath;
103
102
  console.log(
@@ -114,13 +113,16 @@ export default function getImportedExports(
114
113
  const basePath = srcPath
115
114
  .replace(/\.d\.ts$/, '')
116
115
  .replace(/\.js$/, '');
117
- importedExportFile = project.files.find(
118
- (f) =>
119
- f.path === basePath + '.ts' ||
120
- f.path === basePath + '.tsx' ||
121
- (f.path.startsWith(basePath.replace('/index', '/')) &&
122
- !f.path.includes('/dist/')),
123
- );
116
+ importedExportFile =
117
+ getFileByPathSafe(projectAnalyzer, basePath + '.ts') ??
118
+ getFileByPathSafe(projectAnalyzer, basePath + '.tsx');
119
+ if (!importedExportFile) {
120
+ importedExportFile = project.files.find(
121
+ (f) =>
122
+ f.path.startsWith(basePath.replace('/index', '/')) &&
123
+ !f.path.includes('/dist/'),
124
+ );
125
+ }
124
126
  if (importedExportFile) {
125
127
  actualResolvedPath = importedExportFile.path;
126
128
  console.log(
@@ -101,27 +101,47 @@ export default function enrichArrayTypesFromChildSignatures(
101
101
  ): void => {
102
102
  // Group by prop name (e.g., signature[0].survey.id, signature[0].survey.name -> "survey")
103
103
  const propGroups: Record<string, Record<string, string>> = {};
104
+ // Also collect direct fields on signature[0] (e.g., signature[0].filePath -> "filePath")
105
+ const directFields: Record<string, string> = {};
104
106
 
105
107
  for (const [path, type] of Object.entries(schema)) {
106
108
  // Match signature[0].propName.field or signature[0].propName[].field patterns
107
109
  // The (?:\[\])? makes array props optional, handling both:
108
110
  // - signature[0].survey.updatedAt (object prop)
109
111
  // - signature[0].questionAnswers[].question.questionText (array prop)
110
- const match = path.match(/^signature\[0\]\.(\w+)(?:\[\])?\.(.+)$/);
111
- if (match) {
112
- const [, propName, fieldPath] = match;
112
+ const nestedMatch = path.match(/^signature\[0\]\.(\w+)(?:\[\])?\.(.+)$/);
113
+ if (nestedMatch) {
114
+ const [, propName, fieldPath] = nestedMatch;
113
115
  propGroups[propName] ||= {};
114
116
  propGroups[propName][fieldPath] = type;
117
+ continue;
118
+ }
119
+
120
+ // Also match direct fields: signature[0].fieldName (single level, no nested path)
121
+ // This handles cases like TruncatedFilePath where filePath is passed directly
122
+ const directMatch = path.match(/^signature\[0\]\.(\w+)$/);
123
+ if (directMatch) {
124
+ const [, fieldName] = directMatch;
125
+ directFields[fieldName] = type;
115
126
  }
116
127
  }
117
128
 
118
- // Store with the full signature path as key
129
+ // Store nested prop groups with the full signature path as key
119
130
  for (const [propName, fields] of Object.entries(propGroups)) {
120
131
  if (Object.keys(fields).length > 0) {
121
132
  const signaturePath = `${entityName}().signature[0].${propName}`;
122
133
  childSignatureFieldsByPath.set(signaturePath, { propName, fields });
123
134
  }
124
135
  }
136
+
137
+ // Store direct fields with signature[0] as key (for when entire object is passed)
138
+ if (Object.keys(directFields).length > 0) {
139
+ const signaturePath = `${entityName}().signature[0]`;
140
+ childSignatureFieldsByPath.set(signaturePath, {
141
+ propName: '',
142
+ fields: directFields,
143
+ });
144
+ }
125
145
  };
126
146
 
127
147
  // Process all schemas in dependencySchemas
@@ -187,15 +207,20 @@ export default function enrichArrayTypesFromChildSignatures(
187
207
  for (const { filePath, entityName, arrayPath } of mockedArrayPaths) {
188
208
  const schema = dependencySchemas[filePath][entityName].returnValueSchema;
189
209
 
190
- // Check if this array already has element type definitions
210
+ // Check if this array already has INDEXED element type definitions (e.g., [0]: null)
191
211
  // If so, skip it - it's likely a specialized array like [null] for refs
212
+ // But DON'T skip if it only has general [] element types - those can be enriched
192
213
  const elementPathPrefix = `${arrayPath}[`;
193
- const hasExistingElementType = Object.keys(schema).some((path) =>
194
- path.startsWith(elementPathPrefix),
195
- );
196
-
197
- if (hasExistingElementType) {
198
- // Array already has element type info (e.g., useAutoAnimate()[0]: null)
214
+ const hasIndexedElementType = Object.keys(schema).some((path) => {
215
+ if (!path.startsWith(elementPathPrefix)) return false;
216
+ // Check if it's an indexed type like [0], [1], etc. vs general []
217
+ const afterPrefix = path.slice(elementPathPrefix.length);
218
+ // Indexed types start with a digit: [0], [1], etc.
219
+ return /^\d/.test(afterPrefix);
220
+ });
221
+
222
+ if (hasIndexedElementType) {
223
+ // Array already has indexed element type info (e.g., useAutoAnimate()[0]: null)
199
224
  // Don't override it with child signature fields
200
225
  continue;
201
226
  }
@@ -218,7 +243,13 @@ export default function enrichArrayTypesFromChildSignatures(
218
243
 
219
244
  // Check if this local variable could have come from this mocked function
220
245
  // Use heuristics: name matching (getSurveys -> surveys, getUsers -> users)
221
- if (!couldBeDerivedFrom(localVarName, entityName)) {
246
+ // Also check if the array path ends with the local variable name
247
+ // e.g., useLoaderData().functionCallReturnValue.entities ends with .entities
248
+ const arrayEndsWithLocalVar = arrayPath.endsWith(`.${localVarName}`);
249
+ if (
250
+ !couldBeDerivedFrom(localVarName, entityName) &&
251
+ !arrayEndsWithLocalVar
252
+ ) {
222
253
  continue;
223
254
  }
224
255
 
@@ -257,8 +288,21 @@ export default function enrichArrayTypesFromChildSignatures(
257
288
 
258
289
  for (const [fieldPath, type] of Object.entries(matchedFields)) {
259
290
  const fullPath = `${elementPath}.${fieldPath}`;
260
- if (!schema[fullPath]) {
291
+ const existingType = schema[fullPath];
292
+ if (!existingType) {
261
293
  schema[fullPath] = type;
294
+ } else {
295
+ // Prefer the more specific (narrower) type
296
+ const existingIsOptional =
297
+ existingType.includes('| undefined') ||
298
+ existingType.includes('| null');
299
+ const newIsOptional =
300
+ type.includes('| undefined') || type.includes('| null');
301
+ if (existingIsOptional && !newIsOptional) {
302
+ // Use the new type - it's more specific
303
+ schema[fullPath] = type;
304
+ }
305
+ // Otherwise keep existing (same specificity or existing is more specific)
262
306
  }
263
307
  }
264
308
  }
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  convertDotNotation,
3
3
  fillInDirectSchemaGapsAndUnknowns,
4
+ buildSchemaIndexes,
5
+ type SchemaIndexes,
4
6
  joinParenthesesAndArrays,
5
7
  mergeJsonTypeDefinitions,
6
8
  splitOutsideParenthesesAndArrays,
@@ -165,6 +167,7 @@ function processCall(
165
167
  variableNameOccurrence: number | undefined,
166
168
  options: GatherDataForMocksOptions | undefined,
167
169
  hookCallIndex: number,
170
+ prebuiltIndexes?: SchemaIndexes,
168
171
  ): { key: string; value: JsonTypeDefinition } | null {
169
172
  const callName = importedExport.calls?.[callIndex] ?? importedExport.name;
170
173
 
@@ -465,6 +468,7 @@ function processCall(
465
468
  const filledSchema = fillInDirectSchemaGapsAndUnknowns({
466
469
  scopeName: importedExport.name,
467
470
  schema: relevantMergedDependencySchema,
471
+ prebuiltIndexes,
468
472
  });
469
473
 
470
474
  const schema = convertDotNotation(filledSchema);
@@ -568,6 +572,25 @@ export default function gatherDataForMocks(
568
572
  dependencySchemas: DataStructure['dependencySchemas'],
569
573
  options?: GatherDataForMocksOptions,
570
574
  ) {
575
+ // Build schema indexes ONCE from all dependency schemas combined.
576
+ // This avoids rebuilding indexes (~1-2 seconds for 18k keys) for each processCall.
577
+ const allSchemaKeys: Record<string, string> = {};
578
+ if (dependencySchemas) {
579
+ for (const filePath in dependencySchemas) {
580
+ for (const entityName in dependencySchemas[filePath]) {
581
+ const schema =
582
+ dependencySchemas[filePath][entityName]?.returnValueSchema;
583
+ if (schema) {
584
+ Object.assign(allSchemaKeys, schema);
585
+ }
586
+ }
587
+ }
588
+ }
589
+ const prebuiltIndexes =
590
+ Object.keys(allSchemaKeys).length > 0
591
+ ? buildSchemaIndexes(allSchemaKeys)
592
+ : undefined;
593
+
571
594
  // Track the global index for each hook name across all imports
572
595
  // This enables canonical keys like EntityName::useLoaderData::0, EntityName::useFetcher::0, etc.
573
596
  const hookCallIndices: Record<string, number> = {};
@@ -653,6 +676,7 @@ export default function gatherDataForMocks(
653
676
  undefined,
654
677
  options,
655
678
  hookCallIndex,
679
+ prebuiltIndexes,
656
680
  );
657
681
 
658
682
  if (processResult) {
@@ -717,6 +741,7 @@ export default function gatherDataForMocks(
717
741
  occurrence,
718
742
  options,
719
743
  hookCallIndex,
744
+ prebuiltIndexes,
720
745
  );
721
746
  if (processResult) {
722
747
  addResultToAccumulator(acc, processResult.key, processResult.value);
@@ -749,6 +774,7 @@ export default function gatherDataForMocks(
749
774
  undefined,
750
775
  options,
751
776
  hookCallIndex,
777
+ prebuiltIndexes,
752
778
  );
753
779
 
754
780
  if (processResult) {
@@ -769,6 +795,7 @@ export default function gatherDataForMocks(
769
795
  undefined,
770
796
  options,
771
797
  hookCallIndex,
798
+ prebuiltIndexes,
772
799
  );
773
800
 
774
801
  // Check if we got a meaningful result (not empty object or null)
@@ -813,6 +840,7 @@ export default function gatherDataForMocks(
813
840
  0, // Use 0 to trigger variable-qualified lookup
814
841
  options,
815
842
  hookCallIndex,
843
+ prebuiltIndexes,
816
844
  );
817
845
  if (varQualifiedResult && varQualifiedResult.value) {
818
846
  mergedValue = mergeJsonTypeDefinitions(
@@ -849,6 +877,7 @@ export default function gatherDataForMocks(
849
877
  0, // Use 0 to trigger variable-qualified lookup
850
878
  options,
851
879
  hookCallIndex,
880
+ prebuiltIndexes,
852
881
  );
853
882
  if (varQualifiedResult && varQualifiedResult.value) {
854
883
  // Deep merge the value into the accumulated result
@@ -16,6 +16,7 @@ import { awsLog } from '~codeyam/utils';
16
16
  import gatherDataForMocks from './gatherDataForMocks';
17
17
  import enrichArrayTypesFromChildSignatures from './enrichArrayTypesFromChildSignatures';
18
18
  import enrichUnknownTypesFromSourceEquivalencies from './enrichUnknownTypesFromSourceEquivalencies';
19
+ // import propagateArrayItemSchemas from './propagateArrayItemSchemas';
19
20
  export interface GenerateDataStructureArgs {
20
21
  entity: Entity;
21
22
  dependentAnalyses: ReadonlyAnalysisMap;
@@ -469,11 +470,34 @@ export default function generateDataStructure({
469
470
 
470
471
  if (existingSchema) {
471
472
  // Merge schemas - child schemas add to parent schemas
473
+ // But prefer more specific types (without | undefined) when both exist
474
+ const mergedSignatureSchema = { ...existingSchema.signatureSchema };
475
+ for (const path in childSchema.signatureSchema) {
476
+ const existingType = mergedSignatureSchema[path];
477
+ const childType = childSchema.signatureSchema[path];
478
+ if (!existingType) {
479
+ mergedSignatureSchema[path] = childType;
480
+ } else {
481
+ // Prefer the more specific (narrower) type
482
+ const existingIsOptional =
483
+ existingType.includes('| undefined') ||
484
+ existingType.includes('| null');
485
+ const childIsOptional =
486
+ childType.includes('| undefined') ||
487
+ childType.includes('| null');
488
+ if (childIsOptional && !existingIsOptional) {
489
+ // Keep existing - it's more specific
490
+ } else if (!childIsOptional && existingIsOptional) {
491
+ // Use child - it's more specific
492
+ mergedSignatureSchema[path] = childType;
493
+ } else {
494
+ // Same specificity - use child (original behavior)
495
+ mergedSignatureSchema[path] = childType;
496
+ }
497
+ }
498
+ }
472
499
  mergedDataStructure.dependencySchemas[schemaFilePath][schemaName] = {
473
- signatureSchema: {
474
- ...existingSchema.signatureSchema,
475
- ...childSchema.signatureSchema,
476
- },
500
+ signatureSchema: mergedSignatureSchema,
477
501
  returnValueSchema: mergeJsonTypeDefinitions(
478
502
  existingSchema.returnValueSchema as Record<string, unknown>,
479
503
  childSchema.returnValueSchema as Record<string, unknown>,
@@ -560,6 +584,13 @@ export default function generateDataStructure({
560
584
  allImportedExports,
561
585
  );
562
586
 
587
+ // DISABLED: Testing if ternary fix eliminates the need for this heuristic
588
+ // Propagate array item schemas between arrays that likely have the same type.
589
+ // When one array (e.g., entities) has item schema traced but another similar array
590
+ // (e.g., currentEntities) doesn't (because it's only used in an OR expression fallback),
591
+ // copy the item schema from the rich array to the empty one.
592
+ // propagateArrayItemSchemas(mergedDataStructure.dependencySchemas);
593
+
563
594
  let dataForMocks = gatherDataForMocks(
564
595
  allImportedExports,
565
596
  mergedDataStructure.dependencySchemas,