onbuzz 4.9.13 → 4.10.0

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 (451) hide show
  1. package/node_modules/glob/README.md +31 -5
  2. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  3. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  4. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  5. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  6. package/node_modules/glob/dist/commonjs/index.min.js +3 -3
  7. package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
  8. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  9. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  10. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  11. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  12. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  13. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  14. package/node_modules/glob/dist/esm/glob.js +2 -1
  15. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  16. package/node_modules/glob/dist/esm/index.min.js +3 -3
  17. package/node_modules/glob/dist/esm/index.min.js.map +4 -4
  18. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  19. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  20. package/node_modules/glob/dist/esm/pattern.js +4 -0
  21. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  22. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
  23. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
  24. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
  25. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
  26. package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
  27. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
  28. package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
  29. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
  30. package/node_modules/glob/node_modules/minimatch/README.md +76 -1
  31. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
  32. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
  33. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
  34. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
  35. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
  36. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
  37. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
  38. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
  39. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
  40. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
  41. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
  42. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
  43. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
  44. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
  45. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
  46. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
  47. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
  48. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
  49. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
  50. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
  51. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
  52. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
  53. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
  54. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
  55. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  56. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
  57. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
  58. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
  59. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  60. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
  61. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
  62. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  63. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
  64. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
  65. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
  66. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  67. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
  68. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
  69. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
  70. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
  71. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
  72. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
  73. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
  74. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  75. package/node_modules/glob/node_modules/minimatch/package.json +17 -11
  76. package/node_modules/glob/package.json +10 -13
  77. package/node_modules/minipass/LICENSE.md +55 -0
  78. package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
  79. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
  80. package/node_modules/minipass/dist/commonjs/index.js +13 -3
  81. package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
  82. package/node_modules/minipass/dist/esm/index.d.ts +12 -16
  83. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
  84. package/node_modules/minipass/dist/esm/index.js +3 -1
  85. package/node_modules/minipass/dist/esm/index.js.map +1 -1
  86. package/node_modules/minipass/package.json +9 -14
  87. package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
  88. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
  89. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
  90. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
  91. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  92. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
  93. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
  94. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  95. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
  96. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  97. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
  98. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
  99. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
  100. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  101. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
  102. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
  103. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  104. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
  105. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  106. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
  107. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  108. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
  109. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  110. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  111. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
  112. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
  113. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
  114. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
  115. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  116. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
  117. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
  118. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  119. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
  120. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  121. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
  122. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
  123. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
  124. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  125. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
  126. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
  127. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
  128. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  129. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
  130. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
  131. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
  132. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
  133. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  134. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
  135. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
  136. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  137. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
  138. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  139. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
  140. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
  141. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
  142. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  143. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
  144. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  145. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
  146. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
  147. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  148. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
  149. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  150. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
  151. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  152. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  153. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
  154. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
  155. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
  156. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
  157. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  158. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
  173. package/node_modules/path-scurry/package.json +8 -24
  174. package/package.json +1 -1
  175. package/scripts/debug-balance-probe.mjs +35 -35
  176. package/scripts/push-image.sh +43 -43
  177. package/scripts/setup-acr.sh +65 -65
  178. package/scripts/verify-optional-deps.js +96 -1
  179. package/src/__tests__/composioCliFlags.test.js +239 -239
  180. package/src/analyzers/CSSAnalyzer.js +298 -297
  181. package/src/analyzers/ConfigValidator.js +691 -690
  182. package/src/analyzers/ESLintAnalyzer.js +320 -320
  183. package/src/analyzers/JavaScriptAnalyzer.js +260 -261
  184. package/src/analyzers/PrettierFormatter.js +246 -247
  185. package/src/analyzers/PythonAnalyzer.js +283 -283
  186. package/src/analyzers/SecurityAnalyzer.js +729 -729
  187. package/src/analyzers/SparrowAnalyzer.js +341 -341
  188. package/src/analyzers/TypeScriptAnalyzer.js +247 -247
  189. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
  190. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
  191. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
  192. package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
  193. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
  194. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
  195. package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
  196. package/src/analyzers/codeCloneDetector/detector.js +250 -250
  197. package/src/analyzers/codeCloneDetector/index.js +194 -192
  198. package/src/analyzers/codeCloneDetector/parser.js +199 -199
  199. package/src/core/__tests__/agentPool.test.js +866 -866
  200. package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
  201. package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
  202. package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
  203. package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
  204. package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
  205. package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
  206. package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
  207. package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
  208. package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
  209. package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
  210. package/src/core/__tests__/flowEndToEnd.test.js +565 -565
  211. package/src/core/__tests__/flowFieldMapping.test.js +188 -189
  212. package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
  213. package/src/core/__tests__/flowSavePayload.test.js +170 -169
  214. package/src/core/__tests__/flowTemplates.test.js +311 -311
  215. package/src/core/__tests__/flowVersionStore.test.js +123 -123
  216. package/src/core/__tests__/messageProcessor.test.js +669 -669
  217. package/src/core/__tests__/stateManager.test.js +0 -1
  218. package/src/core/agentPool.js +2474 -2475
  219. package/src/core/agentScheduler.js +1 -4
  220. package/src/core/contextManager.js +708 -708
  221. package/src/core/flowExecutor.js +1510 -1510
  222. package/src/core/flowFieldMapping.js +136 -138
  223. package/src/core/messageProcessor.js +953 -954
  224. package/src/core/orchestrator.js +593 -595
  225. package/src/core/stateManager.js +1765 -1752
  226. package/src/index.js +1221 -1221
  227. package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
  228. package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
  229. package/src/interfaces/__tests__/imageServing.test.js +228 -228
  230. package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
  231. package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
  232. package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
  233. package/src/interfaces/schedulerRoutes.js +50 -50
  234. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
  235. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
  236. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
  237. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
  238. package/src/interfaces/terminal/api/session.js +265 -266
  239. package/src/interfaces/terminal/api/websocket.js +496 -497
  240. package/src/interfaces/terminal/components/AgentCreator.js +691 -705
  241. package/src/interfaces/terminal/components/AgentEditor.js +676 -678
  242. package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
  243. package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
  244. package/src/interfaces/terminal/components/Header.js +28 -28
  245. package/src/interfaces/terminal/components/Layout.js +598 -603
  246. package/src/interfaces/terminal/components/MessageList.js +280 -281
  247. package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
  248. package/src/interfaces/terminal/components/StatusBar.js +2 -0
  249. package/src/interfaces/terminal/index.js +168 -168
  250. package/src/interfaces/terminal/state/useAgentControl.js +496 -496
  251. package/src/interfaces/terminal/state/useAgents.js +537 -537
  252. package/src/interfaces/terminal/state/useMessages.js +629 -630
  253. package/src/interfaces/terminal/state/useTools.js +554 -554
  254. package/src/interfaces/terminal/utils/debugLogger.js +44 -44
  255. package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
  256. package/src/interfaces/webServer.js +7578 -7579
  257. package/src/interfaces/webServer.js.bak +7046 -7046
  258. package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
  259. package/src/modules/fileExplorer/controller.js +470 -469
  260. package/src/modules/fileExplorer/routes.js +285 -286
  261. package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
  262. package/src/modules/widget/__tests__/routes.test.js +677 -678
  263. package/src/modules/widget/__tests__/runtime.test.js +401 -401
  264. package/src/modules/widget/__tests__/versioning.test.js +309 -309
  265. package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
  266. package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
  267. package/src/modules/widget/routes.js +435 -435
  268. package/src/modules/widget/runtime/bundle.js +640 -640
  269. package/src/modules/widget/runtime/webComponentBundle.js +470 -470
  270. package/src/modules/widget/schema.js +182 -181
  271. package/src/modules/widget/widgetTool.js +1389 -1389
  272. package/src/services/__tests__/agentActivityService.test.js +401 -402
  273. package/src/services/__tests__/benchmarkService.test.js +184 -184
  274. package/src/services/__tests__/contextInjectionService.test.js +246 -246
  275. package/src/services/__tests__/conversationQuery.test.js +721 -723
  276. package/src/services/__tests__/credentialVault.test.js +469 -469
  277. package/src/services/__tests__/discordService.integration.test.js +638 -639
  278. package/src/services/__tests__/flowContextService.test.js +590 -590
  279. package/src/services/__tests__/memoryService.test.js +1 -1
  280. package/src/services/__tests__/messageSource.test.js +380 -380
  281. package/src/services/__tests__/modelRouterNaming.test.js +111 -111
  282. package/src/services/__tests__/projectDetector.test.js +34 -34
  283. package/src/services/__tests__/promptService.test.js +242 -242
  284. package/src/services/__tests__/telegramService.test.js +941 -941
  285. package/src/services/__tests__/tokenCountingService.test.js +48 -48
  286. package/src/services/agentActivityService.js +419 -420
  287. package/src/services/aiService.js +2997 -3001
  288. package/src/services/apiKeyManager.js +359 -359
  289. package/src/services/benchmarkService.js +196 -196
  290. package/src/services/codebaseKnowledgeService.js +2 -2
  291. package/src/services/composioService.js +738 -738
  292. package/src/services/conversationCompactionService.js +1258 -1257
  293. package/src/services/credentialVault.js +685 -685
  294. package/src/services/discordService.js +792 -793
  295. package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
  296. package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
  297. package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
  298. package/src/services/embeddings/autoRecall.js +218 -219
  299. package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
  300. package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
  301. package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
  302. package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
  303. package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
  304. package/src/services/embeddings/indexers/composioIndexer.js +279 -279
  305. package/src/services/embeddings/providerInterface.js +206 -206
  306. package/src/services/embeddings/providers/localProvider.js +11 -7
  307. package/src/services/embeddings/providers/openaiProvider.js +101 -101
  308. package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
  309. package/src/services/errorHandler.js +809 -809
  310. package/src/services/flowContextService.js +586 -586
  311. package/src/services/grounding/MockAdapter.js +125 -125
  312. package/src/services/modelRouterService.js +26 -31
  313. package/src/services/modelsService.js +322 -322
  314. package/src/services/ollamaService.js +452 -452
  315. package/src/services/projectDetector.js +403 -404
  316. package/src/services/promptService.js +418 -418
  317. package/src/services/qualityInspector.js +795 -795
  318. package/src/services/scheduleService.js +726 -726
  319. package/src/services/serviceRegistry.js +386 -386
  320. package/src/services/telegrafBot.js +174 -174
  321. package/src/services/telegramService.js +1972 -1972
  322. package/src/services/visualEditorBridge.js +1033 -1033
  323. package/src/services/visualEditorServer.js +1769 -1774
  324. package/src/services/whatsappService.js +667 -668
  325. package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
  326. package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
  327. package/src/tools/__tests__/agentDelayTool.test.js +342 -342
  328. package/src/tools/__tests__/baseTool.test.js +3 -3
  329. package/src/tools/__tests__/codeMapTool.test.js +915 -915
  330. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
  331. package/src/tools/__tests__/fileTreeTool.test.js +274 -274
  332. package/src/tools/__tests__/filesystemTool.test.js +815 -815
  333. package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
  334. package/src/tools/__tests__/imageTool.validator.test.js +194 -194
  335. package/src/tools/__tests__/jobDoneTool.test.js +580 -581
  336. package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
  337. package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
  338. package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
  339. package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
  340. package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
  341. package/src/tools/__tests__/memoryTool.test.js +1 -1
  342. package/src/tools/__tests__/seekTool.test.js +282 -282
  343. package/src/tools/__tests__/skillsTool.search.test.js +164 -164
  344. package/src/tools/__tests__/skillsTool.test.js +226 -226
  345. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
  346. package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
  347. package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
  348. package/src/tools/__tests__/taskManagerTool.test.js +866 -866
  349. package/src/tools/__tests__/terminalTool.test.js +448 -448
  350. package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
  351. package/src/tools/__tests__/userPromptTool.test.js +297 -297
  352. package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
  353. package/src/tools/__tests__/webTool.e2e.test.js +609 -603
  354. package/src/tools/__tests__/webTool.unit.test.js +195 -195
  355. package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
  356. package/src/tools/agentCommunicationTool.js +8 -10
  357. package/src/tools/agentDelayTool.js +496 -497
  358. package/src/tools/asyncToolManager.js +602 -603
  359. package/src/tools/baseTool.js +12 -11
  360. package/src/tools/cloneDetectionTool.js +576 -581
  361. package/src/tools/codeMapTool.js +0 -6
  362. package/src/tools/composioTool.js +617 -617
  363. package/src/tools/dependencyResolverTool.js +1211 -1212
  364. package/src/tools/desktop/DesktopTool.js +629 -638
  365. package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
  366. package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
  367. package/src/tools/desktop/__tests__/osController.test.js +364 -364
  368. package/src/tools/desktop/osController.js +491 -491
  369. package/src/tools/docxTool.js +623 -623
  370. package/src/tools/excelTool.js +636 -636
  371. package/src/tools/fileContentReplaceTool.js +5 -7
  372. package/src/tools/fileSystemTool.js +12 -19
  373. package/src/tools/fileTreeTool.js +840 -840
  374. package/src/tools/foundryWebSearchTool.js +273 -273
  375. package/src/tools/helpTool.js +198 -198
  376. package/src/tools/imageTool.js +1397 -1397
  377. package/src/tools/importAnalyzerTool.js +1056 -1056
  378. package/src/tools/jobDoneTool.js +495 -495
  379. package/src/tools/memoryTool.js +1 -1
  380. package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
  381. package/src/tools/office/pres/archetypes/agenda.js +61 -61
  382. package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
  383. package/src/tools/office/pres/archetypes/bigStat.js +140 -142
  384. package/src/tools/office/pres/archetypes/closing.js +70 -70
  385. package/src/tools/office/pres/archetypes/hero.js +70 -70
  386. package/src/tools/office/pres/archetypes/productHero.js +93 -94
  387. package/src/tools/office/pres/archetypes/table.js +73 -74
  388. package/src/tools/office/pres/backgrounds/orb.js +66 -66
  389. package/src/tools/office/pres/components.js +422 -423
  390. package/src/tools/officeTool.js +441 -441
  391. package/src/tools/pdfTool.js +625 -627
  392. package/src/tools/platformControlTool.js +1081 -1081
  393. package/src/tools/seekTool.js +917 -918
  394. package/src/tools/skillsTool.js +1 -1
  395. package/src/tools/staticAnalysisTool.js +2143 -2146
  396. package/src/tools/taskManagerTool.js +3324 -3324
  397. package/src/tools/terminalTool.js +2615 -2618
  398. package/src/tools/videoTool.js +1303 -1303
  399. package/src/tools/visionTool.js +508 -508
  400. package/src/tools/visualEditorTool.js +1289 -1290
  401. package/src/tools/webTool.js +3368 -3368
  402. package/src/tools/whatsappTool.js +464 -464
  403. package/src/types/__tests__/agent.test.js +499 -499
  404. package/src/types/__tests__/contextReference.test.js +606 -606
  405. package/src/types/__tests__/conversation.test.js +555 -555
  406. package/src/types/__tests__/toolCommand.test.js +584 -584
  407. package/src/types/contextReference.js +974 -971
  408. package/src/types/conversation.js +729 -729
  409. package/src/types/toolCommand.js +746 -746
  410. package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
  411. package/src/utilities/__tests__/auditReport.test.js +328 -328
  412. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
  413. package/src/utilities/__tests__/jsonRepair.test.js +103 -104
  414. package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
  415. package/src/utilities/__tests__/platformUtils.test.js +80 -87
  416. package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
  417. package/src/utilities/__tests__/toolConstants.test.js +92 -94
  418. package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
  419. package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
  420. package/src/utilities/attachmentValidator.js +284 -288
  421. package/src/utilities/authCache.js.backup-1779570472481 +121 -121
  422. package/src/utilities/browserStealth.js +631 -630
  423. package/src/utilities/configManager.js +616 -617
  424. package/src/utilities/directoryAccessManager.js +564 -565
  425. package/src/utilities/fileProcessor.js +308 -307
  426. package/src/utilities/humanBehavior.js +454 -453
  427. package/src/utilities/logger.js +479 -479
  428. package/src/utilities/structuredFileValidator.js +696 -699
  429. package/src/utilities/tagParser.js +5 -10
  430. package/src/utilities/userDataDir.js +308 -308
  431. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
  432. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
  433. package/node_modules/minipass/LICENSE +0 -15
  434. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
  435. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
  436. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
  437. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
  438. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
  439. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
  440. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
  441. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
  442. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
  443. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
  444. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
  445. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
  446. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
  447. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
  448. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
  449. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
  450. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
  451. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
@@ -1,915 +1,915 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { createMockLogger, createMockConfig } from '../../__test-utils__/mockFactories.js';
3
-
4
- // Mock fs before import
5
- const mockFsPromises = {
6
- stat: jest.fn(),
7
- readFile: jest.fn(),
8
- readdir: jest.fn()
9
- };
10
-
11
- jest.unstable_mockModule('fs', () => ({
12
- default: { promises: mockFsPromises, readFileSync: jest.fn(() => { throw new Error('no file'); }) },
13
- promises: mockFsPromises,
14
- readFileSync: jest.fn(() => { throw new Error('no file'); })
15
- }));
16
-
17
- // Mock constants
18
- jest.unstable_mockModule('../../utilities/constants.js', () => ({
19
- TOOL_STATUS: { PENDING: 'pending', EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed' },
20
- OPERATION_STATUS: { NOT_FOUND: 'not_found' },
21
- ERROR_TYPES: {},
22
- SYSTEM_DEFAULTS: { MAX_TOOL_EXECUTION_TIME: 300000 }
23
- }));
24
-
25
- const { default: CodeMapTool } = await import('../codeMapTool.js');
26
-
27
- describe('CodeMapTool', () => {
28
- let tool;
29
- let logger;
30
- const context = { projectDir: '/project', agentId: 'agent-1' };
31
-
32
- beforeEach(() => {
33
- jest.clearAllMocks();
34
- logger = createMockLogger();
35
- tool = new CodeMapTool({}, logger);
36
- });
37
-
38
- test('constructor sets metadata correctly', () => {
39
- expect(tool.id).toBe('code-map');
40
- expect(tool.requiresProject).toBe(true);
41
- expect(tool.isAsync).toBe(true);
42
- });
43
-
44
- test('getDescription mentions skeleton and read-range', () => {
45
- const desc = tool.getDescription();
46
- expect(desc).toContain('skeleton');
47
- expect(desc).toContain('read-range');
48
- });
49
-
50
- test('getRequiredParameters returns action', () => {
51
- expect(tool.getRequiredParameters()).toEqual(['action']);
52
- });
53
-
54
- test('parseParameters parses JSON content', () => {
55
- const content = JSON.stringify({
56
- action: 'skeleton',
57
- path: 'src/',
58
- level: 'B.0'
59
- });
60
- const result = tool.parseParameters(content);
61
- expect(result.action).toBe('skeleton');
62
- expect(result.path).toBe('src/');
63
- expect(result.level).toBe('B.0');
64
- });
65
-
66
- test('parseParameters parses nested parameters JSON', () => {
67
- const content = JSON.stringify({
68
- parameters: { action: 'read-range', filePath: 'index.js', startLine: 1, endLine: 10 }
69
- });
70
- const result = tool.parseParameters(content);
71
- expect(result.action).toBe('read-range');
72
- expect(result.filePath).toBe('index.js');
73
- expect(result.startLine).toBe(1);
74
- expect(result.endLine).toBe(10);
75
- });
76
-
77
- test('parseParameters parses XML content', () => {
78
- const content = '<action>skeleton</action><path>src/</path><level>A.0</level>';
79
- const result = tool.parseParameters(content);
80
- expect(result.action).toBe('skeleton');
81
- expect(result.path).toBe('src/');
82
- expect(result.level).toBe('A.0');
83
- });
84
-
85
- test('parseParameters returns parseError on bad JSON', () => {
86
- const result = tool.parseParameters('{ broken');
87
- expect(result).toHaveProperty('parseError');
88
- });
89
-
90
- test('customValidateParameters rejects missing action', () => {
91
- const result = tool.customValidateParameters({});
92
- expect(result.valid).toBe(false);
93
- });
94
-
95
- test('customValidateParameters rejects invalid action', () => {
96
- const result = tool.customValidateParameters({ action: 'invalid' });
97
- expect(result.valid).toBe(false);
98
- });
99
-
100
- test('customValidateParameters requires path for skeleton', () => {
101
- const result = tool.customValidateParameters({ action: 'skeleton' });
102
- expect(result.valid).toBe(false);
103
- expect(result.errors.some(e => e.includes('path'))).toBe(true);
104
- });
105
-
106
- test('customValidateParameters rejects invalid level', () => {
107
- const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/', level: 'X.9' });
108
- expect(result.valid).toBe(false);
109
- });
110
-
111
- test('customValidateParameters requires filePath/startLine/endLine for read-range', () => {
112
- const result = tool.customValidateParameters({ action: 'read-range' });
113
- expect(result.valid).toBe(false);
114
- expect(result.errors.length).toBeGreaterThanOrEqual(3);
115
- });
116
-
117
- test('customValidateParameters rejects endLine < startLine', () => {
118
- const result = tool.customValidateParameters({
119
- action: 'read-range', filePath: 'a.js', startLine: 10, endLine: 5
120
- });
121
- expect(result.valid).toBe(false);
122
- });
123
-
124
- test('customValidateParameters rejects range exceeding max', () => {
125
- const result = tool.customValidateParameters({
126
- action: 'read-range', filePath: 'a.js', startLine: 1, endLine: 600
127
- });
128
- expect(result.valid).toBe(false);
129
- });
130
-
131
- test('customValidateParameters accepts valid skeleton params', () => {
132
- const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/' });
133
- expect(result.valid).toBe(true);
134
- });
135
-
136
- test('execute skeleton on single JS file', async () => {
137
- const jsContent = [
138
- 'import express from "express";',
139
- '',
140
- 'export class App {',
141
- ' constructor() {}',
142
- ' start() {',
143
- ' console.log("started");',
144
- ' }',
145
- '}',
146
- '',
147
- 'export function main() {',
148
- ' return new App();',
149
- '}'
150
- ].join('\n');
151
-
152
- mockFsPromises.stat.mockResolvedValue({
153
- isFile: () => true,
154
- isDirectory: () => false,
155
- size: jsContent.length
156
- });
157
- mockFsPromises.readFile.mockResolvedValue(jsContent);
158
-
159
- const result = await tool.execute(
160
- { action: 'skeleton', path: 'src/app.js', level: 'B.0' },
161
- context
162
- );
163
-
164
- expect(result.success).toBe(true);
165
- expect(result.action).toBe('skeleton');
166
- expect(result.totalFiles).toBeGreaterThanOrEqual(1);
167
- expect(result.totalEntries).toBeGreaterThanOrEqual(1);
168
- });
169
-
170
- test('execute skeleton on directory with JS files', async () => {
171
- // First stat: directory check
172
- mockFsPromises.stat
173
- .mockResolvedValueOnce({ isFile: () => false, isDirectory: () => true }) // path stat
174
- .mockResolvedValueOnce({ size: 100 }); // file stat
175
-
176
- // Discover files - readdir for root
177
- mockFsPromises.readdir.mockResolvedValueOnce([
178
- { name: 'index.js', isDirectory: () => false, isFile: () => true, isSymbolicLink: () => false }
179
- ]);
180
-
181
- mockFsPromises.readFile
182
- .mockRejectedValueOnce(new Error('no .gitignore')) // _loadGitignoreRules
183
- .mockResolvedValueOnce('export function hello() { return 1; }\n'); // file content
184
-
185
- const result = await tool.execute(
186
- { action: 'skeleton', path: 'src/', level: 'A.0' },
187
- context
188
- );
189
-
190
- expect(result.success).toBe(true);
191
- expect(result.action).toBe('skeleton');
192
- });
193
-
194
- test('execute skeleton returns empty when no supported files', async () => {
195
- mockFsPromises.stat.mockResolvedValue({ isFile: () => false, isDirectory: () => true });
196
- mockFsPromises.readdir.mockResolvedValue([]);
197
- mockFsPromises.readFile.mockRejectedValue(new Error('no file'));
198
-
199
- const result = await tool.execute(
200
- { action: 'skeleton', path: 'empty/' },
201
- context
202
- );
203
-
204
- expect(result.success).toBe(true);
205
- expect(result.totalFiles).toBe(0);
206
- expect(result.message).toContain('No supported files');
207
- });
208
-
209
- test('execute skeleton throws for non-existent path', async () => {
210
- mockFsPromises.stat.mockRejectedValue(new Error('ENOENT'));
211
-
212
- await expect(tool.execute(
213
- { action: 'skeleton', path: 'missing/' },
214
- context
215
- )).rejects.toThrow('Path not found');
216
- });
217
-
218
- test('execute read-range returns formatted lines', async () => {
219
- const content = 'line1\nline2\nline3\nline4\nline5\n';
220
- mockFsPromises.readFile.mockResolvedValue(content);
221
-
222
- const result = await tool.execute(
223
- { action: 'read-range', filePath: 'src/index.js', startLine: 2, endLine: 4 },
224
- context
225
- );
226
-
227
- expect(result.success).toBe(true);
228
- expect(result.action).toBe('read-range');
229
- expect(result.linesReturned).toBe(3);
230
- expect(result.content).toContain('line2');
231
- expect(result.content).toContain('line3');
232
- expect(result.content).toContain('line4');
233
- });
234
-
235
- test('execute read-range throws when startLine exceeds file length', async () => {
236
- mockFsPromises.readFile.mockResolvedValue('line1\nline2\n');
237
-
238
- await expect(tool.execute(
239
- { action: 'read-range', filePath: 'a.js', startLine: 100, endLine: 110 },
240
- context
241
- )).rejects.toThrow('exceeds file length');
242
- });
243
-
244
- test('execute read-range throws for missing file', async () => {
245
- mockFsPromises.readFile.mockRejectedValue(new Error('ENOENT'));
246
-
247
- await expect(tool.execute(
248
- { action: 'read-range', filePath: 'missing.js', startLine: 1, endLine: 5 },
249
- context
250
- )).rejects.toThrow('File not found');
251
- });
252
-
253
- test('execute throws on unknown action', async () => {
254
- await expect(tool.execute(
255
- { action: 'unknown' },
256
- context
257
- )).rejects.toThrow('Unknown action');
258
- });
259
-
260
- test('_langOf detects python files', () => {
261
- expect(tool._langOf('script.py')).toBe('python');
262
- expect(tool._langOf('app.js')).toBe('js');
263
- expect(tool._langOf('component.tsx')).toBe('js');
264
- });
265
-
266
- test('_parseJS extracts exported functions', () => {
267
- const lines = [
268
- 'export function hello() {',
269
- ' return 1;',
270
- '}'
271
- ];
272
- const entries = tool._parseJS(lines, { publicOnly: true, withComments: false, includeImports: false });
273
- expect(entries.length).toBeGreaterThanOrEqual(1);
274
- expect(entries[0].kind).toBe('signature');
275
- });
276
-
277
- test('_parseJS extracts imports when includeImports is true', () => {
278
- const lines = [
279
- 'import express from "express";',
280
- 'const x = require("path");',
281
- 'export function hello() {}'
282
- ];
283
- const entries = tool._parseJS(lines, { publicOnly: false, withComments: false, includeImports: true });
284
- const imports = entries.filter(e => e.kind === 'import');
285
- expect(imports.length).toBe(2);
286
- });
287
-
288
- test('_parseJS includes comments when withComments is true', () => {
289
- const lines = [
290
- '/** My doc */',
291
- 'export function hello() {}'
292
- ];
293
- const entries = tool._parseJS(lines, { publicOnly: false, withComments: true, includeImports: false });
294
- const comments = entries.filter(e => e.kind === 'comment');
295
- expect(comments.length).toBeGreaterThanOrEqual(1);
296
- });
297
-
298
- test('_parsePython extracts def and class', () => {
299
- const lines = [
300
- 'class MyClass:',
301
- ' def __init__(self):',
302
- ' pass',
303
- '',
304
- 'def public_func():',
305
- ' return 1'
306
- ];
307
- const entries = tool._parsePython(lines, { publicOnly: false, withComments: false, includeImports: false });
308
- const sigs = entries.filter(e => e.kind === 'signature');
309
- expect(sigs.length).toBeGreaterThanOrEqual(2);
310
- });
311
-
312
- test('_parsePython respects publicOnly', () => {
313
- const lines = [
314
- 'def public_func():',
315
- ' pass',
316
- 'def _private_func():',
317
- ' pass'
318
- ];
319
- const entries = tool._parsePython(lines, { publicOnly: true, withComments: false, includeImports: false });
320
- const sigs = entries.filter(e => e.kind === 'signature');
321
- expect(sigs.length).toBe(1);
322
- expect(sigs[0].text).toContain('public_func');
323
- });
324
-
325
- test('_parseGitignore parses rules', () => {
326
- const content = '# comment\nnode_modules/\n*.log\n!important.log';
327
- const rules = tool._parseGitignore(content, '');
328
- expect(rules.length).toBe(3);
329
- expect(rules[2].negate).toBe(true);
330
- });
331
-
332
- test('_gitignorePatternToRegex handles ** patterns', () => {
333
- const re = tool._gitignorePatternToRegex('**/test');
334
- expect(re).toContain('(.+/)?');
335
- });
336
-
337
- // ─────────────────────────────────────────────────────────────────
338
- // Browser-JS / pre-ES-module patterns. Before this set of regexes,
339
- // CodeMap on a vanilla client-side .js file produced an empty map
340
- // because the parser only matched ESM exports + class methods.
341
- // These tests pin the new patterns. Each test exercises ONE pattern
342
- // in isolation so a future regression on any one of them surfaces
343
- // a precise failure.
344
- // ─────────────────────────────────────────────────────────────────
345
- describe('_parseJS — browser-JS patterns', () => {
346
- const opts = { publicOnly: false, withComments: false, includeImports: false };
347
- const signatures = (entries) =>
348
- entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
349
-
350
- test('top-level IIFE is captured', () => {
351
- const entries = tool._parseJS([
352
- '(function () {',
353
- ' var x = 1;',
354
- '})();',
355
- ], opts);
356
- expect(signatures(entries)).toContain('(function () {');
357
- });
358
-
359
- test('IIFE with leading ! or + prefix (Webpack-UMD style)', () => {
360
- const entries = tool._parseJS([
361
- '!function () { return 1; }();',
362
- '+function () { return 2; }();',
363
- ], opts);
364
- expect(signatures(entries)).toEqual(expect.arrayContaining([
365
- '!function () { return 1; }();',
366
- '+function () { return 2; }();',
367
- ]));
368
- });
369
-
370
- test('window.X = function — global assignment', () => {
371
- const entries = tool._parseJS([
372
- 'window.MyApp = function () {',
373
- ' return 1;',
374
- '};',
375
- ], opts);
376
- expect(signatures(entries).join('\n')).toMatch(/window\.MyApp\s*=\s*function/);
377
- });
378
-
379
- test('globalThis.foo = () => — global arrow assignment', () => {
380
- const entries = tool._parseJS([
381
- 'globalThis.greet = (name) => `hi ${name}`;',
382
- ], opts);
383
- expect(signatures(entries).join('\n')).toMatch(/globalThis\.greet/);
384
- });
385
-
386
- test('Namespace.utils.foo = function — namespaced global assignment', () => {
387
- const entries = tool._parseJS([
388
- 'MyApp.utils.formatDate = function (d) {',
389
- ' return d.toISOString();',
390
- '};',
391
- ], opts);
392
- expect(signatures(entries).join('\n')).toMatch(/MyApp\.utils\.formatDate/);
393
- });
394
-
395
- test('Foo.prototype.bar = function — prototype method', () => {
396
- const entries = tool._parseJS([
397
- 'Foo.prototype.bar = function () {',
398
- ' return this.x;',
399
- '};',
400
- ], opts);
401
- expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
402
- });
403
-
404
- test('Foo.prototype.bar = () => — prototype arrow assignment', () => {
405
- // (Real-world prototype-as-arrow is unusual but the regex
406
- // covers it for completeness.)
407
- const entries = tool._parseJS([
408
- 'Foo.prototype.bar = () => 42;',
409
- ], opts);
410
- expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
411
- });
412
-
413
- test('jQuery $.fn.plugin = function — jQuery plugin pattern', () => {
414
- const entries = tool._parseJS([
415
- '$.fn.myPlugin = function (opts) {',
416
- ' return this.each(function () {});',
417
- '};',
418
- ], opts);
419
- expect(signatures(entries).join('\n')).toMatch(/\$\.fn\.myPlugin/);
420
- });
421
-
422
- test('jQuery.fn.plugin = function — full jQuery prefix', () => {
423
- const entries = tool._parseJS([
424
- 'jQuery.fn.banner = function () { return this; };',
425
- ], opts);
426
- expect(signatures(entries).join('\n')).toMatch(/jQuery\.fn\.banner/);
427
- });
428
-
429
- test('document.addEventListener("DOMContentLoaded", …) — page lifecycle', () => {
430
- const entries = tool._parseJS([
431
- 'document.addEventListener("DOMContentLoaded", () => {',
432
- ' init();',
433
- '});',
434
- ], opts);
435
- expect(signatures(entries).join('\n')).toMatch(/document\.addEventListener/);
436
- });
437
-
438
- test('window.addEventListener("load", …) — page lifecycle (alt)', () => {
439
- const entries = tool._parseJS([
440
- 'window.addEventListener("load", function () { boot(); });',
441
- ], opts);
442
- expect(signatures(entries).join('\n')).toMatch(/window\.addEventListener/);
443
- });
444
-
445
- test('REGRESSION: a vanilla browser file (IIFE wrapping jQuery plugins) now produces a non-empty map', () => {
446
- // This is the original failure case: pure client-side .js with
447
- // none of the new patterns produced zero entries. Pre-fix this
448
- // file's signatures.length would have been 0; post-fix it's > 0.
449
- const entries = tool._parseJS([
450
- '(function ($) {',
451
- ' $.fn.tooltip = function (options) {',
452
- ' return this.each(function () {});',
453
- ' };',
454
- ' $.fn.popover = function (options) {',
455
- ' return this.each(function () {});',
456
- ' };',
457
- '})(jQuery);',
458
- ], opts);
459
- expect(signatures(entries).length).toBeGreaterThanOrEqual(3);
460
- });
461
-
462
- test('REGRESSION: file unaffected by changes — modern ESM still matches', () => {
463
- // Make sure adding the new regexes didn't regress the original
464
- // matcher set.
465
- const entries = tool._parseJS([
466
- 'export function hello() {}',
467
- 'export class Greeter {}',
468
- 'export const sum = (a, b) => a + b;',
469
- ], opts);
470
- expect(signatures(entries).length).toBe(3);
471
- });
472
- });
473
-
474
- // ─────────────────────────────────────────────────────────────────
475
- // TypeScript / TSX coverage. The JS parser is the same parser used
476
- // for .ts / .tsx / .mjs / .cjs (see _langOf); these tests pin which
477
- // TypeScript-specific patterns the no-regex parser captures TODAY
478
- // and which it MISSES, so a future tree-sitter migration (see the
479
- // file-header comment in codeMapTool.js) has an explicit baseline
480
- // to preserve / improve against.
481
- // ─────────────────────────────────────────────────────────────────
482
- describe('_parseJS — TypeScript / TSX coverage', () => {
483
- const opts = { publicOnly: false, withComments: false, includeImports: false };
484
- const sigs = (lines) =>
485
- tool._parseJS(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
486
-
487
- // ── Captures we rely on (regressions here would break TS skeletons) ──
488
-
489
- test('export interface — one-line', () => {
490
- const out = sigs(['export interface User { id: string; name: string; }']);
491
- expect(out.join('\n')).toMatch(/export interface User/);
492
- });
493
-
494
- test('export type alias', () => {
495
- const out = sigs(['export type ID = string | number;']);
496
- expect(out.join('\n')).toMatch(/export type ID/);
497
- });
498
-
499
- test('export enum', () => {
500
- const out = sigs(['export enum Color { Red, Green, Blue }']);
501
- expect(out.join('\n')).toMatch(/export enum Color/);
502
- });
503
-
504
- test('export interface multi-line opening', () => {
505
- const out = sigs([
506
- 'export interface User {',
507
- ' id: string;',
508
- ' name: string;',
509
- '}',
510
- ]);
511
- expect(out.join('\n')).toMatch(/export interface User \{/);
512
- });
513
-
514
- test('abstract class — declaration + abstract method', () => {
515
- const out = sigs([
516
- 'abstract class Animal {',
517
- ' abstract sound(): string;',
518
- '}',
519
- ]);
520
- expect(out.join('\n')).toMatch(/abstract class Animal/);
521
- expect(out.join('\n')).toMatch(/abstract sound\(\): string/);
522
- });
523
-
524
- test('generic function: identity<T>(x: T): T', () => {
525
- const out = sigs(['function identity<T>(x: T): T { return x; }']);
526
- expect(out.join('\n')).toMatch(/function identity<T>\(x: T\): T/);
527
- });
528
-
529
- test('generic class: Container<T>', () => {
530
- const out = sigs([
531
- 'class Container<T> {',
532
- ' value: T;',
533
- '}',
534
- ]);
535
- expect(out.join('\n')).toMatch(/class Container<T>/);
536
- });
537
-
538
- test('class method with TS return type annotation', () => {
539
- const out = sigs([
540
- 'class C {',
541
- ' foo(x: number): string { return String(x); }',
542
- '}',
543
- ]);
544
- expect(out.join('\n')).toMatch(/foo\(x: number\): string/);
545
- });
546
-
547
- test('TSX function component: () => JSX.Element', () => {
548
- const out = sigs([
549
- 'function App(): JSX.Element {',
550
- ' return <div />;',
551
- '}',
552
- ]);
553
- expect(out.join('\n')).toMatch(/function App\(\): JSX\.Element/);
554
- });
555
-
556
- test('ESM re-export: export { foo } from "./bar"', () => {
557
- const out = sigs([`export { foo } from './bar';`]);
558
- expect(out.join('\n')).toMatch(/export \{ foo \} from/);
559
- });
560
-
561
- test('ESM aliased re-export: export { foo as bar } from "./baz"', () => {
562
- const out = sigs([`export { foo as bar } from './baz';`]);
563
- expect(out.join('\n')).toMatch(/export \{ foo as bar \} from/);
564
- });
565
-
566
- test('decorator above class — class is still captured (decorator dropped is acceptable)', () => {
567
- const out = sigs([
568
- '@Component({ selector: "x" })',
569
- 'class Foo {}',
570
- ]);
571
- expect(out.join('\n')).toMatch(/class Foo/);
572
- });
573
-
574
- test('literal-union return type', () => {
575
- const out = sigs([`function getKind(): "a" | "b" { return "a"; }`]);
576
- expect(out.join('\n')).toMatch(/function getKind\(\):/);
577
- });
578
-
579
- test('export const generic arrow: <T>(x: T): T => x', () => {
580
- const out = sigs(['export const fn = <T>(x: T): T => x;']);
581
- expect(out.join('\n')).toMatch(/export const fn/);
582
- });
583
-
584
- // ── Language detection on all declared TS/TSX/MJS/CJS extensions ──
585
-
586
- test('_langOf maps .ts / .tsx / .mjs / .cjs / .jsx → "js"', () => {
587
- expect(tool._langOf('a.ts')).toBe('js');
588
- expect(tool._langOf('a.tsx')).toBe('js');
589
- expect(tool._langOf('a.mjs')).toBe('js');
590
- expect(tool._langOf('a.cjs')).toBe('js');
591
- expect(tool._langOf('a.jsx')).toBe('js');
592
- // Case-insensitive on the extension.
593
- expect(tool._langOf('a.TS')).toBe('js');
594
- });
595
-
596
- // ── Known gaps. These tests pin CURRENT (limited) behavior so an
597
- // improvement to the parser fails them — at which point you
598
- // update the assertion. Each gap is real and worth fixing in a
599
- // tree-sitter migration. ─────────────────────────────────────
600
-
601
- describe('KNOWN GAPS — pin current limitations', () => {
602
- test('GAP: bare `interface` (no `export`) is NOT captured', () => {
603
- const out = sigs(['interface User { id: string; name: string; }']);
604
- expect(out).toEqual([]);
605
- });
606
-
607
- test('GAP: bare `type` alias (no `export`) is NOT captured', () => {
608
- const out = sigs(['type ID = string | number;']);
609
- expect(out).toEqual([]);
610
- });
611
-
612
- test('GAP: bare `enum` (no `export`) is NOT captured', () => {
613
- const out = sigs(['enum Color { Red, Green, Blue }']);
614
- expect(out).toEqual([]);
615
- });
616
-
617
- test('GAP: bare multi-line `interface` (no `export`) is NOT captured', () => {
618
- const out = sigs([
619
- 'interface User {',
620
- ' id: string;',
621
- '}',
622
- ]);
623
- expect(out).toEqual([]);
624
- });
625
-
626
- test('GAP: typed arrow component `const App: React.FC = () => <div />` is NOT captured', () => {
627
- // The `: React.FC` annotation between the identifier and `=`
628
- // breaks the parser's "ident = arrow" recognition. Common in
629
- // older React+TS codebases.
630
- const out = sigs(['const App: React.FC = () => <div />;']);
631
- expect(out).toEqual([]);
632
- });
633
-
634
- test('GAP: async generator `async function* foo()` is NOT captured', () => {
635
- const out = sigs([
636
- 'async function* stream(): AsyncIterableIterator<number> {',
637
- ' yield 1;',
638
- '}',
639
- ]);
640
- expect(out).toEqual([]);
641
- });
642
-
643
- test('GAP: destructured-arg arrow with type annotation is NOT captured', () => {
644
- // `const fn = ({ name }: { name: string }): string => …`
645
- // The destructured + typed parameter list trips the
646
- // ident = arrow recognition.
647
- const out = sigs([`const greet = ({ name }: { name: string }): string => \`hi \${name}\`;`]);
648
- expect(out).toEqual([]);
649
- });
650
- });
651
- });
652
-
653
- // ─────────────────────────────────────────────────────────────────
654
- // C / C++ — _parseC. Same approach as the JS path; we lock the
655
- // patterns the regex needs to handle on real-world C/CPP files so
656
- // a future refactor (e.g. swapping for tree-sitter) has explicit
657
- // behavior to preserve.
658
- // ─────────────────────────────────────────────────────────────────
659
- describe('_parseC — C/C++ skeleton extraction', () => {
660
- const opts = { publicOnly: false, withComments: false, includeImports: true };
661
- const sig = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
662
- const imp = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'import').map(e => e.text.trim());
663
-
664
- test('#include with angle brackets and quotes', () => {
665
- const lines = [
666
- '#include <stdio.h>',
667
- '#include "myheader.h"',
668
- ];
669
- expect(imp(lines)).toEqual([
670
- '#include <stdio.h>',
671
- '#include "myheader.h"',
672
- ]);
673
- });
674
-
675
- test('#define captured as signature', () => {
676
- expect(sig(['#define MAX_BUF 1024', '#define PI 3.14'])).toEqual([
677
- '#define MAX_BUF 1024',
678
- '#define PI 3.14',
679
- ]);
680
- });
681
-
682
- test('class / struct / union / enum / namespace headers', () => {
683
- const lines = [
684
- 'class Foo {',
685
- '};',
686
- 'struct Bar {',
687
- '};',
688
- 'union Baz { int a; };',
689
- 'enum class Color { RED, GREEN };',
690
- 'namespace loxia {',
691
- '}',
692
- ];
693
- const found = sig(lines).join('\n');
694
- expect(found).toMatch(/class Foo/);
695
- expect(found).toMatch(/struct Bar/);
696
- expect(found).toMatch(/union Baz/);
697
- expect(found).toMatch(/enum class Color/);
698
- expect(found).toMatch(/namespace loxia/);
699
- });
700
-
701
- test('typedef and using alias', () => {
702
- const lines = [
703
- 'typedef unsigned long u64;',
704
- 'using IntList = std::vector<int>;',
705
- ];
706
- const found = sig(lines).join('\n');
707
- expect(found).toMatch(/typedef unsigned long u64/);
708
- expect(found).toMatch(/using IntList/);
709
- });
710
-
711
- test('free function definition with simple return type', () => {
712
- const lines = [
713
- 'int add(int a, int b) {',
714
- ' return a + b;',
715
- '}',
716
- ];
717
- expect(sig(lines).join('\n')).toMatch(/int add\(int a, int b\)/);
718
- });
719
-
720
- test('static + inline + const-qualified function definitions', () => {
721
- const lines = [
722
- 'static inline int square(int x) { return x * x; }',
723
- 'const char* greet(const char* name) {',
724
- ' return name;',
725
- '}',
726
- ];
727
- const found = sig(lines).join('\n');
728
- expect(found).toMatch(/static inline int square/);
729
- expect(found).toMatch(/const char\* greet/);
730
- });
731
-
732
- test('method, constructor, and destructor', () => {
733
- const lines = [
734
- 'Foo::Foo(int x) : x_(x) {',
735
- '}',
736
- 'Foo::~Foo() {',
737
- '}',
738
- 'int Foo::compute(int n) const {',
739
- ' return n * x_;',
740
- '}',
741
- ];
742
- const found = sig(lines).join('\n');
743
- expect(found).toMatch(/Foo::Foo\(int x\)/);
744
- expect(found).toMatch(/Foo::~Foo\(\)/);
745
- expect(found).toMatch(/int Foo::compute/);
746
- });
747
-
748
- test('REGRESSION: function CALLS are NOT misidentified as definitions', () => {
749
- // Without the call-rejection heuristic, the regex would happily
750
- // grab `printf("hi");` as a signature. Lock that it doesn't.
751
- const lines = [
752
- 'int main(void) {',
753
- ' printf("hi");',
754
- ' foo();',
755
- ' return 0;',
756
- '}',
757
- ];
758
- // Only main(void) should be a signature; printf/foo are calls.
759
- const found = sig(lines);
760
- expect(found).toEqual(['int main(void) {']);
761
- });
762
-
763
- test('REGRESSION: a tiny realistic C file produces a useful skeleton', () => {
764
- const lines = [
765
- '#include <stdio.h>',
766
- '',
767
- '#define BUF_SIZE 256',
768
- '',
769
- 'typedef struct {',
770
- ' int x;',
771
- ' int y;',
772
- '} Point;',
773
- '',
774
- 'static int distance_sq(Point p) {',
775
- ' return p.x * p.x + p.y * p.y;',
776
- '}',
777
- '',
778
- 'int main(int argc, char** argv) {',
779
- ' Point p = { 3, 4 };',
780
- ' printf("d² = %d\\n", distance_sq(p));',
781
- ' return 0;',
782
- '}',
783
- ];
784
- const all = tool._parseC(lines, opts);
785
- // Includes count as imports; the rest are signatures.
786
- const imports = all.filter(e => e.kind === 'import').map(e => e.text.trim());
787
- const signatures = all.filter(e => e.kind === 'signature').map(e => e.text.trim());
788
- expect(imports).toEqual(['#include <stdio.h>']);
789
- expect(signatures).toEqual(expect.arrayContaining([
790
- '#define BUF_SIZE 256',
791
- 'static int distance_sq(Point p) {',
792
- 'int main(int argc, char** argv) {',
793
- ]));
794
- });
795
- });
796
-
797
- test('_langOf detects C/C++ files', () => {
798
- expect(tool._langOf('foo.c')).toBe('c');
799
- expect(tool._langOf('foo.h')).toBe('c');
800
- expect(tool._langOf('foo.cpp')).toBe('c');
801
- expect(tool._langOf('foo.cxx')).toBe('c');
802
- expect(tool._langOf('foo.hpp')).toBe('c');
803
- expect(tool._langOf('foo.cc')).toBe('c');
804
- expect(tool._langOf('app.js')).toBe('js'); // sanity
805
- expect(tool._langOf('script.py')).toBe('python'); // sanity
806
- });
807
-
808
- test('execute skeleton on unsupported file type throws', async () => {
809
- mockFsPromises.stat.mockResolvedValue({
810
- isFile: () => true,
811
- isDirectory: () => false
812
- });
813
-
814
- await expect(tool.execute(
815
- { action: 'skeleton', path: 'data.json' },
816
- context
817
- )).rejects.toThrow('Unsupported file type');
818
- });
819
-
820
- // ─────────────────────────────────────────────────────────────────
821
- // Regex-free parser regressions — these cases are exactly the
822
- // kind of pattern that brittle regex would silently mis-classify.
823
- // They lock down the new string-utility implementation against
824
- // the most common false-positive risks.
825
- // ─────────────────────────────────────────────────────────────────
826
- describe('REGRESSION: no-regex parsers — false-positive guards', () => {
827
- const opts = { publicOnly: false, withComments: false, includeImports: true };
828
-
829
- // JS
830
- test('JS: `class` keyword does NOT match `classroom = …`', () => {
831
- const entries = tool._parseJS(['classroom = 1;'], opts);
832
- // No signature should be emitted — `classroom` is a plain var, not a class.
833
- expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
834
- });
835
- test('JS: string literal containing `class` does NOT push a class frame', () => {
836
- // Pre-fix the original regex `(stripped.match(/\{/g) || []).length` could
837
- // be tripped by content inside strings if not pre-masked. The no-regex
838
- // implementation masks strings before counting braces.
839
- const entries = tool._parseJS([
840
- 'const x = "class Foo {";',
841
- ], opts);
842
- // Only the `const` line should produce something; no class signature.
843
- const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
844
- expect(sigs.some(s => s.startsWith('class '))).toBe(false);
845
- });
846
- test('JS: `// foo` and `/* foo */` line/block comments still suppress signatures', () => {
847
- const entries = tool._parseJS([
848
- '// export function fake() {}',
849
- '/* export function alsoFake() {} */',
850
- 'export function real() {}',
851
- ], opts);
852
- const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
853
- expect(sigs).toEqual(['export function real() {}']);
854
- });
855
- test('JS: `require` as a substring inside another identifier is NOT an import', () => {
856
- // E.g. `prerequires = []` or `xrequireY()` — neither should be classified
857
- // as an import line by the require-call detector.
858
- const entries = tool._parseJS([
859
- 'const xrequireY = makeIt();',
860
- ], opts);
861
- expect(entries.filter(e => e.kind === 'import').length).toBe(0);
862
- });
863
- test('JS: `==` after a global LHS does NOT trigger an assignment match', () => {
864
- const entries = tool._parseJS([
865
- 'window.foo == bar;',
866
- ], opts);
867
- expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
868
- });
869
-
870
- // C
871
- test('C: `class` inside a string literal does NOT push a class signature', () => {
872
- const entries = tool._parseC([
873
- 'const char* msg = "class is a keyword in C++";',
874
- ], opts);
875
- // The C parser is line-based and doesn't deeply track string state;
876
- // this asserts the realistic case where the assignment isn't a class.
877
- // The line shouldn't surface as a "class …" signature.
878
- const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
879
- expect(sigs.some(s => s.trim().startsWith('class '))).toBe(false);
880
- });
881
- test('C: bare function CALL `foo(x, y);` is NOT a definition', () => {
882
- const entries = tool._parseC([' foo(1, 2);'], opts);
883
- expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
884
- });
885
-
886
- // Python
887
- test('Python: `class` keyword does NOT match `classroom = 1`', () => {
888
- const entries = tool._parsePython(['classroom = 1'], opts);
889
- // It's a top-level data assignment, NOT a class signature.
890
- const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
891
- expect(sigs.length).toBe(0);
892
- const datas = entries.filter(e => e.kind === 'data');
893
- expect(datas.length).toBe(1);
894
- });
895
- test('Python: `def` keyword on a line where it is NOT the leading token (e.g. `nodef(x):`) is not a function', () => {
896
- // The word-boundary check in startsWithKeyword catches this case.
897
- const entries = tool._parsePython(['nodef(x):'], opts);
898
- expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
899
- });
900
- test('Python: relative import `from . import foo` is captured', () => {
901
- const entries = tool._parsePython(['from . import foo'], opts);
902
- expect(entries.filter(e => e.kind === 'import').length).toBe(1);
903
- });
904
- test('Python: docstring closes correctly on a one-liner', () => {
905
- const entries = tool._parsePython([
906
- 'def hello():',
907
- ' """One-liner docstring."""',
908
- ' return 1',
909
- ], { ...opts, withComments: true });
910
- const kinds = entries.map(e => e.kind);
911
- expect(kinds).toContain('signature');
912
- expect(kinds).toContain('comment');
913
- });
914
- });
915
- });
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger } from '../../__test-utils__/mockFactories.js';
3
+
4
+ // Mock fs before import
5
+ const mockFsPromises = {
6
+ stat: jest.fn(),
7
+ readFile: jest.fn(),
8
+ readdir: jest.fn()
9
+ };
10
+
11
+ jest.unstable_mockModule('fs', () => ({
12
+ default: { promises: mockFsPromises, readFileSync: jest.fn(() => { throw new Error('no file'); }) },
13
+ promises: mockFsPromises,
14
+ readFileSync: jest.fn(() => { throw new Error('no file'); })
15
+ }));
16
+
17
+ // Mock constants
18
+ jest.unstable_mockModule('../../utilities/constants.js', () => ({
19
+ TOOL_STATUS: { PENDING: 'pending', EXECUTING: 'executing', COMPLETED: 'completed', FAILED: 'failed' },
20
+ OPERATION_STATUS: { NOT_FOUND: 'not_found' },
21
+ ERROR_TYPES: {},
22
+ SYSTEM_DEFAULTS: { MAX_TOOL_EXECUTION_TIME: 300000 }
23
+ }));
24
+
25
+ const { default: CodeMapTool } = await import('../codeMapTool.js');
26
+
27
+ describe('CodeMapTool', () => {
28
+ let tool;
29
+ let logger;
30
+ const context = { projectDir: '/project', agentId: 'agent-1' };
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ logger = createMockLogger();
35
+ tool = new CodeMapTool({}, logger);
36
+ });
37
+
38
+ test('constructor sets metadata correctly', () => {
39
+ expect(tool.id).toBe('code-map');
40
+ expect(tool.requiresProject).toBe(true);
41
+ expect(tool.isAsync).toBe(true);
42
+ });
43
+
44
+ test('getDescription mentions skeleton and read-range', () => {
45
+ const desc = tool.getDescription();
46
+ expect(desc).toContain('skeleton');
47
+ expect(desc).toContain('read-range');
48
+ });
49
+
50
+ test('getRequiredParameters returns action', () => {
51
+ expect(tool.getRequiredParameters()).toEqual(['action']);
52
+ });
53
+
54
+ test('parseParameters parses JSON content', () => {
55
+ const content = JSON.stringify({
56
+ action: 'skeleton',
57
+ path: 'src/',
58
+ level: 'B.0'
59
+ });
60
+ const result = tool.parseParameters(content);
61
+ expect(result.action).toBe('skeleton');
62
+ expect(result.path).toBe('src/');
63
+ expect(result.level).toBe('B.0');
64
+ });
65
+
66
+ test('parseParameters parses nested parameters JSON', () => {
67
+ const content = JSON.stringify({
68
+ parameters: { action: 'read-range', filePath: 'index.js', startLine: 1, endLine: 10 }
69
+ });
70
+ const result = tool.parseParameters(content);
71
+ expect(result.action).toBe('read-range');
72
+ expect(result.filePath).toBe('index.js');
73
+ expect(result.startLine).toBe(1);
74
+ expect(result.endLine).toBe(10);
75
+ });
76
+
77
+ test('parseParameters parses XML content', () => {
78
+ const content = '<action>skeleton</action><path>src/</path><level>A.0</level>';
79
+ const result = tool.parseParameters(content);
80
+ expect(result.action).toBe('skeleton');
81
+ expect(result.path).toBe('src/');
82
+ expect(result.level).toBe('A.0');
83
+ });
84
+
85
+ test('parseParameters returns parseError on bad JSON', () => {
86
+ const result = tool.parseParameters('{ broken');
87
+ expect(result).toHaveProperty('parseError');
88
+ });
89
+
90
+ test('customValidateParameters rejects missing action', () => {
91
+ const result = tool.customValidateParameters({});
92
+ expect(result.valid).toBe(false);
93
+ });
94
+
95
+ test('customValidateParameters rejects invalid action', () => {
96
+ const result = tool.customValidateParameters({ action: 'invalid' });
97
+ expect(result.valid).toBe(false);
98
+ });
99
+
100
+ test('customValidateParameters requires path for skeleton', () => {
101
+ const result = tool.customValidateParameters({ action: 'skeleton' });
102
+ expect(result.valid).toBe(false);
103
+ expect(result.errors.some(e => e.includes('path'))).toBe(true);
104
+ });
105
+
106
+ test('customValidateParameters rejects invalid level', () => {
107
+ const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/', level: 'X.9' });
108
+ expect(result.valid).toBe(false);
109
+ });
110
+
111
+ test('customValidateParameters requires filePath/startLine/endLine for read-range', () => {
112
+ const result = tool.customValidateParameters({ action: 'read-range' });
113
+ expect(result.valid).toBe(false);
114
+ expect(result.errors.length).toBeGreaterThanOrEqual(3);
115
+ });
116
+
117
+ test('customValidateParameters rejects endLine < startLine', () => {
118
+ const result = tool.customValidateParameters({
119
+ action: 'read-range', filePath: 'a.js', startLine: 10, endLine: 5
120
+ });
121
+ expect(result.valid).toBe(false);
122
+ });
123
+
124
+ test('customValidateParameters rejects range exceeding max', () => {
125
+ const result = tool.customValidateParameters({
126
+ action: 'read-range', filePath: 'a.js', startLine: 1, endLine: 600
127
+ });
128
+ expect(result.valid).toBe(false);
129
+ });
130
+
131
+ test('customValidateParameters accepts valid skeleton params', () => {
132
+ const result = tool.customValidateParameters({ action: 'skeleton', path: 'src/' });
133
+ expect(result.valid).toBe(true);
134
+ });
135
+
136
+ test('execute skeleton on single JS file', async () => {
137
+ const jsContent = [
138
+ 'import express from "express";',
139
+ '',
140
+ 'export class App {',
141
+ ' constructor() {}',
142
+ ' start() {',
143
+ ' console.log("started");',
144
+ ' }',
145
+ '}',
146
+ '',
147
+ 'export function main() {',
148
+ ' return new App();',
149
+ '}'
150
+ ].join('\n');
151
+
152
+ mockFsPromises.stat.mockResolvedValue({
153
+ isFile: () => true,
154
+ isDirectory: () => false,
155
+ size: jsContent.length
156
+ });
157
+ mockFsPromises.readFile.mockResolvedValue(jsContent);
158
+
159
+ const result = await tool.execute(
160
+ { action: 'skeleton', path: 'src/app.js', level: 'B.0' },
161
+ context
162
+ );
163
+
164
+ expect(result.success).toBe(true);
165
+ expect(result.action).toBe('skeleton');
166
+ expect(result.totalFiles).toBeGreaterThanOrEqual(1);
167
+ expect(result.totalEntries).toBeGreaterThanOrEqual(1);
168
+ });
169
+
170
+ test('execute skeleton on directory with JS files', async () => {
171
+ // First stat: directory check
172
+ mockFsPromises.stat
173
+ .mockResolvedValueOnce({ isFile: () => false, isDirectory: () => true }) // path stat
174
+ .mockResolvedValueOnce({ size: 100 }); // file stat
175
+
176
+ // Discover files - readdir for root
177
+ mockFsPromises.readdir.mockResolvedValueOnce([
178
+ { name: 'index.js', isDirectory: () => false, isFile: () => true, isSymbolicLink: () => false }
179
+ ]);
180
+
181
+ mockFsPromises.readFile
182
+ .mockRejectedValueOnce(new Error('no .gitignore')) // _loadGitignoreRules
183
+ .mockResolvedValueOnce('export function hello() { return 1; }\n'); // file content
184
+
185
+ const result = await tool.execute(
186
+ { action: 'skeleton', path: 'src/', level: 'A.0' },
187
+ context
188
+ );
189
+
190
+ expect(result.success).toBe(true);
191
+ expect(result.action).toBe('skeleton');
192
+ });
193
+
194
+ test('execute skeleton returns empty when no supported files', async () => {
195
+ mockFsPromises.stat.mockResolvedValue({ isFile: () => false, isDirectory: () => true });
196
+ mockFsPromises.readdir.mockResolvedValue([]);
197
+ mockFsPromises.readFile.mockRejectedValue(new Error('no file'));
198
+
199
+ const result = await tool.execute(
200
+ { action: 'skeleton', path: 'empty/' },
201
+ context
202
+ );
203
+
204
+ expect(result.success).toBe(true);
205
+ expect(result.totalFiles).toBe(0);
206
+ expect(result.message).toContain('No supported files');
207
+ });
208
+
209
+ test('execute skeleton throws for non-existent path', async () => {
210
+ mockFsPromises.stat.mockRejectedValue(new Error('ENOENT'));
211
+
212
+ await expect(tool.execute(
213
+ { action: 'skeleton', path: 'missing/' },
214
+ context
215
+ )).rejects.toThrow('Path not found');
216
+ });
217
+
218
+ test('execute read-range returns formatted lines', async () => {
219
+ const content = 'line1\nline2\nline3\nline4\nline5\n';
220
+ mockFsPromises.readFile.mockResolvedValue(content);
221
+
222
+ const result = await tool.execute(
223
+ { action: 'read-range', filePath: 'src/index.js', startLine: 2, endLine: 4 },
224
+ context
225
+ );
226
+
227
+ expect(result.success).toBe(true);
228
+ expect(result.action).toBe('read-range');
229
+ expect(result.linesReturned).toBe(3);
230
+ expect(result.content).toContain('line2');
231
+ expect(result.content).toContain('line3');
232
+ expect(result.content).toContain('line4');
233
+ });
234
+
235
+ test('execute read-range throws when startLine exceeds file length', async () => {
236
+ mockFsPromises.readFile.mockResolvedValue('line1\nline2\n');
237
+
238
+ await expect(tool.execute(
239
+ { action: 'read-range', filePath: 'a.js', startLine: 100, endLine: 110 },
240
+ context
241
+ )).rejects.toThrow('exceeds file length');
242
+ });
243
+
244
+ test('execute read-range throws for missing file', async () => {
245
+ mockFsPromises.readFile.mockRejectedValue(new Error('ENOENT'));
246
+
247
+ await expect(tool.execute(
248
+ { action: 'read-range', filePath: 'missing.js', startLine: 1, endLine: 5 },
249
+ context
250
+ )).rejects.toThrow('File not found');
251
+ });
252
+
253
+ test('execute throws on unknown action', async () => {
254
+ await expect(tool.execute(
255
+ { action: 'unknown' },
256
+ context
257
+ )).rejects.toThrow('Unknown action');
258
+ });
259
+
260
+ test('_langOf detects python files', () => {
261
+ expect(tool._langOf('script.py')).toBe('python');
262
+ expect(tool._langOf('app.js')).toBe('js');
263
+ expect(tool._langOf('component.tsx')).toBe('js');
264
+ });
265
+
266
+ test('_parseJS extracts exported functions', () => {
267
+ const lines = [
268
+ 'export function hello() {',
269
+ ' return 1;',
270
+ '}'
271
+ ];
272
+ const entries = tool._parseJS(lines, { publicOnly: true, withComments: false, includeImports: false });
273
+ expect(entries.length).toBeGreaterThanOrEqual(1);
274
+ expect(entries[0].kind).toBe('signature');
275
+ });
276
+
277
+ test('_parseJS extracts imports when includeImports is true', () => {
278
+ const lines = [
279
+ 'import express from "express";',
280
+ 'const x = require("path");',
281
+ 'export function hello() {}'
282
+ ];
283
+ const entries = tool._parseJS(lines, { publicOnly: false, withComments: false, includeImports: true });
284
+ const imports = entries.filter(e => e.kind === 'import');
285
+ expect(imports.length).toBe(2);
286
+ });
287
+
288
+ test('_parseJS includes comments when withComments is true', () => {
289
+ const lines = [
290
+ '/** My doc */',
291
+ 'export function hello() {}'
292
+ ];
293
+ const entries = tool._parseJS(lines, { publicOnly: false, withComments: true, includeImports: false });
294
+ const comments = entries.filter(e => e.kind === 'comment');
295
+ expect(comments.length).toBeGreaterThanOrEqual(1);
296
+ });
297
+
298
+ test('_parsePython extracts def and class', () => {
299
+ const lines = [
300
+ 'class MyClass:',
301
+ ' def __init__(self):',
302
+ ' pass',
303
+ '',
304
+ 'def public_func():',
305
+ ' return 1'
306
+ ];
307
+ const entries = tool._parsePython(lines, { publicOnly: false, withComments: false, includeImports: false });
308
+ const sigs = entries.filter(e => e.kind === 'signature');
309
+ expect(sigs.length).toBeGreaterThanOrEqual(2);
310
+ });
311
+
312
+ test('_parsePython respects publicOnly', () => {
313
+ const lines = [
314
+ 'def public_func():',
315
+ ' pass',
316
+ 'def _private_func():',
317
+ ' pass'
318
+ ];
319
+ const entries = tool._parsePython(lines, { publicOnly: true, withComments: false, includeImports: false });
320
+ const sigs = entries.filter(e => e.kind === 'signature');
321
+ expect(sigs.length).toBe(1);
322
+ expect(sigs[0].text).toContain('public_func');
323
+ });
324
+
325
+ test('_parseGitignore parses rules', () => {
326
+ const content = '# comment\nnode_modules/\n*.log\n!important.log';
327
+ const rules = tool._parseGitignore(content, '');
328
+ expect(rules.length).toBe(3);
329
+ expect(rules[2].negate).toBe(true);
330
+ });
331
+
332
+ test('_gitignorePatternToRegex handles ** patterns', () => {
333
+ const re = tool._gitignorePatternToRegex('**/test');
334
+ expect(re).toContain('(.+/)?');
335
+ });
336
+
337
+ // ─────────────────────────────────────────────────────────────────
338
+ // Browser-JS / pre-ES-module patterns. Before this set of regexes,
339
+ // CodeMap on a vanilla client-side .js file produced an empty map
340
+ // because the parser only matched ESM exports + class methods.
341
+ // These tests pin the new patterns. Each test exercises ONE pattern
342
+ // in isolation so a future regression on any one of them surfaces
343
+ // a precise failure.
344
+ // ─────────────────────────────────────────────────────────────────
345
+ describe('_parseJS — browser-JS patterns', () => {
346
+ const opts = { publicOnly: false, withComments: false, includeImports: false };
347
+ const signatures = (entries) =>
348
+ entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
349
+
350
+ test('top-level IIFE is captured', () => {
351
+ const entries = tool._parseJS([
352
+ '(function () {',
353
+ ' var x = 1;',
354
+ '})();',
355
+ ], opts);
356
+ expect(signatures(entries)).toContain('(function () {');
357
+ });
358
+
359
+ test('IIFE with leading ! or + prefix (Webpack-UMD style)', () => {
360
+ const entries = tool._parseJS([
361
+ '!function () { return 1; }();',
362
+ '+function () { return 2; }();',
363
+ ], opts);
364
+ expect(signatures(entries)).toEqual(expect.arrayContaining([
365
+ '!function () { return 1; }();',
366
+ '+function () { return 2; }();',
367
+ ]));
368
+ });
369
+
370
+ test('window.X = function — global assignment', () => {
371
+ const entries = tool._parseJS([
372
+ 'window.MyApp = function () {',
373
+ ' return 1;',
374
+ '};',
375
+ ], opts);
376
+ expect(signatures(entries).join('\n')).toMatch(/window\.MyApp\s*=\s*function/);
377
+ });
378
+
379
+ test('globalThis.foo = () => — global arrow assignment', () => {
380
+ const entries = tool._parseJS([
381
+ 'globalThis.greet = (name) => `hi ${name}`;',
382
+ ], opts);
383
+ expect(signatures(entries).join('\n')).toMatch(/globalThis\.greet/);
384
+ });
385
+
386
+ test('Namespace.utils.foo = function — namespaced global assignment', () => {
387
+ const entries = tool._parseJS([
388
+ 'MyApp.utils.formatDate = function (d) {',
389
+ ' return d.toISOString();',
390
+ '};',
391
+ ], opts);
392
+ expect(signatures(entries).join('\n')).toMatch(/MyApp\.utils\.formatDate/);
393
+ });
394
+
395
+ test('Foo.prototype.bar = function — prototype method', () => {
396
+ const entries = tool._parseJS([
397
+ 'Foo.prototype.bar = function () {',
398
+ ' return this.x;',
399
+ '};',
400
+ ], opts);
401
+ expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
402
+ });
403
+
404
+ test('Foo.prototype.bar = () => — prototype arrow assignment', () => {
405
+ // (Real-world prototype-as-arrow is unusual but the regex
406
+ // covers it for completeness.)
407
+ const entries = tool._parseJS([
408
+ 'Foo.prototype.bar = () => 42;',
409
+ ], opts);
410
+ expect(signatures(entries).join('\n')).toMatch(/Foo\.prototype\.bar/);
411
+ });
412
+
413
+ test('jQuery $.fn.plugin = function — jQuery plugin pattern', () => {
414
+ const entries = tool._parseJS([
415
+ '$.fn.myPlugin = function (opts) {',
416
+ ' return this.each(function () {});',
417
+ '};',
418
+ ], opts);
419
+ expect(signatures(entries).join('\n')).toMatch(/\$\.fn\.myPlugin/);
420
+ });
421
+
422
+ test('jQuery.fn.plugin = function — full jQuery prefix', () => {
423
+ const entries = tool._parseJS([
424
+ 'jQuery.fn.banner = function () { return this; };',
425
+ ], opts);
426
+ expect(signatures(entries).join('\n')).toMatch(/jQuery\.fn\.banner/);
427
+ });
428
+
429
+ test('document.addEventListener("DOMContentLoaded", …) — page lifecycle', () => {
430
+ const entries = tool._parseJS([
431
+ 'document.addEventListener("DOMContentLoaded", () => {',
432
+ ' init();',
433
+ '});',
434
+ ], opts);
435
+ expect(signatures(entries).join('\n')).toMatch(/document\.addEventListener/);
436
+ });
437
+
438
+ test('window.addEventListener("load", …) — page lifecycle (alt)', () => {
439
+ const entries = tool._parseJS([
440
+ 'window.addEventListener("load", function () { boot(); });',
441
+ ], opts);
442
+ expect(signatures(entries).join('\n')).toMatch(/window\.addEventListener/);
443
+ });
444
+
445
+ test('REGRESSION: a vanilla browser file (IIFE wrapping jQuery plugins) now produces a non-empty map', () => {
446
+ // This is the original failure case: pure client-side .js with
447
+ // none of the new patterns produced zero entries. Pre-fix this
448
+ // file's signatures.length would have been 0; post-fix it's > 0.
449
+ const entries = tool._parseJS([
450
+ '(function ($) {',
451
+ ' $.fn.tooltip = function (options) {',
452
+ ' return this.each(function () {});',
453
+ ' };',
454
+ ' $.fn.popover = function (options) {',
455
+ ' return this.each(function () {});',
456
+ ' };',
457
+ '})(jQuery);',
458
+ ], opts);
459
+ expect(signatures(entries).length).toBeGreaterThanOrEqual(3);
460
+ });
461
+
462
+ test('REGRESSION: file unaffected by changes — modern ESM still matches', () => {
463
+ // Make sure adding the new regexes didn't regress the original
464
+ // matcher set.
465
+ const entries = tool._parseJS([
466
+ 'export function hello() {}',
467
+ 'export class Greeter {}',
468
+ 'export const sum = (a, b) => a + b;',
469
+ ], opts);
470
+ expect(signatures(entries).length).toBe(3);
471
+ });
472
+ });
473
+
474
+ // ─────────────────────────────────────────────────────────────────
475
+ // TypeScript / TSX coverage. The JS parser is the same parser used
476
+ // for .ts / .tsx / .mjs / .cjs (see _langOf); these tests pin which
477
+ // TypeScript-specific patterns the no-regex parser captures TODAY
478
+ // and which it MISSES, so a future tree-sitter migration (see the
479
+ // file-header comment in codeMapTool.js) has an explicit baseline
480
+ // to preserve / improve against.
481
+ // ─────────────────────────────────────────────────────────────────
482
+ describe('_parseJS — TypeScript / TSX coverage', () => {
483
+ const opts = { publicOnly: false, withComments: false, includeImports: false };
484
+ const sigs = (lines) =>
485
+ tool._parseJS(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
486
+
487
+ // ── Captures we rely on (regressions here would break TS skeletons) ──
488
+
489
+ test('export interface — one-line', () => {
490
+ const out = sigs(['export interface User { id: string; name: string; }']);
491
+ expect(out.join('\n')).toMatch(/export interface User/);
492
+ });
493
+
494
+ test('export type alias', () => {
495
+ const out = sigs(['export type ID = string | number;']);
496
+ expect(out.join('\n')).toMatch(/export type ID/);
497
+ });
498
+
499
+ test('export enum', () => {
500
+ const out = sigs(['export enum Color { Red, Green, Blue }']);
501
+ expect(out.join('\n')).toMatch(/export enum Color/);
502
+ });
503
+
504
+ test('export interface multi-line opening', () => {
505
+ const out = sigs([
506
+ 'export interface User {',
507
+ ' id: string;',
508
+ ' name: string;',
509
+ '}',
510
+ ]);
511
+ expect(out.join('\n')).toMatch(/export interface User \{/);
512
+ });
513
+
514
+ test('abstract class — declaration + abstract method', () => {
515
+ const out = sigs([
516
+ 'abstract class Animal {',
517
+ ' abstract sound(): string;',
518
+ '}',
519
+ ]);
520
+ expect(out.join('\n')).toMatch(/abstract class Animal/);
521
+ expect(out.join('\n')).toMatch(/abstract sound\(\): string/);
522
+ });
523
+
524
+ test('generic function: identity<T>(x: T): T', () => {
525
+ const out = sigs(['function identity<T>(x: T): T { return x; }']);
526
+ expect(out.join('\n')).toMatch(/function identity<T>\(x: T\): T/);
527
+ });
528
+
529
+ test('generic class: Container<T>', () => {
530
+ const out = sigs([
531
+ 'class Container<T> {',
532
+ ' value: T;',
533
+ '}',
534
+ ]);
535
+ expect(out.join('\n')).toMatch(/class Container<T>/);
536
+ });
537
+
538
+ test('class method with TS return type annotation', () => {
539
+ const out = sigs([
540
+ 'class C {',
541
+ ' foo(x: number): string { return String(x); }',
542
+ '}',
543
+ ]);
544
+ expect(out.join('\n')).toMatch(/foo\(x: number\): string/);
545
+ });
546
+
547
+ test('TSX function component: () => JSX.Element', () => {
548
+ const out = sigs([
549
+ 'function App(): JSX.Element {',
550
+ ' return <div />;',
551
+ '}',
552
+ ]);
553
+ expect(out.join('\n')).toMatch(/function App\(\): JSX\.Element/);
554
+ });
555
+
556
+ test('ESM re-export: export { foo } from "./bar"', () => {
557
+ const out = sigs([`export { foo } from './bar';`]);
558
+ expect(out.join('\n')).toMatch(/export \{ foo \} from/);
559
+ });
560
+
561
+ test('ESM aliased re-export: export { foo as bar } from "./baz"', () => {
562
+ const out = sigs([`export { foo as bar } from './baz';`]);
563
+ expect(out.join('\n')).toMatch(/export \{ foo as bar \} from/);
564
+ });
565
+
566
+ test('decorator above class — class is still captured (decorator dropped is acceptable)', () => {
567
+ const out = sigs([
568
+ '@Component({ selector: "x" })',
569
+ 'class Foo {}',
570
+ ]);
571
+ expect(out.join('\n')).toMatch(/class Foo/);
572
+ });
573
+
574
+ test('literal-union return type', () => {
575
+ const out = sigs([`function getKind(): "a" | "b" { return "a"; }`]);
576
+ expect(out.join('\n')).toMatch(/function getKind\(\):/);
577
+ });
578
+
579
+ test('export const generic arrow: <T>(x: T): T => x', () => {
580
+ const out = sigs(['export const fn = <T>(x: T): T => x;']);
581
+ expect(out.join('\n')).toMatch(/export const fn/);
582
+ });
583
+
584
+ // ── Language detection on all declared TS/TSX/MJS/CJS extensions ──
585
+
586
+ test('_langOf maps .ts / .tsx / .mjs / .cjs / .jsx → "js"', () => {
587
+ expect(tool._langOf('a.ts')).toBe('js');
588
+ expect(tool._langOf('a.tsx')).toBe('js');
589
+ expect(tool._langOf('a.mjs')).toBe('js');
590
+ expect(tool._langOf('a.cjs')).toBe('js');
591
+ expect(tool._langOf('a.jsx')).toBe('js');
592
+ // Case-insensitive on the extension.
593
+ expect(tool._langOf('a.TS')).toBe('js');
594
+ });
595
+
596
+ // ── Known gaps. These tests pin CURRENT (limited) behavior so an
597
+ // improvement to the parser fails them — at which point you
598
+ // update the assertion. Each gap is real and worth fixing in a
599
+ // tree-sitter migration. ─────────────────────────────────────
600
+
601
+ describe('KNOWN GAPS — pin current limitations', () => {
602
+ test('GAP: bare `interface` (no `export`) is NOT captured', () => {
603
+ const out = sigs(['interface User { id: string; name: string; }']);
604
+ expect(out).toEqual([]);
605
+ });
606
+
607
+ test('GAP: bare `type` alias (no `export`) is NOT captured', () => {
608
+ const out = sigs(['type ID = string | number;']);
609
+ expect(out).toEqual([]);
610
+ });
611
+
612
+ test('GAP: bare `enum` (no `export`) is NOT captured', () => {
613
+ const out = sigs(['enum Color { Red, Green, Blue }']);
614
+ expect(out).toEqual([]);
615
+ });
616
+
617
+ test('GAP: bare multi-line `interface` (no `export`) is NOT captured', () => {
618
+ const out = sigs([
619
+ 'interface User {',
620
+ ' id: string;',
621
+ '}',
622
+ ]);
623
+ expect(out).toEqual([]);
624
+ });
625
+
626
+ test('GAP: typed arrow component `const App: React.FC = () => <div />` is NOT captured', () => {
627
+ // The `: React.FC` annotation between the identifier and `=`
628
+ // breaks the parser's "ident = arrow" recognition. Common in
629
+ // older React+TS codebases.
630
+ const out = sigs(['const App: React.FC = () => <div />;']);
631
+ expect(out).toEqual([]);
632
+ });
633
+
634
+ test('GAP: async generator `async function* foo()` is NOT captured', () => {
635
+ const out = sigs([
636
+ 'async function* stream(): AsyncIterableIterator<number> {',
637
+ ' yield 1;',
638
+ '}',
639
+ ]);
640
+ expect(out).toEqual([]);
641
+ });
642
+
643
+ test('GAP: destructured-arg arrow with type annotation is NOT captured', () => {
644
+ // `const fn = ({ name }: { name: string }): string => …`
645
+ // The destructured + typed parameter list trips the
646
+ // ident = arrow recognition.
647
+ const out = sigs([`const greet = ({ name }: { name: string }): string => \`hi \${name}\`;`]);
648
+ expect(out).toEqual([]);
649
+ });
650
+ });
651
+ });
652
+
653
+ // ─────────────────────────────────────────────────────────────────
654
+ // C / C++ — _parseC. Same approach as the JS path; we lock the
655
+ // patterns the regex needs to handle on real-world C/CPP files so
656
+ // a future refactor (e.g. swapping for tree-sitter) has explicit
657
+ // behavior to preserve.
658
+ // ─────────────────────────────────────────────────────────────────
659
+ describe('_parseC — C/C++ skeleton extraction', () => {
660
+ const opts = { publicOnly: false, withComments: false, includeImports: true };
661
+ const sig = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'signature').map(e => e.text.trim());
662
+ const imp = (lines) => tool._parseC(lines, opts).filter(e => e.kind === 'import').map(e => e.text.trim());
663
+
664
+ test('#include with angle brackets and quotes', () => {
665
+ const lines = [
666
+ '#include <stdio.h>',
667
+ '#include "myheader.h"',
668
+ ];
669
+ expect(imp(lines)).toEqual([
670
+ '#include <stdio.h>',
671
+ '#include "myheader.h"',
672
+ ]);
673
+ });
674
+
675
+ test('#define captured as signature', () => {
676
+ expect(sig(['#define MAX_BUF 1024', '#define PI 3.14'])).toEqual([
677
+ '#define MAX_BUF 1024',
678
+ '#define PI 3.14',
679
+ ]);
680
+ });
681
+
682
+ test('class / struct / union / enum / namespace headers', () => {
683
+ const lines = [
684
+ 'class Foo {',
685
+ '};',
686
+ 'struct Bar {',
687
+ '};',
688
+ 'union Baz { int a; };',
689
+ 'enum class Color { RED, GREEN };',
690
+ 'namespace loxia {',
691
+ '}',
692
+ ];
693
+ const found = sig(lines).join('\n');
694
+ expect(found).toMatch(/class Foo/);
695
+ expect(found).toMatch(/struct Bar/);
696
+ expect(found).toMatch(/union Baz/);
697
+ expect(found).toMatch(/enum class Color/);
698
+ expect(found).toMatch(/namespace loxia/);
699
+ });
700
+
701
+ test('typedef and using alias', () => {
702
+ const lines = [
703
+ 'typedef unsigned long u64;',
704
+ 'using IntList = std::vector<int>;',
705
+ ];
706
+ const found = sig(lines).join('\n');
707
+ expect(found).toMatch(/typedef unsigned long u64/);
708
+ expect(found).toMatch(/using IntList/);
709
+ });
710
+
711
+ test('free function definition with simple return type', () => {
712
+ const lines = [
713
+ 'int add(int a, int b) {',
714
+ ' return a + b;',
715
+ '}',
716
+ ];
717
+ expect(sig(lines).join('\n')).toMatch(/int add\(int a, int b\)/);
718
+ });
719
+
720
+ test('static + inline + const-qualified function definitions', () => {
721
+ const lines = [
722
+ 'static inline int square(int x) { return x * x; }',
723
+ 'const char* greet(const char* name) {',
724
+ ' return name;',
725
+ '}',
726
+ ];
727
+ const found = sig(lines).join('\n');
728
+ expect(found).toMatch(/static inline int square/);
729
+ expect(found).toMatch(/const char\* greet/);
730
+ });
731
+
732
+ test('method, constructor, and destructor', () => {
733
+ const lines = [
734
+ 'Foo::Foo(int x) : x_(x) {',
735
+ '}',
736
+ 'Foo::~Foo() {',
737
+ '}',
738
+ 'int Foo::compute(int n) const {',
739
+ ' return n * x_;',
740
+ '}',
741
+ ];
742
+ const found = sig(lines).join('\n');
743
+ expect(found).toMatch(/Foo::Foo\(int x\)/);
744
+ expect(found).toMatch(/Foo::~Foo\(\)/);
745
+ expect(found).toMatch(/int Foo::compute/);
746
+ });
747
+
748
+ test('REGRESSION: function CALLS are NOT misidentified as definitions', () => {
749
+ // Without the call-rejection heuristic, the regex would happily
750
+ // grab `printf("hi");` as a signature. Lock that it doesn't.
751
+ const lines = [
752
+ 'int main(void) {',
753
+ ' printf("hi");',
754
+ ' foo();',
755
+ ' return 0;',
756
+ '}',
757
+ ];
758
+ // Only main(void) should be a signature; printf/foo are calls.
759
+ const found = sig(lines);
760
+ expect(found).toEqual(['int main(void) {']);
761
+ });
762
+
763
+ test('REGRESSION: a tiny realistic C file produces a useful skeleton', () => {
764
+ const lines = [
765
+ '#include <stdio.h>',
766
+ '',
767
+ '#define BUF_SIZE 256',
768
+ '',
769
+ 'typedef struct {',
770
+ ' int x;',
771
+ ' int y;',
772
+ '} Point;',
773
+ '',
774
+ 'static int distance_sq(Point p) {',
775
+ ' return p.x * p.x + p.y * p.y;',
776
+ '}',
777
+ '',
778
+ 'int main(int argc, char** argv) {',
779
+ ' Point p = { 3, 4 };',
780
+ ' printf("d² = %d\\n", distance_sq(p));',
781
+ ' return 0;',
782
+ '}',
783
+ ];
784
+ const all = tool._parseC(lines, opts);
785
+ // Includes count as imports; the rest are signatures.
786
+ const imports = all.filter(e => e.kind === 'import').map(e => e.text.trim());
787
+ const signatures = all.filter(e => e.kind === 'signature').map(e => e.text.trim());
788
+ expect(imports).toEqual(['#include <stdio.h>']);
789
+ expect(signatures).toEqual(expect.arrayContaining([
790
+ '#define BUF_SIZE 256',
791
+ 'static int distance_sq(Point p) {',
792
+ 'int main(int argc, char** argv) {',
793
+ ]));
794
+ });
795
+ });
796
+
797
+ test('_langOf detects C/C++ files', () => {
798
+ expect(tool._langOf('foo.c')).toBe('c');
799
+ expect(tool._langOf('foo.h')).toBe('c');
800
+ expect(tool._langOf('foo.cpp')).toBe('c');
801
+ expect(tool._langOf('foo.cxx')).toBe('c');
802
+ expect(tool._langOf('foo.hpp')).toBe('c');
803
+ expect(tool._langOf('foo.cc')).toBe('c');
804
+ expect(tool._langOf('app.js')).toBe('js'); // sanity
805
+ expect(tool._langOf('script.py')).toBe('python'); // sanity
806
+ });
807
+
808
+ test('execute skeleton on unsupported file type throws', async () => {
809
+ mockFsPromises.stat.mockResolvedValue({
810
+ isFile: () => true,
811
+ isDirectory: () => false
812
+ });
813
+
814
+ await expect(tool.execute(
815
+ { action: 'skeleton', path: 'data.json' },
816
+ context
817
+ )).rejects.toThrow('Unsupported file type');
818
+ });
819
+
820
+ // ─────────────────────────────────────────────────────────────────
821
+ // Regex-free parser regressions — these cases are exactly the
822
+ // kind of pattern that brittle regex would silently mis-classify.
823
+ // They lock down the new string-utility implementation against
824
+ // the most common false-positive risks.
825
+ // ─────────────────────────────────────────────────────────────────
826
+ describe('REGRESSION: no-regex parsers — false-positive guards', () => {
827
+ const opts = { publicOnly: false, withComments: false, includeImports: true };
828
+
829
+ // JS
830
+ test('JS: `class` keyword does NOT match `classroom = …`', () => {
831
+ const entries = tool._parseJS(['classroom = 1;'], opts);
832
+ // No signature should be emitted — `classroom` is a plain var, not a class.
833
+ expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
834
+ });
835
+ test('JS: string literal containing `class` does NOT push a class frame', () => {
836
+ // Pre-fix the original regex `(stripped.match(/\{/g) || []).length` could
837
+ // be tripped by content inside strings if not pre-masked. The no-regex
838
+ // implementation masks strings before counting braces.
839
+ const entries = tool._parseJS([
840
+ 'const x = "class Foo {";',
841
+ ], opts);
842
+ // Only the `const` line should produce something; no class signature.
843
+ const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
844
+ expect(sigs.some(s => s.startsWith('class '))).toBe(false);
845
+ });
846
+ test('JS: `// foo` and `/* foo */` line/block comments still suppress signatures', () => {
847
+ const entries = tool._parseJS([
848
+ '// export function fake() {}',
849
+ '/* export function alsoFake() {} */',
850
+ 'export function real() {}',
851
+ ], opts);
852
+ const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text.trim());
853
+ expect(sigs).toEqual(['export function real() {}']);
854
+ });
855
+ test('JS: `require` as a substring inside another identifier is NOT an import', () => {
856
+ // E.g. `prerequires = []` or `xrequireY()` — neither should be classified
857
+ // as an import line by the require-call detector.
858
+ const entries = tool._parseJS([
859
+ 'const xrequireY = makeIt();',
860
+ ], opts);
861
+ expect(entries.filter(e => e.kind === 'import').length).toBe(0);
862
+ });
863
+ test('JS: `==` after a global LHS does NOT trigger an assignment match', () => {
864
+ const entries = tool._parseJS([
865
+ 'window.foo == bar;',
866
+ ], opts);
867
+ expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
868
+ });
869
+
870
+ // C
871
+ test('C: `class` inside a string literal does NOT push a class signature', () => {
872
+ const entries = tool._parseC([
873
+ 'const char* msg = "class is a keyword in C++";',
874
+ ], opts);
875
+ // The C parser is line-based and doesn't deeply track string state;
876
+ // this asserts the realistic case where the assignment isn't a class.
877
+ // The line shouldn't surface as a "class …" signature.
878
+ const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
879
+ expect(sigs.some(s => s.trim().startsWith('class '))).toBe(false);
880
+ });
881
+ test('C: bare function CALL `foo(x, y);` is NOT a definition', () => {
882
+ const entries = tool._parseC([' foo(1, 2);'], opts);
883
+ expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
884
+ });
885
+
886
+ // Python
887
+ test('Python: `class` keyword does NOT match `classroom = 1`', () => {
888
+ const entries = tool._parsePython(['classroom = 1'], opts);
889
+ // It's a top-level data assignment, NOT a class signature.
890
+ const sigs = entries.filter(e => e.kind === 'signature').map(e => e.text);
891
+ expect(sigs.length).toBe(0);
892
+ const datas = entries.filter(e => e.kind === 'data');
893
+ expect(datas.length).toBe(1);
894
+ });
895
+ test('Python: `def` keyword on a line where it is NOT the leading token (e.g. `nodef(x):`) is not a function', () => {
896
+ // The word-boundary check in startsWithKeyword catches this case.
897
+ const entries = tool._parsePython(['nodef(x):'], opts);
898
+ expect(entries.filter(e => e.kind === 'signature').length).toBe(0);
899
+ });
900
+ test('Python: relative import `from . import foo` is captured', () => {
901
+ const entries = tool._parsePython(['from . import foo'], opts);
902
+ expect(entries.filter(e => e.kind === 'import').length).toBe(1);
903
+ });
904
+ test('Python: docstring closes correctly on a one-liner', () => {
905
+ const entries = tool._parsePython([
906
+ 'def hello():',
907
+ ' """One-liner docstring."""',
908
+ ' return 1',
909
+ ], { ...opts, withComments: true });
910
+ const kinds = entries.map(e => e.kind);
911
+ expect(kinds).toContain('signature');
912
+ expect(kinds).toContain('comment');
913
+ });
914
+ });
915
+ });