onbuzz 4.8.3 → 4.9.1

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 (1100) hide show
  1. package/bin/cli.js +27 -1
  2. package/bin/composio-cli.js +431 -0
  3. package/package.json +1 -1
  4. package/scripts/build-onboarding-doc.cjs +805 -0
  5. package/scripts/composio-bench/README.md +151 -0
  6. package/scripts/memory-bench/bench.mjs +576 -0
  7. package/scripts/memory-bench/live-after-2026-05-21T16-20-52-767Z.json +150 -0
  8. package/scripts/memory-bench/live-after-2026-05-21T16-37-17-147Z.json +795 -0
  9. package/scripts/memory-bench/live-after-final-2026-05-21T17-16-02-947Z.json +766 -0
  10. package/scripts/memory-bench/live-after-v2-2026-05-21T16-52-26-833Z.json +890 -0
  11. package/scripts/memory-bench/live-after-v3-2026-05-21T17-05-40-622Z.json +955 -0
  12. package/scripts/memory-bench/live-probe-v2.mjs +412 -0
  13. package/scripts/memory-bench/live-probe.mjs +387 -0
  14. package/scripts/memory-bench/live-smoke-2026-05-21T16-22-35-945Z.json +36 -0
  15. package/scripts/memory-bench/live-smoke2-2026-05-21T16-24-14-095Z.json +36 -0
  16. package/scripts/memory-bench/live-smoke3-2026-05-21T16-26-54-093Z.json +123 -0
  17. package/scripts/memory-bench/live-v2-baseline-2026-05-23T22-43-20-757Z.json +628 -0
  18. package/scripts/memory-bench/live-v2-directive-2026-05-23T21-32-41-369Z.json +628 -0
  19. package/scripts/memory-bench/live-v2-smoke-2026-05-23T20-06-37-325Z.json +34 -0
  20. package/scripts/memory-bench/live-v2-smoke-2026-05-23T20-12-48-199Z.json +38 -0
  21. package/scripts/memory-bench/results-baseline-2026-05-19T11-23-12-245Z.json +39 -0
  22. package/scripts/memory-bench/results-baseline-2026-05-19T11-30-28-763Z.json +1024 -0
  23. package/scripts/memory-bench/results-baseline-2026-05-19T11-34-19-299Z.json +1007 -0
  24. package/scripts/memory-bench/results-baseline-2026-05-19T11-44-18-054Z.json +1432 -0
  25. package/scripts/memory-bench/results-nudge-combined-2026-05-19T12-00-37-465Z.json +1413 -0
  26. package/scripts/memory-bench/results-nudge-directive-2026-05-19T11-36-52-963Z.json +210 -0
  27. package/scripts/memory-bench/results-nudge-directive-2026-05-19T11-49-55-790Z.json +1464 -0
  28. package/scripts/memory-bench/results-nudge-echo-catalog-2026-05-19T11-39-01-829Z.json +232 -0
  29. package/scripts/memory-bench/results-nudge-echo-catalog-2026-05-19T11-55-12-705Z.json +1397 -0
  30. package/scripts/memory-bench/scenarios-v2.mjs +123 -0
  31. package/scripts/memory-bench/scenarios.mjs +178 -0
  32. package/scripts/office-bench/generate-samples.mjs +180 -0
  33. package/scripts/op-posture-bench/bench.mjs +397 -0
  34. package/scripts/op-posture-bench/probe.mjs +87 -0
  35. package/scripts/op-posture-bench/results-2026-05-18T20-29-13-941Z.json +57 -0
  36. package/scripts/op-posture-bench/results-2026-05-18T20-38-36-400Z.json +3071 -0
  37. package/scripts/op-posture-bench/scenarios.mjs +60 -0
  38. package/scripts/op-posture-bench/variants.mjs +68 -0
  39. package/scripts/stub-bench/bench.mjs +439 -0
  40. package/scripts/stub-bench/fixture/auth.js +41 -0
  41. package/scripts/stub-bench/results-2026-05-19T10-33-27-045Z.json +99 -0
  42. package/scripts/stub-bench/results-2026-05-19T10-35-08-240Z.json +920 -0
  43. package/scripts/stub-bench/results-2026-05-19T10-40-12-166Z.json +463 -0
  44. package/scripts/verify/azure-custom-embeddings.mjs +152 -0
  45. package/scripts/verify/azure-embeddings.mjs +146 -0
  46. package/scripts/verify/composio-toolkits.mjs +453 -0
  47. package/scripts/verify/local-embeddings.mjs +171 -0
  48. package/scripts/verify/openai-embeddings.mjs +140 -0
  49. package/src/__tests__/composioCliFlags.test.js +239 -0
  50. package/src/core/__tests__/agentScheduler.autoRecall.test.js +215 -0
  51. package/src/core/__tests__/agentScheduler.codebaseKnowledge.test.js +65 -0
  52. package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +169 -0
  53. package/src/core/__tests__/agentScheduler.memoryInjection.test.js +78 -0
  54. package/src/core/agentPool.js +10 -0
  55. package/src/core/agentScheduler.js +273 -36
  56. package/src/index.js +82 -4
  57. package/src/interfaces/__tests__/bulkAgentRoute.test.js +290 -0
  58. package/src/interfaces/__tests__/composioRoutes.test.js +318 -0
  59. package/src/interfaces/webServer.js +436 -2
  60. package/src/interfaces/webServer.js.bak +7047 -0
  61. package/src/services/__tests__/aiService.core.test.js +420 -0
  62. package/src/services/__tests__/aiService.embed.test.js +172 -0
  63. package/src/services/__tests__/apiKeyManager.getEmbeddingApiKey.test.js +86 -0
  64. package/src/services/__tests__/codeMapStubRenderer.test.js +207 -0
  65. package/src/services/__tests__/codebaseKnowledgeService.test.js +427 -0
  66. package/src/services/__tests__/composioService.test.js +714 -0
  67. package/src/services/__tests__/memoryService.appendAndCatalog.test.js +139 -0
  68. package/src/services/__tests__/telegramService.test.js +91 -0
  69. package/src/services/aiService.js +137 -0
  70. package/src/services/apiKeyManager.js +61 -0
  71. package/src/services/codeMapStubRenderer.js +142 -0
  72. package/src/services/codebaseKnowledgeService.js +430 -0
  73. package/src/services/composioService.js +729 -0
  74. package/src/services/embeddings/__tests__/autoRecall.test.js +237 -0
  75. package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -0
  76. package/src/services/embeddings/__tests__/cloudProviders.test.js +339 -0
  77. package/src/services/embeddings/__tests__/embeddingService.test.js +417 -0
  78. package/src/services/embeddings/__tests__/embeddingsConfig.test.js +182 -0
  79. package/src/services/embeddings/__tests__/inMemoryJsonStore.test.js +207 -0
  80. package/src/services/embeddings/__tests__/localProvider.test.js +263 -0
  81. package/src/services/embeddings/__tests__/providerInterface.test.js +209 -0
  82. package/src/services/embeddings/autoRecall.js +219 -0
  83. package/src/services/embeddings/embeddingService.js +452 -0
  84. package/src/services/embeddings/embeddingsConfig.js +203 -0
  85. package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -0
  86. package/src/services/embeddings/indexers/__tests__/composioIndexer.test.js +265 -0
  87. package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -0
  88. package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +357 -0
  89. package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -0
  90. package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -0
  91. package/src/services/embeddings/indexers/agentIndexer.js +249 -0
  92. package/src/services/embeddings/indexers/composioIndexer.js +279 -0
  93. package/src/services/embeddings/indexers/memoryIndexer.js +358 -0
  94. package/src/services/embeddings/indexers/reminisceIndexer.js +370 -0
  95. package/src/services/embeddings/indexers/skillsIndexer.js +154 -0
  96. package/src/services/embeddings/indexers/taskIndexer.js +155 -0
  97. package/src/services/embeddings/providerInterface.js +206 -0
  98. package/src/services/embeddings/providers/azureCustomProvider.js +154 -0
  99. package/src/services/embeddings/providers/azureProvider.js +122 -0
  100. package/src/services/embeddings/providers/cloudHttpTransport.js +205 -0
  101. package/src/services/embeddings/providers/localProvider.js +287 -0
  102. package/src/services/embeddings/providers/openaiProvider.js +101 -0
  103. package/src/services/embeddings/utilities/__tests__/textChunker.test.js +217 -0
  104. package/src/services/embeddings/utilities/textChunker.js +290 -0
  105. package/src/services/embeddings/vectorStore/__tests__/scoring.test.js +189 -0
  106. package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -0
  107. package/src/services/embeddings/vectorStore/scoring.js +128 -0
  108. package/src/services/embeddings/vectorStore/storeContractTests.js +179 -0
  109. package/src/services/embeddings/vectorStore/storeInterface.js +91 -0
  110. package/src/services/memoryService.js +98 -0
  111. package/src/services/telegramService.js +140 -1
  112. package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -0
  113. package/src/tools/__tests__/agentCommunicationTool.test.js +17 -2
  114. package/src/tools/__tests__/baseTool.test.js +76 -10
  115. package/src/tools/__tests__/cloneDetectionTool.test.js +430 -0
  116. package/src/tools/__tests__/codeMapTool.pluralCanonical.test.js +83 -0
  117. package/src/tools/__tests__/composioTool.findAction.test.js +448 -0
  118. package/src/tools/__tests__/composioTool.test.js +499 -0
  119. package/src/tools/__tests__/dependencyResolverTool.test.js +567 -0
  120. package/src/tools/__tests__/docxTool.test.js +449 -0
  121. package/src/tools/__tests__/excelTool.test.js +486 -0
  122. package/src/tools/__tests__/importAnalyzerTool.test.js +368 -0
  123. package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -0
  124. package/src/tools/__tests__/memoryTool.pluralCanonical.test.js +189 -0
  125. package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -0
  126. package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -0
  127. package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -0
  128. package/src/tools/__tests__/memoryTool.test.js +1 -1
  129. package/src/tools/__tests__/officeTool.test.js +403 -0
  130. package/src/tools/__tests__/openaiFunctionSchemas.memoryReminisce.test.js +24 -25
  131. package/src/tools/__tests__/openaiFunctionSchemas.validity.test.js +268 -0
  132. package/src/tools/__tests__/pdfTool.test.js +457 -0
  133. package/src/tools/__tests__/singularToolReverseForgive.test.js +97 -0
  134. package/src/tools/__tests__/skillsTool.search.test.js +164 -0
  135. package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -0
  136. package/src/tools/__tests__/taskManagerTool.search.test.js +143 -0
  137. package/src/tools/__tests__/taskManagerTool.transition.test.js +236 -0
  138. package/src/tools/__tests__/toolShapeForgiveness.test.js +260 -0
  139. package/src/tools/agentCommunicationTool.js +120 -5
  140. package/src/tools/baseTool.js +28 -1
  141. package/src/tools/codeMapTool.js +1673 -1521
  142. package/src/tools/composioTool.js +617 -0
  143. package/src/tools/fileContentReplaceTool.js +893 -840
  144. package/src/tools/fileSystemTool.js +1372 -1314
  145. package/src/tools/fileTreeTool.js +7 -0
  146. package/src/tools/memoryTool.js +847 -82
  147. package/src/tools/office/officeDoc.js +425 -0
  148. package/src/tools/office/officePres.js +360 -0
  149. package/src/tools/office/officeSheet.js +350 -0
  150. package/src/tools/officeTool.js +313 -0
  151. package/src/tools/openaiFunctionSchemas.js +824 -44
  152. package/src/tools/platformControlTool.js +5 -0
  153. package/src/tools/seekTool.js +36 -1
  154. package/src/tools/skillsTool.js +133 -0
  155. package/src/tools/taskManagerTool.js +264 -16
  156. package/src/tools/terminalTool.js +23 -1
  157. package/src/tools/visionTool.js +7 -0
  158. package/src/tools/visualEditorTool.js +7 -0
  159. package/src/tools/webTool.js +28 -1
  160. package/src/tools/whatsappTool.js +7 -0
  161. package/src/utilities/authCache.js +47 -6
  162. package/src/utilities/authCache.js.backup-1779570472481 +121 -0
  163. package/src/utilities/toolConstants.js +3 -1
  164. package/web-ui/build/index.html +2 -2
  165. package/web-ui/build/static/1c-CTztA3Xo.js +1 -0
  166. package/web-ui/build/static/abap-BsUhLmBp.js +1 -0
  167. package/web-ui/build/static/abnf-CGKV2y5s.js +1 -0
  168. package/web-ui/build/static/abnf-vQPGuzu_.js +1 -0
  169. package/web-ui/build/static/accesslog-mUZocN9d.js +1 -0
  170. package/web-ui/build/static/actionscript-BmCHyr7v.js +1 -0
  171. package/web-ui/build/static/actionscript-Cc5vZvFE.js +1 -0
  172. package/web-ui/build/static/ada-BHR9NMNv.js +1 -0
  173. package/web-ui/build/static/ada-DnxQs2b9.js +1 -0
  174. package/web-ui/build/static/agda-CHEWVCEZ.js +1 -0
  175. package/web-ui/build/static/al-C0otNZjc.js +1 -0
  176. package/web-ui/build/static/angelscript-Co5e7ZdR.js +1 -0
  177. package/web-ui/build/static/antlr4-Dkky4AXR.js +1 -0
  178. package/web-ui/build/static/apache-BJkYIuV-.js +1 -0
  179. package/web-ui/build/static/apacheconf-XLif_lM5.js +1 -0
  180. package/web-ui/build/static/apex-DiKnugSK.js +1 -0
  181. package/web-ui/build/static/apl-R5OItFx8.js +1 -0
  182. package/web-ui/build/static/applescript-D8uiCoXI.js +1 -0
  183. package/web-ui/build/static/applescript-e5KOVzz4.js +1 -0
  184. package/web-ui/build/static/aql-Cvy5hwR1.js +1 -0
  185. package/web-ui/build/static/arcade-0iAjFk8w.js +1 -0
  186. package/web-ui/build/static/arduino-BJeuGqAT.js +1 -0
  187. package/web-ui/build/static/arduino-QJJjnM1j.js +1 -0
  188. package/web-ui/build/static/arff-C-i_E_-B.js +1 -0
  189. package/web-ui/build/static/armasm-ukFQKHGn.js +1 -0
  190. package/web-ui/build/static/asciidoc-B49qqHJJ.js +1 -0
  191. package/web-ui/build/static/asciidoc-Nkm1Ylrd.js +1 -0
  192. package/web-ui/build/static/asm6502-CMiM3nZs.js +1 -0
  193. package/web-ui/build/static/asmatmel-DG9XzIBf.js +1 -0
  194. package/web-ui/build/static/aspectj-DjRutrRk.js +1 -0
  195. package/web-ui/build/static/aspnet-DCNVRVi9.js +1 -0
  196. package/web-ui/build/static/autohotkey-C7eyvps0.js +1 -0
  197. package/web-ui/build/static/autohotkey-CiaZi-WO.js +1 -0
  198. package/web-ui/build/static/autoit-CaKUQkux.js +1 -0
  199. package/web-ui/build/static/autoit-D-CUHQgt.js +1 -0
  200. package/web-ui/build/static/avisynth-DFP3h4zu.js +1 -0
  201. package/web-ui/build/static/avrasm-BqvUj3fg.js +1 -0
  202. package/web-ui/build/static/avro-idl-D3l0ptuQ.js +1 -0
  203. package/web-ui/build/static/awk-BmDxAZFj.js +1 -0
  204. package/web-ui/build/static/axapta-B424jrgT.js +1 -0
  205. package/web-ui/build/static/bash-CgAffr8Z.js +1 -0
  206. package/web-ui/build/static/bash-D41aILLG.js +1 -0
  207. package/web-ui/build/static/basic-5DFTo35K.js +1 -0
  208. package/web-ui/build/static/basic-BONYG7D2.js +1 -0
  209. package/web-ui/build/static/batch-BnaDGMwS.js +1 -0
  210. package/web-ui/build/static/bbcode-Daujj4hU.js +1 -0
  211. package/web-ui/build/static/bicep-CXidcIK5.js +1 -0
  212. package/web-ui/build/static/birb-CcOzsnqB.js +1 -0
  213. package/web-ui/build/static/bison-rq0w5JFF.js +1 -0
  214. package/web-ui/build/static/bnf-C4uK1dK8.js +1 -0
  215. package/web-ui/build/static/bnf-CvXZ9crt.js +1 -0
  216. package/web-ui/build/static/brainfuck-B5Ezdg2y.js +1 -0
  217. package/web-ui/build/static/brainfuck-BZaTfZ97.js +1 -0
  218. package/web-ui/build/static/brightscript-D_2WBVxt.js +1 -0
  219. package/web-ui/build/static/bro-BD7GT6j_.js +1 -0
  220. package/web-ui/build/static/bsl-Bh-qcQ3w.js +1 -0
  221. package/web-ui/build/static/c-DNmZz1iT.js +1 -0
  222. package/web-ui/build/static/c-dUrSOcB2.js +1 -0
  223. package/web-ui/build/static/c-like-DfzKY8Uu.js +1 -0
  224. package/web-ui/build/static/cal-DgrFs4te.js +1 -0
  225. package/web-ui/build/static/capnproto-BxFR2k8w.js +1 -0
  226. package/web-ui/build/static/ceylon-B2Q7BIPM.js +1 -0
  227. package/web-ui/build/static/cfscript-DeaR8rww.js +1 -0
  228. package/web-ui/build/static/chaiscript-BeBIoz6b.js +1 -0
  229. package/web-ui/build/static/cil-YHiHOb4c.js +1 -0
  230. package/web-ui/build/static/clean-BgOl_0cN.js +1 -0
  231. package/web-ui/build/static/clojure-BlgOza7C.js +1 -0
  232. package/web-ui/build/static/clojure-PlKRYx3q.js +1 -0
  233. package/web-ui/build/static/clojure-repl-Dv2_THNB.js +1 -0
  234. package/web-ui/build/static/cmake-Ccf43gsy.js +1 -0
  235. package/web-ui/build/static/cmake-Dah_b5tK.js +1 -0
  236. package/web-ui/build/static/cobol-o9YRDzXA.js +1 -0
  237. package/web-ui/build/static/coffeescript-DTYLvxG9.js +1 -0
  238. package/web-ui/build/static/coffeescript-Dm4UcexP.js +1 -0
  239. package/web-ui/build/static/concurnas-1OxY7nFV.js +1 -0
  240. package/web-ui/build/static/coq-CvJBSoWT.js +1 -0
  241. package/web-ui/build/static/coq-DPZunybQ.js +1 -0
  242. package/web-ui/build/static/cos-BBstQgB4.js +1 -0
  243. package/web-ui/build/static/cpp-BR1pa5Wr.js +1 -0
  244. package/web-ui/build/static/cpp-C-_tTuKh.js +1 -0
  245. package/web-ui/build/static/crmsh-BIme7ihG.js +1 -0
  246. package/web-ui/build/static/crystal-C2hlJxaB.js +1 -0
  247. package/web-ui/build/static/crystal-Ca6D0fV7.js +1 -0
  248. package/web-ui/build/static/csharp-DGvdwvDH.js +1 -0
  249. package/web-ui/build/static/csharp-DbAjVltn.js +1 -0
  250. package/web-ui/build/static/cshtml-DVC84JPa.js +1 -0
  251. package/web-ui/build/static/csp-BawtG83z.js +1 -0
  252. package/web-ui/build/static/csp-BlaoxdTK.js +1 -0
  253. package/web-ui/build/static/css-52oqss7r.js +1 -0
  254. package/web-ui/build/static/css-extras-BoTVw2IW.js +1 -0
  255. package/web-ui/build/static/csv-nXBSv5kK.js +1 -0
  256. package/web-ui/build/static/cypher-WuesoPm2.js +1 -0
  257. package/web-ui/build/static/d-BmmTjwiV.js +1 -0
  258. package/web-ui/build/static/d-BwHf5A8R.js +1 -0
  259. package/web-ui/build/static/dart-BVZTRb8b.js +1 -0
  260. package/web-ui/build/static/dart-CbWzIBpX.js +1 -0
  261. package/web-ui/build/static/dataweave-qi_fpjJR.js +1 -0
  262. package/web-ui/build/static/dax-BpP02TD3.js +1 -0
  263. package/web-ui/build/static/delphi-TK0E0gLN.js +1 -0
  264. package/web-ui/build/static/dhall-jyNJwUbo.js +1 -0
  265. package/web-ui/build/static/diff-Dbu90HZq.js +1 -0
  266. package/web-ui/build/static/diff-Df6ezhAq.js +1 -0
  267. package/web-ui/build/static/django-BDy-xiWR.js +1 -0
  268. package/web-ui/build/static/django-CbmnWIX9.js +1 -0
  269. package/web-ui/build/static/dns-D_wmKRHl.js +1 -0
  270. package/web-ui/build/static/dns-zone-file-_1osc1Qk.js +1 -0
  271. package/web-ui/build/static/docker-D62hlkjX.js +1 -0
  272. package/web-ui/build/static/dockerfile-DQ080zHh.js +1 -0
  273. package/web-ui/build/static/dos-B5mA8Wna.js +1 -0
  274. package/web-ui/build/static/dot-D497WUfu.js +1 -0
  275. package/web-ui/build/static/dsconfig-dERMCQVk.js +1 -0
  276. package/web-ui/build/static/dts-DC3ujcbX.js +1 -0
  277. package/web-ui/build/static/dust-Bhm1VBQg.js +1 -0
  278. package/web-ui/build/static/ebnf-1lmwK0bv.js +1 -0
  279. package/web-ui/build/static/ebnf-qzzqKVXL.js +1 -0
  280. package/web-ui/build/static/editorconfig-CCe25wQr.js +1 -0
  281. package/web-ui/build/static/eiffel-BFOXbwEN.js +1 -0
  282. package/web-ui/build/static/ejs-D94vVkGZ.js +1 -0
  283. package/web-ui/build/static/elixir-B2W81YV0.js +1 -0
  284. package/web-ui/build/static/elixir-CD1szlP_.js +1 -0
  285. package/web-ui/build/static/elm-BUJhR-kM.js +1 -0
  286. package/web-ui/build/static/elm-DwLnLHSK.js +1 -0
  287. package/web-ui/build/static/erb-CcokD9Fu.js +1 -0
  288. package/web-ui/build/static/erb-lzhmSBEV.js +1 -0
  289. package/web-ui/build/static/erlang-C0jnu48-.js +1 -0
  290. package/web-ui/build/static/erlang-CrevXBnJ.js +1 -0
  291. package/web-ui/build/static/erlang-repl-CNY6yNoO.js +1 -0
  292. package/web-ui/build/static/etlua-BMO_ICdR.js +1 -0
  293. package/web-ui/build/static/excel-C5PbLrVC.js +1 -0
  294. package/web-ui/build/static/excel-formula-CX4pOnnV.js +1 -0
  295. package/web-ui/build/static/factor-bW3WUpa0.js +1 -0
  296. package/web-ui/build/static/false-CQx6Crbr.js +1 -0
  297. package/web-ui/build/static/firestore-security-rules-DT5oRNdf.js +1 -0
  298. package/web-ui/build/static/fix-C4IWe2LI.js +1 -0
  299. package/web-ui/build/static/flix-hi9-cLwA.js +1 -0
  300. package/web-ui/build/static/flow-CkX6DAhF.js +1 -0
  301. package/web-ui/build/static/fortran-C4rIX-2n.js +1 -0
  302. package/web-ui/build/static/fortran-Dvu3q_x_.js +1 -0
  303. package/web-ui/build/static/fsharp-Bwa1UrRI.js +1 -0
  304. package/web-ui/build/static/fsharp-CvadhLcA.js +1 -0
  305. package/web-ui/build/static/ftl-eFEI11YV.js +1 -0
  306. package/web-ui/build/static/gams-BxGx7deQ.js +1 -0
  307. package/web-ui/build/static/gap-Dcv7bqDd.js +1 -0
  308. package/web-ui/build/static/gauss-CcVoF4l8.js +1 -0
  309. package/web-ui/build/static/gcode-B1dc5sh5.js +1 -0
  310. package/web-ui/build/static/gcode-D5OxRG5t.js +1 -0
  311. package/web-ui/build/static/gdscript-CL-EA1wk.js +1 -0
  312. package/web-ui/build/static/gedcom-BbXrNsr0.js +1 -0
  313. package/web-ui/build/static/gherkin-B2wQMbT2.js +1 -0
  314. package/web-ui/build/static/gherkin-CnL4Y3j1.js +1 -0
  315. package/web-ui/build/static/git-DpdutSM5.js +1 -0
  316. package/web-ui/build/static/glsl-C7sgdZCA.js +1 -0
  317. package/web-ui/build/static/glsl-DVaNsn1Z.js +1 -0
  318. package/web-ui/build/static/gml-B_NppoA4.js +1 -0
  319. package/web-ui/build/static/gml-DFrn75UD.js +1 -0
  320. package/web-ui/build/static/gn-BhTU_aTW.js +1 -0
  321. package/web-ui/build/static/go-DKrUPrfb.js +1 -0
  322. package/web-ui/build/static/go-DLAYIsJl.js +1 -0
  323. package/web-ui/build/static/go-module-BXY6q1Ug.js +1 -0
  324. package/web-ui/build/static/golo-CKxjLRpC.js +1 -0
  325. package/web-ui/build/static/gradle-DN8lsmJj.js +1 -0
  326. package/web-ui/build/static/graphql-CMb_4vdm.js +1 -0
  327. package/web-ui/build/static/groovy-Bhi1VPv7.js +1 -0
  328. package/web-ui/build/static/groovy-D3X3r3EX.js +1 -0
  329. package/web-ui/build/static/haml-CvOCw9lL.js +1 -0
  330. package/web-ui/build/static/haml-DYs0wBJO.js +1 -0
  331. package/web-ui/build/static/handlebars-CLnuKGPm.js +1 -0
  332. package/web-ui/build/static/handlebars-CYGG_M1h.js +1 -0
  333. package/web-ui/build/static/haskell-0Ju0XclD.js +1 -0
  334. package/web-ui/build/static/haskell-CL5jUHSd.js +1 -0
  335. package/web-ui/build/static/haxe-BghRbBtK.js +1 -0
  336. package/web-ui/build/static/haxe-dfL7GzPv.js +1 -0
  337. package/web-ui/build/static/hcl-BZP2QME-.js +1 -0
  338. package/web-ui/build/static/hlsl-Djp0TiCX.js +1 -0
  339. package/web-ui/build/static/hoon-Df5gJVqA.js +1 -0
  340. package/web-ui/build/static/hpkp-Cy-hjB4V.js +1 -0
  341. package/web-ui/build/static/hsp-CntMIMs4.js +1 -0
  342. package/web-ui/build/static/hsts-BNBhETNx.js +1 -0
  343. package/web-ui/build/static/htmlbars-VCCYaDHA.js +1 -0
  344. package/web-ui/build/static/http-7DbskohZ.js +1 -0
  345. package/web-ui/build/static/http-BT5FYFAK.js +1 -0
  346. package/web-ui/build/static/hy-DuGSaK_b.js +1 -0
  347. package/web-ui/build/static/ichigojam-CjvnJmlr.js +1 -0
  348. package/web-ui/build/static/icon-BsBz6XP6.js +1 -0
  349. package/web-ui/build/static/icu-message-format-BEpv_nip.js +1 -0
  350. package/web-ui/build/static/idris-DiXQdMPU.js +1 -0
  351. package/web-ui/build/static/iecst-C9tYEzwP.js +1 -0
  352. package/web-ui/build/static/ignore-DNMpCRPN.js +1 -0
  353. package/web-ui/build/static/index-B-r71gxO.js +1 -0
  354. package/web-ui/build/static/index-CTtNXVAZ.js +1217 -0
  355. package/web-ui/build/static/index-DkBBEGYj.css +1 -0
  356. package/web-ui/build/static/index-PvzceRzr.js +13 -0
  357. package/web-ui/build/static/inform7-D38mj1Ty.js +1 -0
  358. package/web-ui/build/static/inform7-SwIRbrNQ.js +1 -0
  359. package/web-ui/build/static/ini-B9xO6J_m.js +1 -0
  360. package/web-ui/build/static/ini-zcp_esam.js +1 -0
  361. package/web-ui/build/static/io-C-Ay4jvg.js +1 -0
  362. package/web-ui/build/static/irpf90-BYnkitLq.js +1 -0
  363. package/web-ui/build/static/isbl-BYtdwd6v.js +1 -0
  364. package/web-ui/build/static/j-ByZQN5E4.js +1 -0
  365. package/web-ui/build/static/java-BNtb6JWL.js +1 -0
  366. package/web-ui/build/static/java-CQZL3scW.js +1 -0
  367. package/web-ui/build/static/javadoc-BciMZ8jg.js +1 -0
  368. package/web-ui/build/static/javadoclike-07BOTXmy.js +1 -0
  369. package/web-ui/build/static/javascript-CViZlg1q.js +1 -0
  370. package/web-ui/build/static/javastacktrace-bWyAK0up.js +1 -0
  371. package/web-ui/build/static/jboss-cli-hz5boSGB.js +1 -0
  372. package/web-ui/build/static/jexl-DdIINSXT.js +1 -0
  373. package/web-ui/build/static/jolie-CQnDX-f0.js +1 -0
  374. package/web-ui/build/static/jq-BzhF0CHG.js +1 -0
  375. package/web-ui/build/static/js-extras-jizEtT14.js +1 -0
  376. package/web-ui/build/static/js-templates-Dr1BqLAt.js +1 -0
  377. package/web-ui/build/static/jsdoc-CI-GPi6r.js +1 -0
  378. package/web-ui/build/static/json-2eDPxnHO.js +1 -0
  379. package/web-ui/build/static/json-BBqUMOAQ.js +1 -0
  380. package/web-ui/build/static/json5-CeIoln-Y.js +1 -0
  381. package/web-ui/build/static/jsonp-DWe9tbr2.js +1 -0
  382. package/web-ui/build/static/jsstacktrace-aE-go9tq.js +1 -0
  383. package/web-ui/build/static/jsx-BTXEPQYI.js +1 -0
  384. package/web-ui/build/static/julia-4VeOirXa.js +1 -0
  385. package/web-ui/build/static/julia-Da6yHEfz.js +1 -0
  386. package/web-ui/build/static/julia-repl-CFrizGkE.js +1 -0
  387. package/web-ui/build/static/keepalived-39SDM4j5.js +1 -0
  388. package/web-ui/build/static/keyman-DU5yOLYr.js +1 -0
  389. package/web-ui/build/static/kotlin-DJoqh3HG.js +1 -0
  390. package/web-ui/build/static/kotlin-DsKd3r_L.js +1 -0
  391. package/web-ui/build/static/kumir-CO96hby4.js +1 -0
  392. package/web-ui/build/static/kusto-DyuMf8l-.js +1 -0
  393. package/web-ui/build/static/lasso-BiPmQTXQ.js +1 -0
  394. package/web-ui/build/static/latex-Bd1ef5fi.js +1 -0
  395. package/web-ui/build/static/latex-DUrYkmWb.js +1 -0
  396. package/web-ui/build/static/latte-BSD-hpi5.js +1 -0
  397. package/web-ui/build/static/ldif-1ZMSg0qI.js +1 -0
  398. package/web-ui/build/static/leaf-Cg0eq01F.js +1 -0
  399. package/web-ui/build/static/less-BKcd1_xh.js +1 -0
  400. package/web-ui/build/static/less-SB5bcusC.js +1 -0
  401. package/web-ui/build/static/lilypond-BX_kuAqT.js +1 -0
  402. package/web-ui/build/static/liquid-C337T5bt.js +1 -0
  403. package/web-ui/build/static/lisp-B_bJRelF.js +1 -0
  404. package/web-ui/build/static/lisp-Du04Vdmx.js +1 -0
  405. package/web-ui/build/static/livecodeserver-BOkA9fV6.js +1 -0
  406. package/web-ui/build/static/livescript-DkMTCCBM.js +1 -0
  407. package/web-ui/build/static/livescript-rTaLq5oP.js +1 -0
  408. package/web-ui/build/static/llvm-BrBlvl5p.js +1 -0
  409. package/web-ui/build/static/llvm-DQbygw7z.js +1 -0
  410. package/web-ui/build/static/log-DoAzOt8c.js +1 -0
  411. package/web-ui/build/static/lolcode-DnB7KhRR.js +1 -0
  412. package/web-ui/build/static/lsl-DpzE7F5A.js +1 -0
  413. package/web-ui/build/static/lua-CIceROsO.js +1 -0
  414. package/web-ui/build/static/lua-DhnvJuMn.js +1 -0
  415. package/web-ui/build/static/magma-BwjzpKxi.js +1 -0
  416. package/web-ui/build/static/makefile-D10Y62dl.js +1 -0
  417. package/web-ui/build/static/makefile-esgkquGi.js +1 -0
  418. package/web-ui/build/static/markdown-B-Y7Y0Nn.js +1 -0
  419. package/web-ui/build/static/markdown-Cv3Onm-Q.js +1 -0
  420. package/web-ui/build/static/markup-templating-B1-9WWYa.js +1 -0
  421. package/web-ui/build/static/mathematica-gSyKdw2h.js +1 -0
  422. package/web-ui/build/static/matlab-6i1uXuBx.js +1 -0
  423. package/web-ui/build/static/matlab-CqcSZ1d9.js +1 -0
  424. package/web-ui/build/static/maxima-Bc-OnIOQ.js +1 -0
  425. package/web-ui/build/static/maxscript-Bce44o6c.js +1 -0
  426. package/web-ui/build/static/mel-0Jk3y-vw.js +1 -0
  427. package/web-ui/build/static/mel-DmA_8WPg.js +1 -0
  428. package/web-ui/build/static/mercury-DNdP1u-O.js +1 -0
  429. package/web-ui/build/static/mermaid-DrLkSIIb.js +1 -0
  430. package/web-ui/build/static/mipsasm-BIQVs3Ff.js +1 -0
  431. package/web-ui/build/static/mizar-BD5ZD_Rk.js +1 -0
  432. package/web-ui/build/static/mizar-DThX8Mdy.js +1 -0
  433. package/web-ui/build/static/mojolicious-Bra-ClwJ.js +1 -0
  434. package/web-ui/build/static/mongodb-DirByEEg.js +1 -0
  435. package/web-ui/build/static/monkey-C-Pvu43G.js +1 -0
  436. package/web-ui/build/static/monkey-n4IFmaA7.js +1 -0
  437. package/web-ui/build/static/moonscript-B1DM34lp.js +1 -0
  438. package/web-ui/build/static/moonscript-B82JBytX.js +1 -0
  439. package/web-ui/build/static/n1ql-C46iq3Hh.js +1 -0
  440. package/web-ui/build/static/n1ql-DOs3gJQo.js +1 -0
  441. package/web-ui/build/static/n4js-CPYgal3G.js +1 -0
  442. package/web-ui/build/static/nand2tetris-hdl-BhlUIq7M.js +1 -0
  443. package/web-ui/build/static/naniscript-D1YZXYcf.js +1 -0
  444. package/web-ui/build/static/nasm-2-TtCBo4.js +1 -0
  445. package/web-ui/build/static/neon-BzoXMDea.js +1 -0
  446. package/web-ui/build/static/nevod-DW_87wHj.js +1 -0
  447. package/web-ui/build/static/nginx-BgieYUhk.js +1 -0
  448. package/web-ui/build/static/nginx-DW0RVJbs.js +1 -0
  449. package/web-ui/build/static/nim-BbTa7YZN.js +1 -0
  450. package/web-ui/build/static/nim-VdXk3QT9.js +1 -0
  451. package/web-ui/build/static/nix-3tNNprEI.js +1 -0
  452. package/web-ui/build/static/nix-BDghlfzh.js +1 -0
  453. package/web-ui/build/static/node-repl-BBLv3tyt.js +1 -0
  454. package/web-ui/build/static/nsis-DxjfnSO8.js +1 -0
  455. package/web-ui/build/static/nsis-FY5xiiar.js +1 -0
  456. package/web-ui/build/static/objectivec-CXhf_sp_.js +1 -0
  457. package/web-ui/build/static/objectivec-DcRKjWcd.js +1 -0
  458. package/web-ui/build/static/ocaml-BLRL6Zro.js +1 -0
  459. package/web-ui/build/static/ocaml-C4S4QQtS.js +1 -0
  460. package/web-ui/build/static/opencl-ruEAYetM.js +1 -0
  461. package/web-ui/build/static/openqasm-D6r_FwIh.js +1 -0
  462. package/web-ui/build/static/openscad-BEPjoj_N.js +1 -0
  463. package/web-ui/build/static/oxygene-A61yc5Or.js +1 -0
  464. package/web-ui/build/static/oz-DICidCTs.js +1 -0
  465. package/web-ui/build/static/parigp-MjTNwy2s.js +1 -0
  466. package/web-ui/build/static/parser-DmI7lg_r.js +1 -0
  467. package/web-ui/build/static/parser3-Bv9kXAib.js +1 -0
  468. package/web-ui/build/static/pascal-B23PBLTj.js +1 -0
  469. package/web-ui/build/static/pascaligo-CPIJtX2C.js +1 -0
  470. package/web-ui/build/static/pcaxis-Bsj_6x8j.js +1 -0
  471. package/web-ui/build/static/peoplecode-VvQbMjsD.js +1 -0
  472. package/web-ui/build/static/perl-Cr6Y9-hl.js +1 -0
  473. package/web-ui/build/static/perl-D37oTJip.js +1 -0
  474. package/web-ui/build/static/pf-BKHaWcGm.js +1 -0
  475. package/web-ui/build/static/pgsql-ChusgWw2.js +1 -0
  476. package/web-ui/build/static/php-3Qr4hNV1.js +1 -0
  477. package/web-ui/build/static/php-CgxeEh4Y.js +1 -0
  478. package/web-ui/build/static/php-extras-DeLqwT6H.js +1 -0
  479. package/web-ui/build/static/php-template-DcOJxuIf.js +1 -0
  480. package/web-ui/build/static/phpdoc-amFph5Ax.js +1 -0
  481. package/web-ui/build/static/plaintext-mqBcO1hG.js +1 -0
  482. package/web-ui/build/static/plsql-ioyqfxP7.js +1 -0
  483. package/web-ui/build/static/pony-nxjy8Qot.js +1 -0
  484. package/web-ui/build/static/powerquery-Ci_Fxyg8.js +1 -0
  485. package/web-ui/build/static/powershell-CB_GgRqr.js +1 -0
  486. package/web-ui/build/static/powershell-au4fYIm7.js +1 -0
  487. package/web-ui/build/static/processing-Bkd3f-Wz.js +1 -0
  488. package/web-ui/build/static/processing-PgCPZm30.js +1 -0
  489. package/web-ui/build/static/profile-C2GBY6C9.js +1 -0
  490. package/web-ui/build/static/prolog-BRjYwjIO.js +1 -0
  491. package/web-ui/build/static/prolog-BheGlH8Q.js +1 -0
  492. package/web-ui/build/static/promql-CtCmTKys.js +1 -0
  493. package/web-ui/build/static/properties-BpLqVZha.js +1 -0
  494. package/web-ui/build/static/properties-UQxavHUY.js +1 -0
  495. package/web-ui/build/static/protobuf-BSuul6Uw.js +1 -0
  496. package/web-ui/build/static/protobuf-CLRi8VTE.js +1 -0
  497. package/web-ui/build/static/psl-CJsJilqO.js +1 -0
  498. package/web-ui/build/static/pug-5BY6sdv1.js +1 -0
  499. package/web-ui/build/static/puppet-DBg45gHV.js +1 -0
  500. package/web-ui/build/static/puppet-DPANCbOj.js +1 -0
  501. package/web-ui/build/static/pure-Cn4p-7Bj.js +1 -0
  502. package/web-ui/build/static/purebasic-CoXbIzZJ.js +1 -0
  503. package/web-ui/build/static/purebasic-DZ_eLoUV.js +1 -0
  504. package/web-ui/build/static/purescript-B98GoUb_.js +1 -0
  505. package/web-ui/build/static/python-B-Lm1OME.js +1 -0
  506. package/web-ui/build/static/python-C_ApFGS1.js +1 -0
  507. package/web-ui/build/static/python-repl-CJW5_RON.js +1 -0
  508. package/web-ui/build/static/q-BB2BYhxG.js +1 -0
  509. package/web-ui/build/static/q-DVqo_ScG.js +1 -0
  510. package/web-ui/build/static/qml-Co-8g096.js +1 -0
  511. package/web-ui/build/static/qml-Du0WFcoy.js +1 -0
  512. package/web-ui/build/static/qore-C6XBzy3U.js +1 -0
  513. package/web-ui/build/static/qsharp-BCMklAuq.js +1 -0
  514. package/web-ui/build/static/r-B5CGUQwN.js +1 -0
  515. package/web-ui/build/static/r-Dxhot-za.js +1 -0
  516. package/web-ui/build/static/racket-C0dCdrKh.js +1 -0
  517. package/web-ui/build/static/reason-DGJ1fFJs.js +1 -0
  518. package/web-ui/build/static/reasonml-gt_AvBg7.js +1 -0
  519. package/web-ui/build/static/regex-B5iNH8MZ.js +1 -0
  520. package/web-ui/build/static/rego-hfPajYet.js +1 -0
  521. package/web-ui/build/static/renpy-BkG5aM4I.js +1 -0
  522. package/web-ui/build/static/rest-C4uwtrF8.js +1 -0
  523. package/web-ui/build/static/rib-D32ew8Y7.js +1 -0
  524. package/web-ui/build/static/rip-BIEbWzxe.js +1 -0
  525. package/web-ui/build/static/roboconf-CMN8BoHf.js +1 -0
  526. package/web-ui/build/static/roboconf-GVMh3ioj.js +1 -0
  527. package/web-ui/build/static/robotframework-CUQybJQ9.js +1 -0
  528. package/web-ui/build/static/routeros-DUEdLmuc.js +1 -0
  529. package/web-ui/build/static/rsl-BsAePIwQ.js +1 -0
  530. package/web-ui/build/static/ruby-CZMMjJFw.js +1 -0
  531. package/web-ui/build/static/ruby-WOVZyoXO.js +1 -0
  532. package/web-ui/build/static/ruleslanguage-xV-HszUl.js +1 -0
  533. package/web-ui/build/static/rust-Da9GRxLz.js +1 -0
  534. package/web-ui/build/static/rust-uCND428m.js +1 -0
  535. package/web-ui/build/static/sas-BW15NPzf.js +1 -0
  536. package/web-ui/build/static/sas-DCx9gext.js +1 -0
  537. package/web-ui/build/static/sass-BMD7eETo.js +1 -0
  538. package/web-ui/build/static/scala-0wnxLTlF.js +1 -0
  539. package/web-ui/build/static/scala-D3Hk-q5-.js +1 -0
  540. package/web-ui/build/static/scheme-Ci9QOUcD.js +1 -0
  541. package/web-ui/build/static/scheme-CtqjB_HN.js +1 -0
  542. package/web-ui/build/static/scilab-CnITQd23.js +1 -0
  543. package/web-ui/build/static/scss-DCceV6xw.js +1 -0
  544. package/web-ui/build/static/scss-N5EuPQOS.js +1 -0
  545. package/web-ui/build/static/shell-BegJTton.js +1 -0
  546. package/web-ui/build/static/shell-session-BHBoosfJ.js +1 -0
  547. package/web-ui/build/static/smali-CoHePNQf.js +1 -0
  548. package/web-ui/build/static/smali-D64W1f0Y.js +1 -0
  549. package/web-ui/build/static/smalltalk-CagI-Ky8.js +1 -0
  550. package/web-ui/build/static/smalltalk-DHLuWDjp.js +1 -0
  551. package/web-ui/build/static/smarty-CSS2XBuu.js +1 -0
  552. package/web-ui/build/static/sml-D8Oyp9xr.js +1 -0
  553. package/web-ui/build/static/sml-DvC_VDmX.js +1 -0
  554. package/web-ui/build/static/solidity-DVkeulrL.js +1 -0
  555. package/web-ui/build/static/solution-file-Bd7qWzoo.js +1 -0
  556. package/web-ui/build/static/soy-BRSlVWf7.js +1 -0
  557. package/web-ui/build/static/sparql-DNnCQmqM.js +1 -0
  558. package/web-ui/build/static/splunk-spl-DXCDUv2Y.js +1 -0
  559. package/web-ui/build/static/sqf-C_IKzP5b.js +1 -0
  560. package/web-ui/build/static/sqf-DBqlUGoG.js +1 -0
  561. package/web-ui/build/static/sql-Dt3ZutCc.js +1 -0
  562. package/web-ui/build/static/sql-q0UMLqb7.js +1 -0
  563. package/web-ui/build/static/sql_more-DAuY5p3F.js +1 -0
  564. package/web-ui/build/static/squirrel-CgQZeaNT.js +1 -0
  565. package/web-ui/build/static/stan-D5kYsid7.js +1 -0
  566. package/web-ui/build/static/stan-JSHToKMA.js +1 -0
  567. package/web-ui/build/static/stata-DRVhVjVb.js +1 -0
  568. package/web-ui/build/static/step21-C7h5ssgs.js +1 -0
  569. package/web-ui/build/static/stylus-BXIDNFUe.js +1 -0
  570. package/web-ui/build/static/stylus-CyMBrStz.js +1 -0
  571. package/web-ui/build/static/subunit-Lbpuvy4t.js +1 -0
  572. package/web-ui/build/static/swift-BCxWOc8n.js +1 -0
  573. package/web-ui/build/static/swift-B_YEL6eF.js +1 -0
  574. package/web-ui/build/static/systemd-_zWfs8U5.js +1 -0
  575. package/web-ui/build/static/t4-cs-BCiVX6dp.js +1 -0
  576. package/web-ui/build/static/t4-templating-CDm61rWh.js +1 -0
  577. package/web-ui/build/static/t4-vb-OO2oZZrL.js +1 -0
  578. package/web-ui/build/static/taggerscript-CkXxiITO.js +1 -0
  579. package/web-ui/build/static/tap-C612JPhJ.js +1 -0
  580. package/web-ui/build/static/tap-J-zR66jL.js +1 -0
  581. package/web-ui/build/static/tcl-DIGan5K8.js +1 -0
  582. package/web-ui/build/static/tcl-SFVWFwbb.js +1 -0
  583. package/web-ui/build/static/textile-D0WxA5g8.js +1 -0
  584. package/web-ui/build/static/thrift-jboo4tJl.js +1 -0
  585. package/web-ui/build/static/toml-DIK32ROb.js +1 -0
  586. package/web-ui/build/static/tp-Bhs1iGD3.js +1 -0
  587. package/web-ui/build/static/tremor-4d_juLiN.js +1 -0
  588. package/web-ui/build/static/tsx-hEN3qQzd.js +1 -0
  589. package/web-ui/build/static/tt2-WU6EuPNO.js +1 -0
  590. package/web-ui/build/static/turtle-CUM3xwNI.js +1 -0
  591. package/web-ui/build/static/twig-B9JwTKv9.js +1 -0
  592. package/web-ui/build/static/twig-Crv6BtK7.js +1 -0
  593. package/web-ui/build/static/typescript-BmabW_ur.js +1 -0
  594. package/web-ui/build/static/typescript-oMKx2dph.js +1 -0
  595. package/web-ui/build/static/typoscript-X48MNTeK.js +1 -0
  596. package/web-ui/build/static/unrealscript-qnu9Ogmz.js +1 -0
  597. package/web-ui/build/static/uorazor-BicTvjGt.js +1 -0
  598. package/web-ui/build/static/uri-CyRoMB-u.js +1 -0
  599. package/web-ui/build/static/v-BDM6ZJR7.js +1 -0
  600. package/web-ui/build/static/vala-BWCf7DTx.js +1 -0
  601. package/web-ui/build/static/vala-COL5roWV.js +1 -0
  602. package/web-ui/build/static/vbnet-BHPgkiip.js +1 -0
  603. package/web-ui/build/static/vbnet-BadWmTKs.js +1 -0
  604. package/web-ui/build/static/vbscript-BfUpIAaV.js +1 -0
  605. package/web-ui/build/static/vbscript-html-BBR6zcvV.js +1 -0
  606. package/web-ui/build/static/velocity-k4gGPuiP.js +1 -0
  607. package/web-ui/build/static/verilog-BoNroxW8.js +1 -0
  608. package/web-ui/build/static/verilog-C7cplLIi.js +1 -0
  609. package/web-ui/build/static/vhdl-BrKkx69g.js +1 -0
  610. package/web-ui/build/static/vhdl-CWacsAXR.js +1 -0
  611. package/web-ui/build/static/vim-BcdueaEC.js +1 -0
  612. package/web-ui/build/static/vim-BwsUolG_.js +1 -0
  613. package/web-ui/build/static/visual-basic-DTXlX5HN.js +1 -0
  614. package/web-ui/build/static/warpscript-2w0_2KsB.js +1 -0
  615. package/web-ui/build/static/wasm-BPaXQO-A.js +1 -0
  616. package/web-ui/build/static/web-idl-BoCTFQod.js +1 -0
  617. package/web-ui/build/static/wiki-BkkCi9wm.js +1 -0
  618. package/web-ui/build/static/wolfram-CvRNTaAX.js +1 -0
  619. package/web-ui/build/static/wren-Bz8Smtz-.js +1 -0
  620. package/web-ui/build/static/x86asm-BM83B0N_.js +1 -0
  621. package/web-ui/build/static/xeora-BjuDukl7.js +1 -0
  622. package/web-ui/build/static/xl-ByiuKWwm.js +1 -0
  623. package/web-ui/build/static/xml-Dut2p53G.js +1 -0
  624. package/web-ui/build/static/xml-doc-CxgwiuaA.js +1 -0
  625. package/web-ui/build/static/xojo-CBqUB8JI.js +1 -0
  626. package/web-ui/build/static/xquery-D-agM4jx.js +1 -0
  627. package/web-ui/build/static/xquery-DL_eetrQ.js +1 -0
  628. package/web-ui/build/static/yaml-DZ7HVZSe.js +1 -0
  629. package/web-ui/build/static/yaml-DatJAv7_.js +1 -0
  630. package/web-ui/build/static/yang-D0nrKE-r.js +1 -0
  631. package/web-ui/build/static/zephir--M2g15-k.js +1 -0
  632. package/web-ui/build/static/zig-CK6rUH8E.js +1 -0
  633. package/web-ui/build/static/1c-DGpIT7i5.js +0 -1
  634. package/web-ui/build/static/abap-5wFDdWLh.js +0 -1
  635. package/web-ui/build/static/abnf-BP1dpNSE.js +0 -1
  636. package/web-ui/build/static/abnf-DBEIAl8g.js +0 -1
  637. package/web-ui/build/static/accesslog-CWSM_T5E.js +0 -1
  638. package/web-ui/build/static/actionscript-DONkco1J.js +0 -1
  639. package/web-ui/build/static/actionscript-FqBYk5er.js +0 -1
  640. package/web-ui/build/static/ada-C2JLRIaM.js +0 -1
  641. package/web-ui/build/static/ada-gKiygTRK.js +0 -1
  642. package/web-ui/build/static/agda-CkSODqK2.js +0 -1
  643. package/web-ui/build/static/al-BJ_YR6p7.js +0 -1
  644. package/web-ui/build/static/angelscript-Dg2byMGg.js +0 -1
  645. package/web-ui/build/static/antlr4-BnpyaFNr.js +0 -1
  646. package/web-ui/build/static/apache-Dffxsd7O.js +0 -1
  647. package/web-ui/build/static/apacheconf-DLitjtWj.js +0 -1
  648. package/web-ui/build/static/apex-Drr_IvU2.js +0 -1
  649. package/web-ui/build/static/apl-CF6qxmXG.js +0 -1
  650. package/web-ui/build/static/applescript-CjOlw3b_.js +0 -1
  651. package/web-ui/build/static/applescript-DjkSl1Ry.js +0 -1
  652. package/web-ui/build/static/aql-KwVmK1gP.js +0 -1
  653. package/web-ui/build/static/arcade-CENSXx0R.js +0 -1
  654. package/web-ui/build/static/arduino-B3Ta9Fll.js +0 -1
  655. package/web-ui/build/static/arduino-CzcsaB9_.js +0 -1
  656. package/web-ui/build/static/arff-CMJSVt_O.js +0 -1
  657. package/web-ui/build/static/armasm-0zSgSPB4.js +0 -1
  658. package/web-ui/build/static/asciidoc-B8K5ctWq.js +0 -1
  659. package/web-ui/build/static/asciidoc-bk2Sg6b6.js +0 -1
  660. package/web-ui/build/static/asm6502-ji6zm4FQ.js +0 -1
  661. package/web-ui/build/static/asmatmel-DJqObM4Y.js +0 -1
  662. package/web-ui/build/static/aspectj-DqQKI7J5.js +0 -1
  663. package/web-ui/build/static/aspnet-9cenTxW7.js +0 -1
  664. package/web-ui/build/static/autohotkey-C6EhiZvl.js +0 -1
  665. package/web-ui/build/static/autohotkey-sTGJOvMM.js +0 -1
  666. package/web-ui/build/static/autoit-B0Im8iQ1.js +0 -1
  667. package/web-ui/build/static/autoit-CO6pUD0H.js +0 -1
  668. package/web-ui/build/static/avisynth-OvOHTfj5.js +0 -1
  669. package/web-ui/build/static/avrasm-BLWZ5Mye.js +0 -1
  670. package/web-ui/build/static/avro-idl-BB2ODvnw.js +0 -1
  671. package/web-ui/build/static/awk-_jhMabQ0.js +0 -1
  672. package/web-ui/build/static/axapta-DdBRgoYy.js +0 -1
  673. package/web-ui/build/static/bash-CsaRGXBQ.js +0 -1
  674. package/web-ui/build/static/bash-CxLFkwAC.js +0 -1
  675. package/web-ui/build/static/basic-BJyy7JJE.js +0 -1
  676. package/web-ui/build/static/basic-CrMb-gv0.js +0 -1
  677. package/web-ui/build/static/batch-BvcykMe5.js +0 -1
  678. package/web-ui/build/static/bbcode-EOBuY5Y6.js +0 -1
  679. package/web-ui/build/static/bicep-QkDJBA34.js +0 -1
  680. package/web-ui/build/static/birb-TAOxKurn.js +0 -1
  681. package/web-ui/build/static/bison-BEK6cnad.js +0 -1
  682. package/web-ui/build/static/bnf-DfEODxsr.js +0 -1
  683. package/web-ui/build/static/bnf-DhbMjsuA.js +0 -1
  684. package/web-ui/build/static/brainfuck-BoVocOa7.js +0 -1
  685. package/web-ui/build/static/brainfuck-DDC5QXPK.js +0 -1
  686. package/web-ui/build/static/brightscript-DNN598w2.js +0 -1
  687. package/web-ui/build/static/bro-Cn_jjZ8P.js +0 -1
  688. package/web-ui/build/static/bsl-CMCN75Gu.js +0 -1
  689. package/web-ui/build/static/c-DbOGQnEJ.js +0 -1
  690. package/web-ui/build/static/c-kggwYFsy.js +0 -1
  691. package/web-ui/build/static/c-like-BIsuUvof.js +0 -1
  692. package/web-ui/build/static/cal-DpgMsBZE.js +0 -1
  693. package/web-ui/build/static/capnproto-C1AHYI-M.js +0 -1
  694. package/web-ui/build/static/ceylon-pvJffDe1.js +0 -1
  695. package/web-ui/build/static/cfscript-C1qylu52.js +0 -1
  696. package/web-ui/build/static/chaiscript-D417WKRI.js +0 -1
  697. package/web-ui/build/static/cil-CjbZHbcY.js +0 -1
  698. package/web-ui/build/static/clean-Dvc6R-F2.js +0 -1
  699. package/web-ui/build/static/clojure-CSVRQPMQ.js +0 -1
  700. package/web-ui/build/static/clojure-ig1Vkg7X.js +0 -1
  701. package/web-ui/build/static/clojure-repl-C1uyEabk.js +0 -1
  702. package/web-ui/build/static/cmake-DSbVcAB3.js +0 -1
  703. package/web-ui/build/static/cmake-Zp8kPwnH.js +0 -1
  704. package/web-ui/build/static/cobol-OsZSiK_P.js +0 -1
  705. package/web-ui/build/static/coffeescript-D8r0416S.js +0 -1
  706. package/web-ui/build/static/coffeescript-DCpgClxh.js +0 -1
  707. package/web-ui/build/static/concurnas-DKnsxUOc.js +0 -1
  708. package/web-ui/build/static/coq-BQFlywVI.js +0 -1
  709. package/web-ui/build/static/coq-BvS9mQB2.js +0 -1
  710. package/web-ui/build/static/cos-D3ze2791.js +0 -1
  711. package/web-ui/build/static/cpp-Bw-cV3P1.js +0 -1
  712. package/web-ui/build/static/cpp-vi1p7XpB.js +0 -1
  713. package/web-ui/build/static/crmsh-BClnJTeQ.js +0 -1
  714. package/web-ui/build/static/crystal-DGzOUYZq.js +0 -1
  715. package/web-ui/build/static/crystal-DMzk6EvA.js +0 -1
  716. package/web-ui/build/static/csharp-BTom8s2X.js +0 -1
  717. package/web-ui/build/static/csharp-Cp2V3jfR.js +0 -1
  718. package/web-ui/build/static/cshtml-Dmtt9Kto.js +0 -1
  719. package/web-ui/build/static/csp-WPVsLe9_.js +0 -1
  720. package/web-ui/build/static/csp-qddj5fu_.js +0 -1
  721. package/web-ui/build/static/css-B0FAm7kj.js +0 -1
  722. package/web-ui/build/static/css-extras-Bx3pvjiG.js +0 -1
  723. package/web-ui/build/static/csv-CH-edgS5.js +0 -1
  724. package/web-ui/build/static/cypher-c3G8Leew.js +0 -1
  725. package/web-ui/build/static/d-BMDSy22_.js +0 -1
  726. package/web-ui/build/static/d-BdH4oW8-.js +0 -1
  727. package/web-ui/build/static/dart-C-r72q-O.js +0 -1
  728. package/web-ui/build/static/dart-CwpBMrKa.js +0 -1
  729. package/web-ui/build/static/dataweave-BPvqdt4k.js +0 -1
  730. package/web-ui/build/static/dax-Eyy6ixcN.js +0 -1
  731. package/web-ui/build/static/delphi-DhEbPj_5.js +0 -1
  732. package/web-ui/build/static/dhall-Ct1L8sN1.js +0 -1
  733. package/web-ui/build/static/diff-CSTqCgwn.js +0 -1
  734. package/web-ui/build/static/diff-Da97B5vW.js +0 -1
  735. package/web-ui/build/static/django-DGit_lCg.js +0 -1
  736. package/web-ui/build/static/django-DPgqFB8k.js +0 -1
  737. package/web-ui/build/static/dns-DUyXuZ-a.js +0 -1
  738. package/web-ui/build/static/dns-zone-file-D79MDxVG.js +0 -1
  739. package/web-ui/build/static/docker-DTiy4o38.js +0 -1
  740. package/web-ui/build/static/dockerfile-CvwPP8wE.js +0 -1
  741. package/web-ui/build/static/dos-CaIUWxvb.js +0 -1
  742. package/web-ui/build/static/dot-D3504w6Y.js +0 -1
  743. package/web-ui/build/static/dsconfig-Smy1eeX_.js +0 -1
  744. package/web-ui/build/static/dts-DyTYSXZj.js +0 -1
  745. package/web-ui/build/static/dust-CrJyi6qA.js +0 -1
  746. package/web-ui/build/static/ebnf-C8nqfkBH.js +0 -1
  747. package/web-ui/build/static/ebnf-Cuh5Vh2-.js +0 -1
  748. package/web-ui/build/static/editorconfig-C7pTGl6n.js +0 -1
  749. package/web-ui/build/static/eiffel-C75MSJCA.js +0 -1
  750. package/web-ui/build/static/ejs-Dyo7DF5w.js +0 -1
  751. package/web-ui/build/static/elixir-D4yJefOc.js +0 -1
  752. package/web-ui/build/static/elixir-DKRow2SJ.js +0 -1
  753. package/web-ui/build/static/elm-CgbdDlkT.js +0 -1
  754. package/web-ui/build/static/elm-D3N-UgU0.js +0 -1
  755. package/web-ui/build/static/erb-BsITh8qW.js +0 -1
  756. package/web-ui/build/static/erb-lY_LyKyS.js +0 -1
  757. package/web-ui/build/static/erlang-BCnPiYmB.js +0 -1
  758. package/web-ui/build/static/erlang-JEghiPXc.js +0 -1
  759. package/web-ui/build/static/erlang-repl-DzU97ugC.js +0 -1
  760. package/web-ui/build/static/etlua-CsBo8cJa.js +0 -1
  761. package/web-ui/build/static/excel-formula-C2piiCYa.js +0 -1
  762. package/web-ui/build/static/excel-kqNypwQD.js +0 -1
  763. package/web-ui/build/static/factor-DZTOhkyU.js +0 -1
  764. package/web-ui/build/static/false-yLjhfaSw.js +0 -1
  765. package/web-ui/build/static/firestore-security-rules-oaQNoH8l.js +0 -1
  766. package/web-ui/build/static/fix-C9IfnTuS.js +0 -1
  767. package/web-ui/build/static/flix-EmJ_JhPo.js +0 -1
  768. package/web-ui/build/static/flow-DCUl7IAQ.js +0 -1
  769. package/web-ui/build/static/fortran-DvyxJmFN.js +0 -1
  770. package/web-ui/build/static/fortran-EEP9R3S5.js +0 -1
  771. package/web-ui/build/static/fsharp-D_98s3RX.js +0 -1
  772. package/web-ui/build/static/fsharp-Dt9jOO5G.js +0 -1
  773. package/web-ui/build/static/ftl-C8gMHWLo.js +0 -1
  774. package/web-ui/build/static/gams-BHxVPb4z.js +0 -1
  775. package/web-ui/build/static/gap-DFtyHk-q.js +0 -1
  776. package/web-ui/build/static/gauss-BwhJWUcg.js +0 -1
  777. package/web-ui/build/static/gcode-BvmXwp09.js +0 -1
  778. package/web-ui/build/static/gcode-gWcD6Vc7.js +0 -1
  779. package/web-ui/build/static/gdscript-btHoX8RE.js +0 -1
  780. package/web-ui/build/static/gedcom-MgPs9iqh.js +0 -1
  781. package/web-ui/build/static/gherkin-B9c_Q16A.js +0 -1
  782. package/web-ui/build/static/gherkin-BvOgkK6h.js +0 -1
  783. package/web-ui/build/static/git-D9XWOVcQ.js +0 -1
  784. package/web-ui/build/static/glsl-CshBHxHY.js +0 -1
  785. package/web-ui/build/static/glsl-fNRUMTDr.js +0 -1
  786. package/web-ui/build/static/gml-B78deHK8.js +0 -1
  787. package/web-ui/build/static/gml-Cj7d2u8O.js +0 -1
  788. package/web-ui/build/static/gn-CJLD-mF1.js +0 -1
  789. package/web-ui/build/static/go-CT93BEkL.js +0 -1
  790. package/web-ui/build/static/go-CTTlNuQO.js +0 -1
  791. package/web-ui/build/static/go-module-BgnXboUE.js +0 -1
  792. package/web-ui/build/static/golo-2S5tXS-l.js +0 -1
  793. package/web-ui/build/static/gradle-8W8DCcWJ.js +0 -1
  794. package/web-ui/build/static/graphql-DZfp6FNU.js +0 -1
  795. package/web-ui/build/static/groovy-BfsyMb3X.js +0 -1
  796. package/web-ui/build/static/groovy-Dxov7ENz.js +0 -1
  797. package/web-ui/build/static/haml-BlUFsdVV.js +0 -1
  798. package/web-ui/build/static/haml-CjVj6vvW.js +0 -1
  799. package/web-ui/build/static/handlebars-DxrQFkyA.js +0 -1
  800. package/web-ui/build/static/handlebars-FE6fotYl.js +0 -1
  801. package/web-ui/build/static/haskell-B6z0RCD_.js +0 -1
  802. package/web-ui/build/static/haskell-JBFmJTRy.js +0 -1
  803. package/web-ui/build/static/haxe-BtMZMi-_.js +0 -1
  804. package/web-ui/build/static/haxe-C1J8n-fH.js +0 -1
  805. package/web-ui/build/static/hcl-DJP-bFWE.js +0 -1
  806. package/web-ui/build/static/hlsl-DrGjhs1b.js +0 -1
  807. package/web-ui/build/static/hoon-Cg-ZhqIT.js +0 -1
  808. package/web-ui/build/static/hpkp-D07vgmoZ.js +0 -1
  809. package/web-ui/build/static/hsp-CsypPWoA.js +0 -1
  810. package/web-ui/build/static/hsts-B6DMRxvP.js +0 -1
  811. package/web-ui/build/static/htmlbars-BI7_Hw4e.js +0 -1
  812. package/web-ui/build/static/http-90ihEi4s.js +0 -1
  813. package/web-ui/build/static/http-Dp4QXj9E.js +0 -1
  814. package/web-ui/build/static/hy-BMgKvP4K.js +0 -1
  815. package/web-ui/build/static/ichigojam-D6wKvJDb.js +0 -1
  816. package/web-ui/build/static/icon-CrgkmCwl.js +0 -1
  817. package/web-ui/build/static/icu-message-format-Bi2JxCXs.js +0 -1
  818. package/web-ui/build/static/idris-DbUsyZt5.js +0 -1
  819. package/web-ui/build/static/iecst-CQZ9t8fW.js +0 -1
  820. package/web-ui/build/static/ignore-BaFgtNMs.js +0 -1
  821. package/web-ui/build/static/index-DEWSWosh.css +0 -1
  822. package/web-ui/build/static/index-Di1bjCFA.js +0 -13
  823. package/web-ui/build/static/index-otR_WSsL.js +0 -1
  824. package/web-ui/build/static/index-whZPU4as.js +0 -1183
  825. package/web-ui/build/static/inform7-ClyX1Gro.js +0 -1
  826. package/web-ui/build/static/inform7-tZHBS5XP.js +0 -1
  827. package/web-ui/build/static/ini-BrP5JNYL.js +0 -1
  828. package/web-ui/build/static/ini-CVkrAhwV.js +0 -1
  829. package/web-ui/build/static/io-BJ1Y6Bdc.js +0 -1
  830. package/web-ui/build/static/irpf90-fjjcKS_8.js +0 -1
  831. package/web-ui/build/static/isbl-ClpvfUIz.js +0 -1
  832. package/web-ui/build/static/j-CICW77xS.js +0 -1
  833. package/web-ui/build/static/java-B9DFK-0E.js +0 -1
  834. package/web-ui/build/static/java-BL_6rYko.js +0 -1
  835. package/web-ui/build/static/javadoc-Cc4HKpKK.js +0 -1
  836. package/web-ui/build/static/javadoclike-CgNkUDOm.js +0 -1
  837. package/web-ui/build/static/javascript-CM40ZECq.js +0 -1
  838. package/web-ui/build/static/javastacktrace-BHSqXfG5.js +0 -1
  839. package/web-ui/build/static/jboss-cli-Bv5NhVSZ.js +0 -1
  840. package/web-ui/build/static/jexl--Ohk_keA.js +0 -1
  841. package/web-ui/build/static/jolie-DMTN5Vdf.js +0 -1
  842. package/web-ui/build/static/jq-nXRLldXX.js +0 -1
  843. package/web-ui/build/static/js-extras-eLwv3frs.js +0 -1
  844. package/web-ui/build/static/js-templates-Ca0owlrg.js +0 -1
  845. package/web-ui/build/static/jsdoc-KERXp0da.js +0 -1
  846. package/web-ui/build/static/json-BTjLaRsy.js +0 -1
  847. package/web-ui/build/static/json-p7pU0qdW.js +0 -1
  848. package/web-ui/build/static/json5-Cxjy1udc.js +0 -1
  849. package/web-ui/build/static/jsonp-CO52H7Gy.js +0 -1
  850. package/web-ui/build/static/jsstacktrace-8YKfoyJP.js +0 -1
  851. package/web-ui/build/static/jsx-Ct_05KbM.js +0 -1
  852. package/web-ui/build/static/julia-23I1ubCE.js +0 -1
  853. package/web-ui/build/static/julia-d8rVGed_.js +0 -1
  854. package/web-ui/build/static/julia-repl-mwnHedW_.js +0 -1
  855. package/web-ui/build/static/keepalived-E85Rx_fF.js +0 -1
  856. package/web-ui/build/static/keyman-aWd3QUDq.js +0 -1
  857. package/web-ui/build/static/kotlin-Cu370hQq.js +0 -1
  858. package/web-ui/build/static/kotlin-DoJ2WnmZ.js +0 -1
  859. package/web-ui/build/static/kumir-oiOgqcQO.js +0 -1
  860. package/web-ui/build/static/kusto-Bp-B02K5.js +0 -1
  861. package/web-ui/build/static/lasso-BxoQVwOO.js +0 -1
  862. package/web-ui/build/static/latex-CANm5vsX.js +0 -1
  863. package/web-ui/build/static/latex-PMroeNch.js +0 -1
  864. package/web-ui/build/static/latte-2ErU_2XF.js +0 -1
  865. package/web-ui/build/static/ldif-ChPn_F7o.js +0 -1
  866. package/web-ui/build/static/leaf-UkCugDgG.js +0 -1
  867. package/web-ui/build/static/less-CCH5RA89.js +0 -1
  868. package/web-ui/build/static/less-CGZbVU1g.js +0 -1
  869. package/web-ui/build/static/lilypond-BoyM37sv.js +0 -1
  870. package/web-ui/build/static/liquid-DGJVpRBi.js +0 -1
  871. package/web-ui/build/static/lisp-93nne61u.js +0 -1
  872. package/web-ui/build/static/lisp-DYjIRsXz.js +0 -1
  873. package/web-ui/build/static/livecodeserver-CfNaxIE4.js +0 -1
  874. package/web-ui/build/static/livescript-C8kDlQkz.js +0 -1
  875. package/web-ui/build/static/livescript-CoarnRHq.js +0 -1
  876. package/web-ui/build/static/llvm-CBVyNmAh.js +0 -1
  877. package/web-ui/build/static/llvm-DGxq8a7u.js +0 -1
  878. package/web-ui/build/static/log-BivgwFql.js +0 -1
  879. package/web-ui/build/static/lolcode-l5sK2cZz.js +0 -1
  880. package/web-ui/build/static/lsl-Xm44xLRQ.js +0 -1
  881. package/web-ui/build/static/lua-CGvrzfKp.js +0 -1
  882. package/web-ui/build/static/lua-nHwXeY4c.js +0 -1
  883. package/web-ui/build/static/magma-DmNItmna.js +0 -1
  884. package/web-ui/build/static/makefile-CKJNNHGb.js +0 -1
  885. package/web-ui/build/static/makefile-DI6y5Qml.js +0 -1
  886. package/web-ui/build/static/markdown-BgpbxKd-.js +0 -1
  887. package/web-ui/build/static/markdown-CvcxA4yk.js +0 -1
  888. package/web-ui/build/static/markup-templating-DijqesiA.js +0 -1
  889. package/web-ui/build/static/mathematica-Cxll1Q10.js +0 -1
  890. package/web-ui/build/static/matlab-BJo2T1A-.js +0 -1
  891. package/web-ui/build/static/matlab-C6DlZX4l.js +0 -1
  892. package/web-ui/build/static/maxima-DJXO4sbL.js +0 -1
  893. package/web-ui/build/static/maxscript-DdLpUYBs.js +0 -1
  894. package/web-ui/build/static/mel-BIpfnSyZ.js +0 -1
  895. package/web-ui/build/static/mel-BYcTUZJW.js +0 -1
  896. package/web-ui/build/static/mercury-C_LSpbD8.js +0 -1
  897. package/web-ui/build/static/mermaid-D9yBWnrT.js +0 -1
  898. package/web-ui/build/static/mipsasm-nR_K2Ue-.js +0 -1
  899. package/web-ui/build/static/mizar-BeLUPncD.js +0 -1
  900. package/web-ui/build/static/mizar-BesIbZd9.js +0 -1
  901. package/web-ui/build/static/mojolicious-cBx3OWa-.js +0 -1
  902. package/web-ui/build/static/mongodb-DTWKy9ac.js +0 -1
  903. package/web-ui/build/static/monkey-B72bZC3c.js +0 -1
  904. package/web-ui/build/static/monkey-G9XELYPQ.js +0 -1
  905. package/web-ui/build/static/moonscript-BXYVQiqj.js +0 -1
  906. package/web-ui/build/static/moonscript-sDd-5knz.js +0 -1
  907. package/web-ui/build/static/n1ql-0vKSfFAO.js +0 -1
  908. package/web-ui/build/static/n1ql-C9_BSZfz.js +0 -1
  909. package/web-ui/build/static/n4js-B7Ct8dds.js +0 -1
  910. package/web-ui/build/static/nand2tetris-hdl-p9gpphTM.js +0 -1
  911. package/web-ui/build/static/naniscript-6ZVr8Aug.js +0 -1
  912. package/web-ui/build/static/nasm-Ca73yTUt.js +0 -1
  913. package/web-ui/build/static/neon-DNP49oyX.js +0 -1
  914. package/web-ui/build/static/nevod-Qhgt7Bce.js +0 -1
  915. package/web-ui/build/static/nginx-D5e7lu62.js +0 -1
  916. package/web-ui/build/static/nginx-DobnyESB.js +0 -1
  917. package/web-ui/build/static/nim-BPBivUOV.js +0 -1
  918. package/web-ui/build/static/nim-Baoug1Wa.js +0 -1
  919. package/web-ui/build/static/nix-CSPTQs5y.js +0 -1
  920. package/web-ui/build/static/nix-Dk4eNw49.js +0 -1
  921. package/web-ui/build/static/node-repl-NJNL8VFR.js +0 -1
  922. package/web-ui/build/static/nsis-BZ0oMzEw.js +0 -1
  923. package/web-ui/build/static/nsis-CKPCjtCU.js +0 -1
  924. package/web-ui/build/static/objectivec-BkfIRhhV.js +0 -1
  925. package/web-ui/build/static/objectivec-CnQgqhbJ.js +0 -1
  926. package/web-ui/build/static/ocaml-9rGNzRrK.js +0 -1
  927. package/web-ui/build/static/ocaml-CTolCqxL.js +0 -1
  928. package/web-ui/build/static/opencl-9Q3vRDxv.js +0 -1
  929. package/web-ui/build/static/openqasm-D2QiuFBp.js +0 -1
  930. package/web-ui/build/static/openscad-C3HyuzpB.js +0 -1
  931. package/web-ui/build/static/oxygene-4gi-VYy_.js +0 -1
  932. package/web-ui/build/static/oz-BGDEB-1A.js +0 -1
  933. package/web-ui/build/static/parigp-CfZzp1uE.js +0 -1
  934. package/web-ui/build/static/parser-UbGteTcy.js +0 -1
  935. package/web-ui/build/static/parser3-C-Jx-fy7.js +0 -1
  936. package/web-ui/build/static/pascal-D1_R0gW-.js +0 -1
  937. package/web-ui/build/static/pascaligo-B8C-98Np.js +0 -1
  938. package/web-ui/build/static/pcaxis-TGnlUKNs.js +0 -1
  939. package/web-ui/build/static/peoplecode-C5Vf1AH0.js +0 -1
  940. package/web-ui/build/static/perl-CBp1N62T.js +0 -1
  941. package/web-ui/build/static/perl-Z59j904t.js +0 -1
  942. package/web-ui/build/static/pf-Cq8B_xwQ.js +0 -1
  943. package/web-ui/build/static/pgsql-ofJbUHkL.js +0 -1
  944. package/web-ui/build/static/php-OlH7HLQJ.js +0 -1
  945. package/web-ui/build/static/php-extras-GqtrxLuk.js +0 -1
  946. package/web-ui/build/static/php-r09kMDOB.js +0 -1
  947. package/web-ui/build/static/php-template-18uT97Qo.js +0 -1
  948. package/web-ui/build/static/phpdoc-9tRFvup9.js +0 -1
  949. package/web-ui/build/static/plaintext-DsYT6Mu-.js +0 -1
  950. package/web-ui/build/static/plsql-RhWQNJVb.js +0 -1
  951. package/web-ui/build/static/pony-DD6JMLYI.js +0 -1
  952. package/web-ui/build/static/powerquery-CALow-bt.js +0 -1
  953. package/web-ui/build/static/powershell-C2QvIuKF.js +0 -1
  954. package/web-ui/build/static/powershell-yrbJEhCh.js +0 -1
  955. package/web-ui/build/static/processing-1T5w_Q03.js +0 -1
  956. package/web-ui/build/static/processing-DBim_dO-.js +0 -1
  957. package/web-ui/build/static/profile-Cdloh8mZ.js +0 -1
  958. package/web-ui/build/static/prolog-DRSsNnns.js +0 -1
  959. package/web-ui/build/static/prolog-D_ajweDr.js +0 -1
  960. package/web-ui/build/static/promql-BRuwn6Bn.js +0 -1
  961. package/web-ui/build/static/properties-D5Wyl4X4.js +0 -1
  962. package/web-ui/build/static/properties-kn4fl1bl.js +0 -1
  963. package/web-ui/build/static/protobuf-BMO76zWi.js +0 -1
  964. package/web-ui/build/static/protobuf-CTUCF-U-.js +0 -1
  965. package/web-ui/build/static/psl-CiqYdQbY.js +0 -1
  966. package/web-ui/build/static/pug-BLE2Qayj.js +0 -1
  967. package/web-ui/build/static/puppet-CFKLWXft.js +0 -1
  968. package/web-ui/build/static/puppet-DQci0Dl5.js +0 -1
  969. package/web-ui/build/static/pure-D2h_GynV.js +0 -1
  970. package/web-ui/build/static/purebasic-BF8MVw8V.js +0 -1
  971. package/web-ui/build/static/purebasic-BTtHiCkh.js +0 -1
  972. package/web-ui/build/static/purescript-D1ZSh-sH.js +0 -1
  973. package/web-ui/build/static/python-Cp9_Vdhb.js +0 -1
  974. package/web-ui/build/static/python-DdgNw8IW.js +0 -1
  975. package/web-ui/build/static/python-repl-DvK89VMC.js +0 -1
  976. package/web-ui/build/static/q-COaIgwhT.js +0 -1
  977. package/web-ui/build/static/q-Cm0dQkW8.js +0 -1
  978. package/web-ui/build/static/qml-BpsOqqJM.js +0 -1
  979. package/web-ui/build/static/qml-BziQXlU4.js +0 -1
  980. package/web-ui/build/static/qore-Cutz6g-2.js +0 -1
  981. package/web-ui/build/static/qsharp-B16619X1.js +0 -1
  982. package/web-ui/build/static/r-CFUIj5Hd.js +0 -1
  983. package/web-ui/build/static/r-CPrwCi5w.js +0 -1
  984. package/web-ui/build/static/racket-Bh08DFXF.js +0 -1
  985. package/web-ui/build/static/reason-BxjDq4e-.js +0 -1
  986. package/web-ui/build/static/reasonml-Ds5SsGP8.js +0 -1
  987. package/web-ui/build/static/regex-DhBIDIMI.js +0 -1
  988. package/web-ui/build/static/rego-DCwxZXcB.js +0 -1
  989. package/web-ui/build/static/renpy-C2fuQfqb.js +0 -1
  990. package/web-ui/build/static/rest-C52ZpxWQ.js +0 -1
  991. package/web-ui/build/static/rib-Cbl2Mzyj.js +0 -1
  992. package/web-ui/build/static/rip-BTOu5ZIE.js +0 -1
  993. package/web-ui/build/static/roboconf-3Oi2wuVk.js +0 -1
  994. package/web-ui/build/static/roboconf-ndLZLE39.js +0 -1
  995. package/web-ui/build/static/robotframework-BzHXiIj1.js +0 -1
  996. package/web-ui/build/static/routeros-CkpPoqx-.js +0 -1
  997. package/web-ui/build/static/rsl-C0bwOQ38.js +0 -1
  998. package/web-ui/build/static/ruby-C_hIhtuQ.js +0 -1
  999. package/web-ui/build/static/ruby-DvKfZPzj.js +0 -1
  1000. package/web-ui/build/static/ruleslanguage-CbZJlddz.js +0 -1
  1001. package/web-ui/build/static/rust-BFPIhB-X.js +0 -1
  1002. package/web-ui/build/static/rust-sBpUq-qE.js +0 -1
  1003. package/web-ui/build/static/sas-BCXvYN9x.js +0 -1
  1004. package/web-ui/build/static/sas-D7GAsTY3.js +0 -1
  1005. package/web-ui/build/static/sass-BdaErGMN.js +0 -1
  1006. package/web-ui/build/static/scala-Cj81oCl9.js +0 -1
  1007. package/web-ui/build/static/scala-CvbksfY6.js +0 -1
  1008. package/web-ui/build/static/scheme-BM5ZqKnl.js +0 -1
  1009. package/web-ui/build/static/scheme-BPvlu9Tk.js +0 -1
  1010. package/web-ui/build/static/scilab-CJanLKQN.js +0 -1
  1011. package/web-ui/build/static/scss-C53YF_7f.js +0 -1
  1012. package/web-ui/build/static/scss-DPp8UZbr.js +0 -1
  1013. package/web-ui/build/static/shell-CC9bQXMe.js +0 -1
  1014. package/web-ui/build/static/shell-session-CuR3fbf-.js +0 -1
  1015. package/web-ui/build/static/smali-CUgLls3D.js +0 -1
  1016. package/web-ui/build/static/smali-YODSX8qt.js +0 -1
  1017. package/web-ui/build/static/smalltalk-BMJQ4bbs.js +0 -1
  1018. package/web-ui/build/static/smalltalk-BVGY3CTl.js +0 -1
  1019. package/web-ui/build/static/smarty-C9aNt4-p.js +0 -1
  1020. package/web-ui/build/static/sml-C3BIHhfq.js +0 -1
  1021. package/web-ui/build/static/sml-DTipyRmY.js +0 -1
  1022. package/web-ui/build/static/solidity-1yuPUqoC.js +0 -1
  1023. package/web-ui/build/static/solution-file-BgzK4GOU.js +0 -1
  1024. package/web-ui/build/static/soy-CFJXRvqc.js +0 -1
  1025. package/web-ui/build/static/sparql-CAosYMpl.js +0 -1
  1026. package/web-ui/build/static/splunk-spl-DkINtWr8.js +0 -1
  1027. package/web-ui/build/static/sqf-DbrWIS2M.js +0 -1
  1028. package/web-ui/build/static/sqf-nq8Q9J2W.js +0 -1
  1029. package/web-ui/build/static/sql-9bwClhZQ.js +0 -1
  1030. package/web-ui/build/static/sql-CqPkY-lX.js +0 -1
  1031. package/web-ui/build/static/sql_more-CsY5ts77.js +0 -1
  1032. package/web-ui/build/static/squirrel-BuqtzRBD.js +0 -1
  1033. package/web-ui/build/static/stan-BNxBSglc.js +0 -1
  1034. package/web-ui/build/static/stan-CZVMc34l.js +0 -1
  1035. package/web-ui/build/static/stata-DKnVdHCd.js +0 -1
  1036. package/web-ui/build/static/step21-Cu_TaBGF.js +0 -1
  1037. package/web-ui/build/static/stylus-BS7-OJew.js +0 -1
  1038. package/web-ui/build/static/stylus-DtFrp1Nk.js +0 -1
  1039. package/web-ui/build/static/subunit-DDCoWkkc.js +0 -1
  1040. package/web-ui/build/static/swift-BaguUZbl.js +0 -1
  1041. package/web-ui/build/static/swift-mwBsb8Bx.js +0 -1
  1042. package/web-ui/build/static/systemd-D6PpyDKk.js +0 -1
  1043. package/web-ui/build/static/t4-cs-Cem8g4Ck.js +0 -1
  1044. package/web-ui/build/static/t4-templating-BZo-HjmD.js +0 -1
  1045. package/web-ui/build/static/t4-vb-B4oVnKa4.js +0 -1
  1046. package/web-ui/build/static/taggerscript-DweAZ5pw.js +0 -1
  1047. package/web-ui/build/static/tap-DpuvKHHF.js +0 -1
  1048. package/web-ui/build/static/tap-hUKmJObZ.js +0 -1
  1049. package/web-ui/build/static/tcl-BUvhAi7u.js +0 -1
  1050. package/web-ui/build/static/tcl-Dsck63d8.js +0 -1
  1051. package/web-ui/build/static/textile-CgKbqJ-j.js +0 -1
  1052. package/web-ui/build/static/thrift-B5H6rApp.js +0 -1
  1053. package/web-ui/build/static/toml-DggTpfOo.js +0 -1
  1054. package/web-ui/build/static/tp-CazB2P2X.js +0 -1
  1055. package/web-ui/build/static/tremor-B1jy7S5p.js +0 -1
  1056. package/web-ui/build/static/tsx-BqF2lVDi.js +0 -1
  1057. package/web-ui/build/static/tt2-BNoNXpEe.js +0 -1
  1058. package/web-ui/build/static/turtle-B0evd5mn.js +0 -1
  1059. package/web-ui/build/static/twig-B5A7nMdv.js +0 -1
  1060. package/web-ui/build/static/twig-DZnLWvh8.js +0 -1
  1061. package/web-ui/build/static/typescript-Bbe_P093.js +0 -1
  1062. package/web-ui/build/static/typescript-BfhvmSSG.js +0 -1
  1063. package/web-ui/build/static/typoscript-BjuZEIgw.js +0 -1
  1064. package/web-ui/build/static/unrealscript-C3iUCFRi.js +0 -1
  1065. package/web-ui/build/static/uorazor-CbZXwzIj.js +0 -1
  1066. package/web-ui/build/static/uri-xMPSnp6m.js +0 -1
  1067. package/web-ui/build/static/v-BKcGo5I6.js +0 -1
  1068. package/web-ui/build/static/vala-BRoBE4am.js +0 -1
  1069. package/web-ui/build/static/vala-B__Iyrma.js +0 -1
  1070. package/web-ui/build/static/vbnet-DBxlMRvN.js +0 -1
  1071. package/web-ui/build/static/vbnet-DjAXt5BE.js +0 -1
  1072. package/web-ui/build/static/vbscript-BNA4oANi.js +0 -1
  1073. package/web-ui/build/static/vbscript-html-o8ckLPKG.js +0 -1
  1074. package/web-ui/build/static/velocity-D7sc5ggA.js +0 -1
  1075. package/web-ui/build/static/verilog-BWJfMIng.js +0 -1
  1076. package/web-ui/build/static/verilog-jW2GPC--.js +0 -1
  1077. package/web-ui/build/static/vhdl-BIVlXRPa.js +0 -1
  1078. package/web-ui/build/static/vhdl-BeqdhhxD.js +0 -1
  1079. package/web-ui/build/static/vim-712lI4-g.js +0 -1
  1080. package/web-ui/build/static/vim-DMROTzr0.js +0 -1
  1081. package/web-ui/build/static/visual-basic-CQfbM-ta.js +0 -1
  1082. package/web-ui/build/static/warpscript-DFAvCXFQ.js +0 -1
  1083. package/web-ui/build/static/wasm-CiYoxBl0.js +0 -1
  1084. package/web-ui/build/static/web-idl-DoCkPK8y.js +0 -1
  1085. package/web-ui/build/static/wiki-bvz0AGzB.js +0 -1
  1086. package/web-ui/build/static/wolfram-fBuyFEgU.js +0 -1
  1087. package/web-ui/build/static/wren-DvATFxjF.js +0 -1
  1088. package/web-ui/build/static/x86asm-r4bPbUR_.js +0 -1
  1089. package/web-ui/build/static/xeora-B6iOnDJY.js +0 -1
  1090. package/web-ui/build/static/xl-Ce6B5slc.js +0 -1
  1091. package/web-ui/build/static/xml-DNjyPmhQ.js +0 -1
  1092. package/web-ui/build/static/xml-doc-DydogmZD.js +0 -1
  1093. package/web-ui/build/static/xojo-DCQLltvr.js +0 -1
  1094. package/web-ui/build/static/xquery-C-fAnA0H.js +0 -1
  1095. package/web-ui/build/static/xquery-CNeqzhLO.js +0 -1
  1096. package/web-ui/build/static/yaml-BsVaGsk5.js +0 -1
  1097. package/web-ui/build/static/yaml-kVZvwv_C.js +0 -1
  1098. package/web-ui/build/static/yang-B_gq9JEq.js +0 -1
  1099. package/web-ui/build/static/zephir-ECIXgXhX.js +0 -1
  1100. package/web-ui/build/static/zig-DY325EKG.js +0 -1
@@ -1,1521 +1,1673 @@
1
- /**
2
- * @file src/tools/codeMapTool.js
3
- * @description Code Map Tool — structural code exploration with multi-level skeleton extraction
4
- * and line-range reading. Enables agents to understand large codebases without reading entire files.
5
- *
6
- * Workflow: skeleton scan → identify interesting areas → read-range for details
7
- */
8
-
9
- import { BaseTool } from './baseTool.js';
10
- import fs from 'fs';
11
- import path from 'path';
12
- import {
13
- isWhitespace,
14
- skipWhitespace,
15
- readIdent,
16
- startsWithKeyword,
17
- consumeKeyword,
18
- consumeAnyKeyword,
19
- trimmedStartsWithAny,
20
- indexOfWord,
21
- containsWord,
22
- stripStringsAndLineComments,
23
- countChar,
24
- consumeDottedAssignTail,
25
- trimmedEndsWith,
26
- isIdentStart,
27
- isWordChar,
28
- } from './parserHelpers.js';
29
-
30
- const CODE_MAP_CONFIG = {
31
- SUPPORTED_EXTENSIONS: new Set([
32
- '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.py',
33
- // C / C++ via a focused regex pass — see _parseC below. The
34
- // proper tree-sitter migration that would replace the JS + Python
35
- // regex parsers also covers these and adds full accuracy; this
36
- // pass is the v1 that unblocks `code-map` on C/CPP codebases today.
37
- '.c', '.h', '.cc', '.cpp', '.cxx', '.hpp', '.hh', '.hxx',
38
- ]),
39
- MAX_FILES: 500,
40
- MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB per file
41
- MAX_READ_RANGE: 500, // Max lines per read-range call
42
- MAX_DIRECTORY_DEPTH: 20,
43
- VALID_LEVELS: ['A.0', 'A.1', 'B.0', 'B.1'],
44
- DEFAULT_LEVEL: 'B.0',
45
- SKIP_DIRS: new Set(['node_modules', '__pycache__', '.git', 'dist', 'build', 'coverage', '.next', '.nuxt'])
46
- };
47
-
48
- class CodeMapTool extends BaseTool {
49
- constructor(config = {}, logger = null) {
50
- super(config, logger);
51
-
52
- this.id = 'code-map';
53
- this.requiresProject = true;
54
- this.isAsync = true;
55
- this.timeout = config.timeout || 120000;
56
- }
57
-
58
- getDescription() {
59
- return `Code Map Tool: Explore code structure without reading entire files. Two-step workflow:
60
-
61
- 1. **skeleton** — Extract structural overview (signatures, classes, functions) with line numbers
62
- 2. **read-range** — Read specific line ranges identified from the skeleton
63
-
64
- SKELETON LEVELS:
65
- - A.0: Public/exported signatures only
66
- - A.1: Public/exported signatures + comments/docstrings
67
- - B.0: All signatures (public + private + methods) — DEFAULT
68
- - B.1: All signatures + comments/docstrings
69
-
70
- Supported files: .js, .ts, .jsx, .tsx, .mjs, .cjs, .py, .c, .h, .cc, .cpp, .cxx, .hpp, .hh, .hxx
71
- Respects .gitignore rules. Skips node_modules, __pycache__, .git, dist, build.
72
-
73
- USAGE — Skeleton scan:
74
- \`\`\`json
75
- {
76
- "toolId": "code-map",
77
- "parameters": {
78
- "action": "skeleton",
79
- "path": "src/",
80
- "level": "B.0",
81
- "includeImports": false
82
- }
83
- }
84
- \`\`\`
85
-
86
- Or for a single file:
87
- \`\`\`json
88
- {
89
- "toolId": "code-map",
90
- "parameters": {
91
- "action": "skeleton",
92
- "path": "src/services/aiService.js",
93
- "level": "A.0"
94
- }
95
- }
96
- \`\`\`
97
-
98
- USAGE Read specific lines (use line numbers from skeleton output):
99
- \`\`\`json
100
- {
101
- "toolId": "code-map",
102
- "parameters": {
103
- "action": "read-range",
104
- "filePath": "src/services/aiService.js",
105
- "startLine": 42,
106
- "endLine": 90
107
- }
108
- }
109
- \`\`\`
110
-
111
- XML format also supported:
112
- <code-map>
113
- <action>skeleton</action>
114
- <path>src/</path>
115
- <level>B.0</level>
116
- </code-map>
117
-
118
- <code-map>
119
- <action>read-range</action>
120
- <file-path>src/index.js</file-path>
121
- <start-line>100</start-line>
122
- <end-line>150</end-line>
123
- </code-map>`;
124
- }
125
-
126
- parseParameters(content) {
127
- try {
128
- if (content.trim().startsWith('{')) {
129
- const parsed = JSON.parse(content);
130
- return {
131
- action: parsed.action || parsed.parameters?.action,
132
- path: parsed.path || parsed.parameters?.path,
133
- level: parsed.level || parsed.parameters?.level,
134
- includeImports: parsed.includeImports ?? parsed.parameters?.includeImports ?? false,
135
- filePath: parsed.filePath || parsed.parameters?.filePath,
136
- startLine: parsed.startLine ?? parsed.parameters?.startLine,
137
- endLine: parsed.endLine ?? parsed.parameters?.endLine
138
- };
139
- }
140
-
141
- // XML parsing
142
- const params = {};
143
- const actionMatch = /<action>(.*?)<\/action>/i.exec(content);
144
- if (actionMatch) params.action = actionMatch[1].trim();
145
-
146
- const pathMatch = /<path>(.*?)<\/path>/i.exec(content);
147
- if (pathMatch) params.path = pathMatch[1].trim();
148
-
149
- const levelMatch = /<level>(.*?)<\/level>/i.exec(content);
150
- if (levelMatch) params.level = levelMatch[1].trim();
151
-
152
- const importsMatch = /<include-imports>(.*?)<\/include-imports>/i.exec(content);
153
- if (importsMatch) params.includeImports = importsMatch[1].trim() === 'true';
154
-
155
- const filePathMatch = /<file-path>(.*?)<\/file-path>/i.exec(content);
156
- if (filePathMatch) params.filePath = filePathMatch[1].trim();
157
-
158
- const startLineMatch = /<start-line>(.*?)<\/start-line>/i.exec(content);
159
- if (startLineMatch) params.startLine = parseInt(startLineMatch[1].trim(), 10);
160
-
161
- const endLineMatch = /<end-line>(.*?)<\/end-line>/i.exec(content);
162
- if (endLineMatch) params.endLine = parseInt(endLineMatch[1].trim(), 10);
163
-
164
- return params;
165
- } catch (error) {
166
- this.logger?.error('Failed to parse code-map parameters', { error: error.message });
167
- return { parseError: error.message };
168
- }
169
- }
170
-
171
- getRequiredParameters() {
172
- return ['action'];
173
- }
174
-
175
- customValidateParameters(params) {
176
- const errors = [];
177
-
178
- if (!params.action) {
179
- errors.push('action is required (skeleton or read-range)');
180
- } else if (!['skeleton', 'read-range'].includes(params.action)) {
181
- errors.push(`Invalid action "${params.action}". Must be "skeleton" or "read-range"`);
182
- }
183
-
184
- if (params.action === 'skeleton') {
185
- if (!params.path) errors.push('path is required for skeleton action');
186
- if (params.level && !CODE_MAP_CONFIG.VALID_LEVELS.includes(params.level.toUpperCase())) {
187
- errors.push(`Invalid level "${params.level}". Must be one of: ${CODE_MAP_CONFIG.VALID_LEVELS.join(', ')}`);
188
- }
189
- }
190
-
191
- if (params.action === 'read-range') {
192
- if (!params.filePath) errors.push('filePath is required for read-range action');
193
- if (params.startLine == null) errors.push('startLine is required for read-range action');
194
- if (params.endLine == null) errors.push('endLine is required for read-range action');
195
- if (params.startLine != null && params.endLine != null) {
196
- if (params.startLine < 1) errors.push('startLine must be >= 1');
197
- if (params.endLine < params.startLine) errors.push('endLine must be >= startLine');
198
- if (params.endLine - params.startLine + 1 > CODE_MAP_CONFIG.MAX_READ_RANGE) {
199
- errors.push(`Range too large. Maximum ${CODE_MAP_CONFIG.MAX_READ_RANGE} lines per request`);
200
- }
201
- }
202
- }
203
-
204
- return { valid: errors.length === 0, errors };
205
- }
206
-
207
- async execute(params, context) {
208
- const { projectDir, directoryAccess } = context;
209
-
210
- let workingDir = projectDir || process.cwd();
211
- if (directoryAccess?.workingDirectory) {
212
- workingDir = directoryAccess.workingDirectory;
213
- }
214
-
215
- const accessibleDirs = this._getAccessibleDirectories(directoryAccess, workingDir);
216
-
217
- switch (params.action) {
218
- case 'skeleton':
219
- return await this._executeSkeleton(params, workingDir, accessibleDirs);
220
- case 'read-range':
221
- return await this._executeReadRange(params, workingDir, accessibleDirs);
222
- default:
223
- throw new Error(`Unknown action: ${params.action}`);
224
- }
225
- }
226
-
227
- // ── Directory access helpers ──────────────────────────────────────────────
228
-
229
- _getAccessibleDirectories(directoryAccess, fallbackDir) {
230
- const dirs = new Set();
231
- dirs.add(fallbackDir);
232
-
233
- if (directoryAccess) {
234
- if (directoryAccess.workingDirectory) dirs.add(directoryAccess.workingDirectory);
235
- if (directoryAccess.readOnlyDirectories) {
236
- for (const d of directoryAccess.readOnlyDirectories) dirs.add(d);
237
- }
238
- if (directoryAccess.writeEnabledDirectories) {
239
- for (const d of directoryAccess.writeEnabledDirectories) dirs.add(d);
240
- }
241
- }
242
-
243
- return Array.from(dirs);
244
- }
245
-
246
- _isPathAccessible(targetPath, accessibleDirs) {
247
- for (const dir of accessibleDirs) {
248
- const relative = path.relative(dir, targetPath);
249
- if (!relative.startsWith('..') && !path.isAbsolute(relative)) return true;
250
- }
251
- return false;
252
- }
253
-
254
- // ── Skeleton action ───────────────────────────────────────────────────────
255
-
256
- async _executeSkeleton(params, workingDir, accessibleDirs) {
257
- const targetPath = path.resolve(workingDir, params.path);
258
-
259
- if (!this._isPathAccessible(targetPath, accessibleDirs)) {
260
- throw new Error(`Path not accessible: ${params.path}`);
261
- }
262
-
263
- const level = (params.level || CODE_MAP_CONFIG.DEFAULT_LEVEL).toUpperCase();
264
- const publicOnly = level.startsWith('A');
265
- const withComments = level.endsWith('.1');
266
- const includeImports = params.includeImports || false;
267
- const parseOptions = { publicOnly, withComments, includeImports };
268
-
269
- let stat;
270
- try {
271
- stat = await fs.promises.stat(targetPath);
272
- } catch {
273
- throw new Error(`Path not found: ${params.path}`);
274
- }
275
-
276
- let files;
277
- if (stat.isFile()) {
278
- const ext = path.extname(targetPath).toLowerCase();
279
- if (!CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS.has(ext)) {
280
- throw new Error(`Unsupported file type: ${ext}. Supported: ${[...CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS].join(', ')}`);
281
- }
282
- files = [targetPath];
283
- } else {
284
- files = await this._discoverFiles(targetPath, accessibleDirs);
285
- }
286
-
287
- if (files.length === 0) {
288
- return {
289
- success: true,
290
- action: 'skeleton',
291
- level,
292
- path: params.path,
293
- files: [],
294
- totalFiles: 0,
295
- totalEntries: 0,
296
- message: 'No supported files found in the specified path.',
297
- guidance: 'Try a different path or verify the directory contains supported files (.js, .ts, .jsx, .tsx, .mjs, .cjs, .py, .c, .h, .cc, .cpp, .cxx, .hpp, .hh, .hxx).'
298
- };
299
- }
300
-
301
- if (files.length > CODE_MAP_CONFIG.MAX_FILES) {
302
- files = files.slice(0, CODE_MAP_CONFIG.MAX_FILES);
303
- }
304
-
305
- const basePath = stat.isDirectory() ? targetPath : path.dirname(targetPath);
306
- const result = [];
307
- let totalEntries = 0;
308
-
309
- for (const file of files) {
310
- try {
311
- const fileStat = await fs.promises.stat(file);
312
- if (fileStat.size > CODE_MAP_CONFIG.MAX_FILE_SIZE) continue;
313
-
314
- const content = await fs.promises.readFile(file, 'utf-8');
315
- const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
316
- const lang = this._langOf(file);
317
-
318
- const entries =
319
- lang === 'python' ? this._parsePython(lines, parseOptions) :
320
- lang === 'c' ? this._parseC(lines, parseOptions) :
321
- this._parseJS(lines, parseOptions);
322
-
323
- if (entries.length === 0) continue;
324
-
325
- const relPath = path.relative(basePath, file);
326
- result.push({
327
- file: relPath,
328
- totalLines: lines.length,
329
- entries
330
- });
331
- totalEntries += entries.length;
332
- } catch (err) {
333
- this.logger?.debug(`Skipping file ${file}: ${err.message}`);
334
- }
335
- }
336
-
337
- return {
338
- success: true,
339
- action: 'skeleton',
340
- level,
341
- path: params.path,
342
- files: result,
343
- totalFiles: result.length,
344
- totalEntries,
345
- guidance: totalEntries > 0
346
- ? (() => {
347
- const sampleFile = result[0];
348
- const sampleLine = sampleFile?.entries[0]?.line || 1;
349
- const examplePath = stat.isDirectory()
350
- ? (params.path.endsWith('/') ? params.path : params.path + '/') + sampleFile.file
351
- : params.path;
352
- return `Found ${totalEntries} entries across ${result.length} files. Use code-map read-range to examine specific sections by line number. Example: {"toolId":"code-map","parameters":{"action":"read-range","filePath":"${examplePath}","startLine":${sampleLine},"endLine":${sampleLine + 30}}}`;
353
- })()
354
- : 'No code signatures found at this level. Try level B.0 or B.1 for more detail.'
355
- };
356
- }
357
-
358
- // ── Read-range action ─────────────────────────────────────────────────────
359
-
360
- async _executeReadRange(params, workingDir, accessibleDirs) {
361
- const filePath = path.resolve(workingDir, params.filePath);
362
-
363
- if (!this._isPathAccessible(filePath, accessibleDirs)) {
364
- throw new Error(`Path not accessible: ${params.filePath}`);
365
- }
366
-
367
- let content;
368
- try {
369
- content = await fs.promises.readFile(filePath, 'utf-8');
370
- } catch {
371
- throw new Error(`File not found: ${params.filePath}`);
372
- }
373
-
374
- const allLines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
375
- const totalLines = allLines.length;
376
- const startLine = Math.max(1, params.startLine);
377
- const endLine = Math.min(totalLines, params.endLine);
378
-
379
- if (startLine > totalLines) {
380
- throw new Error(`startLine ${startLine} exceeds file length (${totalLines} lines)`);
381
- }
382
-
383
- const selectedLines = allLines.slice(startLine - 1, endLine);
384
- const maxWidth = String(endLine).length;
385
- const formatted = selectedLines.map((line, i) => {
386
- const num = String(startLine + i).padStart(maxWidth);
387
- return `${num}│${line}`;
388
- }).join('\n');
389
-
390
- return {
391
- success: true,
392
- action: 'read-range',
393
- filePath: params.filePath,
394
- startLine,
395
- endLine,
396
- linesReturned: selectedLines.length,
397
- totalLines,
398
- content: formatted,
399
- guidance: `Showing lines ${startLine}-${endLine} of ${totalLines}. ` +
400
- (endLine < totalLines
401
- ? `Use read-range with startLine:${endLine + 1} to continue reading. `
402
- : '') +
403
- 'Use code-map skeleton to discover more code structure in other files.'
404
- };
405
- }
406
-
407
- // ── File discovery with .gitignore ────────────────────────────────────────
408
-
409
- async _discoverFiles(rootDir, accessibleDirs) {
410
- const gi = await this._loadGitignoreRules(rootDir);
411
- const results = [];
412
- let depth = 0;
413
-
414
- const walk = async (dir) => {
415
- if (depth > CODE_MAP_CONFIG.MAX_DIRECTORY_DEPTH) return;
416
- if (results.length >= CODE_MAP_CONFIG.MAX_FILES) return;
417
-
418
- const rel = path.relative(rootDir, dir);
419
- if (rel) gi.loadNested(dir, rel);
420
-
421
- let entries;
422
- try {
423
- entries = await fs.promises.readdir(dir, { withFileTypes: true });
424
- } catch {
425
- return;
426
- }
427
-
428
- // Sort for deterministic output
429
- entries.sort((a, b) => a.name.localeCompare(b.name));
430
-
431
- for (const entry of entries) {
432
- if (results.length >= CODE_MAP_CONFIG.MAX_FILES) break;
433
- if (entry.name.startsWith('.')) continue;
434
-
435
- const full = path.join(dir, entry.name);
436
- const entryRel = path.relative(rootDir, full);
437
-
438
- if (entry.isDirectory()) {
439
- if (CODE_MAP_CONFIG.SKIP_DIRS.has(entry.name)) continue;
440
- if (gi.isIgnored(entryRel, true) && !gi.hasNegationUnder(entryRel)) continue;
441
- if (!this._isPathAccessible(full, accessibleDirs)) continue;
442
-
443
- depth++;
444
- await walk(full);
445
- depth--;
446
- } else if (CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
447
- if (gi.isIgnored(entryRel, false)) continue;
448
- results.push(full);
449
- }
450
- }
451
- };
452
-
453
- await walk(rootDir);
454
- return results;
455
- }
456
-
457
- // ── .gitignore support ────────────────────────────────────────────────────
458
-
459
- async _loadGitignoreRules(rootDir) {
460
- const allRules = [];
461
-
462
- const giPath = path.join(rootDir, '.gitignore');
463
- try {
464
- const content = await fs.promises.readFile(giPath, 'utf-8');
465
- allRules.push(...this._parseGitignore(content, ''));
466
- } catch {
467
- // No .gitignore at root — that's fine
468
- }
469
-
470
- return {
471
- rules: allRules,
472
-
473
- loadNested: (subDir, relPath) => {
474
- const nested = path.join(subDir, '.gitignore');
475
- try {
476
- const content = fs.readFileSync(nested, 'utf-8');
477
- const prefix = relPath ? relPath + '/' : '';
478
- allRules.push(...this._parseGitignore(content, prefix));
479
- } catch {
480
- // No nested .gitignore
481
- }
482
- },
483
-
484
- isIgnored: (relPath, isDir) => {
485
- let ignored = false;
486
- for (const rule of allRules) {
487
- if (rule.dirOnly && !isDir) {
488
- const parts = relPath.split('/');
489
- let parentMatch = false;
490
- for (let k = 1; k < parts.length; k++) {
491
- const parentPath = parts.slice(0, k).join('/');
492
- if (rule.regex.test(parentPath)) { parentMatch = true; break; }
493
- }
494
- if (!parentMatch) continue;
495
- } else if (!rule.regex.test(relPath)) {
496
- continue;
497
- }
498
- ignored = !rule.negate;
499
- }
500
- return ignored;
501
- },
502
-
503
- hasNegationUnder: (dirRelPath) => {
504
- const prefix = dirRelPath + '/';
505
- for (const rule of allRules) {
506
- if (!rule.negate) continue;
507
- if (rule.regex.test(prefix + 'x') || rule.regex.test(prefix + 'x.js')) return true;
508
- if (rule.regex.source.includes(dirRelPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) return true;
509
- }
510
- return false;
511
- }
512
- };
513
- }
514
-
515
- _parseGitignore(content, prefix) {
516
- const rules = [];
517
- for (const rawLine of content.split('\n')) {
518
- let line = rawLine.trim();
519
- if (!line || line.startsWith('#')) continue;
520
-
521
- let negate = false;
522
- if (line.startsWith('!')) {
523
- negate = true;
524
- line = line.slice(1);
525
- }
526
-
527
- line = line.replace(/\\(\s)$/, '$1');
528
-
529
- let dirOnly = false;
530
- if (line.endsWith('/')) {
531
- dirOnly = true;
532
- line = line.slice(0, -1);
533
- }
534
-
535
- const hasSlash = line.includes('/');
536
- const rooted = line.startsWith('/');
537
- if (rooted) line = line.slice(1);
538
-
539
- let reStr;
540
- if (hasSlash || rooted) {
541
- reStr = '^' + prefix + this._gitignorePatternToRegex(line) + '(/.*)?$';
542
- } else {
543
- reStr = '(^|.+/)' + prefix + this._gitignorePatternToRegex(line) + '(/.*)?$';
544
- }
545
-
546
- try {
547
- rules.push({ regex: new RegExp(reStr), negate, dirOnly });
548
- } catch {
549
- // skip invalid patterns
550
- }
551
- }
552
- return rules;
553
- }
554
-
555
- _gitignorePatternToRegex(pattern) {
556
- let re = '';
557
- let i = 0;
558
- while (i < pattern.length) {
559
- const ch = pattern[i];
560
- if (ch === '*' && pattern[i + 1] === '*') {
561
- if (pattern[i + 2] === '/') {
562
- re += '(.+/)?';
563
- i += 3;
564
- } else {
565
- re += '.*';
566
- i += 2;
567
- }
568
- } else if (ch === '*') {
569
- re += '[^/]*';
570
- i++;
571
- } else if (ch === '?') {
572
- re += '[^/]';
573
- i++;
574
- } else if (ch === '[') {
575
- const end = pattern.indexOf(']', i + 1);
576
- if (end === -1) { re += '\\['; i++; }
577
- else { re += pattern.slice(i, end + 1); i = end + 1; }
578
- } else if ('.+^${}()|\\'.includes(ch)) {
579
- re += '\\' + ch;
580
- i++;
581
- } else {
582
- re += ch;
583
- i++;
584
- }
585
- }
586
- return re;
587
- }
588
-
589
- // ── Language detection ────────────────────────────────────────────────────
590
-
591
- _langOf(file) {
592
- const ext = path.extname(file).toLowerCase();
593
- if (ext === '.py') return 'python';
594
- if (['.c', '.h', '.cc', '.cpp', '.cxx', '.hpp', '.hh', '.hxx'].includes(ext)) return 'c';
595
- return 'js';
596
- }
597
-
598
- // ── JS / TS parser ────────────────────────────────────────────────────────
599
- //
600
- // No-regex implementation. Pattern matchers below each return either
601
- // `true`/`false` or `null`/idx, using the primitives from
602
- // parserHelpers.js. This trades five lines of regex for ten lines of
603
- // explicit cursor-walking, but the result is far more debuggable and
604
- // every behavior is locked by parserHelpers.test.js + codeMapTool.test.js.
605
-
606
- // Modifier keywords that can precede a class-method name (used by the
607
- // "method inside class" matcher).
608
- static _METHOD_MODIFIERS = new Set([
609
- 'private', 'protected', 'public', 'readonly', 'abstract',
610
- 'override', 'async', 'static', 'get', 'set',
611
- ]);
612
-
613
- // Reserved words that look like methods but aren't — `if (x) {` etc.
614
- static _METHOD_KEYWORD_EXCLUDE = new Set([
615
- 'if', 'for', 'while', 'switch', 'catch', 'return', 'throw', 'new',
616
- 'typeof', 'delete', 'void', 'await', 'class', 'function',
617
- 'const', 'let', 'var', 'super', 'this',
618
- ]);
619
-
620
- /**
621
- * Does the RHS of an assignment (starting at idx) look like a function
622
- * or arrow expression? Recognizes:
623
- * function
624
- * async function …
625
- * () => … or (a, b) =>
626
- * ident => … or async ident => …
627
- *
628
- * Returns true on hit. Allocation-free.
629
- */
630
- _rhsLooksLikeFunction(s, idx) {
631
- let i = skipWhitespace(s, idx);
632
- // optional `async`
633
- const afterAsync = consumeKeyword(s, 'async', i);
634
- if (afterAsync !== -1) i = skipWhitespace(s, afterAsync);
635
- if (startsWithKeyword(s, 'function', i)) return true;
636
-
637
- // `(…) =>` — find a balanced paren run, then `=>`
638
- if (s[i] === '(') {
639
- let depth = 1; let j = i + 1;
640
- while (j < s.length && depth > 0) {
641
- if (s[j] === '(') depth += 1;
642
- else if (s[j] === ')') depth -= 1;
643
- j += 1;
644
- }
645
- const k = skipWhitespace(s, j);
646
- if (s[k] === '=' && s[k + 1] === '>') return true;
647
- return false;
648
- }
649
-
650
- // `ident =>`
651
- const id = readIdent(s, i);
652
- if (id) {
653
- const k = skipWhitespace(s, id.nextIdx);
654
- if (s[k] === '=' && s[k + 1] === '>') return true;
655
- }
656
- return false;
657
- }
658
-
659
- /** Line is `import ...` (ESM). */
660
- _isImportLine(raw) {
661
- const i = skipWhitespace(raw, 0);
662
- return startsWithKeyword(raw, 'import', i);
663
- }
664
- /** Line contains `require(` as a whole word call. */
665
- _hasRequireCall(raw) {
666
- const i = indexOfWord(raw, 'require');
667
- if (i === -1) return false;
668
- const j = skipWhitespace(raw, i + 'require'.length);
669
- return raw[j] === '(';
670
- }
671
-
672
- /** Walks across the prefix keywords `export default abstract async ...`. */
673
- _skipDecoratorPrefix(raw) {
674
- let i = skipWhitespace(raw, 0);
675
- // export, then optional `default`
676
- const afterExport = consumeKeyword(raw, 'export', i);
677
- if (afterExport !== -1) i = skipWhitespace(raw, afterExport);
678
- const afterDefault = consumeKeyword(raw, 'default', i);
679
- if (afterDefault !== -1) i = skipWhitespace(raw, afterDefault);
680
- const afterAbstract = consumeKeyword(raw, 'abstract', i);
681
- if (afterAbstract !== -1) i = skipWhitespace(raw, afterAbstract);
682
- const afterAsync = consumeKeyword(raw, 'async', i);
683
- if (afterAsync !== -1) i = skipWhitespace(raw, afterAsync);
684
- return i;
685
- }
686
-
687
- /**
688
- * The line starts with one of these declaration keywords (after the
689
- * optional `export [default] [abstract] [async]` prefix run):
690
- * function | class | const | let | var | enum | interface | type
691
- *
692
- * Returns true if so.
693
- */
694
- _isExportableDecl(raw) {
695
- const i = this._skipDecoratorPrefix(raw);
696
- return (
697
- startsWithKeyword(raw, 'function', i) ||
698
- startsWithKeyword(raw, 'class', i) ||
699
- startsWithKeyword(raw, 'const', i) ||
700
- startsWithKeyword(raw, 'let', i) ||
701
- startsWithKeyword(raw, 'var', i) ||
702
- startsWithKeyword(raw, 'enum', i) ||
703
- startsWithKeyword(raw, 'interface', i) ||
704
- startsWithKeyword(raw, 'type', i) ||
705
- startsWithKeyword(raw, 'abstract', i) // bare `export abstract foo`
706
- );
707
- }
708
-
709
- /** `module.exports = …` (whole or property: `module.exports.foo = …`). */
710
- _isModuleExportsAssign(raw) {
711
- const i = skipWhitespace(raw, 0);
712
- if (!startsWithKeyword(raw, 'module', i)) return false;
713
- let j = i + 'module'.length;
714
- if (raw[j] !== '.') return false;
715
- j += 1;
716
- if (!startsWithKeyword(raw, 'exports', j)) return false;
717
- j += 'exports'.length;
718
- // optional `.ident`
719
- if (raw[j] === '.') {
720
- const id = readIdent(raw, j + 1);
721
- if (!id) return false;
722
- j = id.nextIdx;
723
- }
724
- j = skipWhitespace(raw, j);
725
- return raw[j] === '=' && raw[j + 1] !== '=';
726
- }
727
-
728
- /** `exports.foo = …` */
729
- _isExportsAssign(raw) {
730
- const i = skipWhitespace(raw, 0);
731
- if (!startsWithKeyword(raw, 'exports', i)) return false;
732
- let j = i + 'exports'.length;
733
- if (raw[j] !== '.') return false;
734
- const id = readIdent(raw, j + 1);
735
- if (!id) return false;
736
- j = skipWhitespace(raw, id.nextIdx);
737
- return raw[j] === '=' && raw[j + 1] !== '=';
738
- }
739
-
740
- /** `(async) function name(…)` top-level form. */
741
- _isTopLevelFunctionDecl(raw) {
742
- let i = skipWhitespace(raw, 0);
743
- const aa = consumeKeyword(raw, 'async', i);
744
- if (aa !== -1) i = skipWhitespace(raw, aa);
745
- if (!startsWithKeyword(raw, 'function', i)) return false;
746
- i = skipWhitespace(raw, i + 'function'.length);
747
- return readIdent(raw, i) !== null;
748
- }
749
-
750
- /** `(abstract) class Name`. */
751
- _isTopLevelClassDecl(raw) {
752
- let i = skipWhitespace(raw, 0);
753
- const aa = consumeKeyword(raw, 'abstract', i);
754
- if (aa !== -1) i = skipWhitespace(raw, aa);
755
- if (!startsWithKeyword(raw, 'class', i)) return false;
756
- i = skipWhitespace(raw, i + 'class'.length);
757
- return readIdent(raw, i) !== null;
758
- }
759
-
760
- /** `const|let|var name = <function-ish>`. */
761
- _isConstAssignedFunction(raw) {
762
- let i = skipWhitespace(raw, 0);
763
- const dk = consumeAnyKeyword(raw, ['const', 'let', 'var'], i);
764
- if (!dk) return false;
765
- i = skipWhitespace(raw, dk.nextIdx);
766
- const id = readIdent(raw, i);
767
- if (!id) return false;
768
- i = skipWhitespace(raw, id.nextIdx);
769
- if (raw[i] !== '=' || raw[i + 1] === '=' || raw[i + 1] === '>') return false;
770
- return this._rhsLooksLikeFunction(raw, i + 1);
771
- }
772
-
773
- /** Class-with-an-opening-brace check for the brace stack. */
774
- _isClassDeclWithOpenBrace(raw, opens) {
775
- if (opens <= 0) return false;
776
- let i = skipWhitespace(raw, 0);
777
- const ex = consumeKeyword(raw, 'export', i);
778
- if (ex !== -1) i = skipWhitespace(raw, ex);
779
- const df = consumeKeyword(raw, 'default', i);
780
- if (df !== -1) i = skipWhitespace(raw, df);
781
- const ab = consumeKeyword(raw, 'abstract', i);
782
- if (ab !== -1) i = skipWhitespace(raw, ab);
783
- return startsWithKeyword(raw, 'class', i);
784
- }
785
-
786
- /** IIFE prefix-op form: `!function`, `+function`, `~function`, `-function`. */
787
- _isIifePrefixOp(raw) {
788
- const i = skipWhitespace(raw, 0);
789
- const ch = raw[i];
790
- if (ch !== '!' && ch !== '+' && ch !== '~' && ch !== '-') return false;
791
- let j = skipWhitespace(raw, i + 1);
792
- const aa = consumeKeyword(raw, 'async', j);
793
- if (aa !== -1) j = skipWhitespace(raw, aa);
794
- return startsWithKeyword(raw, 'function', j);
795
- }
796
-
797
- /** IIFE paren-wrapped form: `(function ...)` or `(async function ...)`. */
798
- _isIifeParens(raw) {
799
- const i = skipWhitespace(raw, 0);
800
- if (raw[i] !== '(') return false;
801
- let j = skipWhitespace(raw, i + 1);
802
- const aa = consumeKeyword(raw, 'async', j);
803
- if (aa !== -1) j = skipWhitespace(raw, aa);
804
- return startsWithKeyword(raw, 'function', j);
805
- }
806
-
807
- /**
808
- * Global/namespace assignment of a function-ish value:
809
- * window.X.Y = function / globalThis.X = () => / MyApp.foo = …
810
- * Conditions:
811
- * - LHS head is `window|globalThis|self` (and optional dotted tail)
812
- * OR a TitleCase identifier followed by AT LEAST one dotted tail
813
- * segment (so we don't grab bare `Foo = …`)
814
- * - RHS is function or arrow
815
- */
816
- _isGlobalAssign(raw) {
817
- let i = skipWhitespace(raw, 0);
818
- const headKw = consumeAnyKeyword(raw, ['window', 'globalThis', 'self'], i);
819
- let after;
820
- let hadGlobalHead = false;
821
- if (headKw) {
822
- hadGlobalHead = true;
823
- after = consumeDottedAssignTail(raw, headKw.nextIdx);
824
- } else {
825
- // TitleCase ident + at least one dotted tail
826
- const id = readIdent(raw, i);
827
- if (!id) return false;
828
- // TitleCase = uppercase first char
829
- const first = id.ident[0];
830
- if (first < 'A' || first > 'Z') return false;
831
- // Need at least one '.ident' before '='
832
- if (raw[id.nextIdx] !== '.') return false;
833
- after = consumeDottedAssignTail(raw, id.nextIdx);
834
- }
835
- if (after === -1) return false;
836
- return this._rhsLooksLikeFunction(raw, after);
837
- }
838
-
839
- /**
840
- * Prototype-method assignment: `Foo.prototype.bar = function|=>…`.
841
- * Note: leverages `_isGlobalAssign`'s tail logic but requires
842
- * `.prototype.<ident>` somewhere in the LHS.
843
- */
844
- _isPrototypeAssign(raw) {
845
- let i = skipWhitespace(raw, 0);
846
- const id = readIdent(raw, i);
847
- if (!id) return false;
848
- let j = id.nextIdx;
849
- // walk `.ident*` until we hit `.prototype`
850
- let sawPrototype = false;
851
- while (raw[j] === '.') {
852
- // peek the next ident
853
- if (startsWithKeyword(raw, 'prototype', j + 1)) {
854
- sawPrototype = true;
855
- j = j + 1 + 'prototype'.length;
856
- break;
857
- }
858
- const more = readIdent(raw, j + 1);
859
- if (!more) return false;
860
- j = more.nextIdx;
861
- }
862
- if (!sawPrototype) return false;
863
- // Need `.ident` + `=` + function-ish
864
- if (raw[j] !== '.') return false;
865
- const methodId = readIdent(raw, j + 1);
866
- if (!methodId) return false;
867
- j = skipWhitespace(raw, methodId.nextIdx);
868
- if (raw[j] !== '=' || raw[j + 1] === '=') return false;
869
- return this._rhsLooksLikeFunction(raw, j + 1);
870
- }
871
-
872
- /** jQuery-style plugin: `$.fn.X = function …` / `jQuery.fn.X = function …`. */
873
- _isJqueryPluginAssign(raw) {
874
- let i = skipWhitespace(raw, 0);
875
- let afterHead;
876
- if (raw[i] === '$') {
877
- afterHead = i + 1;
878
- } else if (startsWithKeyword(raw, 'jQuery', i)) {
879
- afterHead = i + 'jQuery'.length;
880
- } else {
881
- return false;
882
- }
883
- if (raw[afterHead] !== '.') return false;
884
- if (!startsWithKeyword(raw, 'fn', afterHead + 1)) return false;
885
- let j = afterHead + 1 + 'fn'.length;
886
- if (raw[j] !== '.') return false;
887
- const id = readIdent(raw, j + 1);
888
- if (!id) return false;
889
- j = skipWhitespace(raw, id.nextIdx);
890
- if (raw[j] !== '=' || raw[j + 1] === '=') return false;
891
- // We only insist on `function` here (matches the original regex).
892
- let r = skipWhitespace(raw, j + 1);
893
- const aa = consumeKeyword(raw, 'async', r);
894
- if (aa !== -1) r = skipWhitespace(raw, aa);
895
- return startsWithKeyword(raw, 'function', r);
896
- }
897
-
898
- /** `document.addEventListener(` or `window.addEventListener(`. */
899
- _isAddEventListenerCall(raw) {
900
- const i = skipWhitespace(raw, 0);
901
- const head = consumeAnyKeyword(raw, ['document', 'window'], i);
902
- if (!head) return false;
903
- let j = head.nextIdx;
904
- if (raw[j] !== '.') return false;
905
- if (!startsWithKeyword(raw, 'addEventListener', j + 1)) return false;
906
- j = skipWhitespace(raw, j + 1 + 'addEventListener'.length);
907
- return raw[j] === '(';
908
- }
909
-
910
- /**
911
- * Method inside a class. Caller already established `classDepth > 0`
912
- * and `braceDepthBeforeLine === methodDepth`. We just decide if the
913
- * line LOOKS like a method declaration vs a method call.
914
- *
915
- * Returns the method name or null.
916
- */
917
- _detectMethodName(raw) {
918
- // Must start with at least one space (methods are indented).
919
- if (raw[0] !== ' ' && raw[0] !== '\t') return null;
920
- let i = skipWhitespace(raw, 0);
921
- // Walk through any sequence of modifier keywords.
922
- while (true) {
923
- const id = readIdent(raw, i);
924
- if (!id) return null;
925
- if (!CodeMapTool._METHOD_MODIFIERS.has(id.ident)) {
926
- // This is the method name candidate.
927
- const afterName = skipWhitespace(raw, id.nextIdx);
928
- if (raw[afterName] !== '(' && raw[afterName] !== '<') return null;
929
- // Excluded reserved words (`if`, `for`, )
930
- if (CodeMapTool._METHOD_KEYWORD_EXCLUDE.has(id.ident)) return null;
931
- // Is this a call rather than a definition?
932
- // Calls end with `)` (optionally `;`) and have NO `) {` or `) : …`.
933
- const trimmed = raw.trim();
934
- const endsWithClose = trimmedEndsWith(trimmed, ')');
935
- const endsWithCloseBrace = trimmedEndsWith(trimmed, ') {');
936
- // `): Type` — a TS-style return-type annotation continuation
937
- const hasReturnTypeAnnotation = (() => {
938
- // Walk past the open paren to find the matching `)`, then check
939
- // if the next non-ws char is `:` followed by a non-empty token.
940
- let k = afterName + 1;
941
- let depth = 1;
942
- while (k < raw.length && depth > 0) {
943
- if (raw[k] === '(') depth += 1;
944
- else if (raw[k] === ')') depth -= 1;
945
- k += 1;
946
- }
947
- k = skipWhitespace(raw, k);
948
- return raw[k] === ':' && raw[k + 1] !== '\0' && raw[k + 1] !== undefined;
949
- })();
950
- if (endsWithClose && !endsWithCloseBrace && !hasReturnTypeAnnotation) return null;
951
- return id.ident;
952
- }
953
- // It WAS a modifier — consume + continue.
954
- i = skipWhitespace(raw, id.nextIdx);
955
- }
956
- }
957
-
958
- _parseJS(lines, { publicOnly, withComments, includeImports }) {
959
- const entries = [];
960
- let pendingComments = [];
961
- let inBlockComment = false;
962
- let blockCommentLines = [];
963
-
964
- let classDepth = 0;
965
- let braceStack = [];
966
- let braceDepth = 0;
967
- let methodDepths = [];
968
-
969
- for (let i = 0; i < lines.length; i += 1) {
970
- const lineNum = i + 1;
971
- const raw = lines[i];
972
- const start = skipWhitespace(raw, 0);
973
-
974
- // block-comment tracking
975
- if (inBlockComment) {
976
- blockCommentLines.push({ line: lineNum, text: raw, kind: 'comment' });
977
- if (raw.includes('*/')) {
978
- inBlockComment = false;
979
- pendingComments = blockCommentLines;
980
- blockCommentLines = [];
981
- }
982
- continue;
983
- }
984
- if (raw[start] === '/' && raw[start + 1] === '*') {
985
- blockCommentLines = [{ line: lineNum, text: raw, kind: 'comment' }];
986
- if (raw.slice(start + 2).includes('*/')) {
987
- pendingComments = blockCommentLines;
988
- blockCommentLines = [];
989
- } else {
990
- inBlockComment = true;
991
- }
992
- continue;
993
- }
994
- if (raw[start] === '/' && raw[start + 1] === '/') {
995
- pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
996
- continue;
997
- }
998
-
999
- // imports
1000
- if (includeImports) {
1001
- if (this._isImportLine(raw) || this._hasRequireCall(raw)) {
1002
- entries.push({ line: lineNum, text: raw, kind: 'import' });
1003
- pendingComments = [];
1004
- continue;
1005
- }
1006
- }
1007
-
1008
- // Brace tracking for class context — mask strings + line comments
1009
- // before counting so e.g. `var s = "class Foo {"` doesn't push a
1010
- // bogus class frame.
1011
- const masked = stripStringsAndLineComments(raw);
1012
- const opens = countChar(masked, '{');
1013
- const closes = countChar(masked, '}');
1014
- const braceDepthBeforeLine = braceDepth;
1015
-
1016
- if (this._isClassDeclWithOpenBrace(raw, opens)) {
1017
- braceStack.push({ depth: braceDepth, type: 'class' });
1018
- methodDepths.push(braceDepth + 1);
1019
- classDepth += 1;
1020
- }
1021
- braceDepth += opens - closes;
1022
- while (braceStack.length > 0 && braceDepth <= braceStack[braceStack.length - 1].depth) {
1023
- const popped = braceStack.pop();
1024
- if (popped.type === 'class') {
1025
- classDepth -= 1;
1026
- methodDepths.pop();
1027
- }
1028
- }
1029
-
1030
- // Signature detection
1031
- const isExport = startsWithKeyword(raw, 'export', start);
1032
- const isModuleExports = this._isModuleExportsAssign(raw);
1033
- const isExportsAssign = this._isExportsAssign(raw);
1034
- const isPublic = isExport || isModuleExports || isExportsAssign;
1035
-
1036
- let matched = false;
1037
-
1038
- if (isExport && this._isExportableDecl(raw)) matched = true;
1039
- if (isModuleExports || isExportsAssign) matched = true;
1040
-
1041
- if (!matched && !publicOnly) {
1042
- if (this._isTopLevelFunctionDecl(raw)) matched = true;
1043
- if (this._isTopLevelClassDecl(raw)) matched = true;
1044
- if (this._isConstAssignedFunction(raw)) matched = true;
1045
-
1046
- // Browser-JS / pre-ESM patterns
1047
- if (this._isIifePrefixOp(raw)) matched = true;
1048
- if (this._isIifeParens(raw)) matched = true;
1049
- if (this._isGlobalAssign(raw)) matched = true;
1050
- if (this._isPrototypeAssign(raw)) matched = true;
1051
- if (this._isJqueryPluginAssign(raw)) matched = true;
1052
- if (this._isAddEventListenerCall(raw)) matched = true;
1053
-
1054
- // Method inside class
1055
- const atMethodDepth = methodDepths.length > 0 &&
1056
- braceDepthBeforeLine === methodDepths[methodDepths.length - 1];
1057
- if (classDepth > 0 && atMethodDepth && !isExport) {
1058
- if (this._detectMethodName(raw)) matched = true;
1059
- }
1060
- }
1061
-
1062
- if (!matched && isPublic) matched = true;
1063
- if (matched && publicOnly && !isPublic) {
1064
- pendingComments = [];
1065
- continue;
1066
- }
1067
-
1068
- if (matched) {
1069
- if (withComments && pendingComments.length > 0) {
1070
- for (const c of pendingComments) entries.push(c);
1071
- }
1072
- entries.push({ line: lineNum, text: raw, kind: 'signature' });
1073
- pendingComments = [];
1074
- } else {
1075
- pendingComments = [];
1076
- }
1077
- }
1078
- return entries;
1079
- }
1080
-
1081
- // ── Python parser ─────────────────────────────────────────────────────────
1082
- //
1083
- // No-regex implementation. Identifiers are ASCII per PEP 8's
1084
- // recommendation; this is a documented tradeoff vs. the previous
1085
- // Unicode-aware (\p{L}) regex. The alternative — bundling a Unicode
1086
- // category table adds ~50 KB for a feature ~0.1 % of Python
1087
- // codebases use. If users surface a non-ASCII-identifier Python
1088
- // codebase the fix is to bring in tree-sitter (already planned for
1089
- // #5 follow-on) which is Unicode-correct by default.
1090
-
1091
- /** Try to read a `def` or `async def` head. Returns the function name or null. */
1092
- _readPythonDefName(raw) {
1093
- let i = skipWhitespace(raw, 0);
1094
- // optional `async`
1095
- const aa = consumeKeyword(raw, 'async', i);
1096
- if (aa !== -1) i = skipWhitespace(raw, aa);
1097
- if (!startsWithKeyword(raw, 'def', i)) return null;
1098
- i = skipWhitespace(raw, i + 'def'.length);
1099
- const id = readIdent(raw, i);
1100
- if (!id) return null;
1101
- i = skipWhitespace(raw, id.nextIdx);
1102
- if (raw[i] !== '(') return null;
1103
- return id.ident;
1104
- }
1105
- /** Try to read a `class Foo` head. Returns class name or null. */
1106
- _readPythonClassName(raw) {
1107
- let i = skipWhitespace(raw, 0);
1108
- if (!startsWithKeyword(raw, 'class', i)) return null;
1109
- i = skipWhitespace(raw, i + 'class'.length);
1110
- const id = readIdent(raw, i);
1111
- return id ? id.ident : null;
1112
- }
1113
-
1114
- /**
1115
- * Returns true if `s` (at or after leading whitespace) starts with a
1116
- * Python triple-quote prefix (`"""` or `'''`). Returns null otherwise.
1117
- * On hit returns the 3-char quote string.
1118
- */
1119
- _pythonTripleQuoteAtStart(s) {
1120
- const i = skipWhitespace(s, 0);
1121
- if (s[i] === '"' && s[i + 1] === '"' && s[i + 2] === '"') return '"""';
1122
- if (s[i] === "'" && s[i + 1] === "'" && s[i + 2] === "'") return "'''";
1123
- return null;
1124
- }
1125
- /** True if the single-line docstring on a triple-quote line is closed on the same line. */
1126
- _pythonTripleQuoteClosedOnLine(s, quote) {
1127
- const i = skipWhitespace(s, 0);
1128
- return s.indexOf(quote, i + 3) !== -1;
1129
- }
1130
-
1131
- _parsePython(lines, { publicOnly, withComments, includeImports }) {
1132
- const entries = [];
1133
- let pendingComments = [];
1134
- let i = 0;
1135
-
1136
- while (i < lines.length) {
1137
- const lineNum = i + 1;
1138
- const raw = lines[i];
1139
- const start = skipWhitespace(raw, 0);
1140
-
1141
- // imports — `import …` or `from <module> import …`
1142
- if (includeImports) {
1143
- if (startsWithKeyword(raw, 'import', start)) {
1144
- entries.push({ line: lineNum, text: raw, kind: 'import' });
1145
- pendingComments = [];
1146
- i += 1; continue;
1147
- }
1148
- if (startsWithKeyword(raw, 'from', start)) {
1149
- // require a non-empty module then `import`
1150
- let j = skipWhitespace(raw, start + 'from'.length);
1151
- // Read the dotted module path; allow leading dots (relative).
1152
- let progressed = false;
1153
- while (j < raw.length && raw[j] === '.') { j += 1; progressed = true; }
1154
- const id = readIdent(raw, j);
1155
- if (id) { j = id.nextIdx; progressed = true; }
1156
- while (raw[j] === '.') {
1157
- const more = readIdent(raw, j + 1);
1158
- if (!more) { progressed = true; break; }
1159
- j = more.nextIdx;
1160
- }
1161
- j = skipWhitespace(raw, j);
1162
- if (progressed && startsWithKeyword(raw, 'import', j)) {
1163
- entries.push({ line: lineNum, text: raw, kind: 'import' });
1164
- pendingComments = [];
1165
- i += 1; continue;
1166
- }
1167
- }
1168
- }
1169
-
1170
- // # comments
1171
- if (raw[start] === '#') {
1172
- pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1173
- i += 1; continue;
1174
- }
1175
- // decorators
1176
- if (raw[start] === '@') {
1177
- pendingComments.push({ line: lineNum, text: raw, kind: 'decorator' });
1178
- i += 1; continue;
1179
- }
1180
-
1181
- // def / async def / class
1182
- const defName = this._readPythonDefName(raw);
1183
- const className = defName ? null : this._readPythonClassName(raw);
1184
- const name = defName || className;
1185
-
1186
- if (name) {
1187
- const isPublic = !name.startsWith('_');
1188
-
1189
- // Helper: continuation lines for a multi-line signature like
1190
- // def foo(
1191
- // x: int,
1192
- // y: int,
1193
- // ) -> int:
1194
- // The original advanced past the line that closes with `):` or
1195
- // a line starting with `)` containing `:`. Preserve that logic.
1196
- const advancePastMultilineSig = () => {
1197
- // Only def's have parameter parens; class doesn't.
1198
- if (!defName) return;
1199
- if (raw.includes('):')) return;
1200
- if (raw.includes(')')) return;
1201
- // walk forward
1202
- while (i < lines.length) {
1203
- const pline = lines[i].trim();
1204
- i += 1;
1205
- if (pline.includes('):') ||
1206
- (pline.startsWith(')') && pline.includes(':'))) break;
1207
- }
1208
- };
1209
-
1210
- if (publicOnly && !isPublic) {
1211
- pendingComments = [];
1212
- i += 1;
1213
- advancePastMultilineSig();
1214
- i = this._skipPythonDocstring(lines, i);
1215
- continue;
1216
- }
1217
-
1218
- if (withComments && pendingComments.length > 0) {
1219
- for (const c of pendingComments) entries.push(c);
1220
- }
1221
- entries.push({ line: lineNum, text: raw, kind: 'signature' });
1222
- pendingComments = [];
1223
- i += 1;
1224
- advancePastMultilineSig();
1225
-
1226
- if (withComments) {
1227
- const dsLines = this._collectPythonDocstring(lines, i);
1228
- for (const ds of dsLines) entries.push(ds);
1229
- i += dsLines.length;
1230
- } else {
1231
- i = this._skipPythonDocstring(lines, i);
1232
- }
1233
- continue;
1234
- }
1235
-
1236
- // Top-level assignments (data lines):
1237
- // ident = expr (only when starting at column 0; indented
1238
- // lines are method-internal and ignored)
1239
- if (raw.length > 0 && !isWhitespace(raw[0]) && raw[0] !== '\n') {
1240
- const id = readIdent(raw, 0);
1241
- if (id) {
1242
- let j = skipWhitespace(raw, id.nextIdx);
1243
- if (raw[j] === '=' && raw[j + 1] !== '=') {
1244
- // looks like `ident = …`
1245
- // require there to BE an RHS — `name =` alone is invalid
1246
- const afterEq = skipWhitespace(raw, j + 1);
1247
- if (afterEq < raw.length) {
1248
- const isPublicVar = !id.ident.startsWith('_');
1249
- if (!publicOnly || isPublicVar) {
1250
- if (withComments && pendingComments.length > 0) {
1251
- for (const c of pendingComments) entries.push(c);
1252
- }
1253
- entries.push({ line: lineNum, text: raw, kind: 'data' });
1254
- }
1255
- pendingComments = [];
1256
- i += 1; continue;
1257
- }
1258
- }
1259
- }
1260
- }
1261
-
1262
- pendingComments = [];
1263
- i += 1;
1264
- }
1265
-
1266
- return entries;
1267
- }
1268
-
1269
- _collectPythonDocstring(lines, startIdx) {
1270
- if (startIdx >= lines.length) return [];
1271
- const first = lines[startIdx];
1272
- const quote = this._pythonTripleQuoteAtStart(first);
1273
- if (!quote) return [];
1274
- // single-line case: triple-quote opens AND closes on the same line
1275
- if (this._pythonTripleQuoteClosedOnLine(first, quote)) {
1276
- return [{ line: startIdx + 1, text: first, kind: 'comment' }];
1277
- }
1278
- // multi-line: collect until the closing triple-quote
1279
- const result = [{ line: startIdx + 1, text: first, kind: 'comment' }];
1280
- for (let j = startIdx + 1; j < lines.length; j += 1) {
1281
- result.push({ line: j + 1, text: lines[j], kind: 'comment' });
1282
- if (lines[j].includes(quote)) break;
1283
- }
1284
- return result;
1285
- }
1286
-
1287
- _skipPythonDocstring(lines, startIdx) {
1288
- if (startIdx >= lines.length) return startIdx;
1289
- const first = lines[startIdx];
1290
- const quote = this._pythonTripleQuoteAtStart(first);
1291
- if (!quote) return startIdx;
1292
- if (this._pythonTripleQuoteClosedOnLine(first, quote)) return startIdx + 1;
1293
- for (let j = startIdx + 1; j < lines.length; j += 1) {
1294
- if (lines[j].includes(quote)) return j + 1;
1295
- }
1296
- return lines.length;
1297
- }
1298
-
1299
- // ── C / C++ parser ────────────────────────────────────────────────────────
1300
- //
1301
- // Regex-based, same approach as the JS path: walk lines, match a set
1302
- // of declaration patterns. Mirrors what _parseJS does for JS — gets
1303
- // the agent a high-level skeleton of any C/CPP file (functions,
1304
- // classes/structs, includes, top-level macros) without bringing in
1305
- // a full C parser. The proper tree-sitter migration (see #5 plan)
1306
- // replaces this for full accuracy on edge cases like multi-line
1307
- // function signatures, templated declarations, and trailing return
1308
- // types those work in 90 % of real code already because most
1309
- // declarations fit on one line.
1310
- //
1311
- // Patterns covered:
1312
- // - #include <...> / #include "..." → 'import' kind
1313
- // - #define MACRO ... → 'signature' kind
1314
- // - class Foo { → 'signature'
1315
- // - struct Foo { → 'signature'
1316
- // - enum class Foo { → 'signature'
1317
- // - namespace foo { → 'signature'
1318
- // - typedef … name; → 'signature'
1319
- // - using name = …; → 'signature'
1320
- // - free-function: `<type> name(args) {` → 'signature'
1321
- // - method: `<type> Class::name(args) {` → 'signature'
1322
- // - constructor: `Class::Class(args) {` → 'signature'
1323
- // - destructor: `Class::~Class()` → 'signature'
1324
- //
1325
- // Skipped (intentional):
1326
- // - Function prototypes (declarations w/o body) — too noisy in
1327
- // headers; the user usually wants the definitions
1328
- // - Lambdas covered by treating the enclosing function as the
1329
- // signature
1330
- _parseC(lines, { publicOnly, withComments, includeImports }) {
1331
- const entries = [];
1332
- let pendingComments = [];
1333
- let inBlockComment = false;
1334
-
1335
- // C/C++ tokens we accept at the head of a function/type declaration.
1336
- // The classifier walks them one at a time using consumeKeyword —
1337
- // no regex. After the keyword run we require at least one
1338
- // additional token before the `(` so we don't misidentify a bare
1339
- // function call as a definition.
1340
- const C_TYPE_KEYWORDS = [
1341
- 'static', 'inline', 'extern', 'constexpr', 'virtual', 'explicit',
1342
- 'friend', 'const', 'mutable', 'register', 'volatile',
1343
- 'signed', 'unsigned', 'short', 'long',
1344
- // Built-in primitive types — frequently followed by an ident.
1345
- 'void', 'bool', 'char', 'int', 'float', 'double',
1346
- 'auto', 'wchar_t', 'char16_t', 'char32_t',
1347
- ];
1348
-
1349
- for (let i = 0; i < lines.length; i += 1) {
1350
- const lineNum = i + 1;
1351
- const raw = lines[i];
1352
- const start = skipWhitespace(raw, 0);
1353
-
1354
- // ── block + line comments ────────────────────────────────────
1355
- if (inBlockComment) {
1356
- if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1357
- if (raw.includes('*/')) inBlockComment = false;
1358
- continue;
1359
- }
1360
- if (raw[start] === '/' && raw[start + 1] === '*') {
1361
- if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1362
- // The block-comment scan checks anywhere on the line; templates
1363
- // / preproc lines don't typically contain '*/' so this is safe.
1364
- if (!raw.slice(start + 2).includes('*/')) inBlockComment = true;
1365
- continue;
1366
- }
1367
- if (raw[start] === '/' && raw[start + 1] === '/') {
1368
- if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1369
- continue;
1370
- }
1371
-
1372
- // ── #include ─────────────────────────────────────────────────
1373
- if (includeImports && raw[start] === '#') {
1374
- const afterHash = skipWhitespace(raw, start + 1);
1375
- if (startsWithKeyword(raw, 'include', afterHash)) {
1376
- entries.push({ line: lineNum, text: raw, kind: 'import' });
1377
- pendingComments = [];
1378
- continue;
1379
- }
1380
- }
1381
-
1382
- let matched = false;
1383
-
1384
- // ── #define MACRO … ──────────────────────────────────────────
1385
- if (raw[start] === '#') {
1386
- const afterHash = skipWhitespace(raw, start + 1);
1387
- if (startsWithKeyword(raw, 'define', afterHash)) {
1388
- const afterKw = skipWhitespace(raw, afterHash + 'define'.length);
1389
- if (readIdent(raw, afterKw)) matched = true;
1390
- }
1391
- }
1392
-
1393
- // ── namespace / class / struct / union / enum (incl. `enum class`) ──
1394
- // Tip: also handle a leading `template <…>` by skipping over it.
1395
- let probeIdx = start;
1396
- if (startsWithKeyword(raw, 'template', probeIdx)) {
1397
- // skip "template" + optional whitespace + `<…>` if present
1398
- let j = skipWhitespace(raw, probeIdx + 'template'.length);
1399
- if (raw[j] === '<') {
1400
- let depth = 1;
1401
- j += 1;
1402
- while (j < raw.length && depth > 0) {
1403
- if (raw[j] === '<') depth += 1;
1404
- else if (raw[j] === '>') depth -= 1;
1405
- j += 1;
1406
- }
1407
- probeIdx = skipWhitespace(raw, j);
1408
- }
1409
- }
1410
- const typeHit = consumeAnyKeyword(raw, ['class', 'struct', 'union', 'enum', 'namespace'], probeIdx);
1411
- if (typeHit) {
1412
- let after = skipWhitespace(raw, typeHit.nextIdx);
1413
- // `enum class Color` — accept either form
1414
- if (typeHit.keyword === 'enum' && startsWithKeyword(raw, 'class', after)) {
1415
- after = skipWhitespace(raw, after + 'class'.length);
1416
- }
1417
- if (readIdent(raw, after)) matched = true;
1418
- }
1419
-
1420
- // ── typedef … name; ──────────────────────────────────────────
1421
- if (!matched && startsWithKeyword(raw, 'typedef', start)) {
1422
- // very permissive just require a `;` somewhere after typedef
1423
- if (raw.indexOf(';', start) !== -1) matched = true;
1424
- }
1425
-
1426
- // ── using name = ───────────────────────────────────────────
1427
- if (!matched && startsWithKeyword(raw, 'using', start)) {
1428
- const afterKw = skipWhitespace(raw, start + 'using'.length);
1429
- const id = readIdent(raw, afterKw);
1430
- if (id) {
1431
- const afterId = skipWhitespace(raw, id.nextIdx);
1432
- if (raw[afterId] === '=' && raw[afterId + 1] !== '=') matched = true;
1433
- }
1434
- }
1435
-
1436
- // ── Function definition: <type-tokens> name(…) ───────────────
1437
- // Walk past at least one type-ish token before the function name.
1438
- // The "type-ish" tokens are either keywords from C_TYPE_KEYWORDS
1439
- // or any plain identifier (could be a user-defined type) — we
1440
- // require AT LEAST TWO tokens before the `(` so a bare call
1441
- // `foo(x)` isn't misidentified.
1442
- if (!matched) {
1443
- let j = probeIdx;
1444
- let tokensBeforeParen = 0;
1445
- let lastWasIdent = false;
1446
- while (j < raw.length) {
1447
- // pointer / reference / qualifiers in between tokens
1448
- if (raw[j] === '*' || raw[j] === '&') { j += 1; continue; }
1449
- j = skipWhitespace(raw, j);
1450
- if (j >= raw.length) break;
1451
- if (raw[j] === '(') break;
1452
- // accept a templated type like `vector<int>` — skip the `<…>`
1453
- if (raw[j] === '<' && lastWasIdent) {
1454
- let depth = 1; j += 1;
1455
- while (j < raw.length && depth > 0) {
1456
- if (raw[j] === '<') depth += 1;
1457
- else if (raw[j] === '>') depth -= 1;
1458
- j += 1;
1459
- }
1460
- continue;
1461
- }
1462
- // qualifier keyword?
1463
- const kw = consumeAnyKeyword(raw, C_TYPE_KEYWORDS, j);
1464
- if (kw) { j = kw.nextIdx; tokensBeforeParen += 1; lastWasIdent = false; continue; }
1465
- // plain identifier (possibly `Foo::bar`)
1466
- const id = readIdent(raw, j);
1467
- if (!id) break;
1468
- j = id.nextIdx;
1469
- // handle `Foo::bar` and similar
1470
- while (raw[j] === ':' && raw[j + 1] === ':') {
1471
- j += 2;
1472
- const more = readIdent(raw, j);
1473
- if (!more) break;
1474
- j = more.nextIdx;
1475
- }
1476
- tokensBeforeParen += 1;
1477
- lastWasIdent = true;
1478
- }
1479
- if (raw[j] === '(' && tokensBeforeParen >= 2) {
1480
- // Reject "looks like a call" `name(args);` ending tail.
1481
- // For a definition we expect either `) {` or just `,` (multi-line)
1482
- // or `)` ending the line. A call ends with `);`.
1483
- const endsWithCallSemi = trimmedEndsWith(raw, ');');
1484
- if (!endsWithCallSemi) matched = true;
1485
- }
1486
- }
1487
-
1488
- // ── Constructor / destructor: `Foo::Foo(...)` / `Foo::~Foo(...)` ──
1489
- if (!matched) {
1490
- const id1 = readIdent(raw, start);
1491
- if (id1 && raw[id1.nextIdx] === ':' && raw[id1.nextIdx + 1] === ':') {
1492
- let k = id1.nextIdx + 2;
1493
- if (raw[k] === '~') k += 1;
1494
- const id2 = readIdent(raw, k);
1495
- if (id2) {
1496
- const afterName = skipWhitespace(raw, id2.nextIdx);
1497
- if (raw[afterName] === '(') matched = true;
1498
- }
1499
- }
1500
- }
1501
-
1502
- // publicOnly is a no-op for C — left intact for API parity with
1503
- // _parseJS. Could later mean "header-public only".
1504
- void publicOnly;
1505
-
1506
- if (matched) {
1507
- if (withComments && pendingComments.length > 0) {
1508
- for (const c of pendingComments) entries.push(c);
1509
- }
1510
- entries.push({ line: lineNum, text: raw, kind: 'signature' });
1511
- pendingComments = [];
1512
- } else {
1513
- pendingComments = [];
1514
- }
1515
- }
1516
-
1517
- return entries;
1518
- }
1519
- }
1520
-
1521
- export default CodeMapTool;
1
+ /**
2
+ * @file src/tools/codeMapTool.js
3
+ * @description Code Map Tool — structural code exploration with multi-level skeleton extraction
4
+ * and line-range reading. Enables agents to understand large codebases without reading entire files.
5
+ *
6
+ * Workflow: skeleton scan → identify interesting areas → read-range for details
7
+ */
8
+
9
+ import { BaseTool } from './baseTool.js';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import {
13
+ isWhitespace,
14
+ skipWhitespace,
15
+ readIdent,
16
+ startsWithKeyword,
17
+ consumeKeyword,
18
+ consumeAnyKeyword,
19
+ trimmedStartsWithAny,
20
+ indexOfWord,
21
+ containsWord,
22
+ stripStringsAndLineComments,
23
+ countChar,
24
+ consumeDottedAssignTail,
25
+ trimmedEndsWith,
26
+ isIdentStart,
27
+ isWordChar,
28
+ } from './parserHelpers.js';
29
+
30
+ const CODE_MAP_CONFIG = {
31
+ SUPPORTED_EXTENSIONS: new Set([
32
+ '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.py',
33
+ // C / C++ via a focused regex pass — see _parseC below. The
34
+ // proper tree-sitter migration that would replace the JS + Python
35
+ // regex parsers also covers these and adds full accuracy; this
36
+ // pass is the v1 that unblocks `code-map` on C/CPP codebases today.
37
+ '.c', '.h', '.cc', '.cpp', '.cxx', '.hpp', '.hh', '.hxx',
38
+ ]),
39
+ MAX_FILES: 500,
40
+ MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB per file
41
+ MAX_READ_RANGE: 500, // Max lines per read-range call
42
+ MAX_DIRECTORY_DEPTH: 20,
43
+ VALID_LEVELS: ['A.0', 'A.1', 'B.0', 'B.1'],
44
+ DEFAULT_LEVEL: 'B.0',
45
+ SKIP_DIRS: new Set(['node_modules', '__pycache__', '.git', 'dist', 'build', 'coverage', '.next', '.nuxt'])
46
+ };
47
+
48
+ class CodeMapTool extends BaseTool {
49
+ constructor(config = {}, logger = null) {
50
+ super(config, logger);
51
+
52
+ this.id = 'code-map';
53
+ this.requiresProject = true;
54
+ this.isAsync = true;
55
+ this.timeout = config.timeout || 120000;
56
+ }
57
+
58
+ getDescription() {
59
+ return `Code Map Tool: Explore code structure without reading entire files. Two-step workflow:
60
+
61
+ 1. **skeleton** — Extract structural overview (signatures, classes, functions) with line numbers
62
+ 2. **read-range** — Read specific line ranges identified from the skeleton
63
+
64
+ SKELETON LEVELS:
65
+ - A.0: Public/exported signatures only
66
+ - A.1: Public/exported signatures + comments/docstrings
67
+ - B.0: All signatures (public + private + methods) — DEFAULT
68
+ - B.1: All signatures + comments/docstrings
69
+
70
+ Supported files: .js, .ts, .jsx, .tsx, .mjs, .cjs, .py, .c, .h, .cc, .cpp, .cxx, .hpp, .hh, .hxx
71
+ Respects .gitignore rules. Skips node_modules, __pycache__, .git, dist, build.
72
+
73
+ USAGE — Skeleton scan (canonical plural — same shape across all tools):
74
+ \`\`\`json
75
+ {
76
+ "toolId": "code-map",
77
+ "actions": [
78
+ {"type": "skeleton", "path": "src/", "level": "B.0", "includeImports": false}
79
+ ]
80
+ }
81
+ \`\`\`
82
+
83
+ Single file:
84
+ \`\`\`json
85
+ {
86
+ "toolId": "code-map",
87
+ "actions": [
88
+ {"type": "skeleton", "path": "src/services/aiService.js", "level": "A.0"}
89
+ ]
90
+ }
91
+ \`\`\`
92
+
93
+ USAGE — Read specific lines (use line numbers from skeleton output):
94
+ \`\`\`json
95
+ {
96
+ "toolId": "code-map",
97
+ "actions": [
98
+ {"type": "read-range", "filePath": "src/services/aiService.js", "startLine": 42, "endLine": 90}
99
+ ]
100
+ }
101
+ \`\`\`
102
+
103
+ BATCH — skeleton one path AND read a range in one call:
104
+ \`\`\`json
105
+ {
106
+ "toolId": "code-map",
107
+ "actions": [
108
+ {"type": "skeleton", "path": "src/services/", "level": "A.0"},
109
+ {"type": "read-range", "filePath": "src/services/aiService.js", "startLine": 42, "endLine": 90}
110
+ ]
111
+ }
112
+ \`\`\`
113
+
114
+ XML format also supported:
115
+ <code-map>
116
+ <action>skeleton</action>
117
+ <path>src/</path>
118
+ <level>B.0</level>
119
+ </code-map>
120
+
121
+ <code-map>
122
+ <action>read-range</action>
123
+ <file-path>src/index.js</file-path>
124
+ <start-line>100</start-line>
125
+ <end-line>150</end-line>
126
+ </code-map>`;
127
+ }
128
+
129
+ parseParameters(content) {
130
+ try {
131
+ if (content.trim().startsWith('{')) {
132
+ const parsed = JSON.parse(content);
133
+ return {
134
+ action: parsed.action || parsed.parameters?.action,
135
+ path: parsed.path || parsed.parameters?.path,
136
+ level: parsed.level || parsed.parameters?.level,
137
+ includeImports: parsed.includeImports ?? parsed.parameters?.includeImports ?? false,
138
+ filePath: parsed.filePath || parsed.parameters?.filePath,
139
+ startLine: parsed.startLine ?? parsed.parameters?.startLine,
140
+ endLine: parsed.endLine ?? parsed.parameters?.endLine
141
+ };
142
+ }
143
+
144
+ // XML parsing
145
+ const params = {};
146
+ const actionMatch = /<action>(.*?)<\/action>/i.exec(content);
147
+ if (actionMatch) params.action = actionMatch[1].trim();
148
+
149
+ const pathMatch = /<path>(.*?)<\/path>/i.exec(content);
150
+ if (pathMatch) params.path = pathMatch[1].trim();
151
+
152
+ const levelMatch = /<level>(.*?)<\/level>/i.exec(content);
153
+ if (levelMatch) params.level = levelMatch[1].trim();
154
+
155
+ const importsMatch = /<include-imports>(.*?)<\/include-imports>/i.exec(content);
156
+ if (importsMatch) params.includeImports = importsMatch[1].trim() === 'true';
157
+
158
+ const filePathMatch = /<file-path>(.*?)<\/file-path>/i.exec(content);
159
+ if (filePathMatch) params.filePath = filePathMatch[1].trim();
160
+
161
+ const startLineMatch = /<start-line>(.*?)<\/start-line>/i.exec(content);
162
+ if (startLineMatch) params.startLine = parseInt(startLineMatch[1].trim(), 10);
163
+
164
+ const endLineMatch = /<end-line>(.*?)<\/end-line>/i.exec(content);
165
+ if (endLineMatch) params.endLine = parseInt(endLineMatch[1].trim(), 10);
166
+
167
+ return params;
168
+ } catch (error) {
169
+ this.logger?.error('Failed to parse code-map parameters', { error: error.message });
170
+ return { parseError: error.message };
171
+ }
172
+ }
173
+
174
+ getRequiredParameters() {
175
+ return ['action'];
176
+ }
177
+
178
+ customValidateParameters(params) {
179
+ const errors = [];
180
+ const actions = this._normalizeToActions(params);
181
+
182
+ if (actions.length === 0) {
183
+ errors.push('action is required (skeleton or read-range), or `actions: [...]` for batched calls');
184
+ return { valid: false, errors };
185
+ }
186
+
187
+ for (let i = 0; i < actions.length; i++) {
188
+ const a = actions[i];
189
+ const tag = actions.length > 1 ? `actions[${i}]: ` : '';
190
+ const type = a?.type;
191
+ if (!type) {
192
+ errors.push(`${tag}"type" is required (skeleton or read-range)`);
193
+ continue;
194
+ }
195
+ if (!['skeleton', 'read-range'].includes(type)) {
196
+ const lower = String(type).toLowerCase();
197
+ let hint = '';
198
+ if (lower === 'search' || lower === 'grep' || lower === 'find') {
199
+ hint = ' To search for text within files, use the `seek` tool (`{toolId: "seek", filePaths: ["**/*.js"], searchTerms: ["foo"]}`). code-map only extracts structure (skeleton) or reads specific ranges (read-range).';
200
+ } else if (lower === 'list' || lower === 'tree') {
201
+ hint = ' To list files/directories, use the `file-tree` tool. code-map is for parsing file contents.';
202
+ } else if (lower === 'read') {
203
+ hint = ' To read full file contents, use `filesystem` (`{toolId: "filesystem", actions: [{type: "read", filePath: "X"}]}`) or use code-map `read-range` for a specific line range.';
204
+ }
205
+ errors.push(`${tag}Invalid type "${type}". Must be "skeleton" or "read-range".${hint}`);
206
+ continue;
207
+ }
208
+ if (type === 'skeleton') {
209
+ if (!a.path) errors.push(`${tag}path is required for skeleton`);
210
+ if (a.level && !CODE_MAP_CONFIG.VALID_LEVELS.includes(String(a.level).toUpperCase())) {
211
+ errors.push(`${tag}Invalid level "${a.level}". Must be one of: ${CODE_MAP_CONFIG.VALID_LEVELS.join(', ')}`);
212
+ }
213
+ }
214
+ if (type === 'read-range') {
215
+ if (!a.filePath) errors.push(`${tag}filePath is required for read-range`);
216
+ if (a.startLine == null) errors.push(`${tag}startLine is required for read-range`);
217
+ if (a.endLine == null) errors.push(`${tag}endLine is required for read-range`);
218
+ if (a.startLine != null && a.endLine != null) {
219
+ if (a.startLine < 1) errors.push(`${tag}startLine must be >= 1`);
220
+ if (a.endLine < a.startLine) errors.push(`${tag}endLine must be >= startLine`);
221
+ if (a.endLine - a.startLine + 1 > CODE_MAP_CONFIG.MAX_READ_RANGE) {
222
+ errors.push(`${tag}Range too large. Maximum ${CODE_MAP_CONFIG.MAX_READ_RANGE} lines per request`);
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ return { valid: errors.length === 0, errors };
229
+ }
230
+
231
+ async execute(params, context) {
232
+ // PLURAL-CANONICAL DISPATCH — canonical shape is
233
+ // `{actions: [{type: "skeleton", path: "src/"}, ...]}` matching the
234
+ // rest of the tool conventions. Singular `{action, ...}` is auto-
235
+ // promoted as backward-compat. Multi-action batch returns an array.
236
+ const actions = this._normalizeToActions(params);
237
+
238
+ if (actions.length === 0) {
239
+ throw new Error('code-map tool requires either `action` (singular) or `actions` (plural array). Each action needs a `type` of "skeleton" or "read-range".');
240
+ }
241
+
242
+ // Single-action path → return one result directly (back-compat).
243
+ if (actions.length === 1) {
244
+ return await this._dispatchSingleAction(actions[0], context);
245
+ }
246
+
247
+ // Multi-action path → execute sequentially, return array of results.
248
+ const results = [];
249
+ for (const a of actions) {
250
+ try { results.push(await this._dispatchSingleAction(a, context)); }
251
+ catch (err) { results.push({ success: false, action: a.type, error: err.message }); }
252
+ }
253
+ return {
254
+ success: results.every(r => r.success !== false),
255
+ batched: true,
256
+ actionCount: results.length,
257
+ results,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Promote any param shape into canonical actions[].
263
+ * @private
264
+ */
265
+ _normalizeToActions(params) {
266
+ if (!params || typeof params !== 'object') return [];
267
+ if (Array.isArray(params.actions)) {
268
+ return params.actions.map(a => {
269
+ if (!a || typeof a !== 'object') return a;
270
+ if (!a.type && a.action) {
271
+ const { action, ...rest } = a;
272
+ return { type: action, ...rest };
273
+ }
274
+ return a;
275
+ });
276
+ }
277
+ if (params.action) {
278
+ const { action, parameters, ...rest } = params;
279
+ return [{ type: action, ...(parameters && typeof parameters === 'object' ? parameters : {}), ...rest }];
280
+ }
281
+ return [];
282
+ }
283
+
284
+ /**
285
+ * Dispatch one normalized {type, ...} action with cache-aware
286
+ * lookup (codebase-knowledge service).
287
+ * @private
288
+ */
289
+ async _dispatchSingleAction(action, context) {
290
+ // Adapt to the legacy flat params shape that the cache + execute
291
+ // helpers expect.
292
+ const params = { action: action.type, ...action };
293
+ delete params.type;
294
+
295
+ const { projectDir, directoryAccess, agentId } = context;
296
+ let workingDir = projectDir || process.cwd();
297
+ if (directoryAccess?.workingDirectory) workingDir = directoryAccess.workingDirectory;
298
+ const accessibleDirs = this._getAccessibleDirectories(directoryAccess, workingDir);
299
+
300
+ // Knowledge-cache lookup — Alt D's transparent layer. If this exact
301
+ // request (same file, same kind, same range) has been served before
302
+ // and the file hasn't been written to since, return cached content
303
+ // with a header. Agent can pass `force: true` to bypass. See
304
+ // services/codebaseKnowledgeService.js for design rationale.
305
+ //
306
+ // Best-effort: any error in cache lookup MUST fall through to the
307
+ // real fetch — never break the agent's turn.
308
+ let ks = null;
309
+ if (agentId && params?.force !== true) {
310
+ try {
311
+ const mod = await import('../services/codebaseKnowledgeService.js');
312
+ ks = mod.getCodebaseKnowledgeService(this.logger);
313
+ } catch (e) {
314
+ ks = null;
315
+ this.logger?.debug?.('codebase-knowledge service unavailable, skipping cache', { error: e.message });
316
+ }
317
+ }
318
+
319
+ switch (params.action) {
320
+ case 'skeleton': {
321
+ // Cache lookup (async — does an mtime stat for freshness)
322
+ if (ks && params.filePath) {
323
+ try {
324
+ const hit = await ks.lookupSkeleton(agentId, params.filePath, workingDir);
325
+ if (hit) {
326
+ return {
327
+ success: true,
328
+ action: 'skeleton',
329
+ filePath: params.filePath,
330
+ fromCache: true,
331
+ cacheNote: 'Returned from session cache. File unchanged since last fetch. Pass `force: true` if you need to re-parse.',
332
+ content: hit.content,
333
+ };
334
+ }
335
+ } catch (e) {
336
+ this.logger?.debug?.('codebase-knowledge lookupSkeleton failed (continuing with fresh fetch)', { error: e.message });
337
+ }
338
+ }
339
+ const result = await this._executeSkeleton(params, workingDir, accessibleDirs);
340
+ // Populate cache on success (async — captures mtime at record time)
341
+ if (ks && params.filePath && result?.success && typeof result.content === 'string') {
342
+ try { await ks.recordSkeleton(agentId, params.filePath, result.content, workingDir); }
343
+ catch (e) { this.logger?.debug?.('codebase-knowledge recordSkeleton failed', { error: e.message }); }
344
+ }
345
+ return result;
346
+ }
347
+ case 'read-range': {
348
+ const sl = Number(params.startLine), el = Number(params.endLine);
349
+ if (ks && params.filePath && Number.isFinite(sl) && Number.isFinite(el)) {
350
+ try {
351
+ const hit = await ks.lookupRange(agentId, params.filePath, sl, el, workingDir);
352
+ if (hit) {
353
+ return {
354
+ success: true,
355
+ action: 'read-range',
356
+ filePath: params.filePath,
357
+ startLine: sl, endLine: el,
358
+ fromCache: true,
359
+ cacheNote: 'Returned from session cache. File unchanged since last fetch.',
360
+ content: hit.content,
361
+ };
362
+ }
363
+ } catch (e) {
364
+ this.logger?.debug?.('codebase-knowledge lookupRange failed (continuing with fresh fetch)', { error: e.message });
365
+ }
366
+ }
367
+ const result = await this._executeReadRange(params, workingDir, accessibleDirs);
368
+ if (ks && params.filePath && Number.isFinite(sl) && Number.isFinite(el) && result?.success && typeof result.content === 'string') {
369
+ try { await ks.recordRange(agentId, params.filePath, sl, el, result.content, workingDir); }
370
+ catch (e) { this.logger?.debug?.('codebase-knowledge recordRange failed', { error: e.message }); }
371
+ }
372
+ return result;
373
+ }
374
+ default:
375
+ throw new Error(`Unknown action: ${params.action}`);
376
+ }
377
+ }
378
+
379
+ // ── Directory access helpers ──────────────────────────────────────────────
380
+
381
+ _getAccessibleDirectories(directoryAccess, fallbackDir) {
382
+ const dirs = new Set();
383
+ dirs.add(fallbackDir);
384
+
385
+ if (directoryAccess) {
386
+ if (directoryAccess.workingDirectory) dirs.add(directoryAccess.workingDirectory);
387
+ if (directoryAccess.readOnlyDirectories) {
388
+ for (const d of directoryAccess.readOnlyDirectories) dirs.add(d);
389
+ }
390
+ if (directoryAccess.writeEnabledDirectories) {
391
+ for (const d of directoryAccess.writeEnabledDirectories) dirs.add(d);
392
+ }
393
+ }
394
+
395
+ return Array.from(dirs);
396
+ }
397
+
398
+ _isPathAccessible(targetPath, accessibleDirs) {
399
+ for (const dir of accessibleDirs) {
400
+ const relative = path.relative(dir, targetPath);
401
+ if (!relative.startsWith('..') && !path.isAbsolute(relative)) return true;
402
+ }
403
+ return false;
404
+ }
405
+
406
+ // ── Skeleton action ───────────────────────────────────────────────────────
407
+
408
+ async _executeSkeleton(params, workingDir, accessibleDirs) {
409
+ const targetPath = path.resolve(workingDir, params.path);
410
+
411
+ if (!this._isPathAccessible(targetPath, accessibleDirs)) {
412
+ throw new Error(`Path not accessible: ${params.path}`);
413
+ }
414
+
415
+ const level = (params.level || CODE_MAP_CONFIG.DEFAULT_LEVEL).toUpperCase();
416
+ const publicOnly = level.startsWith('A');
417
+ const withComments = level.endsWith('.1');
418
+ const includeImports = params.includeImports || false;
419
+ const parseOptions = { publicOnly, withComments, includeImports };
420
+
421
+ let stat;
422
+ try {
423
+ stat = await fs.promises.stat(targetPath);
424
+ } catch {
425
+ throw new Error(`Path not found: ${params.path}`);
426
+ }
427
+
428
+ let files;
429
+ if (stat.isFile()) {
430
+ const ext = path.extname(targetPath).toLowerCase();
431
+ if (!CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS.has(ext)) {
432
+ throw new Error(`Unsupported file type: ${ext}. Supported: ${[...CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS].join(', ')}`);
433
+ }
434
+ files = [targetPath];
435
+ } else {
436
+ files = await this._discoverFiles(targetPath, accessibleDirs);
437
+ }
438
+
439
+ if (files.length === 0) {
440
+ return {
441
+ success: true,
442
+ action: 'skeleton',
443
+ level,
444
+ path: params.path,
445
+ files: [],
446
+ totalFiles: 0,
447
+ totalEntries: 0,
448
+ message: 'No supported files found in the specified path.',
449
+ guidance: 'Try a different path or verify the directory contains supported files (.js, .ts, .jsx, .tsx, .mjs, .cjs, .py, .c, .h, .cc, .cpp, .cxx, .hpp, .hh, .hxx).'
450
+ };
451
+ }
452
+
453
+ if (files.length > CODE_MAP_CONFIG.MAX_FILES) {
454
+ files = files.slice(0, CODE_MAP_CONFIG.MAX_FILES);
455
+ }
456
+
457
+ const basePath = stat.isDirectory() ? targetPath : path.dirname(targetPath);
458
+ const result = [];
459
+ let totalEntries = 0;
460
+
461
+ for (const file of files) {
462
+ try {
463
+ const fileStat = await fs.promises.stat(file);
464
+ if (fileStat.size > CODE_MAP_CONFIG.MAX_FILE_SIZE) continue;
465
+
466
+ const content = await fs.promises.readFile(file, 'utf-8');
467
+ const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
468
+ const lang = this._langOf(file);
469
+
470
+ const entries =
471
+ lang === 'python' ? this._parsePython(lines, parseOptions) :
472
+ lang === 'c' ? this._parseC(lines, parseOptions) :
473
+ this._parseJS(lines, parseOptions);
474
+
475
+ if (entries.length === 0) continue;
476
+
477
+ const relPath = path.relative(basePath, file);
478
+ result.push({
479
+ file: relPath,
480
+ totalLines: lines.length,
481
+ entries
482
+ });
483
+ totalEntries += entries.length;
484
+ } catch (err) {
485
+ this.logger?.debug(`Skipping file ${file}: ${err.message}`);
486
+ }
487
+ }
488
+
489
+ return {
490
+ success: true,
491
+ action: 'skeleton',
492
+ level,
493
+ path: params.path,
494
+ files: result,
495
+ totalFiles: result.length,
496
+ totalEntries,
497
+ guidance: totalEntries > 0
498
+ ? (() => {
499
+ const sampleFile = result[0];
500
+ const sampleLine = sampleFile?.entries[0]?.line || 1;
501
+ const examplePath = stat.isDirectory()
502
+ ? (params.path.endsWith('/') ? params.path : params.path + '/') + sampleFile.file
503
+ : params.path;
504
+ return `Found ${totalEntries} entries across ${result.length} files. Use code-map read-range to examine specific sections by line number. Example: {"toolId":"code-map","parameters":{"action":"read-range","filePath":"${examplePath}","startLine":${sampleLine},"endLine":${sampleLine + 30}}}`;
505
+ })()
506
+ : 'No code signatures found at this level. Try level B.0 or B.1 for more detail.'
507
+ };
508
+ }
509
+
510
+ // ── Read-range action ─────────────────────────────────────────────────────
511
+
512
+ async _executeReadRange(params, workingDir, accessibleDirs) {
513
+ const filePath = path.resolve(workingDir, params.filePath);
514
+
515
+ if (!this._isPathAccessible(filePath, accessibleDirs)) {
516
+ throw new Error(`Path not accessible: ${params.filePath}`);
517
+ }
518
+
519
+ let content;
520
+ try {
521
+ content = await fs.promises.readFile(filePath, 'utf-8');
522
+ } catch {
523
+ throw new Error(`File not found: ${params.filePath}`);
524
+ }
525
+
526
+ const allLines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
527
+ const totalLines = allLines.length;
528
+ const startLine = Math.max(1, params.startLine);
529
+ const endLine = Math.min(totalLines, params.endLine);
530
+
531
+ if (startLine > totalLines) {
532
+ throw new Error(`startLine ${startLine} exceeds file length (${totalLines} lines)`);
533
+ }
534
+
535
+ const selectedLines = allLines.slice(startLine - 1, endLine);
536
+ const maxWidth = String(endLine).length;
537
+ const formatted = selectedLines.map((line, i) => {
538
+ const num = String(startLine + i).padStart(maxWidth);
539
+ return `${num}│${line}`;
540
+ }).join('\n');
541
+
542
+ return {
543
+ success: true,
544
+ action: 'read-range',
545
+ filePath: params.filePath,
546
+ startLine,
547
+ endLine,
548
+ linesReturned: selectedLines.length,
549
+ totalLines,
550
+ content: formatted,
551
+ guidance: `Showing lines ${startLine}-${endLine} of ${totalLines}. ` +
552
+ (endLine < totalLines
553
+ ? `Use read-range with startLine:${endLine + 1} to continue reading. `
554
+ : '') +
555
+ 'Use code-map skeleton to discover more code structure in other files.'
556
+ };
557
+ }
558
+
559
+ // ── File discovery with .gitignore ────────────────────────────────────────
560
+
561
+ async _discoverFiles(rootDir, accessibleDirs) {
562
+ const gi = await this._loadGitignoreRules(rootDir);
563
+ const results = [];
564
+ let depth = 0;
565
+
566
+ const walk = async (dir) => {
567
+ if (depth > CODE_MAP_CONFIG.MAX_DIRECTORY_DEPTH) return;
568
+ if (results.length >= CODE_MAP_CONFIG.MAX_FILES) return;
569
+
570
+ const rel = path.relative(rootDir, dir);
571
+ if (rel) gi.loadNested(dir, rel);
572
+
573
+ let entries;
574
+ try {
575
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
576
+ } catch {
577
+ return;
578
+ }
579
+
580
+ // Sort for deterministic output
581
+ entries.sort((a, b) => a.name.localeCompare(b.name));
582
+
583
+ for (const entry of entries) {
584
+ if (results.length >= CODE_MAP_CONFIG.MAX_FILES) break;
585
+ if (entry.name.startsWith('.')) continue;
586
+
587
+ const full = path.join(dir, entry.name);
588
+ const entryRel = path.relative(rootDir, full);
589
+
590
+ if (entry.isDirectory()) {
591
+ if (CODE_MAP_CONFIG.SKIP_DIRS.has(entry.name)) continue;
592
+ if (gi.isIgnored(entryRel, true) && !gi.hasNegationUnder(entryRel)) continue;
593
+ if (!this._isPathAccessible(full, accessibleDirs)) continue;
594
+
595
+ depth++;
596
+ await walk(full);
597
+ depth--;
598
+ } else if (CODE_MAP_CONFIG.SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
599
+ if (gi.isIgnored(entryRel, false)) continue;
600
+ results.push(full);
601
+ }
602
+ }
603
+ };
604
+
605
+ await walk(rootDir);
606
+ return results;
607
+ }
608
+
609
+ // ── .gitignore support ────────────────────────────────────────────────────
610
+
611
+ async _loadGitignoreRules(rootDir) {
612
+ const allRules = [];
613
+
614
+ const giPath = path.join(rootDir, '.gitignore');
615
+ try {
616
+ const content = await fs.promises.readFile(giPath, 'utf-8');
617
+ allRules.push(...this._parseGitignore(content, ''));
618
+ } catch {
619
+ // No .gitignore at root — that's fine
620
+ }
621
+
622
+ return {
623
+ rules: allRules,
624
+
625
+ loadNested: (subDir, relPath) => {
626
+ const nested = path.join(subDir, '.gitignore');
627
+ try {
628
+ const content = fs.readFileSync(nested, 'utf-8');
629
+ const prefix = relPath ? relPath + '/' : '';
630
+ allRules.push(...this._parseGitignore(content, prefix));
631
+ } catch {
632
+ // No nested .gitignore
633
+ }
634
+ },
635
+
636
+ isIgnored: (relPath, isDir) => {
637
+ let ignored = false;
638
+ for (const rule of allRules) {
639
+ if (rule.dirOnly && !isDir) {
640
+ const parts = relPath.split('/');
641
+ let parentMatch = false;
642
+ for (let k = 1; k < parts.length; k++) {
643
+ const parentPath = parts.slice(0, k).join('/');
644
+ if (rule.regex.test(parentPath)) { parentMatch = true; break; }
645
+ }
646
+ if (!parentMatch) continue;
647
+ } else if (!rule.regex.test(relPath)) {
648
+ continue;
649
+ }
650
+ ignored = !rule.negate;
651
+ }
652
+ return ignored;
653
+ },
654
+
655
+ hasNegationUnder: (dirRelPath) => {
656
+ const prefix = dirRelPath + '/';
657
+ for (const rule of allRules) {
658
+ if (!rule.negate) continue;
659
+ if (rule.regex.test(prefix + 'x') || rule.regex.test(prefix + 'x.js')) return true;
660
+ if (rule.regex.source.includes(dirRelPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) return true;
661
+ }
662
+ return false;
663
+ }
664
+ };
665
+ }
666
+
667
+ _parseGitignore(content, prefix) {
668
+ const rules = [];
669
+ for (const rawLine of content.split('\n')) {
670
+ let line = rawLine.trim();
671
+ if (!line || line.startsWith('#')) continue;
672
+
673
+ let negate = false;
674
+ if (line.startsWith('!')) {
675
+ negate = true;
676
+ line = line.slice(1);
677
+ }
678
+
679
+ line = line.replace(/\\(\s)$/, '$1');
680
+
681
+ let dirOnly = false;
682
+ if (line.endsWith('/')) {
683
+ dirOnly = true;
684
+ line = line.slice(0, -1);
685
+ }
686
+
687
+ const hasSlash = line.includes('/');
688
+ const rooted = line.startsWith('/');
689
+ if (rooted) line = line.slice(1);
690
+
691
+ let reStr;
692
+ if (hasSlash || rooted) {
693
+ reStr = '^' + prefix + this._gitignorePatternToRegex(line) + '(/.*)?$';
694
+ } else {
695
+ reStr = '(^|.+/)' + prefix + this._gitignorePatternToRegex(line) + '(/.*)?$';
696
+ }
697
+
698
+ try {
699
+ rules.push({ regex: new RegExp(reStr), negate, dirOnly });
700
+ } catch {
701
+ // skip invalid patterns
702
+ }
703
+ }
704
+ return rules;
705
+ }
706
+
707
+ _gitignorePatternToRegex(pattern) {
708
+ let re = '';
709
+ let i = 0;
710
+ while (i < pattern.length) {
711
+ const ch = pattern[i];
712
+ if (ch === '*' && pattern[i + 1] === '*') {
713
+ if (pattern[i + 2] === '/') {
714
+ re += '(.+/)?';
715
+ i += 3;
716
+ } else {
717
+ re += '.*';
718
+ i += 2;
719
+ }
720
+ } else if (ch === '*') {
721
+ re += '[^/]*';
722
+ i++;
723
+ } else if (ch === '?') {
724
+ re += '[^/]';
725
+ i++;
726
+ } else if (ch === '[') {
727
+ const end = pattern.indexOf(']', i + 1);
728
+ if (end === -1) { re += '\\['; i++; }
729
+ else { re += pattern.slice(i, end + 1); i = end + 1; }
730
+ } else if ('.+^${}()|\\'.includes(ch)) {
731
+ re += '\\' + ch;
732
+ i++;
733
+ } else {
734
+ re += ch;
735
+ i++;
736
+ }
737
+ }
738
+ return re;
739
+ }
740
+
741
+ // ── Language detection ────────────────────────────────────────────────────
742
+
743
+ _langOf(file) {
744
+ const ext = path.extname(file).toLowerCase();
745
+ if (ext === '.py') return 'python';
746
+ if (['.c', '.h', '.cc', '.cpp', '.cxx', '.hpp', '.hh', '.hxx'].includes(ext)) return 'c';
747
+ return 'js';
748
+ }
749
+
750
+ // ── JS / TS parser ────────────────────────────────────────────────────────
751
+ //
752
+ // No-regex implementation. Pattern matchers below each return either
753
+ // `true`/`false` or `null`/idx, using the primitives from
754
+ // parserHelpers.js. This trades five lines of regex for ten lines of
755
+ // explicit cursor-walking, but the result is far more debuggable and
756
+ // every behavior is locked by parserHelpers.test.js + codeMapTool.test.js.
757
+
758
+ // Modifier keywords that can precede a class-method name (used by the
759
+ // "method inside class" matcher).
760
+ static _METHOD_MODIFIERS = new Set([
761
+ 'private', 'protected', 'public', 'readonly', 'abstract',
762
+ 'override', 'async', 'static', 'get', 'set',
763
+ ]);
764
+
765
+ // Reserved words that look like methods but aren't — `if (x) {` etc.
766
+ static _METHOD_KEYWORD_EXCLUDE = new Set([
767
+ 'if', 'for', 'while', 'switch', 'catch', 'return', 'throw', 'new',
768
+ 'typeof', 'delete', 'void', 'await', 'class', 'function',
769
+ 'const', 'let', 'var', 'super', 'this',
770
+ ]);
771
+
772
+ /**
773
+ * Does the RHS of an assignment (starting at idx) look like a function
774
+ * or arrow expression? Recognizes:
775
+ * function
776
+ * async function
777
+ * () => … or (a, b) => …
778
+ * ident => … or async ident =>
779
+ *
780
+ * Returns true on hit. Allocation-free.
781
+ */
782
+ _rhsLooksLikeFunction(s, idx) {
783
+ let i = skipWhitespace(s, idx);
784
+ // optional `async`
785
+ const afterAsync = consumeKeyword(s, 'async', i);
786
+ if (afterAsync !== -1) i = skipWhitespace(s, afterAsync);
787
+ if (startsWithKeyword(s, 'function', i)) return true;
788
+
789
+ // `(…) =>` — find a balanced paren run, then `=>`
790
+ if (s[i] === '(') {
791
+ let depth = 1; let j = i + 1;
792
+ while (j < s.length && depth > 0) {
793
+ if (s[j] === '(') depth += 1;
794
+ else if (s[j] === ')') depth -= 1;
795
+ j += 1;
796
+ }
797
+ const k = skipWhitespace(s, j);
798
+ if (s[k] === '=' && s[k + 1] === '>') return true;
799
+ return false;
800
+ }
801
+
802
+ // `ident =>`
803
+ const id = readIdent(s, i);
804
+ if (id) {
805
+ const k = skipWhitespace(s, id.nextIdx);
806
+ if (s[k] === '=' && s[k + 1] === '>') return true;
807
+ }
808
+ return false;
809
+ }
810
+
811
+ /** Line is `import ...` (ESM). */
812
+ _isImportLine(raw) {
813
+ const i = skipWhitespace(raw, 0);
814
+ return startsWithKeyword(raw, 'import', i);
815
+ }
816
+ /** Line contains `require(` as a whole word call. */
817
+ _hasRequireCall(raw) {
818
+ const i = indexOfWord(raw, 'require');
819
+ if (i === -1) return false;
820
+ const j = skipWhitespace(raw, i + 'require'.length);
821
+ return raw[j] === '(';
822
+ }
823
+
824
+ /** Walks across the prefix keywords `export default abstract async ...`. */
825
+ _skipDecoratorPrefix(raw) {
826
+ let i = skipWhitespace(raw, 0);
827
+ // export, then optional `default`
828
+ const afterExport = consumeKeyword(raw, 'export', i);
829
+ if (afterExport !== -1) i = skipWhitespace(raw, afterExport);
830
+ const afterDefault = consumeKeyword(raw, 'default', i);
831
+ if (afterDefault !== -1) i = skipWhitespace(raw, afterDefault);
832
+ const afterAbstract = consumeKeyword(raw, 'abstract', i);
833
+ if (afterAbstract !== -1) i = skipWhitespace(raw, afterAbstract);
834
+ const afterAsync = consumeKeyword(raw, 'async', i);
835
+ if (afterAsync !== -1) i = skipWhitespace(raw, afterAsync);
836
+ return i;
837
+ }
838
+
839
+ /**
840
+ * The line starts with one of these declaration keywords (after the
841
+ * optional `export [default] [abstract] [async]` prefix run):
842
+ * function | class | const | let | var | enum | interface | type
843
+ *
844
+ * Returns true if so.
845
+ */
846
+ _isExportableDecl(raw) {
847
+ const i = this._skipDecoratorPrefix(raw);
848
+ return (
849
+ startsWithKeyword(raw, 'function', i) ||
850
+ startsWithKeyword(raw, 'class', i) ||
851
+ startsWithKeyword(raw, 'const', i) ||
852
+ startsWithKeyword(raw, 'let', i) ||
853
+ startsWithKeyword(raw, 'var', i) ||
854
+ startsWithKeyword(raw, 'enum', i) ||
855
+ startsWithKeyword(raw, 'interface', i) ||
856
+ startsWithKeyword(raw, 'type', i) ||
857
+ startsWithKeyword(raw, 'abstract', i) // bare `export abstract foo`
858
+ );
859
+ }
860
+
861
+ /** `module.exports = …` (whole or property: `module.exports.foo = …`). */
862
+ _isModuleExportsAssign(raw) {
863
+ const i = skipWhitespace(raw, 0);
864
+ if (!startsWithKeyword(raw, 'module', i)) return false;
865
+ let j = i + 'module'.length;
866
+ if (raw[j] !== '.') return false;
867
+ j += 1;
868
+ if (!startsWithKeyword(raw, 'exports', j)) return false;
869
+ j += 'exports'.length;
870
+ // optional `.ident`
871
+ if (raw[j] === '.') {
872
+ const id = readIdent(raw, j + 1);
873
+ if (!id) return false;
874
+ j = id.nextIdx;
875
+ }
876
+ j = skipWhitespace(raw, j);
877
+ return raw[j] === '=' && raw[j + 1] !== '=';
878
+ }
879
+
880
+ /** `exports.foo = …` */
881
+ _isExportsAssign(raw) {
882
+ const i = skipWhitespace(raw, 0);
883
+ if (!startsWithKeyword(raw, 'exports', i)) return false;
884
+ let j = i + 'exports'.length;
885
+ if (raw[j] !== '.') return false;
886
+ const id = readIdent(raw, j + 1);
887
+ if (!id) return false;
888
+ j = skipWhitespace(raw, id.nextIdx);
889
+ return raw[j] === '=' && raw[j + 1] !== '=';
890
+ }
891
+
892
+ /** `(async) function name(…)` top-level form. */
893
+ _isTopLevelFunctionDecl(raw) {
894
+ let i = skipWhitespace(raw, 0);
895
+ const aa = consumeKeyword(raw, 'async', i);
896
+ if (aa !== -1) i = skipWhitespace(raw, aa);
897
+ if (!startsWithKeyword(raw, 'function', i)) return false;
898
+ i = skipWhitespace(raw, i + 'function'.length);
899
+ return readIdent(raw, i) !== null;
900
+ }
901
+
902
+ /** `(abstract) class Name`. */
903
+ _isTopLevelClassDecl(raw) {
904
+ let i = skipWhitespace(raw, 0);
905
+ const aa = consumeKeyword(raw, 'abstract', i);
906
+ if (aa !== -1) i = skipWhitespace(raw, aa);
907
+ if (!startsWithKeyword(raw, 'class', i)) return false;
908
+ i = skipWhitespace(raw, i + 'class'.length);
909
+ return readIdent(raw, i) !== null;
910
+ }
911
+
912
+ /** `const|let|var name = <function-ish>`. */
913
+ _isConstAssignedFunction(raw) {
914
+ let i = skipWhitespace(raw, 0);
915
+ const dk = consumeAnyKeyword(raw, ['const', 'let', 'var'], i);
916
+ if (!dk) return false;
917
+ i = skipWhitespace(raw, dk.nextIdx);
918
+ const id = readIdent(raw, i);
919
+ if (!id) return false;
920
+ i = skipWhitespace(raw, id.nextIdx);
921
+ if (raw[i] !== '=' || raw[i + 1] === '=' || raw[i + 1] === '>') return false;
922
+ return this._rhsLooksLikeFunction(raw, i + 1);
923
+ }
924
+
925
+ /** Class-with-an-opening-brace check for the brace stack. */
926
+ _isClassDeclWithOpenBrace(raw, opens) {
927
+ if (opens <= 0) return false;
928
+ let i = skipWhitespace(raw, 0);
929
+ const ex = consumeKeyword(raw, 'export', i);
930
+ if (ex !== -1) i = skipWhitespace(raw, ex);
931
+ const df = consumeKeyword(raw, 'default', i);
932
+ if (df !== -1) i = skipWhitespace(raw, df);
933
+ const ab = consumeKeyword(raw, 'abstract', i);
934
+ if (ab !== -1) i = skipWhitespace(raw, ab);
935
+ return startsWithKeyword(raw, 'class', i);
936
+ }
937
+
938
+ /** IIFE prefix-op form: `!function`, `+function`, `~function`, `-function`. */
939
+ _isIifePrefixOp(raw) {
940
+ const i = skipWhitespace(raw, 0);
941
+ const ch = raw[i];
942
+ if (ch !== '!' && ch !== '+' && ch !== '~' && ch !== '-') return false;
943
+ let j = skipWhitespace(raw, i + 1);
944
+ const aa = consumeKeyword(raw, 'async', j);
945
+ if (aa !== -1) j = skipWhitespace(raw, aa);
946
+ return startsWithKeyword(raw, 'function', j);
947
+ }
948
+
949
+ /** IIFE paren-wrapped form: `(function ...)` or `(async function ...)`. */
950
+ _isIifeParens(raw) {
951
+ const i = skipWhitespace(raw, 0);
952
+ if (raw[i] !== '(') return false;
953
+ let j = skipWhitespace(raw, i + 1);
954
+ const aa = consumeKeyword(raw, 'async', j);
955
+ if (aa !== -1) j = skipWhitespace(raw, aa);
956
+ return startsWithKeyword(raw, 'function', j);
957
+ }
958
+
959
+ /**
960
+ * Global/namespace assignment of a function-ish value:
961
+ * window.X.Y = function / globalThis.X = () => / MyApp.foo = …
962
+ * Conditions:
963
+ * - LHS head is `window|globalThis|self` (and optional dotted tail)
964
+ * OR a TitleCase identifier followed by AT LEAST one dotted tail
965
+ * segment (so we don't grab bare `Foo = …`)
966
+ * - RHS is function or arrow
967
+ */
968
+ _isGlobalAssign(raw) {
969
+ let i = skipWhitespace(raw, 0);
970
+ const headKw = consumeAnyKeyword(raw, ['window', 'globalThis', 'self'], i);
971
+ let after;
972
+ let hadGlobalHead = false;
973
+ if (headKw) {
974
+ hadGlobalHead = true;
975
+ after = consumeDottedAssignTail(raw, headKw.nextIdx);
976
+ } else {
977
+ // TitleCase ident + at least one dotted tail
978
+ const id = readIdent(raw, i);
979
+ if (!id) return false;
980
+ // TitleCase = uppercase first char
981
+ const first = id.ident[0];
982
+ if (first < 'A' || first > 'Z') return false;
983
+ // Need at least one '.ident' before '='
984
+ if (raw[id.nextIdx] !== '.') return false;
985
+ after = consumeDottedAssignTail(raw, id.nextIdx);
986
+ }
987
+ if (after === -1) return false;
988
+ return this._rhsLooksLikeFunction(raw, after);
989
+ }
990
+
991
+ /**
992
+ * Prototype-method assignment: `Foo.prototype.bar = function|=>…`.
993
+ * Note: leverages `_isGlobalAssign`'s tail logic but requires
994
+ * `.prototype.<ident>` somewhere in the LHS.
995
+ */
996
+ _isPrototypeAssign(raw) {
997
+ let i = skipWhitespace(raw, 0);
998
+ const id = readIdent(raw, i);
999
+ if (!id) return false;
1000
+ let j = id.nextIdx;
1001
+ // walk `.ident*` until we hit `.prototype`
1002
+ let sawPrototype = false;
1003
+ while (raw[j] === '.') {
1004
+ // peek the next ident
1005
+ if (startsWithKeyword(raw, 'prototype', j + 1)) {
1006
+ sawPrototype = true;
1007
+ j = j + 1 + 'prototype'.length;
1008
+ break;
1009
+ }
1010
+ const more = readIdent(raw, j + 1);
1011
+ if (!more) return false;
1012
+ j = more.nextIdx;
1013
+ }
1014
+ if (!sawPrototype) return false;
1015
+ // Need `.ident` + `=` + function-ish
1016
+ if (raw[j] !== '.') return false;
1017
+ const methodId = readIdent(raw, j + 1);
1018
+ if (!methodId) return false;
1019
+ j = skipWhitespace(raw, methodId.nextIdx);
1020
+ if (raw[j] !== '=' || raw[j + 1] === '=') return false;
1021
+ return this._rhsLooksLikeFunction(raw, j + 1);
1022
+ }
1023
+
1024
+ /** jQuery-style plugin: `$.fn.X = function …` / `jQuery.fn.X = function …`. */
1025
+ _isJqueryPluginAssign(raw) {
1026
+ let i = skipWhitespace(raw, 0);
1027
+ let afterHead;
1028
+ if (raw[i] === '$') {
1029
+ afterHead = i + 1;
1030
+ } else if (startsWithKeyword(raw, 'jQuery', i)) {
1031
+ afterHead = i + 'jQuery'.length;
1032
+ } else {
1033
+ return false;
1034
+ }
1035
+ if (raw[afterHead] !== '.') return false;
1036
+ if (!startsWithKeyword(raw, 'fn', afterHead + 1)) return false;
1037
+ let j = afterHead + 1 + 'fn'.length;
1038
+ if (raw[j] !== '.') return false;
1039
+ const id = readIdent(raw, j + 1);
1040
+ if (!id) return false;
1041
+ j = skipWhitespace(raw, id.nextIdx);
1042
+ if (raw[j] !== '=' || raw[j + 1] === '=') return false;
1043
+ // We only insist on `function` here (matches the original regex).
1044
+ let r = skipWhitespace(raw, j + 1);
1045
+ const aa = consumeKeyword(raw, 'async', r);
1046
+ if (aa !== -1) r = skipWhitespace(raw, aa);
1047
+ return startsWithKeyword(raw, 'function', r);
1048
+ }
1049
+
1050
+ /** `document.addEventListener(` or `window.addEventListener(`. */
1051
+ _isAddEventListenerCall(raw) {
1052
+ const i = skipWhitespace(raw, 0);
1053
+ const head = consumeAnyKeyword(raw, ['document', 'window'], i);
1054
+ if (!head) return false;
1055
+ let j = head.nextIdx;
1056
+ if (raw[j] !== '.') return false;
1057
+ if (!startsWithKeyword(raw, 'addEventListener', j + 1)) return false;
1058
+ j = skipWhitespace(raw, j + 1 + 'addEventListener'.length);
1059
+ return raw[j] === '(';
1060
+ }
1061
+
1062
+ /**
1063
+ * Method inside a class. Caller already established `classDepth > 0`
1064
+ * and `braceDepthBeforeLine === methodDepth`. We just decide if the
1065
+ * line LOOKS like a method declaration vs a method call.
1066
+ *
1067
+ * Returns the method name or null.
1068
+ */
1069
+ _detectMethodName(raw) {
1070
+ // Must start with at least one space (methods are indented).
1071
+ if (raw[0] !== ' ' && raw[0] !== '\t') return null;
1072
+ let i = skipWhitespace(raw, 0);
1073
+ // Walk through any sequence of modifier keywords.
1074
+ while (true) {
1075
+ const id = readIdent(raw, i);
1076
+ if (!id) return null;
1077
+ if (!CodeMapTool._METHOD_MODIFIERS.has(id.ident)) {
1078
+ // This is the method name candidate.
1079
+ const afterName = skipWhitespace(raw, id.nextIdx);
1080
+ if (raw[afterName] !== '(' && raw[afterName] !== '<') return null;
1081
+ // Excluded reserved words (`if`, `for`, …)
1082
+ if (CodeMapTool._METHOD_KEYWORD_EXCLUDE.has(id.ident)) return null;
1083
+ // Is this a call rather than a definition?
1084
+ // Calls end with `)` (optionally `;`) and have NO `) {` or `) : …`.
1085
+ const trimmed = raw.trim();
1086
+ const endsWithClose = trimmedEndsWith(trimmed, ')');
1087
+ const endsWithCloseBrace = trimmedEndsWith(trimmed, ') {');
1088
+ // `): Type` a TS-style return-type annotation continuation
1089
+ const hasReturnTypeAnnotation = (() => {
1090
+ // Walk past the open paren to find the matching `)`, then check
1091
+ // if the next non-ws char is `:` followed by a non-empty token.
1092
+ let k = afterName + 1;
1093
+ let depth = 1;
1094
+ while (k < raw.length && depth > 0) {
1095
+ if (raw[k] === '(') depth += 1;
1096
+ else if (raw[k] === ')') depth -= 1;
1097
+ k += 1;
1098
+ }
1099
+ k = skipWhitespace(raw, k);
1100
+ return raw[k] === ':' && raw[k + 1] !== '\0' && raw[k + 1] !== undefined;
1101
+ })();
1102
+ if (endsWithClose && !endsWithCloseBrace && !hasReturnTypeAnnotation) return null;
1103
+ return id.ident;
1104
+ }
1105
+ // It WAS a modifier consume + continue.
1106
+ i = skipWhitespace(raw, id.nextIdx);
1107
+ }
1108
+ }
1109
+
1110
+ _parseJS(lines, { publicOnly, withComments, includeImports }) {
1111
+ const entries = [];
1112
+ let pendingComments = [];
1113
+ let inBlockComment = false;
1114
+ let blockCommentLines = [];
1115
+
1116
+ let classDepth = 0;
1117
+ let braceStack = [];
1118
+ let braceDepth = 0;
1119
+ let methodDepths = [];
1120
+
1121
+ for (let i = 0; i < lines.length; i += 1) {
1122
+ const lineNum = i + 1;
1123
+ const raw = lines[i];
1124
+ const start = skipWhitespace(raw, 0);
1125
+
1126
+ // block-comment tracking
1127
+ if (inBlockComment) {
1128
+ blockCommentLines.push({ line: lineNum, text: raw, kind: 'comment' });
1129
+ if (raw.includes('*/')) {
1130
+ inBlockComment = false;
1131
+ pendingComments = blockCommentLines;
1132
+ blockCommentLines = [];
1133
+ }
1134
+ continue;
1135
+ }
1136
+ if (raw[start] === '/' && raw[start + 1] === '*') {
1137
+ blockCommentLines = [{ line: lineNum, text: raw, kind: 'comment' }];
1138
+ if (raw.slice(start + 2).includes('*/')) {
1139
+ pendingComments = blockCommentLines;
1140
+ blockCommentLines = [];
1141
+ } else {
1142
+ inBlockComment = true;
1143
+ }
1144
+ continue;
1145
+ }
1146
+ if (raw[start] === '/' && raw[start + 1] === '/') {
1147
+ pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1148
+ continue;
1149
+ }
1150
+
1151
+ // imports
1152
+ if (includeImports) {
1153
+ if (this._isImportLine(raw) || this._hasRequireCall(raw)) {
1154
+ entries.push({ line: lineNum, text: raw, kind: 'import' });
1155
+ pendingComments = [];
1156
+ continue;
1157
+ }
1158
+ }
1159
+
1160
+ // Brace tracking for class context — mask strings + line comments
1161
+ // before counting so e.g. `var s = "class Foo {"` doesn't push a
1162
+ // bogus class frame.
1163
+ const masked = stripStringsAndLineComments(raw);
1164
+ const opens = countChar(masked, '{');
1165
+ const closes = countChar(masked, '}');
1166
+ const braceDepthBeforeLine = braceDepth;
1167
+
1168
+ if (this._isClassDeclWithOpenBrace(raw, opens)) {
1169
+ braceStack.push({ depth: braceDepth, type: 'class' });
1170
+ methodDepths.push(braceDepth + 1);
1171
+ classDepth += 1;
1172
+ }
1173
+ braceDepth += opens - closes;
1174
+ while (braceStack.length > 0 && braceDepth <= braceStack[braceStack.length - 1].depth) {
1175
+ const popped = braceStack.pop();
1176
+ if (popped.type === 'class') {
1177
+ classDepth -= 1;
1178
+ methodDepths.pop();
1179
+ }
1180
+ }
1181
+
1182
+ // Signature detection
1183
+ const isExport = startsWithKeyword(raw, 'export', start);
1184
+ const isModuleExports = this._isModuleExportsAssign(raw);
1185
+ const isExportsAssign = this._isExportsAssign(raw);
1186
+ const isPublic = isExport || isModuleExports || isExportsAssign;
1187
+
1188
+ let matched = false;
1189
+
1190
+ if (isExport && this._isExportableDecl(raw)) matched = true;
1191
+ if (isModuleExports || isExportsAssign) matched = true;
1192
+
1193
+ if (!matched && !publicOnly) {
1194
+ if (this._isTopLevelFunctionDecl(raw)) matched = true;
1195
+ if (this._isTopLevelClassDecl(raw)) matched = true;
1196
+ if (this._isConstAssignedFunction(raw)) matched = true;
1197
+
1198
+ // Browser-JS / pre-ESM patterns
1199
+ if (this._isIifePrefixOp(raw)) matched = true;
1200
+ if (this._isIifeParens(raw)) matched = true;
1201
+ if (this._isGlobalAssign(raw)) matched = true;
1202
+ if (this._isPrototypeAssign(raw)) matched = true;
1203
+ if (this._isJqueryPluginAssign(raw)) matched = true;
1204
+ if (this._isAddEventListenerCall(raw)) matched = true;
1205
+
1206
+ // Method inside class
1207
+ const atMethodDepth = methodDepths.length > 0 &&
1208
+ braceDepthBeforeLine === methodDepths[methodDepths.length - 1];
1209
+ if (classDepth > 0 && atMethodDepth && !isExport) {
1210
+ if (this._detectMethodName(raw)) matched = true;
1211
+ }
1212
+ }
1213
+
1214
+ if (!matched && isPublic) matched = true;
1215
+ if (matched && publicOnly && !isPublic) {
1216
+ pendingComments = [];
1217
+ continue;
1218
+ }
1219
+
1220
+ if (matched) {
1221
+ if (withComments && pendingComments.length > 0) {
1222
+ for (const c of pendingComments) entries.push(c);
1223
+ }
1224
+ entries.push({ line: lineNum, text: raw, kind: 'signature' });
1225
+ pendingComments = [];
1226
+ } else {
1227
+ pendingComments = [];
1228
+ }
1229
+ }
1230
+ return entries;
1231
+ }
1232
+
1233
+ // ── Python parser ─────────────────────────────────────────────────────────
1234
+ //
1235
+ // No-regex implementation. Identifiers are ASCII per PEP 8's
1236
+ // recommendation; this is a documented tradeoff vs. the previous
1237
+ // Unicode-aware (\p{L}) regex. The alternative bundling a Unicode
1238
+ // category table adds ~50 KB for a feature ~0.1 % of Python
1239
+ // codebases use. If users surface a non-ASCII-identifier Python
1240
+ // codebase the fix is to bring in tree-sitter (already planned for
1241
+ // #5 follow-on) which is Unicode-correct by default.
1242
+
1243
+ /** Try to read a `def` or `async def` head. Returns the function name or null. */
1244
+ _readPythonDefName(raw) {
1245
+ let i = skipWhitespace(raw, 0);
1246
+ // optional `async`
1247
+ const aa = consumeKeyword(raw, 'async', i);
1248
+ if (aa !== -1) i = skipWhitespace(raw, aa);
1249
+ if (!startsWithKeyword(raw, 'def', i)) return null;
1250
+ i = skipWhitespace(raw, i + 'def'.length);
1251
+ const id = readIdent(raw, i);
1252
+ if (!id) return null;
1253
+ i = skipWhitespace(raw, id.nextIdx);
1254
+ if (raw[i] !== '(') return null;
1255
+ return id.ident;
1256
+ }
1257
+ /** Try to read a `class Foo` head. Returns class name or null. */
1258
+ _readPythonClassName(raw) {
1259
+ let i = skipWhitespace(raw, 0);
1260
+ if (!startsWithKeyword(raw, 'class', i)) return null;
1261
+ i = skipWhitespace(raw, i + 'class'.length);
1262
+ const id = readIdent(raw, i);
1263
+ return id ? id.ident : null;
1264
+ }
1265
+
1266
+ /**
1267
+ * Returns true if `s` (at or after leading whitespace) starts with a
1268
+ * Python triple-quote prefix (`"""` or `'''`). Returns null otherwise.
1269
+ * On hit returns the 3-char quote string.
1270
+ */
1271
+ _pythonTripleQuoteAtStart(s) {
1272
+ const i = skipWhitespace(s, 0);
1273
+ if (s[i] === '"' && s[i + 1] === '"' && s[i + 2] === '"') return '"""';
1274
+ if (s[i] === "'" && s[i + 1] === "'" && s[i + 2] === "'") return "'''";
1275
+ return null;
1276
+ }
1277
+ /** True if the single-line docstring on a triple-quote line is closed on the same line. */
1278
+ _pythonTripleQuoteClosedOnLine(s, quote) {
1279
+ const i = skipWhitespace(s, 0);
1280
+ return s.indexOf(quote, i + 3) !== -1;
1281
+ }
1282
+
1283
+ _parsePython(lines, { publicOnly, withComments, includeImports }) {
1284
+ const entries = [];
1285
+ let pendingComments = [];
1286
+ let i = 0;
1287
+
1288
+ while (i < lines.length) {
1289
+ const lineNum = i + 1;
1290
+ const raw = lines[i];
1291
+ const start = skipWhitespace(raw, 0);
1292
+
1293
+ // imports `import …` or `from <module> import …`
1294
+ if (includeImports) {
1295
+ if (startsWithKeyword(raw, 'import', start)) {
1296
+ entries.push({ line: lineNum, text: raw, kind: 'import' });
1297
+ pendingComments = [];
1298
+ i += 1; continue;
1299
+ }
1300
+ if (startsWithKeyword(raw, 'from', start)) {
1301
+ // require a non-empty module then `import`
1302
+ let j = skipWhitespace(raw, start + 'from'.length);
1303
+ // Read the dotted module path; allow leading dots (relative).
1304
+ let progressed = false;
1305
+ while (j < raw.length && raw[j] === '.') { j += 1; progressed = true; }
1306
+ const id = readIdent(raw, j);
1307
+ if (id) { j = id.nextIdx; progressed = true; }
1308
+ while (raw[j] === '.') {
1309
+ const more = readIdent(raw, j + 1);
1310
+ if (!more) { progressed = true; break; }
1311
+ j = more.nextIdx;
1312
+ }
1313
+ j = skipWhitespace(raw, j);
1314
+ if (progressed && startsWithKeyword(raw, 'import', j)) {
1315
+ entries.push({ line: lineNum, text: raw, kind: 'import' });
1316
+ pendingComments = [];
1317
+ i += 1; continue;
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ // # comments
1323
+ if (raw[start] === '#') {
1324
+ pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1325
+ i += 1; continue;
1326
+ }
1327
+ // decorators
1328
+ if (raw[start] === '@') {
1329
+ pendingComments.push({ line: lineNum, text: raw, kind: 'decorator' });
1330
+ i += 1; continue;
1331
+ }
1332
+
1333
+ // def / async def / class
1334
+ const defName = this._readPythonDefName(raw);
1335
+ const className = defName ? null : this._readPythonClassName(raw);
1336
+ const name = defName || className;
1337
+
1338
+ if (name) {
1339
+ const isPublic = !name.startsWith('_');
1340
+
1341
+ // Helper: continuation lines for a multi-line signature like
1342
+ // def foo(
1343
+ // x: int,
1344
+ // y: int,
1345
+ // ) -> int:
1346
+ // The original advanced past the line that closes with `):` or
1347
+ // a line starting with `)` containing `:`. Preserve that logic.
1348
+ const advancePastMultilineSig = () => {
1349
+ // Only def's have parameter parens; class doesn't.
1350
+ if (!defName) return;
1351
+ if (raw.includes('):')) return;
1352
+ if (raw.includes(')')) return;
1353
+ // walk forward
1354
+ while (i < lines.length) {
1355
+ const pline = lines[i].trim();
1356
+ i += 1;
1357
+ if (pline.includes('):') ||
1358
+ (pline.startsWith(')') && pline.includes(':'))) break;
1359
+ }
1360
+ };
1361
+
1362
+ if (publicOnly && !isPublic) {
1363
+ pendingComments = [];
1364
+ i += 1;
1365
+ advancePastMultilineSig();
1366
+ i = this._skipPythonDocstring(lines, i);
1367
+ continue;
1368
+ }
1369
+
1370
+ if (withComments && pendingComments.length > 0) {
1371
+ for (const c of pendingComments) entries.push(c);
1372
+ }
1373
+ entries.push({ line: lineNum, text: raw, kind: 'signature' });
1374
+ pendingComments = [];
1375
+ i += 1;
1376
+ advancePastMultilineSig();
1377
+
1378
+ if (withComments) {
1379
+ const dsLines = this._collectPythonDocstring(lines, i);
1380
+ for (const ds of dsLines) entries.push(ds);
1381
+ i += dsLines.length;
1382
+ } else {
1383
+ i = this._skipPythonDocstring(lines, i);
1384
+ }
1385
+ continue;
1386
+ }
1387
+
1388
+ // Top-level assignments (data lines):
1389
+ // ident = expr (only when starting at column 0; indented
1390
+ // lines are method-internal and ignored)
1391
+ if (raw.length > 0 && !isWhitespace(raw[0]) && raw[0] !== '\n') {
1392
+ const id = readIdent(raw, 0);
1393
+ if (id) {
1394
+ let j = skipWhitespace(raw, id.nextIdx);
1395
+ if (raw[j] === '=' && raw[j + 1] !== '=') {
1396
+ // looks like `ident = …`
1397
+ // require there to BE an RHS `name =` alone is invalid
1398
+ const afterEq = skipWhitespace(raw, j + 1);
1399
+ if (afterEq < raw.length) {
1400
+ const isPublicVar = !id.ident.startsWith('_');
1401
+ if (!publicOnly || isPublicVar) {
1402
+ if (withComments && pendingComments.length > 0) {
1403
+ for (const c of pendingComments) entries.push(c);
1404
+ }
1405
+ entries.push({ line: lineNum, text: raw, kind: 'data' });
1406
+ }
1407
+ pendingComments = [];
1408
+ i += 1; continue;
1409
+ }
1410
+ }
1411
+ }
1412
+ }
1413
+
1414
+ pendingComments = [];
1415
+ i += 1;
1416
+ }
1417
+
1418
+ return entries;
1419
+ }
1420
+
1421
+ _collectPythonDocstring(lines, startIdx) {
1422
+ if (startIdx >= lines.length) return [];
1423
+ const first = lines[startIdx];
1424
+ const quote = this._pythonTripleQuoteAtStart(first);
1425
+ if (!quote) return [];
1426
+ // single-line case: triple-quote opens AND closes on the same line
1427
+ if (this._pythonTripleQuoteClosedOnLine(first, quote)) {
1428
+ return [{ line: startIdx + 1, text: first, kind: 'comment' }];
1429
+ }
1430
+ // multi-line: collect until the closing triple-quote
1431
+ const result = [{ line: startIdx + 1, text: first, kind: 'comment' }];
1432
+ for (let j = startIdx + 1; j < lines.length; j += 1) {
1433
+ result.push({ line: j + 1, text: lines[j], kind: 'comment' });
1434
+ if (lines[j].includes(quote)) break;
1435
+ }
1436
+ return result;
1437
+ }
1438
+
1439
+ _skipPythonDocstring(lines, startIdx) {
1440
+ if (startIdx >= lines.length) return startIdx;
1441
+ const first = lines[startIdx];
1442
+ const quote = this._pythonTripleQuoteAtStart(first);
1443
+ if (!quote) return startIdx;
1444
+ if (this._pythonTripleQuoteClosedOnLine(first, quote)) return startIdx + 1;
1445
+ for (let j = startIdx + 1; j < lines.length; j += 1) {
1446
+ if (lines[j].includes(quote)) return j + 1;
1447
+ }
1448
+ return lines.length;
1449
+ }
1450
+
1451
+ // ── C / C++ parser ────────────────────────────────────────────────────────
1452
+ //
1453
+ // Regex-based, same approach as the JS path: walk lines, match a set
1454
+ // of declaration patterns. Mirrors what _parseJS does for JS — gets
1455
+ // the agent a high-level skeleton of any C/CPP file (functions,
1456
+ // classes/structs, includes, top-level macros) without bringing in
1457
+ // a full C parser. The proper tree-sitter migration (see #5 plan)
1458
+ // replaces this for full accuracy on edge cases like multi-line
1459
+ // function signatures, templated declarations, and trailing return
1460
+ // types — those work in 90 % of real code already because most
1461
+ // declarations fit on one line.
1462
+ //
1463
+ // Patterns covered:
1464
+ // - #include <...> / #include "..." → 'import' kind
1465
+ // - #define MACRO ... → 'signature' kind
1466
+ // - class Foo { → 'signature'
1467
+ // - struct Foo { → 'signature'
1468
+ // - enum class Foo { → 'signature'
1469
+ // - namespace foo { → 'signature'
1470
+ // - typedef name; → 'signature'
1471
+ // - using name = …; → 'signature'
1472
+ // - free-function: `<type> name(args) {` → 'signature'
1473
+ // - method: `<type> Class::name(args) {` → 'signature'
1474
+ // - constructor: `Class::Class(args) {` → 'signature'
1475
+ // - destructor: `Class::~Class()` → 'signature'
1476
+ //
1477
+ // Skipped (intentional):
1478
+ // - Function prototypes (declarations w/o body) — too noisy in
1479
+ // headers; the user usually wants the definitions
1480
+ // - Lambdas covered by treating the enclosing function as the
1481
+ // signature
1482
+ _parseC(lines, { publicOnly, withComments, includeImports }) {
1483
+ const entries = [];
1484
+ let pendingComments = [];
1485
+ let inBlockComment = false;
1486
+
1487
+ // C/C++ tokens we accept at the head of a function/type declaration.
1488
+ // The classifier walks them one at a time using consumeKeyword —
1489
+ // no regex. After the keyword run we require at least one
1490
+ // additional token before the `(` so we don't misidentify a bare
1491
+ // function call as a definition.
1492
+ const C_TYPE_KEYWORDS = [
1493
+ 'static', 'inline', 'extern', 'constexpr', 'virtual', 'explicit',
1494
+ 'friend', 'const', 'mutable', 'register', 'volatile',
1495
+ 'signed', 'unsigned', 'short', 'long',
1496
+ // Built-in primitive types — frequently followed by an ident.
1497
+ 'void', 'bool', 'char', 'int', 'float', 'double',
1498
+ 'auto', 'wchar_t', 'char16_t', 'char32_t',
1499
+ ];
1500
+
1501
+ for (let i = 0; i < lines.length; i += 1) {
1502
+ const lineNum = i + 1;
1503
+ const raw = lines[i];
1504
+ const start = skipWhitespace(raw, 0);
1505
+
1506
+ // ── block + line comments ────────────────────────────────────
1507
+ if (inBlockComment) {
1508
+ if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1509
+ if (raw.includes('*/')) inBlockComment = false;
1510
+ continue;
1511
+ }
1512
+ if (raw[start] === '/' && raw[start + 1] === '*') {
1513
+ if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1514
+ // The block-comment scan checks anywhere on the line; templates
1515
+ // / preproc lines don't typically contain '*/' so this is safe.
1516
+ if (!raw.slice(start + 2).includes('*/')) inBlockComment = true;
1517
+ continue;
1518
+ }
1519
+ if (raw[start] === '/' && raw[start + 1] === '/') {
1520
+ if (withComments) pendingComments.push({ line: lineNum, text: raw, kind: 'comment' });
1521
+ continue;
1522
+ }
1523
+
1524
+ // ── #include ─────────────────────────────────────────────────
1525
+ if (includeImports && raw[start] === '#') {
1526
+ const afterHash = skipWhitespace(raw, start + 1);
1527
+ if (startsWithKeyword(raw, 'include', afterHash)) {
1528
+ entries.push({ line: lineNum, text: raw, kind: 'import' });
1529
+ pendingComments = [];
1530
+ continue;
1531
+ }
1532
+ }
1533
+
1534
+ let matched = false;
1535
+
1536
+ // ── #define MACRO … ──────────────────────────────────────────
1537
+ if (raw[start] === '#') {
1538
+ const afterHash = skipWhitespace(raw, start + 1);
1539
+ if (startsWithKeyword(raw, 'define', afterHash)) {
1540
+ const afterKw = skipWhitespace(raw, afterHash + 'define'.length);
1541
+ if (readIdent(raw, afterKw)) matched = true;
1542
+ }
1543
+ }
1544
+
1545
+ // ── namespace / class / struct / union / enum (incl. `enum class`) ──
1546
+ // Tip: also handle a leading `template <…>` by skipping over it.
1547
+ let probeIdx = start;
1548
+ if (startsWithKeyword(raw, 'template', probeIdx)) {
1549
+ // skip "template" + optional whitespace + `<…>` if present
1550
+ let j = skipWhitespace(raw, probeIdx + 'template'.length);
1551
+ if (raw[j] === '<') {
1552
+ let depth = 1;
1553
+ j += 1;
1554
+ while (j < raw.length && depth > 0) {
1555
+ if (raw[j] === '<') depth += 1;
1556
+ else if (raw[j] === '>') depth -= 1;
1557
+ j += 1;
1558
+ }
1559
+ probeIdx = skipWhitespace(raw, j);
1560
+ }
1561
+ }
1562
+ const typeHit = consumeAnyKeyword(raw, ['class', 'struct', 'union', 'enum', 'namespace'], probeIdx);
1563
+ if (typeHit) {
1564
+ let after = skipWhitespace(raw, typeHit.nextIdx);
1565
+ // `enum class Color` — accept either form
1566
+ if (typeHit.keyword === 'enum' && startsWithKeyword(raw, 'class', after)) {
1567
+ after = skipWhitespace(raw, after + 'class'.length);
1568
+ }
1569
+ if (readIdent(raw, after)) matched = true;
1570
+ }
1571
+
1572
+ // ── typedef … name; ──────────────────────────────────────────
1573
+ if (!matched && startsWithKeyword(raw, 'typedef', start)) {
1574
+ // very permissive — just require a `;` somewhere after typedef
1575
+ if (raw.indexOf(';', start) !== -1) matched = true;
1576
+ }
1577
+
1578
+ // ── using name = … ───────────────────────────────────────────
1579
+ if (!matched && startsWithKeyword(raw, 'using', start)) {
1580
+ const afterKw = skipWhitespace(raw, start + 'using'.length);
1581
+ const id = readIdent(raw, afterKw);
1582
+ if (id) {
1583
+ const afterId = skipWhitespace(raw, id.nextIdx);
1584
+ if (raw[afterId] === '=' && raw[afterId + 1] !== '=') matched = true;
1585
+ }
1586
+ }
1587
+
1588
+ // ── Function definition: <type-tokens> name(…) ───────────────
1589
+ // Walk past at least one type-ish token before the function name.
1590
+ // The "type-ish" tokens are either keywords from C_TYPE_KEYWORDS
1591
+ // or any plain identifier (could be a user-defined type) — we
1592
+ // require AT LEAST TWO tokens before the `(` so a bare call
1593
+ // `foo(x)` isn't misidentified.
1594
+ if (!matched) {
1595
+ let j = probeIdx;
1596
+ let tokensBeforeParen = 0;
1597
+ let lastWasIdent = false;
1598
+ while (j < raw.length) {
1599
+ // pointer / reference / qualifiers in between tokens
1600
+ if (raw[j] === '*' || raw[j] === '&') { j += 1; continue; }
1601
+ j = skipWhitespace(raw, j);
1602
+ if (j >= raw.length) break;
1603
+ if (raw[j] === '(') break;
1604
+ // accept a templated type like `vector<int>` — skip the `<…>`
1605
+ if (raw[j] === '<' && lastWasIdent) {
1606
+ let depth = 1; j += 1;
1607
+ while (j < raw.length && depth > 0) {
1608
+ if (raw[j] === '<') depth += 1;
1609
+ else if (raw[j] === '>') depth -= 1;
1610
+ j += 1;
1611
+ }
1612
+ continue;
1613
+ }
1614
+ // qualifier keyword?
1615
+ const kw = consumeAnyKeyword(raw, C_TYPE_KEYWORDS, j);
1616
+ if (kw) { j = kw.nextIdx; tokensBeforeParen += 1; lastWasIdent = false; continue; }
1617
+ // plain identifier (possibly `Foo::bar`)
1618
+ const id = readIdent(raw, j);
1619
+ if (!id) break;
1620
+ j = id.nextIdx;
1621
+ // handle `Foo::bar` and similar
1622
+ while (raw[j] === ':' && raw[j + 1] === ':') {
1623
+ j += 2;
1624
+ const more = readIdent(raw, j);
1625
+ if (!more) break;
1626
+ j = more.nextIdx;
1627
+ }
1628
+ tokensBeforeParen += 1;
1629
+ lastWasIdent = true;
1630
+ }
1631
+ if (raw[j] === '(' && tokensBeforeParen >= 2) {
1632
+ // Reject "looks like a call" — `name(args);` ending tail.
1633
+ // For a definition we expect either `) {` or just `,` (multi-line)
1634
+ // or `)` ending the line. A call ends with `);`.
1635
+ const endsWithCallSemi = trimmedEndsWith(raw, ');');
1636
+ if (!endsWithCallSemi) matched = true;
1637
+ }
1638
+ }
1639
+
1640
+ // ── Constructor / destructor: `Foo::Foo(...)` / `Foo::~Foo(...)` ──
1641
+ if (!matched) {
1642
+ const id1 = readIdent(raw, start);
1643
+ if (id1 && raw[id1.nextIdx] === ':' && raw[id1.nextIdx + 1] === ':') {
1644
+ let k = id1.nextIdx + 2;
1645
+ if (raw[k] === '~') k += 1;
1646
+ const id2 = readIdent(raw, k);
1647
+ if (id2) {
1648
+ const afterName = skipWhitespace(raw, id2.nextIdx);
1649
+ if (raw[afterName] === '(') matched = true;
1650
+ }
1651
+ }
1652
+ }
1653
+
1654
+ // publicOnly is a no-op for C — left intact for API parity with
1655
+ // _parseJS. Could later mean "header-public only".
1656
+ void publicOnly;
1657
+
1658
+ if (matched) {
1659
+ if (withComments && pendingComments.length > 0) {
1660
+ for (const c of pendingComments) entries.push(c);
1661
+ }
1662
+ entries.push({ line: lineNum, text: raw, kind: 'signature' });
1663
+ pendingComments = [];
1664
+ } else {
1665
+ pendingComments = [];
1666
+ }
1667
+ }
1668
+
1669
+ return entries;
1670
+ }
1671
+ }
1672
+
1673
+ export default CodeMapTool;