@codeyam/codeyam-cli 0.1.0-staging.c9dc00c → 0.1.0-staging.d3e886e

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 (189) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  4. package/analyzer-template/packages/ai/index.ts +1 -0
  5. package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +14 -0
  6. package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +101 -0
  7. package/analyzer-template/packages/ai/src/lib/astScopes/sharedPatterns.ts +28 -0
  8. package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +6 -0
  9. package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +172 -8
  10. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +70 -19
  11. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +40 -13
  12. package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +32 -5
  13. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +134 -2
  14. package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +359 -142
  15. package/analyzer-template/packages/ai/src/lib/mergeJsonTypeDefinitions.ts +5 -0
  16. package/analyzer-template/packages/ai/src/lib/promptGenerators/collapseNullableObjects.ts +118 -0
  17. package/analyzer-template/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.ts +24 -4
  18. package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +18 -0
  19. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +50 -25
  20. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +153 -76
  21. package/analyzer-template/packages/database/src/lib/kysely/tables/debugReportsTable.ts +1 -1
  22. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/debugReportsTable.d.ts +1 -1
  23. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  24. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +93 -2
  25. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  26. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +108 -2
  27. package/analyzer-template/project/constructMockCode.ts +2 -2
  28. package/analyzer-template/project/writeScenarioComponents.ts +14 -0
  29. package/background/src/lib/virtualized/project/constructMockCode.js +2 -2
  30. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  31. package/background/src/lib/virtualized/project/writeScenarioComponents.js +10 -0
  32. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  33. package/codeyam-cli/scripts/apply-setup.js +1 -1
  34. package/codeyam-cli/src/codeyam-cli.js +18 -2
  35. package/codeyam-cli/src/codeyam-cli.js.map +1 -1
  36. package/codeyam-cli/src/commands/analyze.js +2 -2
  37. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  38. package/codeyam-cli/src/commands/default.js +18 -17
  39. package/codeyam-cli/src/commands/default.js.map +1 -1
  40. package/codeyam-cli/src/commands/init.js +27 -93
  41. package/codeyam-cli/src/commands/init.js.map +1 -1
  42. package/codeyam-cli/src/commands/memory.js +9 -9
  43. package/codeyam-cli/src/commands/memory.js.map +1 -1
  44. package/codeyam-cli/src/commands/setup-simulations.js +1 -1
  45. package/codeyam-cli/src/commands/verify.js +12 -2
  46. package/codeyam-cli/src/commands/verify.js.map +1 -1
  47. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js +179 -0
  48. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js.map +1 -0
  49. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +11 -11
  50. package/codeyam-cli/src/utils/backgroundServer.js +90 -23
  51. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  52. package/codeyam-cli/src/utils/generateReport.js +2 -2
  53. package/codeyam-cli/src/utils/install-skills.js +13 -13
  54. package/codeyam-cli/src/utils/labsAutoCheck.js +0 -29
  55. package/codeyam-cli/src/utils/labsAutoCheck.js.map +1 -1
  56. package/codeyam-cli/src/utils/npmVersionCheck.js +76 -0
  57. package/codeyam-cli/src/utils/npmVersionCheck.js.map +1 -0
  58. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js +4 -4
  59. package/codeyam-cli/src/utils/ruleReflection/__tests__/contextBuilder.test.js.map +1 -1
  60. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +2 -2
  61. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  62. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +4 -4
  63. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  64. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +3 -3
  65. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  66. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js +23 -23
  67. package/codeyam-cli/src/utils/rules/__tests__/ruleState.test.js.map +1 -1
  68. package/codeyam-cli/src/utils/rules/ruleState.js +10 -10
  69. package/codeyam-cli/src/utils/rules/ruleState.js.map +1 -1
  70. package/codeyam-cli/src/utils/rules/staleness.js +6 -6
  71. package/codeyam-cli/src/utils/rules/staleness.js.map +1 -1
  72. package/codeyam-cli/src/utils/serverState.js +37 -10
  73. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  74. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +7 -7
  75. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js +66 -0
  76. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js.map +1 -0
  77. package/codeyam-cli/src/webserver/app/lib/dbNotifier.js.map +1 -1
  78. package/codeyam-cli/src/webserver/backgroundServer.js +26 -7
  79. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  80. package/codeyam-cli/src/webserver/bootstrap.js +11 -0
  81. package/codeyam-cli/src/webserver/bootstrap.js.map +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CA3JxPb7.js → CopyButton-jNYXRRNI.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-B86KKU7e.js → EntityItem-bwuHPyTa.js} +1 -1
  84. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-B5ctlSYt.js → EntityTypeBadge-CvzqMxcu.js} +1 -1
  85. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BqY8gDAW.js → EntityTypeIcon-BH0XDim7.js} +1 -1
  86. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-ClaLpuOo.js → InlineSpinner-EhOseatT.js} +1 -1
  87. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-BDhPilK7.js → InteractivePreview-yjIHlOGa.js} +2 -2
  88. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-VeqEBv9v.js → LibraryFunctionPreview-Cq5o8jL4.js} +1 -1
  89. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-Bs7Nn1Jr.js → LoadingDots-BvMu2i-g.js} +1 -1
  90. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-Bm3PmcCz.js → LogViewer-kgBTLoJD.js} +1 -1
  91. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C6PKeMYR.js → ReportIssueModal-BzPgx-xO.js} +2 -2
  92. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-Gq3Ocjo6.js → SafeScreenshot-CwZrv-Ok.js} +1 -1
  93. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-BNLaXBHR.js → ScenarioViewer-BX2Ny2Qj.js} +2 -2
  94. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-CiwXDxLh.js → TruncatedFilePath-CDpEprKa.js} +1 -1
  95. package/codeyam-cli/src/webserver/build/client/assets/{_index-B3TDXxnk.js → _index-BRx8ZGZo.js} +1 -1
  96. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BtBFH820.js → activity.(_tab)-4S4yPfFw.js} +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DHKuQSmR.js +17 -0
  98. package/codeyam-cli/src/webserver/build/client/assets/{book-open-PttOB2SF.js → book-open-D4IPYH_y.js} +1 -1
  99. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-TJp6ofnp.js → chevron-down-CG65viiV.js} +1 -1
  100. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-JE9ZIoBl.js → chunk-JZWAC4HX-DB3aFuEO.js} +9 -9
  101. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CXhHQYrI.js → circle-check-igfMr5DY.js} +1 -1
  102. package/codeyam-cli/src/webserver/build/client/assets/{copy-6y9ALfGT.js → copy-Coc4o_8c.js} +1 -1
  103. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Ca9fAY46.js → createLucideIcon-D1zB-pYc.js} +1 -1
  104. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C5lqplTC.js → dev.empty-JTAjQ54M.js} +1 -1
  105. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-n38keI1k.js → entity._sha._-B0h9AqE6.js} +2 -2
  106. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js → entity._sha.scenarios._scenarioId.fullscreen-DjLxr2JB.js} +1 -1
  107. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DGgZjdFg.js → entity._sha_.create-scenario-CtYowLOt.js} +1 -1
  108. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-38yPijoD.js → entity._sha_.edit._scenarioId-PePWg17F.js} +1 -1
  109. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-BSHEfydn.js → entry.client-I-Wo99C_.js} +1 -1
  110. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DCPhhSMo.js → fileTableUtils-9sMMAiWJ.js} +1 -1
  111. package/codeyam-cli/src/webserver/build/client/assets/{files-0N0YJQv7.js → files-Co65J0s3.js} +1 -1
  112. package/codeyam-cli/src/webserver/build/client/assets/{git-DXnyr8uP.js → git-BdHOxVfg.js} +1 -1
  113. package/codeyam-cli/src/webserver/build/client/assets/globals-CCgBKWy4.css +1 -0
  114. package/codeyam-cli/src/webserver/build/client/assets/{index-ChN9-fAY.js → index-CUM5iXwc.js} +1 -1
  115. package/codeyam-cli/src/webserver/build/client/assets/{index-CcsFv748.js → index-_417gcQW.js} +1 -1
  116. package/codeyam-cli/src/webserver/build/client/assets/labs-BK0C1H1T.js +1 -0
  117. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-CTqLEAGU.js → loader-circle-TzRHMVog.js} +1 -1
  118. package/codeyam-cli/src/webserver/build/client/assets/manifest-390cb8fa.js +1 -0
  119. package/codeyam-cli/src/webserver/build/client/assets/memory-CzZySbBE.js +78 -0
  120. package/codeyam-cli/src/webserver/build/client/assets/{pause-D6vreykR.js → pause-hjzB7t2z.js} +1 -1
  121. package/codeyam-cli/src/webserver/build/client/assets/root-DnbDhvTU.js +62 -0
  122. package/codeyam-cli/src/webserver/build/client/assets/{search-B8VUL8nl.js → search-DcAwD_Ln.js} +1 -1
  123. package/codeyam-cli/src/webserver/build/client/assets/settings-CclxrcPK.js +1 -0
  124. package/codeyam-cli/src/webserver/build/client/assets/{simulations-CPoAg7Zo.js → simulations-DVNJVQgD.js} +1 -1
  125. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BrCP7uQo.js → terminal-DbEAHMbA.js} +1 -1
  126. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BZz2NjYa.js → triangle-alert-CAD5b1o_.js} +1 -1
  127. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DNwUduNu.js → useCustomSizes-BqgrAzs3.js} +1 -1
  128. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-COky1GVF.js → useLastLogLine-DAFqfEDH.js} +1 -1
  129. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CpZgwliL.js → useReportContext-DZlYx2c4.js} +1 -1
  130. package/codeyam-cli/src/webserver/build/client/assets/{useToast-Bv9JFvUO.js → useToast-ihdMtlf6.js} +1 -1
  131. package/codeyam-cli/src/webserver/build/server/assets/{index-Cz2RkDCa.js → index-CxaRxKVt.js} +1 -1
  132. package/codeyam-cli/src/webserver/build/server/assets/server-build-D4DT0nM_.js +259 -0
  133. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  134. package/codeyam-cli/src/webserver/build-info.json +5 -5
  135. package/codeyam-cli/templates/{codeyam:debug.md → codeyam-debug.md} +1 -1
  136. package/codeyam-cli/templates/{codeyam:diagnose.md → codeyam-diagnose.md} +1 -1
  137. package/codeyam-cli/templates/codeyam-memory-hook.sh +14 -14
  138. package/codeyam-cli/templates/{codeyam:memory.md → codeyam-memory.md} +16 -23
  139. package/codeyam-cli/templates/{codeyam:new-rule.md → codeyam-new-rule.md} +1 -1
  140. package/codeyam-cli/templates/{codeyam:setup.md → codeyam-setup.md} +1 -1
  141. package/codeyam-cli/templates/{codeyam:sim.md → codeyam-sim.md} +1 -1
  142. package/codeyam-cli/templates/{codeyam:test.md → codeyam-test.md} +1 -1
  143. package/codeyam-cli/templates/{codeyam:verify.md → codeyam-verify.md} +1 -1
  144. package/codeyam-cli/templates/rule-reflection-hook.py +5 -5
  145. package/codeyam-cli/templates/rules-instructions.md +11 -15
  146. package/package.json +8 -8
  147. package/packages/ai/index.js +1 -1
  148. package/packages/ai/index.js.map +1 -1
  149. package/packages/ai/src/lib/analyzeScope.js +14 -0
  150. package/packages/ai/src/lib/analyzeScope.js.map +1 -1
  151. package/packages/ai/src/lib/astScopes/processExpression.js +78 -1
  152. package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
  153. package/packages/ai/src/lib/astScopes/sharedPatterns.js +25 -0
  154. package/packages/ai/src/lib/astScopes/sharedPatterns.js.map +1 -1
  155. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +124 -7
  156. package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
  157. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +59 -17
  158. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  159. package/packages/ai/src/lib/dataStructureChunking.js +30 -11
  160. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  161. package/packages/ai/src/lib/generateEntityScenarioData.js +22 -3
  162. package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
  163. package/packages/ai/src/lib/generateExecutionFlows.js +97 -2
  164. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  165. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +242 -81
  166. package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
  167. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js +5 -0
  168. package/packages/ai/src/lib/mergeJsonTypeDefinitions.js.map +1 -1
  169. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js +97 -0
  170. package/packages/ai/src/lib/promptGenerators/collapseNullableObjects.js.map +1 -0
  171. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js +17 -2
  172. package/packages/ai/src/lib/promptGenerators/generateEntityScenarioDataGenerator.js.map +1 -1
  173. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +11 -1
  174. package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
  175. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +42 -13
  176. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  177. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +123 -67
  178. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  179. package/packages/utils/src/lib/fs/rsyncCopy.js +93 -2
  180. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  181. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +0 -11
  182. package/codeyam-cli/src/webserver/build/client/assets/api.labs-survey-l0sNRNKZ.js +0 -1
  183. package/codeyam-cli/src/webserver/build/client/assets/globals-EVn6Z9pz.css +0 -1
  184. package/codeyam-cli/src/webserver/build/client/assets/labs-CmBYA0PH.js +0 -1
  185. package/codeyam-cli/src/webserver/build/client/assets/manifest-aa4ff97b.js +0 -1
  186. package/codeyam-cli/src/webserver/build/client/assets/memory-BSlqS1QA.js +0 -81
  187. package/codeyam-cli/src/webserver/build/client/assets/root-DVAbJY8B.js +0 -62
  188. package/codeyam-cli/src/webserver/build/client/assets/settings-BK-cnzp-.js +0 -1
  189. package/codeyam-cli/src/webserver/build/server/assets/server-build-CUVsWicu.js +0 -260
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-02-12T00:59:17.612Z",
3
- "buildTime": 1770857957612,
4
- "gitCommit": "c9dc00c107fcfb3afffa887ba71388b8c1845bb5",
2
+ "buildTimestamp": "2026-02-13T23:04:02.101Z",
3
+ "buildTime": 1771023842101,
4
+ "gitCommit": "d3e886e41f5771474b7c08a4b372bac8f4a70537",
5
5
  "nodeVersion": "v20.20.0",
6
- "contentHash": "758171aef85ccd1bc1f053e4d145ca64329fe1057b81fc88cd62eaa648a3dd12",
7
- "buildNumber": 611,
8
- "semanticVersion": "0.1.611",
9
- "version": "0.1.611 (2026-02-12T00:59+758171a)"
6
+ "contentHash": "7f2a19190511996b687bdd764ab6297993590ece837b6bbdec8bda87b904ab50",
7
+ "buildNumber": 625,
8
+ "semanticVersion": "0.1.625",
9
+ "version": "0.1.625 (2026-02-13T23:04+7f2a191)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [2/12/2026, 12:59:17 AM] > codeyam-combo@1.0.0 mergeDependencies
3
- [2/12/2026, 12:59:17 AM] > node ./scripts/mergePackageJsonFiles.cjs
2
+ [2/13/2026, 11:04:01 PM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [2/13/2026, 11:04:01 PM] > node ./scripts/mergePackageJsonFiles.cjs
4
4
 
5
5
 
6
- [2/12/2026, 12:59:17 AM] Merged dependencies into root package.json
6
+ [2/13/2026, 11:04:02 PM] Merged dependencies into root package.json
7
7
 
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@aws-sdk/client-cloudwatch-logs": "^3.980.0",
11
- "@aws-sdk/client-cloudfront": "^3.966.0",
11
+ "@aws-sdk/client-cloudfront": "^3.985.0",
12
12
  "@aws-sdk/client-codebuild": "^3.948.0",
13
13
  "@aws-sdk/client-dynamodb": "^3.956.0",
14
14
  "@aws-sdk/client-ec2": "^3.899.0",
@@ -43,6 +43,7 @@ export { default as isolateScopes } from './src/lib/isolateScopes';
43
43
  export {
44
44
  default as analyzeScope,
45
45
  destroyWorkerPool,
46
+ skipWorkerPool,
46
47
  } from './src/lib/analyzeScope';
47
48
  export { default as logOrderedMap } from './src/lib/logOrderedMap';
48
49
  export {
@@ -50,6 +50,19 @@ let workerPool: Piscina<
50
50
  AnalyzeScopeWorkerOutput
51
51
  > | null = null;
52
52
  let workerPoolDestroyed = false;
53
+ let workerPoolSkipped = false;
54
+
55
+ /**
56
+ * Skip worker pool initialization for the current session.
57
+ *
58
+ * The worker thread creates its own ProjectAnalyzer (ts.Program), which takes
59
+ * 50-70s — far longer than the actual entity processing (<2s for typical batches).
60
+ * When the entity count is small, running analyzeScopeLocal on the main thread
61
+ * reuses the existing ProjectAnalyzer and avoids this overhead entirely.
62
+ */
63
+ export function skipWorkerPool() {
64
+ workerPoolSkipped = true;
65
+ }
53
66
 
54
67
  /**
55
68
  * Check if we're in Node.js main thread
@@ -108,6 +121,7 @@ function ensureWorkerPool() {
108
121
  'analyzeScope should not be called after data structure preparation completes.',
109
122
  );
110
123
  }
124
+ if (workerPoolSkipped) return;
111
125
  if (workerPool !== null) return;
112
126
 
113
127
  // Only initialize worker pool in Node.js environment
@@ -9,6 +9,7 @@ import { StructuredPath } from './paths';
9
9
  import { nodeToSource } from './nodeToSource';
10
10
  import { methodRegistry, ArrayPushSemantics } from './methodSemantics';
11
11
  import {
12
+ getComparisonOperatorString,
12
13
  isArithmeticOperator,
13
14
  isAssignmentOperator,
14
15
  isBitwiseCompoundOperator,
@@ -25,6 +26,74 @@ import {
25
26
  } from './conditionalEffectsExtractor';
26
27
  import { detectArrayDerivedPattern } from './arrayDerivationDetector';
27
28
 
29
+ /**
30
+ * Recursively extracts root variable names from an expression AST node.
31
+ * Used to identify which variables flow into JSX expression children,
32
+ * so we can link them to the return value schema.
33
+ *
34
+ * Examples:
35
+ * - `filteredTopPaths.map(...)` → ['filteredTopPaths']
36
+ * - `a && b` → ['a', 'b']
37
+ * - `condition ? x : y` → ['condition', 'x', 'y']
38
+ */
39
+ function extractRootVariableNames(node: ts.Expression): string[] {
40
+ const ignoredIdentifiers = new Set([
41
+ 'undefined',
42
+ 'null',
43
+ 'true',
44
+ 'false',
45
+ 'NaN',
46
+ 'Infinity',
47
+ ]);
48
+
49
+ if (ts.isIdentifier(node)) {
50
+ const name = node.text;
51
+ return ignoredIdentifiers.has(name) ? [] : [name];
52
+ }
53
+
54
+ if (ts.isPropertyAccessExpression(node)) {
55
+ return extractRootVariableNames(node.expression);
56
+ }
57
+
58
+ if (ts.isCallExpression(node)) {
59
+ return extractRootVariableNames(node.expression);
60
+ }
61
+
62
+ if (ts.isBinaryExpression(node)) {
63
+ return [
64
+ ...extractRootVariableNames(node.left),
65
+ ...extractRootVariableNames(node.right),
66
+ ];
67
+ }
68
+
69
+ if (ts.isPrefixUnaryExpression(node)) {
70
+ return extractRootVariableNames(node.operand);
71
+ }
72
+
73
+ if (ts.isConditionalExpression(node)) {
74
+ return [
75
+ ...extractRootVariableNames(node.condition),
76
+ ...extractRootVariableNames(node.whenTrue),
77
+ ...extractRootVariableNames(node.whenFalse),
78
+ ];
79
+ }
80
+
81
+ if (ts.isParenthesizedExpression(node)) {
82
+ return extractRootVariableNames(node.expression);
83
+ }
84
+
85
+ // Stop recursion at JSX elements and other terminal nodes
86
+ if (
87
+ ts.isJsxElement(node) ||
88
+ ts.isJsxFragment(node) ||
89
+ ts.isJsxSelfClosingElement(node)
90
+ ) {
91
+ return [];
92
+ }
93
+
94
+ return [];
95
+ }
96
+
28
97
  /**
29
98
  * Checks if a JSX element has props that reference variables from the parent scope.
30
99
  * This is used to detect unconditionally-rendered children that should have their
@@ -1294,6 +1363,11 @@ export function extractConditionalUsage(
1294
1363
  return literalValue;
1295
1364
  };
1296
1365
 
1366
+ // Get the comparison operator string for the compound condition
1367
+ const comparisonOperator = getComparisonOperatorString(
1368
+ unwrapped.operatorToken.kind,
1369
+ );
1370
+
1297
1371
  // Helper to add a condition
1298
1372
  const addCondition = (
1299
1373
  path: string,
@@ -1338,6 +1412,7 @@ export function extractConditionalUsage(
1338
1412
  comparedValues,
1339
1413
  isNegated,
1340
1414
  requiredValue,
1415
+ ...(comparisonOperator && { comparisonOperator }),
1341
1416
  ...(chainInfo.currentOrGroupId && {
1342
1417
  orGroupId: chainInfo.currentOrGroupId,
1343
1418
  }),
@@ -3334,6 +3409,19 @@ export function processExpression({
3334
3409
  for (const child of unwrappedNode.children) {
3335
3410
  // Process expressions in JSX children: <div>{expr}</div>
3336
3411
  if (ts.isJsxExpression(child) && child.expression) {
3412
+ // When processing return value JSX, link root variables to return value schema
3413
+ if (targetPath && targetPath.base !== '') {
3414
+ const varNames = [
3415
+ ...new Set(extractRootVariableNames(child.expression)),
3416
+ ];
3417
+ for (const varName of varNames) {
3418
+ context.addEquivalence(
3419
+ targetPath.withProperty(varName),
3420
+ StructuredPath.fromBase(varName),
3421
+ );
3422
+ }
3423
+ }
3424
+
3337
3425
  // Process the expression with StructuredPath.empty() as targetPath
3338
3426
  // to trigger type registration without imposing prefix
3339
3427
  processExpression({
@@ -3368,6 +3456,19 @@ export function processExpression({
3368
3456
  for (const child of unwrappedNode.children) {
3369
3457
  // Process expressions in JSX children: <>{expr}</>
3370
3458
  if (ts.isJsxExpression(child) && child.expression) {
3459
+ // When processing return value JSX, link root variables to return value schema
3460
+ if (targetPath && targetPath.base !== '') {
3461
+ const varNames = [
3462
+ ...new Set(extractRootVariableNames(child.expression)),
3463
+ ];
3464
+ for (const varName of varNames) {
3465
+ context.addEquivalence(
3466
+ targetPath.withProperty(varName),
3467
+ StructuredPath.fromBase(varName),
3468
+ );
3469
+ }
3470
+ }
3471
+
3371
3472
  // Process the expression to extract structure
3372
3473
  processExpression({
3373
3474
  node: child.expression,
@@ -71,6 +71,34 @@ export function isComparisonOperator(kind: ts.SyntaxKind): boolean {
71
71
  );
72
72
  }
73
73
 
74
+ /**
75
+ * Returns the string representation of a comparison operator token.
76
+ */
77
+ export function getComparisonOperatorString(
78
+ kind: ts.SyntaxKind,
79
+ ): string | undefined {
80
+ switch (kind) {
81
+ case ts.SyntaxKind.EqualsEqualsToken:
82
+ return '==';
83
+ case ts.SyntaxKind.EqualsEqualsEqualsToken:
84
+ return '===';
85
+ case ts.SyntaxKind.ExclamationEqualsToken:
86
+ return '!=';
87
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken:
88
+ return '!==';
89
+ case ts.SyntaxKind.LessThanToken:
90
+ return '<';
91
+ case ts.SyntaxKind.LessThanEqualsToken:
92
+ return '<=';
93
+ case ts.SyntaxKind.GreaterThanToken:
94
+ return '>';
95
+ case ts.SyntaxKind.GreaterThanEqualsToken:
96
+ return '>=';
97
+ default:
98
+ return undefined;
99
+ }
100
+ }
101
+
74
102
  /**
75
103
  * Checks if an operator is an arithmetic operator
76
104
  */
@@ -433,6 +433,12 @@ export interface CompoundConditional {
433
433
  isNegated: boolean;
434
434
  /** Required value for this condition to be true */
435
435
  requiredValue?: string | boolean;
436
+ /**
437
+ * The comparison operator used (e.g., '>', '<', '>=', '<=', '===', '!==').
438
+ * Preserves the original operator so flow generation can distinguish
439
+ * `length > 0` from `length === 0`.
440
+ */
441
+ comparisonOperator?: string;
436
442
  /**
437
443
  * When conditions are part of an OR expression within an && chain,
438
444
  * they share the same orGroupId. Conditions with the same orGroupId
@@ -397,6 +397,7 @@ const SILENTLY_IGNORED_EQUIVALENCY_REASONS = new Set([
397
397
  'transformed non-object function equivalency - implicit parent equivalency - rerouted via useCallback',
398
398
  'transformed non-object function equivalency - Array.from() equivalency',
399
399
  'Spread operator equivalency key update: Explicit array deconstruction equivalency value',
400
+ // 'transformed non-object function equivalency - Explicit array deconstruction equivalency value',
400
401
  ]);
401
402
 
402
403
  export class ScopeDataStructure {
@@ -3902,25 +3903,116 @@ export class ScopeDataStructure {
3902
3903
  return [source];
3903
3904
  };
3904
3905
 
3905
- return entries.reduce(
3906
- (acc, entry) => {
3907
- if (entry.sourceCandidates.length === 0) return acc;
3906
+ const acc = entries.reduce(
3907
+ (result, entry) => {
3908
+ if (entry.sourceCandidates.length === 0) return result;
3908
3909
  const usages = entry.usages.filter(usageMatchesScope);
3909
3910
  for (const usage of usages) {
3910
- acc[usage.schemaPath] ||= [];
3911
+ result[usage.schemaPath] ||= [];
3911
3912
  // Resolve each source candidate through the equivalency chain
3912
3913
  for (const source of entry.sourceCandidates) {
3913
3914
  const resolvedSources = resolveToSignature(source, new Set());
3914
- acc[usage.schemaPath].push(...resolvedSources);
3915
+ result[usage.schemaPath].push(...resolvedSources);
3915
3916
  }
3916
3917
  }
3917
- return acc;
3918
+ return result;
3918
3919
  },
3919
3920
  {} as Record<
3920
3921
  string,
3921
3922
  Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[]
3922
3923
  >,
3923
3924
  );
3925
+
3926
+ // Post-processing: enrich useState-backed sources with co-located external
3927
+ // function calls. When a useState value resolves to a setter variable that
3928
+ // lives in the same scope as a fetch/API call, that fetch is a data source.
3929
+ this.enrichUseStateSourcesWithCoLocatedCalls(acc);
3930
+
3931
+ return acc;
3932
+ }
3933
+
3934
+ /**
3935
+ * For each source that ends at a useState path, check if the setter was called
3936
+ * from a scope that also contains external function calls (like fetch).
3937
+ * If so, add those external calls as additional source candidates.
3938
+ */
3939
+ private enrichUseStateSourcesWithCoLocatedCalls(
3940
+ acc: Record<string, Pick<ScopeVariable, 'scopeNodeName' | 'schemaPath'>[]>,
3941
+ ) {
3942
+ const rootScopeName = this.scopeTreeManager.getRootName();
3943
+ const rootScope = this.scopeNodes[rootScopeName];
3944
+ if (!rootScope) return;
3945
+
3946
+ // Collect all descendants for each scope node
3947
+ const getAllDescendants = (
3948
+ node: import('./helpers/ScopeTreeManager').ScopeTreeNode,
3949
+ ): Set<string> => {
3950
+ const names = new Set<string>([node.name]);
3951
+ for (const child of node.children) {
3952
+ for (const name of getAllDescendants(child)) {
3953
+ names.add(name);
3954
+ }
3955
+ }
3956
+ return names;
3957
+ };
3958
+
3959
+ for (const [usagePath, sources] of Object.entries(acc)) {
3960
+ const additionalSources: Pick<
3961
+ ScopeVariable,
3962
+ 'scopeNodeName' | 'schemaPath'
3963
+ >[] = [];
3964
+
3965
+ for (const source of sources) {
3966
+ // Check if this source is a useState-related terminal path
3967
+ // (e.g., useState(X).functionCallReturnValue[1] or useState(X).signature[0])
3968
+ if (!source.schemaPath.match(/^useState\([^)]*\)\./)) continue;
3969
+
3970
+ // Find the useState call from the source path
3971
+ const useStateCallMatch = source.schemaPath.match(
3972
+ /^(useState\([^)]*\))\./,
3973
+ );
3974
+ if (!useStateCallMatch) continue;
3975
+ const useStateCall = useStateCallMatch[1];
3976
+
3977
+ // Look in the root scope for the useState value equivalency
3978
+ // which tells us where the setter was called from
3979
+ const valuePath = `${useStateCall}.functionCallReturnValue[0]`;
3980
+ const valueEquivs = rootScope.equivalencies[valuePath];
3981
+ if (!valueEquivs) continue;
3982
+
3983
+ for (const equiv of valueEquivs) {
3984
+ // Find the scope where the setter was called
3985
+ const setterScopeName = equiv.scopeNodeName;
3986
+ const setterScopeTree =
3987
+ this.scopeTreeManager.findNode(setterScopeName);
3988
+ if (!setterScopeTree) continue;
3989
+
3990
+ // Get all descendant scope names from the setter scope
3991
+ const relatedScopes = getAllDescendants(setterScopeTree);
3992
+
3993
+ // Find external function calls in those scopes whose return values
3994
+ // are actually consumed (assigned to a variable). This excludes
3995
+ // fire-and-forget calls like analytics.track() or console.log().
3996
+ const coLocatedCalls = this.externalFunctionCalls.filter(
3997
+ (efc) =>
3998
+ relatedScopes.has(efc.callScope) &&
3999
+ efc.receivingVariableNames &&
4000
+ efc.receivingVariableNames.length > 0,
4001
+ );
4002
+
4003
+ for (const call of coLocatedCalls) {
4004
+ additionalSources.push({
4005
+ scopeNodeName: call.callScope,
4006
+ schemaPath: `${call.callSignature}.functionCallReturnValue`,
4007
+ });
4008
+ }
4009
+ }
4010
+ }
4011
+
4012
+ if (additionalSources.length > 0) {
4013
+ acc[usagePath].push(...additionalSources);
4014
+ }
4015
+ }
3924
4016
  }
3925
4017
 
3926
4018
  getUsageEquivalencies(functionName?: string) {
@@ -4427,6 +4519,15 @@ export class ScopeDataStructure {
4427
4519
  !equivalentValue.schemaPath.startsWith('signature[') && // not a signature path
4428
4520
  !equivalentValue.schemaPath.endsWith('.functionCallReturnValue') // not already handled above
4429
4521
  ) {
4522
+ // Skip bare "returnValue" from child scopes — this is the child's return value,
4523
+ // not a meaningful data source path in the parent scope
4524
+ if (
4525
+ equivalentValue.schemaPath === 'returnValue' &&
4526
+ equivalentValue.scopeNodeName !==
4527
+ this.scopeTreeManager.getRootName()
4528
+ ) {
4529
+ continue;
4530
+ }
4430
4531
  // Add equivalency (will accumulate if multiple values for OR expressions)
4431
4532
  addEquivalency(path, equivalentValue.schemaPath);
4432
4533
  }
@@ -5123,6 +5224,10 @@ export class ScopeDataStructure {
5123
5224
  getEnrichedConditionalUsages(): Record<string, EnrichedConditionalUsage[]> {
5124
5225
  const enriched: Record<string, EnrichedConditionalUsage[]> = {};
5125
5226
 
5227
+ console.log(
5228
+ `[getEnrichedConditionalUsages] Processing ${Object.keys(this.rawConditionalUsages).length} conditional paths: [${Object.keys(this.rawConditionalUsages).join(', ')}]`,
5229
+ );
5230
+
5126
5231
  for (const [path, usages] of Object.entries(this.rawConditionalUsages)) {
5127
5232
  // Try to trace this path back to a data source
5128
5233
  // First, try the root scope
@@ -5131,10 +5236,69 @@ export class ScopeDataStructure {
5131
5236
 
5132
5237
  let sourceDataPath: string | undefined;
5133
5238
  if (explanation.source) {
5134
- // Build the full data path: scopeName.path
5135
- sourceDataPath = `${explanation.source.scope}.${explanation.source.path}`;
5239
+ const { scope, path: sourcePath } = explanation.source;
5240
+
5241
+ // Build initial path — avoid redundant prefix when path already contains the scope call
5242
+ let fullPath: string;
5243
+ if (sourcePath.startsWith(`${scope}(`)) {
5244
+ fullPath = sourcePath;
5245
+ } else {
5246
+ fullPath = `${scope}.${sourcePath}`;
5247
+ }
5248
+
5249
+ sourceDataPath = fullPath;
5250
+ console.log(
5251
+ `[getEnrichedConditionalUsages] "${path}" explainPath → scope="${scope}", sourcePath="${sourcePath}" → sourceDataPath="${sourceDataPath}"`,
5252
+ );
5253
+ } else {
5254
+ console.log(
5255
+ `[getEnrichedConditionalUsages] "${path}" explainPath → no source found`,
5256
+ );
5136
5257
  }
5137
5258
 
5259
+ // If explainPath didn't find a useful external source (e.g., it traced to
5260
+ // useState or just to the component scope itself), check sourceEquivalencies
5261
+ // for an external function call source like a fetch call
5262
+ const hasExternalSource = sourceDataPath?.includes(
5263
+ '.functionCallReturnValue',
5264
+ );
5265
+ if (!hasExternalSource) {
5266
+ console.log(
5267
+ `[getEnrichedConditionalUsages] "${path}" no external source (sourceDataPath="${sourceDataPath}"), checking sourceEquivalencies fallback...`,
5268
+ );
5269
+ const sourceEquiv = this.getSourceEquivalencies();
5270
+ const returnValueKey = `returnValue.${path}`;
5271
+ const sources = sourceEquiv[returnValueKey];
5272
+ if (sources) {
5273
+ console.log(
5274
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies["${returnValueKey}"] has ${sources.length} sources: [${sources.map((s: { schemaPath: string }) => s.schemaPath).join(', ')}]`,
5275
+ );
5276
+ const externalSource = sources.find(
5277
+ (s: { schemaPath: string }) =>
5278
+ s.schemaPath.includes('.functionCallReturnValue') &&
5279
+ !s.schemaPath.startsWith('useState('),
5280
+ );
5281
+ if (externalSource) {
5282
+ console.log(
5283
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies fallback found external source: "${externalSource.schemaPath}"`,
5284
+ );
5285
+ sourceDataPath = externalSource.schemaPath;
5286
+ } else {
5287
+ console.log(
5288
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies fallback found no external function call source`,
5289
+ );
5290
+ }
5291
+ } else {
5292
+ console.log(
5293
+ `[getEnrichedConditionalUsages] "${path}" sourceEquivalencies["${returnValueKey}"] not found`,
5294
+ );
5295
+ }
5296
+ }
5297
+
5298
+ console.log(
5299
+ `[getEnrichedConditionalUsages] "${path}" FINAL sourceDataPath="${sourceDataPath ?? '(none)'}" (${usages.length} usages)`,
5300
+ );
5301
+
5138
5302
  enriched[path] = usages.map((usage) => ({
5139
5303
  ...usage,
5140
5304
  sourceDataPath,
@@ -14,21 +14,46 @@ const isStandaloneIndex = (s?: string) => !!s && STANDALONE_INDEX_RE.test(s);
14
14
  // The regex matches any path ending with .length that has [] somewhere before it
15
15
  const DYNAMIC_LENGTH_RE = /\[\].*\.length$/;
16
16
 
17
- // Treat these as structural placeholders (don't commit them as concrete leaves)
18
- function isSkippableLeafType(t: string) {
19
- // 'unknown' by itself is a placeholder (but 'boolean | unknown' is not)
20
- // Also handles optional variants like 'object | undefined', 'object | null',
21
- // 'array | undefined', etc. these are still structural placeholders that
22
- // should not overwrite already-populated structures.
17
+ // Cache for type string analysis to avoid repeated split/filter operations.
18
+ // These functions are called multiple times per path segment across thousands of paths.
19
+ const typeAnalysisCache = new Map<
20
+ string,
21
+ { isSkippable: boolean; baseType: string; isNullable: boolean }
22
+ >();
23
+
24
+ function getTypeAnalysis(t: string) {
25
+ const cached = typeAnalysisCache.get(t);
26
+ if (cached) return cached;
23
27
  const parts = t.split('|').map((s) => s.trim());
24
28
  const base = parts.filter((s) => s !== 'undefined' && s !== 'null');
25
- return (
26
- base.length === 1 &&
27
- (base[0] === 'object' ||
28
- base[0] === 'array' ||
29
- base[0] === 'function' ||
30
- base[0] === 'unknown')
31
- );
29
+ const result = {
30
+ isSkippable:
31
+ base.length === 1 &&
32
+ (base[0] === 'object' ||
33
+ base[0] === 'array' ||
34
+ base[0] === 'function' ||
35
+ base[0] === 'unknown'),
36
+ baseType: base[0],
37
+ isNullable: parts.includes('undefined') || parts.includes('null'),
38
+ };
39
+ typeAnalysisCache.set(t, result);
40
+ return result;
41
+ }
42
+
43
+ // Treat these as structural placeholders (don't commit them as concrete leaves)
44
+ function isSkippableLeafType(t: string) {
45
+ return getTypeAnalysis(t).isSkippable;
46
+ }
47
+
48
+ // Extract the base structural type from a potentially nullable type string.
49
+ // e.g., 'object | undefined' → 'object', 'array | null' → 'array'
50
+ function getBaseSkippableType(t: string): string {
51
+ return getTypeAnalysis(t).baseType;
52
+ }
53
+
54
+ // Check if a type string has nullable annotations (| undefined or | null)
55
+ function isNullableType(t: string): boolean {
56
+ return getTypeAnalysis(t).isNullable;
32
57
  }
33
58
 
34
59
  // Matches paths containing [][] — e.g., "items[][]" or "items[][].text"
@@ -317,15 +342,41 @@ export default function convertDotNotation(
317
342
  cursor[key] = typ;
318
343
  } else {
319
344
  // Structural/placeholder terminal
320
- if (typ === 'array') {
345
+ const nullable = isNullableType(typ);
346
+ const baseType = getBaseSkippableType(typ);
347
+
348
+ if (baseType === 'array') {
321
349
  if (!Array.isArray(cursor[key])) cursor[key] = [];
350
+ if (nullable) {
351
+ (cursor[key] as any)._nullable = true;
352
+ }
322
353
  } else if (
323
- typ === 'object' ||
324
- typ === 'function' ||
325
- typ.trim() === 'unknown'
354
+ baseType === 'object' ||
355
+ baseType === 'function' ||
356
+ baseType === 'unknown'
326
357
  ) {
327
- if (cursor[key] === undefined) {
328
- cursor[key] = typ;
358
+ if (nullable) {
359
+ // Nullable object: ensure it's an actual object (not a string
360
+ // placeholder) so _nullable can be set and child paths can
361
+ // populate properties on it.
362
+ if (
363
+ cursor[key] === undefined ||
364
+ typeof cursor[key] === 'string'
365
+ ) {
366
+ cursor[key] = {};
367
+ }
368
+ if (
369
+ typeof cursor[key] === 'object' &&
370
+ cursor[key] !== null &&
371
+ !Array.isArray(cursor[key])
372
+ ) {
373
+ (cursor[key] as any)._nullable = true;
374
+ }
375
+ } else {
376
+ // Non-nullable: preserve existing behavior (string placeholder)
377
+ if (cursor[key] === undefined) {
378
+ cursor[key] = typ;
379
+ }
329
380
  }
330
381
  }
331
382
  }