@codeyam/codeyam-cli 0.1.0-staging.1669d45 → 0.1.0-staging.2a88920

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 (437) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +5 -5
  4. package/analyzer-template/packages/ai/index.ts +15 -2
  5. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +87 -51
  6. package/analyzer-template/packages/ai/src/lib/astScopes/arrayDerivationDetector.ts +199 -0
  7. package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +98 -9
  8. package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +139 -23
  9. package/analyzer-template/packages/ai/src/lib/astScopes/patterns/variableDeclarationHandler.ts +6 -126
  10. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +555 -28
  11. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +88 -7
  12. package/analyzer-template/packages/ai/src/lib/completionCall.ts +198 -34
  13. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +772 -243
  14. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/MuiManager.ts +205 -0
  15. package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/ReactFrameworkManager.ts +10 -2
  16. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
  17. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
  18. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +43 -1
  19. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +122 -15
  20. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +160 -0
  21. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
  22. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +319 -88
  23. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fixNullIdsBySchema.ts +129 -0
  24. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +156 -0
  25. package/analyzer-template/packages/ai/src/lib/e2eDataTracking.ts +334 -0
  26. package/analyzer-template/packages/ai/src/lib/extractCriticalDataKeys.ts +120 -0
  27. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +642 -7
  28. package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +35 -6
  29. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +383 -6
  30. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
  31. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +1299 -51
  32. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +239 -0
  33. package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
  34. package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
  35. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateChunkPrompt.ts +82 -0
  36. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateCriticalKeysPrompt.ts +103 -0
  37. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +23 -6
  38. package/analyzer-template/packages/ai/src/lib/promptGenerators/simplifyKeysForLLM.ts +391 -0
  39. package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +179 -45
  40. package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +26 -4
  41. package/analyzer-template/packages/ai/src/lib/worker/analyzeScopeWorker.ts +114 -2
  42. package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
  43. package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
  44. package/analyzer-template/packages/analyze/src/lib/analysisContext.ts +44 -4
  45. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
  46. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
  47. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
  48. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
  49. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
  50. package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
  51. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +30 -19
  52. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +14 -4
  53. package/analyzer-template/packages/analyze/src/lib/files/analyze/dependencyResolver.ts +6 -0
  54. package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
  55. package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +33 -10
  56. package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
  57. package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
  58. package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +189 -76
  59. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +29 -0
  60. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +77 -9
  61. package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +118 -10
  62. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +276 -17
  63. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeValidatedDataStructures.ts +56 -11
  64. package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
  65. package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
  66. package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
  67. package/analyzer-template/packages/aws/package.json +2 -2
  68. package/analyzer-template/packages/database/src/lib/kysely/db.ts +8 -1
  69. package/analyzer-template/packages/database/src/lib/kysely/tables/commitsTable.ts +6 -0
  70. package/analyzer-template/packages/database/src/lib/loadAnalyses.ts +58 -1
  71. package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +13 -0
  72. package/analyzer-template/packages/database/src/lib/loadBranch.ts +16 -1
  73. package/analyzer-template/packages/database/src/lib/loadCommit.ts +11 -0
  74. package/analyzer-template/packages/database/src/lib/loadCommits.ts +28 -0
  75. package/analyzer-template/packages/database/src/lib/loadEntities.ts +26 -3
  76. package/analyzer-template/packages/database/src/lib/loadEntityBranches.ts +12 -0
  77. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +7 -14
  78. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.d.ts.map +1 -1
  79. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js +8 -1
  80. package/analyzer-template/packages/github/dist/database/src/lib/kysely/db.js.map +1 -1
  81. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/analysesTable.d.ts.map +1 -1
  82. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.d.ts +1 -0
  83. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.d.ts.map +1 -1
  84. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.js +3 -0
  85. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/commitsTable.js.map +1 -1
  86. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.d.ts +2 -0
  87. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.d.ts.map +1 -1
  88. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.js +45 -2
  89. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalyses.js.map +1 -1
  90. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
  91. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +8 -0
  92. package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
  93. package/analyzer-template/packages/github/dist/database/src/lib/loadBranch.js +11 -1
  94. package/analyzer-template/packages/github/dist/database/src/lib/loadBranch.js.map +1 -1
  95. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.d.ts.map +1 -1
  96. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.js +7 -0
  97. package/analyzer-template/packages/github/dist/database/src/lib/loadCommit.js.map +1 -1
  98. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.d.ts +3 -1
  99. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.d.ts.map +1 -1
  100. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js +22 -1
  101. package/analyzer-template/packages/github/dist/database/src/lib/loadCommits.js.map +1 -1
  102. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts +3 -1
  103. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  104. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +23 -4
  105. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  106. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.d.ts.map +1 -1
  107. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.js +9 -0
  108. package/analyzer-template/packages/github/dist/database/src/lib/loadEntityBranches.js.map +1 -1
  109. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts +2 -2
  110. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  111. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +5 -4
  112. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  113. package/analyzer-template/packages/github/dist/types/index.d.ts +1 -1
  114. package/analyzer-template/packages/github/dist/types/index.d.ts.map +1 -1
  115. package/analyzer-template/packages/github/dist/types/index.js.map +1 -1
  116. package/analyzer-template/packages/github/dist/types/src/types/Analysis.d.ts +25 -1
  117. package/analyzer-template/packages/github/dist/types/src/types/Analysis.d.ts.map +1 -1
  118. package/analyzer-template/packages/github/dist/types/src/types/Commit.d.ts +2 -0
  119. package/analyzer-template/packages/github/dist/types/src/types/Commit.d.ts.map +1 -1
  120. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +56 -6
  121. package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  122. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  123. package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  124. package/analyzer-template/packages/github/dist/utils/src/lib/safeFileName.d.ts +9 -1
  125. package/analyzer-template/packages/github/dist/utils/src/lib/safeFileName.d.ts.map +1 -1
  126. package/analyzer-template/packages/github/dist/utils/src/lib/safeFileName.js +29 -3
  127. package/analyzer-template/packages/github/dist/utils/src/lib/safeFileName.js.map +1 -1
  128. package/analyzer-template/packages/github/package.json +1 -1
  129. package/analyzer-template/packages/types/index.ts +1 -0
  130. package/analyzer-template/packages/types/src/types/Analysis.ts +25 -0
  131. package/analyzer-template/packages/types/src/types/Commit.ts +2 -0
  132. package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +70 -6
  133. package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
  134. package/analyzer-template/packages/utils/dist/types/index.d.ts +1 -1
  135. package/analyzer-template/packages/utils/dist/types/index.d.ts.map +1 -1
  136. package/analyzer-template/packages/utils/dist/types/index.js.map +1 -1
  137. package/analyzer-template/packages/utils/dist/types/src/types/Analysis.d.ts +25 -1
  138. package/analyzer-template/packages/utils/dist/types/src/types/Analysis.d.ts.map +1 -1
  139. package/analyzer-template/packages/utils/dist/types/src/types/Commit.d.ts +2 -0
  140. package/analyzer-template/packages/utils/dist/types/src/types/Commit.d.ts.map +1 -1
  141. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +56 -6
  142. package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
  143. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
  144. package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
  145. package/analyzer-template/packages/utils/dist/utils/src/lib/safeFileName.d.ts +9 -1
  146. package/analyzer-template/packages/utils/dist/utils/src/lib/safeFileName.d.ts.map +1 -1
  147. package/analyzer-template/packages/utils/dist/utils/src/lib/safeFileName.js +29 -3
  148. package/analyzer-template/packages/utils/dist/utils/src/lib/safeFileName.js.map +1 -1
  149. package/analyzer-template/packages/utils/src/lib/safeFileName.ts +48 -3
  150. package/analyzer-template/playwright/capture.ts +20 -8
  151. package/analyzer-template/playwright/captureStatic.ts +1 -1
  152. package/analyzer-template/project/analyzeBaselineCommit.ts +5 -0
  153. package/analyzer-template/project/analyzeRegularCommit.ts +5 -0
  154. package/analyzer-template/project/captureLibraryFunctionDirect.ts +29 -26
  155. package/analyzer-template/project/constructMockCode.ts +367 -37
  156. package/analyzer-template/project/createEntitiesAndSortFiles.ts +83 -0
  157. package/analyzer-template/project/loadReadyToBeCaptured.ts +65 -41
  158. package/analyzer-template/project/orchestrateCapture/AwsCaptureTaskRunner.ts +12 -4
  159. package/analyzer-template/project/orchestrateCapture/SequentialCaptureTaskRunner.ts +18 -7
  160. package/analyzer-template/project/orchestrateCapture/taskRunner.ts +4 -2
  161. package/analyzer-template/project/orchestrateCapture.ts +71 -6
  162. package/analyzer-template/project/reconcileMockDataKeys.ts +152 -9
  163. package/analyzer-template/project/runAnalysis.ts +4 -0
  164. package/analyzer-template/project/start.ts +35 -11
  165. package/analyzer-template/project/writeMockDataTsx.ts +127 -4
  166. package/analyzer-template/project/writeScenarioComponents.ts +101 -8
  167. package/analyzer-template/scripts/comboWorkerLoop.cjs +98 -50
  168. package/background/src/lib/virtualized/project/analyzeBaselineCommit.js +5 -0
  169. package/background/src/lib/virtualized/project/analyzeBaselineCommit.js.map +1 -1
  170. package/background/src/lib/virtualized/project/analyzeRegularCommit.js +5 -0
  171. package/background/src/lib/virtualized/project/analyzeRegularCommit.js.map +1 -1
  172. package/background/src/lib/virtualized/project/captureLibraryFunctionDirect.js +3 -3
  173. package/background/src/lib/virtualized/project/captureLibraryFunctionDirect.js.map +1 -1
  174. package/background/src/lib/virtualized/project/constructMockCode.js +300 -7
  175. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  176. package/background/src/lib/virtualized/project/createEntitiesAndSortFiles.js +73 -1
  177. package/background/src/lib/virtualized/project/createEntitiesAndSortFiles.js.map +1 -1
  178. package/background/src/lib/virtualized/project/loadReadyToBeCaptured.js +19 -8
  179. package/background/src/lib/virtualized/project/loadReadyToBeCaptured.js.map +1 -1
  180. package/background/src/lib/virtualized/project/orchestrateCapture/AwsCaptureTaskRunner.js +2 -2
  181. package/background/src/lib/virtualized/project/orchestrateCapture/AwsCaptureTaskRunner.js.map +1 -1
  182. package/background/src/lib/virtualized/project/orchestrateCapture/SequentialCaptureTaskRunner.js +7 -5
  183. package/background/src/lib/virtualized/project/orchestrateCapture/SequentialCaptureTaskRunner.js.map +1 -1
  184. package/background/src/lib/virtualized/project/orchestrateCapture.js +58 -6
  185. package/background/src/lib/virtualized/project/orchestrateCapture.js.map +1 -1
  186. package/background/src/lib/virtualized/project/reconcileMockDataKeys.js +126 -9
  187. package/background/src/lib/virtualized/project/reconcileMockDataKeys.js.map +1 -1
  188. package/background/src/lib/virtualized/project/runAnalysis.js +3 -0
  189. package/background/src/lib/virtualized/project/runAnalysis.js.map +1 -1
  190. package/background/src/lib/virtualized/project/start.js +32 -11
  191. package/background/src/lib/virtualized/project/start.js.map +1 -1
  192. package/background/src/lib/virtualized/project/writeMockDataTsx.js +101 -4
  193. package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
  194. package/background/src/lib/virtualized/project/writeScenarioComponents.js +57 -8
  195. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  196. package/codeyam-cli/src/cli.js +2 -0
  197. package/codeyam-cli/src/cli.js.map +1 -1
  198. package/codeyam-cli/src/commands/debug.js +7 -5
  199. package/codeyam-cli/src/commands/debug.js.map +1 -1
  200. package/codeyam-cli/src/commands/memory.js +273 -0
  201. package/codeyam-cli/src/commands/memory.js.map +1 -0
  202. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +4 -0
  203. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  204. package/codeyam-cli/src/utils/analysisRunner.js +21 -2
  205. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  206. package/codeyam-cli/src/utils/install-skills.js +42 -6
  207. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  208. package/codeyam-cli/src/utils/queue/job.js +1 -0
  209. package/codeyam-cli/src/utils/queue/job.js.map +1 -1
  210. package/codeyam-cli/src/utils/queue/manager.js +6 -0
  211. package/codeyam-cli/src/utils/queue/manager.js.map +1 -1
  212. package/codeyam-cli/src/utils/rules/index.js +5 -0
  213. package/codeyam-cli/src/utils/rules/index.js.map +1 -0
  214. package/codeyam-cli/src/utils/rules/parser.js +106 -0
  215. package/codeyam-cli/src/utils/rules/parser.js.map +1 -0
  216. package/codeyam-cli/src/utils/rules/pathMatcher.js +18 -0
  217. package/codeyam-cli/src/utils/rules/pathMatcher.js.map +1 -0
  218. package/codeyam-cli/src/utils/rules/staleness.js +132 -0
  219. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -0
  220. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +2 -0
  221. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  222. package/codeyam-cli/src/webserver/app/lib/database.js +7 -3
  223. package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
  224. package/codeyam-cli/src/webserver/bootstrap.js +40 -0
  225. package/codeyam-cli/src/webserver/bootstrap.js.map +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/EntityItem-DsN1wKrm.js +11 -0
  227. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-COi5OvsN.js → EntityTypeBadge-DLqD3qNt.js} +1 -1
  228. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BwdQv49w.js → EntityTypeIcon-Ba2JVPzP.js} +1 -1
  229. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-CEleMv_j.js → InlineSpinner-C8lyxW9k.js} +1 -1
  230. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-D68KarMg.js → InteractivePreview-aht4aafF.js} +2 -2
  231. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-L75Wvqgw.js → LibraryFunctionPreview-CVtiBnY5.js} +1 -1
  232. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-C53WM8qn.js → LoadingDots-B0GLXMsr.js} +1 -1
  233. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CrNkmy4i.js → LogViewer-xgeCVgSM.js} +1 -1
  234. package/codeyam-cli/src/webserver/build/client/assets/ReportIssueModal-OApQuNyq.js +16 -0
  235. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CQifa1n-.js → SafeScreenshot-DuDvi0jm.js} +1 -1
  236. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-CyaBFX7l.js → ScenarioViewer-DzccYyI8.js} +3 -13
  237. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-D36O1rzU.js → TruncatedFilePath-DyFZkK0l.js} +1 -1
  238. package/codeyam-cli/src/webserver/build/client/assets/_index-BwqWJOgH.js +11 -0
  239. package/codeyam-cli/src/webserver/build/client/assets/activity.(_tab)-BwavGCpm.js +32 -0
  240. package/codeyam-cli/src/webserver/build/client/assets/api.health-l0sNRNKZ.js +1 -0
  241. package/codeyam-cli/src/webserver/build/client/assets/api.memory-profile-l0sNRNKZ.js +1 -0
  242. package/codeyam-cli/src/webserver/build/client/assets/api.restart-server-l0sNRNKZ.js +1 -0
  243. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-DgTPh8H-.js → chevron-down-Cx24_aWc.js} +1 -1
  244. package/codeyam-cli/src/webserver/build/client/assets/{chunk-EPOLDU6W-DdQKK6on.js → chunk-EPOLDU6W-CXRTFQ3F.js} +1 -1
  245. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-Dmr2bb1R.js → circle-check-BOARzkeR.js} +1 -1
  246. package/codeyam-cli/src/webserver/build/client/assets/copy-Bb-80kDT.js +6 -0
  247. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Do4ZLUYa.js → createLucideIcon-BdhJEx6B.js} +1 -1
  248. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-BBnGWYga.js +1 -0
  249. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-CbdFyxZh.js → entity._sha._-BJUiQqZF.js} +12 -12
  250. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-B4iCfs5M.js → entity._sha.scenarios._scenarioId.fullscreen-DavjRmOY.js} +1 -1
  251. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-wDWZZO1W.js → entity._sha_.create-scenario-D1T4TGjf.js} +1 -1
  252. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMbl7MeQ.js → entity._sha_.edit._scenarioId-CTBG2mmz.js} +1 -1
  253. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-5wRKRIH9.js → entry.client-CS2cb_eZ.js} +1 -1
  254. package/codeyam-cli/src/webserver/build/client/assets/file-code-Dhef1kWN.js +6 -0
  255. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DD3SDH7t.js → fileTableUtils-DMJ7zii9.js} +1 -1
  256. package/codeyam-cli/src/webserver/build/client/assets/files-CJ6lTdTA.js +1 -0
  257. package/codeyam-cli/src/webserver/build/client/assets/{git-zXjT7J0G.js → git-CPTZZ-JZ.js} +8 -8
  258. package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +1 -0
  259. package/codeyam-cli/src/webserver/build/client/assets/{index-DLbXwndH.js → index-B1h680n5.js} +1 -1
  260. package/codeyam-cli/src/webserver/build/client/assets/{index-gPZ-lad1.js → index-lzqtyFU8.js} +1 -1
  261. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-BsPXJ81F.js → loader-circle-B7B9V-bu.js} +1 -1
  262. package/codeyam-cli/src/webserver/build/client/assets/manifest-a78b90a2.js +1 -0
  263. package/codeyam-cli/src/webserver/build/client/assets/memory--GCbFsBE.js +92 -0
  264. package/codeyam-cli/src/webserver/build/client/assets/root-eVAaavTS.js +62 -0
  265. package/codeyam-cli/src/webserver/build/client/assets/{search-P2FKIUql.js → search-CxXUmBSd.js} +1 -1
  266. package/codeyam-cli/src/webserver/build/client/assets/{settings-B2eDuBj8.js → settings-CS5f3WzT.js} +1 -1
  267. package/codeyam-cli/src/webserver/build/client/assets/{simulations-L18M6-kN.js → simulations-DwFIBT09.js} +1 -1
  268. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BDz7kbVA.js → triangle-alert-B6LgvRJg.js} +1 -1
  269. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-29dDmbH8.js → useCustomSizes-C1v1PQzo.js} +1 -1
  270. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-BUm0UVJm.js → useLastLogLine-aSv48UbS.js} +1 -1
  271. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CkIOKTrZ.js → useReportContext-DYxHZQuP.js} +1 -1
  272. package/codeyam-cli/src/webserver/build/client/assets/{useToast-KKw5kTn-.js → useToast-mBRpZPiu.js} +1 -1
  273. package/codeyam-cli/src/webserver/build/server/assets/index-BM6TDT1Y.js +1 -0
  274. package/codeyam-cli/src/webserver/build/server/assets/server-build-dYC34MHw.js +257 -0
  275. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  276. package/codeyam-cli/src/webserver/build-info.json +5 -5
  277. package/codeyam-cli/templates/codeyam-memory-hook.sh +200 -0
  278. package/codeyam-cli/templates/codeyam:debug.md +47 -3
  279. package/codeyam-cli/templates/codeyam:diagnose.md +203 -25
  280. package/codeyam-cli/templates/codeyam:memory.md +341 -0
  281. package/codeyam-cli/templates/codeyam:new-rule.md +13 -0
  282. package/codeyam-cli/templates/rule-reflection-hook.py +160 -0
  283. package/codeyam-cli/templates/rules-instructions.md +93 -0
  284. package/package.json +8 -5
  285. package/packages/ai/index.js +7 -3
  286. package/packages/ai/index.js.map +1 -1
  287. package/packages/ai/src/lib/analyzeScope.js +70 -29
  288. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  289. package/packages/ai/src/lib/astScopes/arrayDerivationDetector.js +150 -0
  290. package/packages/ai/src/lib/astScopes/arrayDerivationDetector.js.map +1 -0
  291. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +78 -8
  292. package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
  293. package/packages/ai/src/lib/astScopes/methodSemantics.js +109 -23
  294. package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
  295. package/packages/ai/src/lib/astScopes/patterns/variableDeclarationHandler.js +1 -102
  296. package/packages/ai/src/lib/astScopes/patterns/variableDeclarationHandler.js.map +1 -1
  297. package/packages/ai/src/lib/astScopes/processExpression.js +440 -27
  298. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  299. package/packages/ai/src/lib/completionCall.js +161 -30
  300. package/packages/ai/src/lib/completionCall.js.map +1 -1
  301. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +589 -166
  302. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  303. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/MuiManager.js +179 -0
  304. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/MuiManager.js.map +1 -1
  305. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/ReactFrameworkManager.js +7 -1
  306. package/packages/ai/src/lib/dataStructure/equivalencyManagers/frameworks/ReactFrameworkManager.js.map +1 -1
  307. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
  308. package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
  309. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
  310. package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
  311. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +41 -1
  312. package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
  313. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +104 -11
  314. package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
  315. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +159 -0
  316. package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
  317. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
  318. package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
  319. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +265 -79
  320. package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
  321. package/packages/ai/src/lib/dataStructure/helpers/fixNullIdsBySchema.js +107 -0
  322. package/packages/ai/src/lib/dataStructure/helpers/fixNullIdsBySchema.js.map +1 -0
  323. package/packages/ai/src/lib/dataStructureChunking.js +111 -0
  324. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -0
  325. package/packages/ai/src/lib/e2eDataTracking.js +241 -0
  326. package/packages/ai/src/lib/e2eDataTracking.js.map +1 -0
  327. package/packages/ai/src/lib/extractCriticalDataKeys.js +96 -0
  328. package/packages/ai/src/lib/extractCriticalDataKeys.js.map +1 -0
  329. package/packages/ai/src/lib/generateEntityScenarioData.js +525 -8
  330. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  331. package/packages/ai/src/lib/generateEntityScenarios.js +26 -2
  332. package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
  333. package/packages/ai/src/lib/generateExecutionFlows.js +281 -4
  334. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  335. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +946 -42
  336. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  337. package/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.js +194 -0
  338. package/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.js.map +1 -0
  339. package/packages/ai/src/lib/mergeStatements.js +70 -51
  340. package/packages/ai/src/lib/mergeStatements.js.map +1 -1
  341. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
  342. package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
  343. package/packages/ai/src/lib/promptGenerators/generateChunkPrompt.js +54 -0
  344. package/packages/ai/src/lib/promptGenerators/generateChunkPrompt.js.map +1 -0
  345. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +15 -7
  346. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
  347. package/packages/ai/src/lib/promptGenerators/simplifyKeysForLLM.js +335 -0
  348. package/packages/ai/src/lib/promptGenerators/simplifyKeysForLLM.js.map +1 -0
  349. package/packages/ai/src/lib/resolvePathToControllable.js +155 -41
  350. package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
  351. package/packages/ai/src/lib/worker/SerializableDataStructure.js +7 -0
  352. package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
  353. package/packages/ai/src/lib/worker/analyzeScopeWorker.js +94 -1
  354. package/packages/ai/src/lib/worker/analyzeScopeWorker.js.map +1 -1
  355. package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
  356. package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
  357. package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
  358. package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
  359. package/packages/analyze/src/lib/analysisContext.js +30 -5
  360. package/packages/analyze/src/lib/analysisContext.js.map +1 -1
  361. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
  362. package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
  363. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
  364. package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
  365. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
  366. package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
  367. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
  368. package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
  369. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
  370. package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
  371. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
  372. package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
  373. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +21 -9
  374. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  375. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +14 -4
  376. package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
  377. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js +5 -0
  378. package/packages/analyze/src/lib/files/analyze/dependencyResolver.js.map +1 -1
  379. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
  380. package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
  381. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +31 -10
  382. package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
  383. package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
  384. package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
  385. package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
  386. package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
  387. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +160 -68
  388. package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
  389. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +25 -8
  390. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  391. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +71 -9
  392. package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
  393. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +57 -9
  394. package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
  395. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +233 -9
  396. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  397. package/packages/analyze/src/lib/files/scenarios/mergeValidatedDataStructures.js +46 -9
  398. package/packages/analyze/src/lib/files/scenarios/mergeValidatedDataStructures.js.map +1 -1
  399. package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
  400. package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
  401. package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
  402. package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
  403. package/packages/database/src/lib/kysely/db.js +8 -1
  404. package/packages/database/src/lib/kysely/db.js.map +1 -1
  405. package/packages/database/src/lib/kysely/tables/commitsTable.js +3 -0
  406. package/packages/database/src/lib/kysely/tables/commitsTable.js.map +1 -1
  407. package/packages/database/src/lib/loadAnalyses.js +45 -2
  408. package/packages/database/src/lib/loadAnalyses.js.map +1 -1
  409. package/packages/database/src/lib/loadAnalysis.js +8 -0
  410. package/packages/database/src/lib/loadAnalysis.js.map +1 -1
  411. package/packages/database/src/lib/loadBranch.js +11 -1
  412. package/packages/database/src/lib/loadBranch.js.map +1 -1
  413. package/packages/database/src/lib/loadCommit.js +7 -0
  414. package/packages/database/src/lib/loadCommit.js.map +1 -1
  415. package/packages/database/src/lib/loadCommits.js +22 -1
  416. package/packages/database/src/lib/loadCommits.js.map +1 -1
  417. package/packages/database/src/lib/loadEntities.js +23 -4
  418. package/packages/database/src/lib/loadEntities.js.map +1 -1
  419. package/packages/database/src/lib/loadEntityBranches.js +9 -0
  420. package/packages/database/src/lib/loadEntityBranches.js.map +1 -1
  421. package/packages/database/src/lib/updateCommitMetadata.js +5 -4
  422. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  423. package/packages/types/index.js.map +1 -1
  424. package/packages/utils/src/lib/safeFileName.js +29 -3
  425. package/packages/utils/src/lib/safeFileName.js.map +1 -1
  426. package/scripts/finalize-analyzer.cjs +3 -3
  427. package/codeyam-cli/src/webserver/build/client/assets/EntityItem-vauWK972.js +0 -1
  428. package/codeyam-cli/src/webserver/build/client/assets/ReportIssueModal-DzJRkCkr.js +0 -11
  429. package/codeyam-cli/src/webserver/build/client/assets/_index-Be83mo_j.js +0 -11
  430. package/codeyam-cli/src/webserver/build/client/assets/activity.(_tab)-BN6wu6Y-.js +0 -37
  431. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Bn6aCAy_.js +0 -1
  432. package/codeyam-cli/src/webserver/build/client/assets/files-DKyMFI90.js +0 -1
  433. package/codeyam-cli/src/webserver/build/client/assets/globals-DTTQ3gY7.css +0 -1
  434. package/codeyam-cli/src/webserver/build/client/assets/manifest-22590fcf.js +0 -1
  435. package/codeyam-cli/src/webserver/build/client/assets/root-BsAarjAM.js +0 -57
  436. package/codeyam-cli/src/webserver/build/server/assets/index-BND5I5fv.js +0 -1
  437. package/codeyam-cli/src/webserver/build/server/assets/server-build-CFXnd7MG.js +0 -228
@@ -82,6 +82,8 @@
82
82
  import { ScopeAnalysis } from '~codeyam/types';
83
83
  import { EquivalencyManager } from './equivalencyManagers/EquivalencyManager';
84
84
  import fillInSchemaGapsAndUnknowns from './helpers/fillInSchemaGapsAndUnknowns';
85
+ import { clearCleanKnownObjectFunctionsCache } from './helpers/cleanKnownObjectFunctions';
86
+ import { clearCleanNonObjectFunctionsCache } from './helpers/cleanNonObjectFunctions';
85
87
 
86
88
  /**
87
89
  * Patterns that indicate recursive type structures in schema paths.
@@ -150,7 +152,7 @@ export interface ScopeInfo {
150
152
  [childComponentName: string]: Array<{
151
153
  path: string;
152
154
  conditionType: 'truthiness' | 'comparison';
153
- location: 'if' | 'ternary' | 'logical-and' | 'switch';
155
+ location: 'if' | 'ternary' | 'logical-and' | 'switch' | 'unconditional';
154
156
  isNegated?: boolean;
155
157
  }>;
156
158
  };
@@ -332,6 +334,19 @@ export function resetScopeDataStructureMetrics() {
332
334
  followEquivalenciesEarlyExitPhase1Count = 0;
333
335
  followEquivalenciesWithWorkCount = 0;
334
336
  addEquivalencyCallCount = 0;
337
+
338
+ // Clear module-level caches to prevent unbounded memory growth across entities
339
+ const knownObjectCache = clearCleanKnownObjectFunctionsCache();
340
+ const nonObjectCache = clearCleanNonObjectFunctionsCache();
341
+ if (knownObjectCache.count > 0 || nonObjectCache.count > 0) {
342
+ const totalBytes =
343
+ knownObjectCache.estimatedBytes + nonObjectCache.estimatedBytes;
344
+ console.log('CodeYam: Cleared analysis caches', {
345
+ knownObjectCache: `${knownObjectCache.count} entries, ${(knownObjectCache.estimatedBytes / 1024).toFixed(1)}KB`,
346
+ nonObjectCache: `${nonObjectCache.count} entries, ${(nonObjectCache.estimatedBytes / 1024).toFixed(1)}KB`,
347
+ totalKB: `${(totalBytes / 1024).toFixed(1)}KB`,
348
+ });
349
+ }
335
350
  }
336
351
 
337
352
  // Performance: Pre-computed Sets for equivalency reason filtering (O(1) vs O(n))
@@ -409,7 +424,7 @@ export class ScopeDataStructure {
409
424
  path: string;
410
425
  conditionType: 'truthiness' | 'comparison' | 'switch';
411
426
  comparedValues?: string[];
412
- location: 'if' | 'ternary' | 'logical-and' | 'switch';
427
+ location: 'if' | 'ternary' | 'logical-and' | 'switch' | 'unconditional';
413
428
  }>
414
429
  > = {};
415
430
 
@@ -436,6 +451,13 @@ export class ScopeDataStructure {
436
451
  import('../astScopes/types').ConditionalUsage[]
437
452
  > = {};
438
453
 
454
+ /**
455
+ * JSX rendering usages collected during AST analysis.
456
+ * Tracks arrays rendered via .map() and strings interpolated in JSX.
457
+ */
458
+ private rawJsxRenderingUsages: import('../astScopes/types').JsxRenderingUsage[] =
459
+ [];
460
+
439
461
  private lastAddToSchemaId = 0;
440
462
  private lastEquivalencyId = 0;
441
463
  private lastEquivalencyDatabaseId = 0;
@@ -1628,17 +1650,26 @@ export class ScopeDataStructure {
1628
1650
  private setInstantiatedVariables(scopeNode: ScopeNode) {
1629
1651
  let instantiatedVariables = scopeNode.analysis?.instantiatedVariables ?? [];
1630
1652
 
1631
- for (const [path, equivalentPath] of Object.entries(
1653
+ for (const [path, rawEquivalentPath] of Object.entries(
1632
1654
  scopeNode.analysis.isolatedEquivalentVariables ?? {},
1633
1655
  )) {
1634
- if (typeof equivalentPath !== 'string') {
1635
- continue;
1636
- }
1656
+ // Normalize to array for consistent handling (supports both string and string[])
1657
+ const equivalentPaths = Array.isArray(rawEquivalentPath)
1658
+ ? rawEquivalentPath
1659
+ : rawEquivalentPath
1660
+ ? [rawEquivalentPath]
1661
+ : [];
1662
+
1663
+ for (const equivalentPath of equivalentPaths) {
1664
+ if (typeof equivalentPath !== 'string') {
1665
+ continue;
1666
+ }
1637
1667
 
1638
- if (equivalentPath.startsWith('signature[')) {
1639
- const equivalentPathParts = this.splitPath(equivalentPath);
1640
- instantiatedVariables.push(equivalentPathParts[0]);
1641
- instantiatedVariables.push(path);
1668
+ if (equivalentPath.startsWith('signature[')) {
1669
+ const equivalentPathParts = this.splitPath(equivalentPath);
1670
+ instantiatedVariables.push(equivalentPathParts[0]);
1671
+ instantiatedVariables.push(path);
1672
+ }
1642
1673
  }
1643
1674
 
1644
1675
  const duplicateInstantiated = instantiatedVariables.find(
@@ -1651,9 +1682,14 @@ export class ScopeDataStructure {
1651
1682
  }
1652
1683
  }
1653
1684
 
1654
- instantiatedVariables = instantiatedVariables.filter(
1655
- (varName, index, self) => self.indexOf(varName) === index,
1656
- );
1685
+ const instantiatedSeen = new Set<string>();
1686
+ instantiatedVariables = instantiatedVariables.filter((varName) => {
1687
+ if (instantiatedSeen.has(varName)) {
1688
+ return false;
1689
+ }
1690
+ instantiatedSeen.add(varName);
1691
+ return true;
1692
+ });
1657
1693
 
1658
1694
  scopeNode.instantiatedVariables = instantiatedVariables;
1659
1695
 
@@ -1674,13 +1710,19 @@ export class ScopeDataStructure {
1674
1710
  ...parentScopeNode.instantiatedVariables.filter(
1675
1711
  (v) => !v.startsWith('signature[') && !v.startsWith('returnValue'),
1676
1712
  ),
1677
- ].filter(
1678
- (varName, index, self) =>
1679
- !instantiatedVariables.includes(varName) &&
1680
- self.indexOf(varName) === index,
1681
- );
1713
+ ].filter((varName) => !instantiatedSeen.has(varName));
1714
+
1715
+ const parentInstantiatedSeen = new Set<string>();
1716
+ const dedupedParentInstantiatedVariables =
1717
+ parentInstantiatedVariables.filter((varName) => {
1718
+ if (parentInstantiatedSeen.has(varName)) {
1719
+ return false;
1720
+ }
1721
+ parentInstantiatedSeen.add(varName);
1722
+ return true;
1723
+ });
1682
1724
 
1683
- scopeNode.parentInstantiatedVariables = parentInstantiatedVariables;
1725
+ scopeNode.parentInstantiatedVariables = dedupedParentInstantiatedVariables;
1684
1726
  }
1685
1727
 
1686
1728
  private trackFunctionCalls(scopeNode: ScopeNode) {
@@ -1696,172 +1738,198 @@ export class ScopeDataStructure {
1696
1738
  const { isolatedStructure, isolatedEquivalentVariables } =
1697
1739
  scopeNode.analysis;
1698
1740
 
1741
+ // Flatten isolatedEquivalentVariables values for allPaths (handles both string and string[])
1742
+ const flattenedEquivValues = Object.values(
1743
+ isolatedEquivalentVariables || {},
1744
+ ).flatMap((v) => (Array.isArray(v) ? v : [v]));
1745
+
1699
1746
  const allPaths = Array.from(
1700
1747
  new Set([
1701
1748
  ...Object.keys(isolatedStructure || {}),
1702
1749
  ...Object.keys(isolatedEquivalentVariables || {}),
1703
- ...Object.values(isolatedEquivalentVariables || {}),
1750
+ ...flattenedEquivValues,
1704
1751
  ]),
1705
1752
  );
1706
1753
 
1707
1754
  for (let path in isolatedEquivalentVariables) {
1708
- let equivalentValue = isolatedEquivalentVariables?.[path];
1709
-
1710
- if (equivalentValue && this.isValidPath(equivalentValue)) {
1711
- // IMPORTANT: DO NOT strip ::cyDuplicateKey:: markers from equivalencies.
1712
- // These markers are critical for distinguishing variable reassignments.
1713
- // For example, with:
1714
- // let fetcher = useFetcher<ConfigData>();
1715
- // const configData = fetcher.data?.data;
1716
- // fetcher = useFetcher<SettingsData>();
1717
- // const settingsData = fetcher.data?.data;
1718
- //
1719
- // mergeStatements creates:
1720
- // fetcher useFetcher<ConfigData>()...
1721
- // fetcher::cyDuplicateKey1:: useFetcher<SettingsData>()...
1722
- // configData fetcher.data.data
1723
- // settingsData → fetcher::cyDuplicateKey1::.data.data
1724
- //
1725
- // If we strip ::cyDuplicateKey::, settingsData would incorrectly trace
1726
- // to useFetcher<ConfigData>() instead of useFetcher<SettingsData>().
1727
- path = cleanPath(path, allPaths);
1728
- equivalentValue = cleanPath(equivalentValue, allPaths);
1729
-
1730
- this.addEquivalency(
1731
- path,
1732
- equivalentValue,
1733
- scopeNode.name,
1734
- scopeNode,
1735
- 'original equivalency',
1736
- );
1755
+ const rawEquivalentValue = isolatedEquivalentVariables?.[path];
1756
+ // Normalize to array for consistent handling
1757
+ const equivalentValues = Array.isArray(rawEquivalentValue)
1758
+ ? rawEquivalentValue
1759
+ : [rawEquivalentValue];
1760
+
1761
+ for (let equivalentValue of equivalentValues) {
1762
+ if (equivalentValue && this.isValidPath(equivalentValue)) {
1763
+ // IMPORTANT: DO NOT strip ::cyDuplicateKey:: markers from equivalencies.
1764
+ // These markers are critical for distinguishing variable reassignments.
1765
+ // For example, with:
1766
+ // let fetcher = useFetcher<ConfigData>();
1767
+ // const configData = fetcher.data?.data;
1768
+ // fetcher = useFetcher<SettingsData>();
1769
+ // const settingsData = fetcher.data?.data;
1770
+ //
1771
+ // mergeStatements creates:
1772
+ // fetcher useFetcher<ConfigData>()...
1773
+ // fetcher::cyDuplicateKey1:: useFetcher<SettingsData>()...
1774
+ // configData fetcher.data.data
1775
+ // settingsData fetcher::cyDuplicateKey1::.data.data
1776
+ //
1777
+ // If we strip ::cyDuplicateKey::, settingsData would incorrectly trace
1778
+ // to useFetcher<ConfigData>() instead of useFetcher<SettingsData>().
1779
+ path = cleanPath(path, allPaths);
1780
+ equivalentValue = cleanPath(equivalentValue, allPaths);
1781
+
1782
+ this.addEquivalency(
1783
+ path,
1784
+ equivalentValue,
1785
+ scopeNode.name,
1786
+ scopeNode,
1787
+ 'original equivalency',
1788
+ );
1737
1789
 
1738
- // Propagate equivalencies involving parent-scope variables to those parent scopes.
1739
- // This handles patterns like: collected.push({...entity}) where 'collected' is defined
1740
- // in a parent scope. The equivalency collected[] -> push().signature[0] needs to be
1741
- // visible when tracing from the parent scope.
1742
- const rootVariable = this.extractRootVariable(path);
1743
- const equivalentRootVariable =
1744
- this.extractRootVariable(equivalentValue);
1745
-
1746
- // Skip propagation for self-referential reassignment patterns like:
1747
- // x = x.method().functionCallReturnValue
1748
- // where the path IS the variable itself (not a sub-path like x[] or x.prop).
1749
- // These create circular references since both sides reference the same variable.
1750
- //
1751
- // But DO propagate for patterns like collected[] -> collected.push(...).signature[0]
1752
- // where the path has additional segments beyond the root variable.
1753
- const pathIsJustRootVariable = path === rootVariable;
1754
- const isSelfReferentialReassignment =
1755
- pathIsJustRootVariable && rootVariable === equivalentRootVariable;
1790
+ // Propagate equivalencies involving parent-scope variables to those parent scopes.
1791
+ // This handles patterns like: collected.push({...entity}) where 'collected' is defined
1792
+ // in a parent scope. The equivalency collected[] -> push().signature[0] needs to be
1793
+ // visible when tracing from the parent scope.
1794
+ const rootVariable = this.extractRootVariable(path);
1795
+ const equivalentRootVariable =
1796
+ this.extractRootVariable(equivalentValue);
1797
+
1798
+ // Skip propagation for self-referential reassignment patterns like:
1799
+ // x = x.method().functionCallReturnValue
1800
+ // where the path IS the variable itself (not a sub-path like x[] or x.prop).
1801
+ // These create circular references since both sides reference the same variable.
1802
+ //
1803
+ // But DO propagate for patterns like collected[] -> collected.push(...).signature[0]
1804
+ // where the path has additional segments beyond the root variable.
1805
+ const pathIsJustRootVariable = path === rootVariable;
1806
+ const isSelfReferentialReassignment =
1807
+ pathIsJustRootVariable && rootVariable === equivalentRootVariable;
1756
1808
 
1757
- if (
1758
- rootVariable &&
1759
- !isSelfReferentialReassignment &&
1760
- scopeNode.parentInstantiatedVariables?.includes(rootVariable)
1761
- ) {
1762
- // Find the parent scope where this variable is defined
1763
- for (const parentScopeName of scopeNode.tree || []) {
1764
- const parentScope = this.scopeNodes[parentScopeName];
1765
- if (parentScope?.instantiatedVariables?.includes(rootVariable)) {
1766
- // Add the equivalency to the parent scope as well
1767
- this.addEquivalency(
1768
- path,
1769
- equivalentValue,
1770
- scopeNode.name, // The equivalent path's scope remains the child scope
1771
- parentScope, // But store it in the parent scope's equivalencies
1772
- 'propagated parent-variable equivalency',
1773
- );
1774
- break;
1809
+ if (
1810
+ rootVariable &&
1811
+ !isSelfReferentialReassignment &&
1812
+ scopeNode.parentInstantiatedVariables?.includes(rootVariable)
1813
+ ) {
1814
+ // Find the parent scope where this variable is defined
1815
+ for (const parentScopeName of scopeNode.tree || []) {
1816
+ const parentScope = this.scopeNodes[parentScopeName];
1817
+ if (parentScope?.instantiatedVariables?.includes(rootVariable)) {
1818
+ // Add the equivalency to the parent scope as well
1819
+ this.addEquivalency(
1820
+ path,
1821
+ equivalentValue,
1822
+ scopeNode.name, // The equivalent path's scope remains the child scope
1823
+ parentScope, // But store it in the parent scope's equivalencies
1824
+ 'propagated parent-variable equivalency',
1825
+ );
1826
+ break;
1827
+ }
1775
1828
  }
1776
1829
  }
1777
- }
1778
1830
 
1779
- // Propagate sub-property equivalencies when the equivalentValue is a simple variable
1780
- // that has sub-properties defined in the isolatedEquivalentVariables.
1781
- // This handles cases like: dataItem={{ structure: completeDataStructure }}
1782
- // where completeDataStructure has sub-properties like completeDataStructure['Function Arguments']
1783
- // We need to propagate these to create: dataItem.structure['Function Arguments'] equivalencies
1784
- const isSimpleVariable =
1785
- !equivalentValue.startsWith('signature[') &&
1786
- !equivalentValue.includes('functionCallReturnValue') &&
1787
- !equivalentValue.includes('.') &&
1788
- !equivalentValue.includes('[');
1789
-
1790
- if (isSimpleVariable) {
1791
- // Look in current scope and all parent scopes for sub-properties
1792
- const scopesToCheck = [scopeNode.name, ...scopeNode.tree];
1793
- for (const scopeName of scopesToCheck) {
1794
- const checkScope = this.scopeNodes[scopeName];
1795
- if (!checkScope?.analysis?.isolatedEquivalentVariables) continue;
1796
-
1797
- for (const [subPath, subValue] of Object.entries(
1798
- checkScope.analysis.isolatedEquivalentVariables,
1799
- )) {
1800
- // Check if this is a sub-property of the equivalentValue variable
1801
- // e.g., completeDataStructure['Function Arguments'] or completeDataStructure.foo
1802
- const matchesDot = subPath.startsWith(equivalentValue + '.');
1803
- const matchesBracket = subPath.startsWith(equivalentValue + '[');
1804
- if (matchesDot || matchesBracket) {
1805
- const subPropertyPath = subPath.substring(
1806
- equivalentValue.length,
1807
- );
1808
- const newPath = cleanPath(path + subPropertyPath, allPaths);
1809
- const newEquivalentValue = cleanPath(
1810
- (subValue as string).replace(/::cyDuplicateKey\d+::/g, ''),
1811
- allPaths,
1831
+ // Propagate sub-property equivalencies when the equivalentValue is a simple variable
1832
+ // that has sub-properties defined in the isolatedEquivalentVariables.
1833
+ // This handles cases like: dataItem={{ structure: completeDataStructure }}
1834
+ // where completeDataStructure has sub-properties like completeDataStructure['Function Arguments']
1835
+ // We need to propagate these to create: dataItem.structure['Function Arguments'] equivalencies
1836
+ const isSimpleVariable =
1837
+ !equivalentValue.startsWith('signature[') &&
1838
+ !equivalentValue.includes('functionCallReturnValue') &&
1839
+ !equivalentValue.includes('.') &&
1840
+ !equivalentValue.includes('[');
1841
+
1842
+ if (isSimpleVariable) {
1843
+ // Look in current scope and all parent scopes for sub-properties
1844
+ const scopesToCheck = [scopeNode.name, ...scopeNode.tree];
1845
+ for (const scopeName of scopesToCheck) {
1846
+ const checkScope = this.scopeNodes[scopeName];
1847
+ if (!checkScope?.analysis?.isolatedEquivalentVariables) continue;
1848
+
1849
+ for (const [subPath, rawSubValue] of Object.entries(
1850
+ checkScope.analysis.isolatedEquivalentVariables,
1851
+ )) {
1852
+ // Normalize to array for consistent handling
1853
+ const subValues = Array.isArray(rawSubValue)
1854
+ ? rawSubValue
1855
+ : rawSubValue
1856
+ ? [rawSubValue]
1857
+ : [];
1858
+
1859
+ // Check if this is a sub-property of the equivalentValue variable
1860
+ // e.g., completeDataStructure['Function Arguments'] or completeDataStructure.foo
1861
+ const matchesDot = subPath.startsWith(equivalentValue + '.');
1862
+ const matchesBracket = subPath.startsWith(
1863
+ equivalentValue + '[',
1812
1864
  );
1813
-
1814
- if (
1815
- newEquivalentValue &&
1816
- this.isValidPath(newEquivalentValue)
1817
- ) {
1818
- this.addEquivalency(
1819
- newPath,
1820
- newEquivalentValue,
1821
- checkScope.name, // Use the scope where the sub-property was found
1822
- scopeNode,
1823
- 'propagated sub-property equivalency',
1865
+ if (matchesDot || matchesBracket) {
1866
+ const subPropertyPath = subPath.substring(
1867
+ equivalentValue.length,
1824
1868
  );
1869
+ const newPath = cleanPath(path + subPropertyPath, allPaths);
1870
+
1871
+ for (const subValue of subValues) {
1872
+ if (typeof subValue !== 'string') continue;
1873
+ const newEquivalentValue = cleanPath(
1874
+ subValue.replace(/::cyDuplicateKey\d+::/g, ''),
1875
+ allPaths,
1876
+ );
1877
+
1878
+ if (
1879
+ newEquivalentValue &&
1880
+ this.isValidPath(newEquivalentValue)
1881
+ ) {
1882
+ this.addEquivalency(
1883
+ newPath,
1884
+ newEquivalentValue,
1885
+ checkScope.name, // Use the scope where the sub-property was found
1886
+ scopeNode,
1887
+ 'propagated sub-property equivalency',
1888
+ );
1889
+ }
1890
+ }
1825
1891
  }
1826
- }
1827
1892
 
1828
- // Also check if equivalentValue itself maps to a functionCallReturnValue
1829
- // e.g., result = useMemo(...).functionCallReturnValue
1830
- if (
1831
- subPath === equivalentValue &&
1832
- typeof subValue === 'string' &&
1833
- subValue.endsWith('.functionCallReturnValue')
1834
- ) {
1835
- this.propagateFunctionCallReturnSubProperties(
1836
- path,
1837
- subValue,
1838
- scopeNode,
1839
- allPaths,
1840
- );
1893
+ // Also check if equivalentValue itself maps to a functionCallReturnValue
1894
+ // e.g., result = useMemo(...).functionCallReturnValue
1895
+ for (const subValue of subValues) {
1896
+ if (
1897
+ subPath === equivalentValue &&
1898
+ typeof subValue === 'string' &&
1899
+ subValue.endsWith('.functionCallReturnValue')
1900
+ ) {
1901
+ this.propagateFunctionCallReturnSubProperties(
1902
+ path,
1903
+ subValue,
1904
+ scopeNode,
1905
+ allPaths,
1906
+ );
1907
+ }
1908
+ }
1841
1909
  }
1842
1910
  }
1843
1911
  }
1844
- }
1845
1912
 
1846
- // Handle function call return values by propagating returnValue.* sub-properties
1847
- // from the callback scope to the usage path
1848
- if (equivalentValue.endsWith('.functionCallReturnValue')) {
1849
- this.propagateFunctionCallReturnSubProperties(
1850
- path,
1851
- equivalentValue,
1852
- scopeNode,
1853
- allPaths,
1854
- );
1913
+ // Handle function call return values by propagating returnValue.* sub-properties
1914
+ // from the callback scope to the usage path
1915
+ if (equivalentValue.endsWith('.functionCallReturnValue')) {
1916
+ this.propagateFunctionCallReturnSubProperties(
1917
+ path,
1918
+ equivalentValue,
1919
+ scopeNode,
1920
+ allPaths,
1921
+ );
1855
1922
 
1856
- // Track which variable receives the return value of each function call
1857
- // This enables generating separate mock data for each call site
1858
- this.trackReceivingVariable(path, equivalentValue);
1859
- }
1923
+ // Track which variable receives the return value of each function call
1924
+ // This enables generating separate mock data for each call site
1925
+ this.trackReceivingVariable(path, equivalentValue);
1926
+ }
1860
1927
 
1861
- // Also track variables that receive destructured properties from function call return values
1862
- // e.g., "userData" -> "db.query('users').functionCallReturnValue.data"
1863
- if (equivalentValue.includes('.functionCallReturnValue.')) {
1864
- this.trackReceivingVariable(path, equivalentValue);
1928
+ // Also track variables that receive destructured properties from function call return values
1929
+ // e.g., "userData" -> "db.query('users').functionCallReturnValue.data"
1930
+ if (equivalentValue.includes('.functionCallReturnValue.')) {
1931
+ this.trackReceivingVariable(path, equivalentValue);
1932
+ }
1865
1933
  }
1866
1934
  }
1867
1935
  }
@@ -2042,9 +2110,18 @@ export class ScopeDataStructure {
2042
2110
  const checkScope = this.scopeNodes[scopeName];
2043
2111
  if (!checkScope?.analysis?.isolatedEquivalentVariables) continue;
2044
2112
 
2045
- const functionRef =
2113
+ const rawFunctionRef =
2046
2114
  checkScope.analysis.isolatedEquivalentVariables[functionName];
2047
- if (typeof functionRef === 'string' && functionRef.endsWith('F')) {
2115
+ // Normalize to array and find first string ending with 'F'
2116
+ const functionRefs = Array.isArray(rawFunctionRef)
2117
+ ? rawFunctionRef
2118
+ : rawFunctionRef
2119
+ ? [rawFunctionRef]
2120
+ : [];
2121
+ const functionRef = functionRefs.find(
2122
+ (r) => typeof r === 'string' && r.endsWith('F'),
2123
+ );
2124
+ if (typeof functionRef === 'string') {
2048
2125
  callbackScopeName = functionRef.slice(0, -1);
2049
2126
  break;
2050
2127
  }
@@ -2072,19 +2149,24 @@ export class ScopeDataStructure {
2072
2149
 
2073
2150
  const isolatedVars = callbackScope.analysis.isolatedEquivalentVariables;
2074
2151
 
2152
+ // Get the first returnValue equivalency (normalize array to single value for these checks)
2153
+ const rawReturnValue = isolatedVars.returnValue;
2154
+ const firstReturnValue = Array.isArray(rawReturnValue)
2155
+ ? rawReturnValue[0]
2156
+ : rawReturnValue;
2157
+
2075
2158
  // First, check if returnValue is an alias to another variable (e.g., returnValue = intermediate)
2076
2159
  // If so, we need to look for that variable's sub-properties too
2077
2160
  const returnValueAlias =
2078
- typeof isolatedVars.returnValue === 'string' &&
2079
- !isolatedVars.returnValue.includes('.')
2080
- ? isolatedVars.returnValue
2161
+ typeof firstReturnValue === 'string' && !firstReturnValue.includes('.')
2162
+ ? firstReturnValue
2081
2163
  : undefined;
2082
2164
 
2083
2165
  // Pattern 3: Object.keys(X).reduce() - the reduce result has the same sub-properties as X
2084
2166
  // When returnValue = "Object.keys(source).reduce(...).functionCallReturnValue", look for source.* sub-properties
2085
2167
  let reduceSourceVar: string | undefined;
2086
- if (typeof isolatedVars.returnValue === 'string') {
2087
- const reduceMatch = isolatedVars.returnValue.match(
2168
+ if (typeof firstReturnValue === 'string') {
2169
+ const reduceMatch = firstReturnValue.match(
2088
2170
  /^Object\.keys\((\w+)\)\.reduce\(.*\)\.functionCallReturnValue$/,
2089
2171
  );
2090
2172
  if (reduceMatch) {
@@ -2092,7 +2174,14 @@ export class ScopeDataStructure {
2092
2174
  }
2093
2175
  }
2094
2176
 
2095
- for (const [subPath, subValue] of Object.entries(isolatedVars)) {
2177
+ for (const [subPath, rawSubValue] of Object.entries(isolatedVars)) {
2178
+ // Normalize to array for consistent handling
2179
+ const subValues = Array.isArray(rawSubValue)
2180
+ ? rawSubValue
2181
+ : rawSubValue
2182
+ ? [rawSubValue]
2183
+ : [];
2184
+
2096
2185
  // Check for direct returnValue.* sub-properties
2097
2186
  const isReturnValueSub =
2098
2187
  subPath.startsWith('returnValue.') ||
@@ -2110,57 +2199,59 @@ export class ScopeDataStructure {
2110
2199
  (subPath.startsWith(reduceSourceVar + '.') ||
2111
2200
  subPath.startsWith(reduceSourceVar + '['));
2112
2201
 
2113
- if (
2114
- typeof subValue !== 'string' ||
2115
- (!isReturnValueSub && !isAliasSub && !isReduceSourceSub)
2116
- )
2117
- continue;
2118
-
2119
- // Convert alias/reduceSource paths to returnValue paths
2120
- let effectiveSubPath = subPath;
2121
- if (isAliasSub && !isReturnValueSub) {
2122
- // Replace the alias prefix with returnValue
2123
- effectiveSubPath =
2124
- 'returnValue' + subPath.substring(returnValueAlias!.length);
2125
- } else if (isReduceSourceSub && !isReturnValueSub && !isAliasSub) {
2126
- // Replace the reduce source prefix with returnValue
2127
- effectiveSubPath =
2128
- 'returnValue' + subPath.substring(reduceSourceVar!.length);
2129
- }
2130
- const subPropertyPath = effectiveSubPath.substring('returnValue'.length);
2131
- const newPath = cleanPath(path + subPropertyPath, allPaths);
2132
- let newEquivalentValue = cleanPath(
2133
- subValue.replace(/::cyDuplicateKey\d+::/g, ''),
2134
- allPaths,
2135
- );
2202
+ if (!isReturnValueSub && !isAliasSub && !isReduceSourceSub) continue;
2203
+
2204
+ for (const subValue of subValues) {
2205
+ if (typeof subValue !== 'string') continue;
2206
+
2207
+ // Convert alias/reduceSource paths to returnValue paths
2208
+ let effectiveSubPath = subPath;
2209
+ if (isAliasSub && !isReturnValueSub) {
2210
+ // Replace the alias prefix with returnValue
2211
+ effectiveSubPath =
2212
+ 'returnValue' + subPath.substring(returnValueAlias!.length);
2213
+ } else if (isReduceSourceSub && !isReturnValueSub && !isAliasSub) {
2214
+ // Replace the reduce source prefix with returnValue
2215
+ effectiveSubPath =
2216
+ 'returnValue' + subPath.substring(reduceSourceVar!.length);
2217
+ }
2218
+ const subPropertyPath = effectiveSubPath.substring(
2219
+ 'returnValue'.length,
2220
+ );
2221
+ const newPath = cleanPath(path + subPropertyPath, allPaths);
2222
+ let newEquivalentValue = cleanPath(
2223
+ subValue.replace(/::cyDuplicateKey\d+::/g, ''),
2224
+ allPaths,
2225
+ );
2136
2226
 
2137
- // Resolve variable references through parent scope equivalencies
2138
- const resolved = this.resolveVariableThroughParentScopes(
2139
- newEquivalentValue,
2140
- callbackScope,
2141
- allPaths,
2142
- );
2143
- newEquivalentValue = resolved.resolvedPath;
2144
- const equivalentScopeName = resolved.scopeName;
2227
+ // Resolve variable references through parent scope equivalencies
2228
+ const resolved = this.resolveVariableThroughParentScopes(
2229
+ newEquivalentValue,
2230
+ callbackScope,
2231
+ allPaths,
2232
+ );
2233
+ newEquivalentValue = resolved.resolvedPath;
2234
+ const equivalentScopeName = resolved.scopeName;
2145
2235
 
2146
- if (!newEquivalentValue || !this.isValidPath(newEquivalentValue))
2147
- continue;
2236
+ if (!newEquivalentValue || !this.isValidPath(newEquivalentValue))
2237
+ continue;
2148
2238
 
2149
- this.addEquivalency(
2150
- newPath,
2151
- newEquivalentValue,
2152
- equivalentScopeName,
2153
- scopeNode,
2154
- 'propagated function call return sub-property equivalency',
2155
- );
2239
+ this.addEquivalency(
2240
+ newPath,
2241
+ newEquivalentValue,
2242
+ equivalentScopeName,
2243
+ scopeNode,
2244
+ 'propagated function call return sub-property equivalency',
2245
+ );
2156
2246
 
2157
- // Ensure the database entry has the usage path
2158
- this.addUsageToEquivalencyDatabaseEntry(
2159
- newPath,
2160
- newEquivalentValue,
2161
- equivalentScopeName,
2162
- scopeNode.name,
2163
- );
2247
+ // Ensure the database entry has the usage path
2248
+ this.addUsageToEquivalencyDatabaseEntry(
2249
+ newPath,
2250
+ newEquivalentValue,
2251
+ equivalentScopeName,
2252
+ scopeNode.name,
2253
+ );
2254
+ }
2164
2255
  }
2165
2256
  }
2166
2257
 
@@ -2200,8 +2291,15 @@ export class ScopeDataStructure {
2200
2291
  const parentScope = this.scopeNodes[parentScopeName];
2201
2292
  if (!parentScope?.analysis?.isolatedEquivalentVariables) continue;
2202
2293
 
2203
- const rootEquiv =
2294
+ const rawRootEquiv =
2204
2295
  parentScope.analysis.isolatedEquivalentVariables[rootVar];
2296
+ // Normalize to array and use first string value
2297
+ const rootEquivs = Array.isArray(rawRootEquiv)
2298
+ ? rawRootEquiv
2299
+ : rawRootEquiv
2300
+ ? [rawRootEquiv]
2301
+ : [];
2302
+ const rootEquiv = rootEquivs.find((r) => typeof r === 'string');
2205
2303
  if (typeof rootEquiv === 'string') {
2206
2304
  return {
2207
2305
  resolvedPath: cleanPath(rootEquiv + restOfPath, allPaths),
@@ -2867,10 +2965,105 @@ export class ScopeDataStructure {
2867
2965
  this.intermediatesOrderIndex.set(pathId, databaseEntry);
2868
2966
 
2869
2967
  if (intermediateIndex === 0) {
2870
- const isValidSourceCandidate =
2968
+ let isValidSourceCandidate =
2871
2969
  pathInfo.schemaPath.startsWith('signature[') ||
2872
2970
  pathInfo.schemaPath.includes('functionCallReturnValue');
2873
- if (isValidSourceCandidate) {
2971
+
2972
+ // Check if path STARTS with a spread pattern like [...var]
2973
+ // This handles cases like [...files][][0] or [...files].sort(...).functionCallReturnValue[][0]
2974
+ // where the spread source variable needs to be resolved to a signature path.
2975
+ // We do this REGARDLESS of isValidSourceCandidate because even paths containing
2976
+ // functionCallReturnValue may need spread resolution to trace back to the signature.
2977
+ const spreadMatch = pathInfo.schemaPath.match(/^\[\.\.\.(\w+)\]/);
2978
+ if (spreadMatch) {
2979
+ const spreadVar = spreadMatch[1];
2980
+ const spreadPattern = spreadMatch[0]; // The full [...var] match
2981
+ const scopeNode = this.scopeNodes[pathInfo.scopeNodeName];
2982
+
2983
+ if (scopeNode?.equivalencies) {
2984
+ // Follow the equivalency chain to find a signature path
2985
+ // e.g., files (cyScope1) → files (root) → signature[0].files
2986
+ const resolveToSignature = (
2987
+ varName: string,
2988
+ currentScopeName: string,
2989
+ visited: Set<string>,
2990
+ ): { schemaPath: string; scopeNodeName: string } | null => {
2991
+ const visitKey = `${currentScopeName}::${varName}`;
2992
+ if (visited.has(visitKey)) return null;
2993
+ visited.add(visitKey);
2994
+
2995
+ const currentScope = this.scopeNodes[currentScopeName];
2996
+ if (!currentScope?.equivalencies) return null;
2997
+
2998
+ const varEquivs = currentScope.equivalencies[varName];
2999
+ if (!varEquivs) return null;
3000
+
3001
+ // First check if any equivalency directly points to a signature path
3002
+ const signatureEquiv = varEquivs.find((eq) =>
3003
+ eq.schemaPath.startsWith('signature['),
3004
+ );
3005
+ if (signatureEquiv) {
3006
+ return signatureEquiv;
3007
+ }
3008
+
3009
+ // Otherwise, follow the chain to other scopes
3010
+ for (const equiv of varEquivs) {
3011
+ // If the equivalency points to the same variable in a different scope,
3012
+ // follow the chain
3013
+ if (
3014
+ equiv.schemaPath === varName &&
3015
+ equiv.scopeNodeName !== currentScopeName
3016
+ ) {
3017
+ const result = resolveToSignature(
3018
+ varName,
3019
+ equiv.scopeNodeName,
3020
+ visited,
3021
+ );
3022
+ if (result) return result;
3023
+ }
3024
+ }
3025
+
3026
+ return null;
3027
+ };
3028
+
3029
+ const signatureEquiv = resolveToSignature(
3030
+ spreadVar,
3031
+ pathInfo.scopeNodeName,
3032
+ new Set(),
3033
+ );
3034
+ if (signatureEquiv) {
3035
+ // Replace ONLY the [...var] part with the resolved signature path
3036
+ // This preserves any suffix like .sort(...).functionCallReturnValue[][0]
3037
+ const resolvedPath = pathInfo.schemaPath.replace(
3038
+ spreadPattern,
3039
+ signatureEquiv.schemaPath,
3040
+ );
3041
+ // Add the resolved path as a source candidate
3042
+ if (
3043
+ !databaseEntry.sourceCandidates.some(
3044
+ (sc) =>
3045
+ sc.schemaPath === resolvedPath &&
3046
+ sc.scopeNodeName === pathInfo.scopeNodeName,
3047
+ )
3048
+ ) {
3049
+ databaseEntry.sourceCandidates.push({
3050
+ scopeNodeName: pathInfo.scopeNodeName,
3051
+ schemaPath: resolvedPath,
3052
+ });
3053
+ }
3054
+ isValidSourceCandidate = true;
3055
+ }
3056
+ }
3057
+ }
3058
+
3059
+ if (
3060
+ isValidSourceCandidate &&
3061
+ !databaseEntry.sourceCandidates.some(
3062
+ (sc) =>
3063
+ sc.schemaPath === pathInfo.schemaPath &&
3064
+ sc.scopeNodeName === pathInfo.scopeNodeName,
3065
+ )
3066
+ ) {
2874
3067
  databaseEntry.sourceCandidates.push(pathInfo);
2875
3068
  }
2876
3069
  } else {
@@ -3457,18 +3650,171 @@ export class ScopeDataStructure {
3457
3650
  return {};
3458
3651
  }
3459
3652
 
3653
+ // Collect all descendant scope names (including the scope itself)
3654
+ // This ensures we include external calls from nested scopes like cyScope2
3655
+ const getAllDescendantScopeNames = (
3656
+ node: import('./helpers/ScopeTreeManager').ScopeTreeNode,
3657
+ ): Set<string> => {
3658
+ const names = new Set<string>([node.name]);
3659
+ for (const child of node.children) {
3660
+ for (const name of getAllDescendantScopeNames(child)) {
3661
+ names.add(name);
3662
+ }
3663
+ }
3664
+ return names;
3665
+ };
3666
+
3667
+ const treeNode = this.scopeTreeManager.findNode(scopeNode.name);
3668
+ const descendantScopeNames = treeNode
3669
+ ? getAllDescendantScopeNames(treeNode)
3670
+ : new Set<string>([scopeNode.name]);
3671
+
3672
+ // Get all external function calls made from this scope or any descendant scope
3673
+ // This allows us to include prop equivalencies from JSX components
3674
+ // that were rendered in nested scopes (e.g., FileTableRow called from cyScope2)
3675
+ const externalCallsFromScope = this.externalFunctionCalls.filter((efc) =>
3676
+ descendantScopeNames.has(efc.callScope),
3677
+ );
3678
+ const externalCallNames = new Set(
3679
+ externalCallsFromScope.map((efc) => efc.name),
3680
+ );
3681
+
3682
+ // Helper to check if a usage belongs to this scope (directly, via descendant, or via external call)
3683
+ const usageMatchesScope = (usage: { scopeNodeName: string }) =>
3684
+ descendantScopeNames.has(usage.scopeNodeName) ||
3685
+ externalCallNames.has(usage.scopeNodeName);
3686
+
3460
3687
  const entries = this.equivalencyDatabase.filter((entry) =>
3461
- entry.usages.some((usage) => usage.scopeNodeName === scopeNode.name),
3688
+ entry.usages.some(usageMatchesScope),
3462
3689
  );
3690
+
3691
+ // Helper to resolve a source candidate through equivalency chains to find signature paths
3692
+ const resolveToSignature = (
3693
+ source: Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>,
3694
+ visited: Set<string>,
3695
+ ): Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[] => {
3696
+ const visitKey = `${source.scopeNodeName}::${source.schemaPath}`;
3697
+ if (visited.has(visitKey)) return [];
3698
+ visited.add(visitKey);
3699
+
3700
+ // If already a signature path, return as-is
3701
+ if (source.schemaPath.startsWith('signature[')) {
3702
+ return [source];
3703
+ }
3704
+
3705
+ const currentScope = this.scopeNodes[source.scopeNodeName];
3706
+ if (!currentScope?.equivalencies) return [source];
3707
+
3708
+ // Check for direct equivalencies FIRST (full path match)
3709
+ // This ensures paths like "useMemo(...).functionCallReturnValue" follow to "cyScope1::returnValue"
3710
+ // before prefix matching tries "useMemo(...)" which goes to the useMemo scope
3711
+ const directEquivs = currentScope.equivalencies[source.schemaPath];
3712
+ if (directEquivs?.length > 0) {
3713
+ const results: Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[] =
3714
+ [];
3715
+ for (const equiv of directEquivs) {
3716
+ const resolved = resolveToSignature(
3717
+ {
3718
+ scopeNodeName: equiv.scopeNodeName,
3719
+ schemaPath: equiv.schemaPath,
3720
+ },
3721
+ visited,
3722
+ );
3723
+ results.push(...resolved);
3724
+ }
3725
+ if (results.length > 0) return results;
3726
+ }
3727
+
3728
+ // Handle spread patterns like [...items].sort().functionCallReturnValue
3729
+ // Extract the spread variable and resolve it through the equivalency chain
3730
+ const spreadMatch = source.schemaPath.match(/^\[\.\.\.(\w+)\]/);
3731
+ if (spreadMatch) {
3732
+ const spreadVar = spreadMatch[1];
3733
+ const spreadPattern = spreadMatch[0];
3734
+ const varEquivs = currentScope.equivalencies[spreadVar];
3735
+
3736
+ if (varEquivs?.length > 0) {
3737
+ const results: Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[] =
3738
+ [];
3739
+ for (const equiv of varEquivs) {
3740
+ // Follow the variable equivalency and then resolve from there
3741
+ const resolvedVar = resolveToSignature(
3742
+ {
3743
+ scopeNodeName: equiv.scopeNodeName,
3744
+ schemaPath: equiv.schemaPath,
3745
+ },
3746
+ visited,
3747
+ );
3748
+ // For each resolved variable path, create the full path with array element suffix
3749
+ for (const rv of resolvedVar) {
3750
+ if (rv.schemaPath.startsWith('signature[')) {
3751
+ // Get the suffix after the spread pattern
3752
+ let suffix = source.schemaPath.slice(spreadPattern.length);
3753
+
3754
+ // Clean the suffix: strip array method chains like .sort(...).functionCallReturnValue[]
3755
+ // These don't change the data identity, just transform it.
3756
+ // Keep only the final element access parts like [0], [1], etc.
3757
+ // Pattern: strip everything from a method call up through functionCallReturnValue[]
3758
+ suffix = suffix.replace(
3759
+ /\.\w+\([^)]*\)\.functionCallReturnValue\[\]/g,
3760
+ '',
3761
+ );
3762
+ // Also handle simpler case without nested parens
3763
+ suffix = suffix.replace(
3764
+ /\.sort\(\w*\(\)\)\.functionCallReturnValue\[\]/g,
3765
+ '',
3766
+ );
3767
+
3768
+ // Add [] to indicate array element access from the spread
3769
+ const resolvedPath = rv.schemaPath + '[]' + suffix;
3770
+ results.push({
3771
+ scopeNodeName: rv.scopeNodeName,
3772
+ schemaPath: resolvedPath,
3773
+ });
3774
+ }
3775
+ }
3776
+ }
3777
+ if (results.length > 0) return results;
3778
+ }
3779
+ }
3780
+
3781
+ // Try to find prefix equivalencies that can resolve this path
3782
+ // For path like "cyScope3().signature[0][0]", check "cyScope3().signature[0]", etc.
3783
+ const pathParts = this.splitPath(source.schemaPath);
3784
+ for (let i = pathParts.length - 1; i > 0; i--) {
3785
+ const prefix = this.joinPathParts(pathParts.slice(0, i));
3786
+ const suffix = this.joinPathParts(pathParts.slice(i));
3787
+ const prefixEquivs = currentScope.equivalencies[prefix];
3788
+
3789
+ if (prefixEquivs?.length > 0) {
3790
+ const results: Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[] =
3791
+ [];
3792
+ for (const equiv of prefixEquivs) {
3793
+ const newPath = this.joinPathParts([equiv.schemaPath, suffix]);
3794
+ const resolved = resolveToSignature(
3795
+ { scopeNodeName: equiv.scopeNodeName, schemaPath: newPath },
3796
+ visited,
3797
+ );
3798
+ results.push(...resolved);
3799
+ }
3800
+ if (results.length > 0) return results;
3801
+ }
3802
+ }
3803
+
3804
+ return [source];
3805
+ };
3806
+
3463
3807
  return entries.reduce(
3464
3808
  (acc, entry) => {
3465
3809
  if (entry.sourceCandidates.length === 0) return acc;
3466
- const usages = entry.usages.filter(
3467
- (u) => u.scopeNodeName === scopeNode.name,
3468
- );
3810
+ const usages = entry.usages.filter(usageMatchesScope);
3469
3811
  for (const usage of usages) {
3470
3812
  acc[usage.schemaPath] ||= [];
3471
- acc[usage.schemaPath].push(...entry.sourceCandidates);
3813
+ // Resolve each source candidate through the equivalency chain
3814
+ for (const source of entry.sourceCandidates) {
3815
+ const resolvedSources = resolveToSignature(source, new Set());
3816
+ acc[usage.schemaPath].push(...resolvedSources);
3817
+ }
3472
3818
  }
3473
3819
  return acc;
3474
3820
  },
@@ -3853,10 +4199,32 @@ export class ScopeDataStructure {
3853
4199
  return scopeText;
3854
4200
  }
3855
4201
 
3856
- getEquivalentSignatureVariables() {
4202
+ getEquivalentSignatureVariables(): Record<string, string | string[]> {
3857
4203
  const scopeNode = this.scopeNodes[this.scopeTreeManager.getRootName()];
3858
4204
 
3859
- const equivalentSignatureVariables: Record<string, string> = {};
4205
+ const equivalentSignatureVariables: Record<string, string | string[]> = {};
4206
+
4207
+ // Helper to add equivalencies - accumulates into array if multiple values for same key
4208
+ // This is critical for OR expressions like `x = a || b` where x should map to both a and b
4209
+ const addEquivalency = (key: string, value: string) => {
4210
+ const existing = equivalentSignatureVariables[key];
4211
+ if (existing === undefined) {
4212
+ // First value - store as string
4213
+ equivalentSignatureVariables[key] = value;
4214
+ } else if (typeof existing === 'string') {
4215
+ if (existing !== value) {
4216
+ // Second different value - convert to array
4217
+ equivalentSignatureVariables[key] = [existing, value];
4218
+ }
4219
+ // Same value - no change needed
4220
+ } else {
4221
+ // Already an array - add if not already present
4222
+ if (!existing.includes(value)) {
4223
+ existing.push(value);
4224
+ }
4225
+ }
4226
+ };
4227
+
3860
4228
  for (const [path, equivalentValues] of Object.entries(
3861
4229
  scopeNode.equivalencies,
3862
4230
  )) {
@@ -3865,7 +4233,7 @@ export class ScopeDataStructure {
3865
4233
  // Maps local variable names to their signature paths
3866
4234
  // e.g., "propValue" -> "signature[0].prop"
3867
4235
  if (path.startsWith('signature[')) {
3868
- equivalentSignatureVariables[equivalentValue.schemaPath] = path;
4236
+ addEquivalency(equivalentValue.schemaPath, path);
3869
4237
  }
3870
4238
 
3871
4239
  // Case 2: Hook variable equivalencies (new behavior)
@@ -3876,14 +4244,30 @@ export class ScopeDataStructure {
3876
4244
  // "useFetcher<...>().state" for execution flow validation
3877
4245
  if (equivalentValue.schemaPath.endsWith('.functionCallReturnValue')) {
3878
4246
  // Extract the hook call path (everything before .functionCallReturnValue)
3879
- const hookCallPath = equivalentValue.schemaPath.slice(
4247
+ let hookCallPath = equivalentValue.schemaPath.slice(
3880
4248
  0,
3881
4249
  -'.functionCallReturnValue'.length,
3882
4250
  );
3883
4251
  // Only include if it looks like a hook call (contains parentheses)
3884
4252
  // and the variable name (path) is a simple identifier (no dots)
3885
4253
  if (hookCallPath.includes('(') && !path.includes('.')) {
3886
- equivalentSignatureVariables[path] = hookCallPath;
4254
+ // Special case: If hookCallPath is a callback scope (cyScope pattern),
4255
+ // trace through it to find what the callback actually returns.
4256
+ // This handles useState(() => { return prop; }) patterns.
4257
+ const cyScopeMatch = hookCallPath.match(/^(cyScope\d+)\(\)$/);
4258
+ if (cyScopeMatch) {
4259
+ // Use the equivalency database to trace the callback's return value
4260
+ // to its actual source (e.g., viewModeFromUrl -> segments -> params -> useParams)
4261
+ const dbEntry = this.getEquivalenciesDatabaseEntry(
4262
+ scopeNode.name, // Component scope
4263
+ path, // variable name (e.g., viewMode)
4264
+ );
4265
+ if (dbEntry?.sourceCandidates?.length > 0) {
4266
+ // Use the traced source instead of the callback scope
4267
+ hookCallPath = dbEntry.sourceCandidates[0].schemaPath;
4268
+ }
4269
+ }
4270
+ addEquivalency(path, hookCallPath);
3887
4271
  }
3888
4272
  }
3889
4273
 
@@ -3897,10 +4281,8 @@ export class ScopeDataStructure {
3897
4281
  !equivalentValue.schemaPath.startsWith('signature[') && // not a signature path
3898
4282
  !equivalentValue.schemaPath.endsWith('.functionCallReturnValue') // not already handled above
3899
4283
  ) {
3900
- // Only add if we haven't already captured this variable in Case 1 or 2
3901
- if (!(path in equivalentSignatureVariables)) {
3902
- equivalentSignatureVariables[path] = equivalentValue.schemaPath;
3903
- }
4284
+ // Add equivalency (will accumulate if multiple values for OR expressions)
4285
+ addEquivalency(path, equivalentValue.schemaPath);
3904
4286
  }
3905
4287
 
3906
4288
  // Case 4: Child component prop mappings (Fix 22)
@@ -3913,7 +4295,7 @@ export class ScopeDataStructure {
3913
4295
  path.includes('().signature[') &&
3914
4296
  !equivalentValue.schemaPath.includes('()') // schemaPath is a simple variable, not a function call
3915
4297
  ) {
3916
- equivalentSignatureVariables[path] = equivalentValue.schemaPath;
4298
+ addEquivalency(path, equivalentValue.schemaPath);
3917
4299
  }
3918
4300
 
3919
4301
  // Case 5: Destructured function parameters (Fix 25)
@@ -3928,7 +4310,7 @@ export class ScopeDataStructure {
3928
4310
  !path.includes('.') && // path is a simple identifier (destructured prop name)
3929
4311
  equivalentValue.schemaPath.startsWith('signature[') // schemaPath IS a signature path
3930
4312
  ) {
3931
- equivalentSignatureVariables[path] = equivalentValue.schemaPath;
4313
+ addEquivalency(path, equivalentValue.schemaPath);
3932
4314
  }
3933
4315
 
3934
4316
  // Case 7: Method calls on variables that result in .functionCallReturnValue (Fix 33)
@@ -3942,8 +4324,7 @@ export class ScopeDataStructure {
3942
4324
  if (
3943
4325
  !path.includes('.') && // path is a simple identifier
3944
4326
  equivalentValue.schemaPath.endsWith('.functionCallReturnValue') && // ends with function return
3945
- equivalentValue.schemaPath.includes('.') && // has property access (method call)
3946
- !(path in equivalentSignatureVariables) // not already captured
4327
+ equivalentValue.schemaPath.includes('.') // has property access (method call)
3947
4328
  ) {
3948
4329
  // Check if this looks like a method call on a variable (not a hook call)
3949
4330
  // Hook calls look like: hookName() or hookName<T>()
@@ -3957,7 +4338,7 @@ export class ScopeDataStructure {
3957
4338
  const parenPos = hookCallPath.indexOf('(');
3958
4339
  if (dotBeforeParen !== -1 && dotBeforeParen < parenPos) {
3959
4340
  // This is a method call like "splat.split('/')", not a hook call
3960
- equivalentSignatureVariables[path] = equivalentValue.schemaPath;
4341
+ addEquivalency(path, equivalentValue.schemaPath);
3961
4342
  }
3962
4343
  }
3963
4344
  }
@@ -3988,8 +4369,9 @@ export class ScopeDataStructure {
3988
4369
  !equivalentValue.schemaPath.includes('()') // schemaPath is a simple variable
3989
4370
  ) {
3990
4371
  // Only add if not already present from the root scope
4372
+ // Root scope values take precedence over child scope values
3991
4373
  if (!(path in equivalentSignatureVariables)) {
3992
- equivalentSignatureVariables[path] = equivalentValue.schemaPath;
4374
+ addEquivalency(path, equivalentValue.schemaPath);
3993
4375
  }
3994
4376
  }
3995
4377
  }
@@ -4000,12 +4382,84 @@ export class ScopeDataStructure {
4000
4382
  // E.g., analysis → currentEntityAnalysis → useLoaderData().functionCallReturnValue.currentEntityAnalysis
4001
4383
  // We need multiple passes because resolutions can depend on each other
4002
4384
  const maxIterations = 5; // Prevent infinite loops
4385
+
4386
+ // Helper function to resolve a single source path using equivalencies
4387
+ const resolveSourcePath = (
4388
+ sourcePath: string,
4389
+ equivMap: Record<string, string | string[]>,
4390
+ ): string | null => {
4391
+ // Extract base variable from the path
4392
+ const dotIndex = sourcePath.indexOf('.');
4393
+ const bracketIndex = sourcePath.indexOf('[');
4394
+
4395
+ let baseVar: string;
4396
+ let rest: string;
4397
+
4398
+ if (dotIndex === -1 && bracketIndex === -1) {
4399
+ baseVar = sourcePath;
4400
+ rest = '';
4401
+ } else if (dotIndex === -1) {
4402
+ baseVar = sourcePath.slice(0, bracketIndex);
4403
+ rest = sourcePath.slice(bracketIndex);
4404
+ } else if (bracketIndex === -1) {
4405
+ baseVar = sourcePath.slice(0, dotIndex);
4406
+ rest = sourcePath.slice(dotIndex);
4407
+ } else {
4408
+ const firstIndex = Math.min(dotIndex, bracketIndex);
4409
+ baseVar = sourcePath.slice(0, firstIndex);
4410
+ rest = sourcePath.slice(firstIndex);
4411
+ }
4412
+
4413
+ // Look up the base variable in equivalencies
4414
+ if (baseVar in equivMap && equivMap[baseVar] !== sourcePath) {
4415
+ const baseResolved = equivMap[baseVar];
4416
+ // Skip if baseResolved is an array (handle later)
4417
+ if (Array.isArray(baseResolved)) return null;
4418
+ // If it resolves to a signature path, build the full resolved path
4419
+ if (
4420
+ baseResolved.startsWith('signature[') ||
4421
+ baseResolved.includes('()')
4422
+ ) {
4423
+ if (baseResolved.endsWith('()')) {
4424
+ return baseResolved + '.functionCallReturnValue' + rest;
4425
+ }
4426
+ return baseResolved + rest;
4427
+ }
4428
+ }
4429
+ return null;
4430
+ };
4431
+
4003
4432
  for (let iteration = 0; iteration < maxIterations; iteration++) {
4004
4433
  let changed = false;
4005
4434
 
4006
- for (const [varName, sourcePath] of Object.entries(
4435
+ for (const [varName, sourcePathOrArray] of Object.entries(
4007
4436
  equivalentSignatureVariables,
4008
4437
  )) {
4438
+ // Handle arrays (OR expressions) by resolving each element
4439
+ if (Array.isArray(sourcePathOrArray)) {
4440
+ const resolvedArray: string[] = [];
4441
+ let arrayChanged = false;
4442
+ for (const sourcePath of sourcePathOrArray) {
4443
+ // Try to resolve this path using transitive resolution
4444
+ const resolved = resolveSourcePath(
4445
+ sourcePath,
4446
+ equivalentSignatureVariables,
4447
+ );
4448
+ if (resolved && resolved !== sourcePath) {
4449
+ resolvedArray.push(resolved);
4450
+ arrayChanged = true;
4451
+ } else {
4452
+ resolvedArray.push(sourcePath);
4453
+ }
4454
+ }
4455
+ if (arrayChanged) {
4456
+ equivalentSignatureVariables[varName] = resolvedArray;
4457
+ changed = true;
4458
+ }
4459
+ continue;
4460
+ }
4461
+ const sourcePath = sourcePathOrArray;
4462
+
4009
4463
  // Skip if already fully resolved (contains function call syntax)
4010
4464
  // BUT first check for computed value patterns that need resolution (Fix 28)
4011
4465
  // AND method call patterns that need base variable resolution (Fix 33)
@@ -4067,6 +4521,8 @@ export class ScopeDataStructure {
4067
4521
  baseVar !== varName
4068
4522
  ) {
4069
4523
  const baseResolved = equivalentSignatureVariables[baseVar];
4524
+ // Skip if baseResolved is an array (OR expression)
4525
+ if (Array.isArray(baseResolved)) continue;
4070
4526
  // Only resolve if the base resolved to something useful (contains () or .)
4071
4527
  if (baseResolved.includes('()') || baseResolved.includes('.')) {
4072
4528
  const newPath = baseResolved + rest;
@@ -4078,6 +4534,40 @@ export class ScopeDataStructure {
4078
4534
  }
4079
4535
  }
4080
4536
 
4537
+ // Fix 38: Handle cyScope lazy initializer return values
4538
+ // When we have viewMode -> cyScope20(), trace through to find what cyScope20 returns.
4539
+ // The lazy initializer's return value should be the controllable data source.
4540
+ // Pattern: cyScopeN() where N is a number
4541
+ const cyScopeMatch = sourcePath.match(/^(cyScope\d+)\(\)$/);
4542
+ if (cyScopeMatch) {
4543
+ const cyScopeName = cyScopeMatch[1];
4544
+ const cyScopeNode = this.scopeNodes[cyScopeName];
4545
+
4546
+ if (cyScopeNode?.equivalencies) {
4547
+ // Look for returnValue equivalency in the cyScope
4548
+ const returnValueEquivs =
4549
+ cyScopeNode.equivalencies['returnValue'];
4550
+ if (returnValueEquivs && returnValueEquivs.length > 0) {
4551
+ // Get the first return value source
4552
+ const returnSource = returnValueEquivs[0].schemaPath;
4553
+
4554
+ // If the return source is a simple variable (not a complex path),
4555
+ // resolve varName directly to that variable
4556
+ if (
4557
+ returnSource &&
4558
+ !returnSource.includes('(') &&
4559
+ !returnSource.includes('[')
4560
+ ) {
4561
+ // Update varName to point to the return source
4562
+ if (equivalentSignatureVariables[varName] !== returnSource) {
4563
+ equivalentSignatureVariables[varName] = returnSource;
4564
+ changed = true;
4565
+ }
4566
+ }
4567
+ }
4568
+ }
4569
+ }
4570
+
4081
4571
  continue;
4082
4572
  }
4083
4573
 
@@ -4097,7 +4587,12 @@ export class ScopeDataStructure {
4097
4587
  }
4098
4588
 
4099
4589
  if (baseVar in equivalentSignatureVariables && baseVar !== varName) {
4100
- const baseResolved = equivalentSignatureVariables[baseVar];
4590
+ // Handle array case (OR expressions) - use first element
4591
+ const rawBaseResolved = equivalentSignatureVariables[baseVar];
4592
+ const baseResolved = Array.isArray(rawBaseResolved)
4593
+ ? rawBaseResolved[0]
4594
+ : rawBaseResolved;
4595
+ if (!baseResolved) continue;
4101
4596
  // If the base resolves to a hook call, add .functionCallReturnValue
4102
4597
  if (baseResolved.endsWith('()')) {
4103
4598
  const newPath = baseResolved + '.functionCallReturnValue' + rest;
@@ -4317,7 +4812,7 @@ export class ScopeDataStructure {
4317
4812
  path: string;
4318
4813
  conditionType: 'truthiness' | 'comparison' | 'switch';
4319
4814
  comparedValues?: string[];
4320
- location: 'if' | 'ternary' | 'logical-and' | 'switch';
4815
+ location: 'if' | 'ternary' | 'logical-and' | 'switch' | 'unconditional';
4321
4816
  }>
4322
4817
  >,
4323
4818
  ): void {
@@ -4503,6 +4998,33 @@ export class ScopeDataStructure {
4503
4998
  return enriched;
4504
4999
  }
4505
5000
 
5001
+ /**
5002
+ * Add JSX rendering usages from AST analysis.
5003
+ * These track arrays rendered via .map() and strings interpolated in JSX.
5004
+ */
5005
+ addJsxRenderingUsages(
5006
+ usages: import('../astScopes/types').JsxRenderingUsage[],
5007
+ ): void {
5008
+ // Add usages, avoiding duplicates based on path and renderingType
5009
+ for (const usage of usages) {
5010
+ const exists = this.rawJsxRenderingUsages.some(
5011
+ (existing) =>
5012
+ existing.path === usage.path &&
5013
+ existing.renderingType === usage.renderingType,
5014
+ );
5015
+ if (!exists) {
5016
+ this.rawJsxRenderingUsages.push(usage);
5017
+ }
5018
+ }
5019
+ }
5020
+
5021
+ /**
5022
+ * Get JSX rendering usages collected during analysis.
5023
+ */
5024
+ getJsxRenderingUsages(): import('../astScopes/types').JsxRenderingUsage[] {
5025
+ return this.rawJsxRenderingUsages;
5026
+ }
5027
+
4506
5028
  toSerializable(): SerializableDataStructure {
4507
5029
  // Helper to clean cyScope and cyDuplicateKey from a string for output
4508
5030
  const cleanCyScope = (str: string): string =>
@@ -5003,6 +5525,12 @@ export class ScopeDataStructure {
5003
5525
  ? enrichedGatingConditions
5004
5526
  : undefined;
5005
5527
 
5528
+ // Get JSX rendering usages (arrays via .map(), strings via interpolation)
5529
+ const jsxRenderingUsages =
5530
+ this.rawJsxRenderingUsages.length > 0
5531
+ ? this.rawJsxRenderingUsages
5532
+ : undefined;
5533
+
5006
5534
  return {
5007
5535
  externalFunctionCalls: deduplicatedExternalFunctionCalls,
5008
5536
  rootFunction,
@@ -5013,6 +5541,7 @@ export class ScopeDataStructure {
5013
5541
  conditionalEffects,
5014
5542
  compoundConditionals,
5015
5543
  childBoundaryGatingConditions,
5544
+ jsxRenderingUsages,
5016
5545
  };
5017
5546
  }
5018
5547