From c96a14c541c7a0102810c088ad46538155c43f70 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:28:02 +0300 Subject: [PATCH 1/9] refactor: extract KIND_TO_NODE_TYPE as module-level constant in GraphBuilder Previously the mapping object was recreated on every mapKindToNodeType call. Moving it to module level means it is allocated once at load time instead of once per definition node processed. --- pnpm-lock.yaml | 286 +++++++++++++----- .../core/src/analyzer/graph-builder.ts | 53 ++-- 2 files changed, 239 insertions(+), 100 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 327a918..d01ba6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 5.9.3 vitest: specifier: ^3.1.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) homepage: dependencies: astro: specifier: ^6.0.4 - version: 6.0.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.3) + version: 6.0.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.3) understand-anything-plugin: dependencies: @@ -35,7 +35,7 @@ importers: version: 5.9.3 vitest: specifier: ^3.1.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) understand-anything-plugin/packages/core: dependencies: @@ -66,13 +66,13 @@ importers: version: 25.5.0 '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) typescript: specifier: ^5.7.0 version: 5.9.3 vitest: specifier: ^3.1.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) understand-anything-plugin/packages/dashboard: dependencies: @@ -109,7 +109,7 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.2.1(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + version: 4.2.1(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) '@types/d3-force': specifier: ^3.0.10 version: 3.0.10 @@ -121,7 +121,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) tailwindcss: specifier: ^4.0.0 version: 4.2.1 @@ -130,7 +130,7 @@ importers: version: 5.9.3 vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + version: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) packages: @@ -205,8 +205,8 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} '@babel/parser@7.29.0': @@ -1191,8 +1191,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.7: - resolution: {integrity: sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==} + baseline-browser-mapping@2.10.18: + resolution: {integrity: sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==} engines: {node: '>=6.0.0'} hasBin: true @@ -1206,8 +1206,8 @@ packages: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1215,8 +1215,8 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - caniuse-lite@1.0.30001778: - resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} + caniuse-lite@1.0.30001787: + resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1422,8 +1422,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.313: - resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} + electron-to-chromium@1.5.335: + resolution: {integrity: sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1688,70 +1688,140 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.31.1: resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.31.1: resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.31.1: resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.31.1: resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.31.1: resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.31.1: resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.31.1: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -1967,8 +2037,8 @@ packages: node-mock-http@1.0.4: resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2048,6 +2118,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -2462,8 +2536,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -2732,7 +2806,7 @@ snapshots: '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 + '@babel/helpers': 7.29.2 '@babel/parser': 7.29.0 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 @@ -2758,7 +2832,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -2788,7 +2862,7 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.6': + '@babel/helpers@7.29.2': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 @@ -3330,12 +3404,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 - '@tailwindcss/vite@4.2.1(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.1(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) '@types/babel__core@7.20.5': dependencies: @@ -3434,7 +3508,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -3442,11 +3516,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -3461,7 +3535,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -3473,13 +3547,21 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -3559,7 +3641,7 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 - astro@6.0.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.3): + astro@6.0.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.3): dependencies: '@astrojs/compiler': 3.0.0 '@astrojs/internal-helpers': 0.8.0 @@ -3611,8 +3693,8 @@ snapshots: unist-util-visit: 5.1.0 unstorage: 1.17.4 vfile: 6.0.3 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) xxhash-wasm: 1.1.0 yargs-parser: 22.0.0 zod: 4.3.6 @@ -3661,7 +3743,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.7: {} + baseline-browser-mapping@2.10.18: {} boolbase@1.0.0: {} @@ -3673,17 +3755,17 @@ snapshots: dependencies: balanced-match: 4.0.4 - browserslist@4.28.1: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.7 - caniuse-lite: 1.0.30001778 - electron-to-chromium: 1.5.313 - node-releases: 2.0.36 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + baseline-browser-mapping: 2.10.18 + caniuse-lite: 1.0.30001787 + electron-to-chromium: 1.5.335 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) cac@6.7.14: {} - caniuse-lite@1.0.30001778: {} + caniuse-lite@1.0.30001787: {} ccount@2.0.1: {} @@ -3863,7 +3945,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.313: {} + electron-to-chromium@1.5.335: {} emoji-regex@8.0.0: {} @@ -3962,6 +4044,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + flattie@1.1.1: {} fontace@0.4.1: @@ -4207,36 +4293,69 @@ snapshots: lightningcss-android-arm64@1.31.1: optional: true + lightningcss-android-arm64@1.32.0: + optional: true + lightningcss-darwin-arm64@1.31.1: optional: true + lightningcss-darwin-arm64@1.32.0: + optional: true + lightningcss-darwin-x64@1.31.1: optional: true + lightningcss-darwin-x64@1.32.0: + optional: true + lightningcss-freebsd-x64@1.31.1: optional: true + lightningcss-freebsd-x64@1.32.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.31.1: optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + lightningcss-linux-arm64-gnu@1.31.1: optional: true + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + lightningcss-linux-arm64-musl@1.31.1: optional: true + lightningcss-linux-arm64-musl@1.32.0: + optional: true + lightningcss-linux-x64-gnu@1.31.1: optional: true + lightningcss-linux-x64-gnu@1.32.0: + optional: true + lightningcss-linux-x64-musl@1.31.1: optional: true + lightningcss-linux-x64-musl@1.32.0: + optional: true + lightningcss-win32-arm64-msvc@1.31.1: optional: true + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + lightningcss-win32-x64-msvc@1.31.1: optional: true + lightningcss-win32-x64-msvc@1.32.0: + optional: true + lightningcss@1.31.1: dependencies: detect-libc: 2.1.2 @@ -4253,6 +4372,23 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + optional: true + longest-streak@3.1.0: {} loupe@3.2.1: {} @@ -4671,7 +4807,7 @@ snapshots: node-mock-http@1.0.4: {} - node-releases@2.0.36: {} + node-releases@2.0.37: {} normalize-path@3.0.0: {} @@ -4754,6 +4890,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.4: {} + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -5190,9 +5328,9 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 - update-browserslist-db@1.2.3(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -5215,13 +5353,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 6.4.2(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - jiti @@ -5236,13 +5374,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - jiti @@ -5257,11 +5395,11 @@ snapshots: - tsx - yaml - vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite@6.4.2(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.8 rollup: 4.59.0 tinyglobby: 0.2.15 @@ -5269,14 +5407,14 @@ snapshots: '@types/node': 22.19.15 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 yaml: 2.8.3 - vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite@6.4.2(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.8 rollup: 4.59.0 tinyglobby: 0.2.15 @@ -5284,10 +5422,10 @@ snapshots: '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 yaml: 2.8.3 - vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.3) @@ -5299,10 +5437,10 @@ snapshots: '@types/node': 22.19.15 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 yaml: 2.8.3 - vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.3) @@ -5314,18 +5452,18 @@ snapshots: '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 yaml: 2.8.3 - vitefu@1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)): + vitefu@1.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)): optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -5343,8 +5481,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -5363,11 +5501,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -5385,8 +5523,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index 423b49f..94fd121 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -35,6 +35,27 @@ interface NonCodeFileAnalysisMeta extends NonCodeFileMeta { sections?: SectionInfo[]; } +const KIND_TO_NODE_TYPE: Record = { + table: "table", + view: "table", + index: "table", + message: "schema", + type: "schema", + enum: "schema", + resource: "resource", + module: "resource", + service: "service", + deployment: "service", + job: "pipeline", + stage: "pipeline", + target: "pipeline", + route: "endpoint", + query: "endpoint", + mutation: "endpoint", + variable: "config", + output: "config", +}; + const EXTENSION_LANGUAGE: Record = { // Code languages ".ts": "typescript", @@ -102,11 +123,11 @@ function detectLanguage(filePath: string): string { } export class GraphBuilder { - private nodes: GraphNode[] = []; - private edges: GraphEdge[] = []; - private languages = new Set(); - private projectName: string; - private gitHash: string; + private readonly nodes: GraphNode[] = []; + private readonly edges: GraphEdge[] = []; + private readonly languages = new Set(); + private readonly projectName: string; + private readonly gitHash: string; constructor(projectName: string, gitHash: string) { this.projectName = projectName; @@ -355,27 +376,7 @@ export class GraphBuilder { } private mapKindToNodeType(kind: string): GraphNode["type"] { - const mapping: Record = { - table: "table", - view: "table", - index: "table", - message: "schema", - type: "schema", - enum: "schema", - resource: "resource", - module: "resource", - service: "service", - deployment: "service", - job: "pipeline", - stage: "pipeline", - target: "pipeline", - route: "endpoint", - query: "endpoint", - mutation: "endpoint", - variable: "config", - output: "config", - }; - const mapped = mapping[kind]; + const mapped = KIND_TO_NODE_TYPE[kind]; if (!mapped) { console.warn(`[GraphBuilder] Unknown definition kind "${kind}" — falling back to "concept" node type`); } From a89555d36911b250a8075f1f67d2fd3da6437730 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:33:18 +0300 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20promote=20nodeIds=20to=20class?= =?UTF-8?q?=20field=20to=20avoid=20O(n=C2=B2)=20set=20rebuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously addNonCodeFileWithAnalysis rebuilt a full Set from this.nodes on every call, making duplicate checks O(n) per file and O(n²) overall. Moving nodeIds to a class field and updating it incrementally at each insertion reduces duplicate detection to O(1) per check. --- .../core/src/analyzer/graph-builder.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index 94fd121..fdb8f48 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -126,6 +126,7 @@ export class GraphBuilder { private readonly nodes: GraphNode[] = []; private readonly edges: GraphEdge[] = []; private readonly languages = new Set(); + private readonly nodeIds = new Set(); private readonly projectName: string; private readonly gitHash: string; @@ -142,8 +143,10 @@ export class GraphBuilder { const name = filePath.split("/").pop() ?? filePath; + const id = `file:${filePath}`; + this.nodeIds.add(id); this.nodes.push({ - id: `file:${filePath}`, + id, type: "file", name, filePath, @@ -167,6 +170,7 @@ export class GraphBuilder { const fileId = `file:${filePath}`; // Create the file node + this.nodeIds.add(fileId); this.nodes.push({ id: fileId, type: "file", @@ -180,6 +184,7 @@ export class GraphBuilder { // Create function nodes with "contains" edges for (const fn of analysis.functions) { const funcId = `function:${filePath}:${fn.name}`; + this.nodeIds.add(funcId); this.nodes.push({ id: funcId, type: "function", @@ -203,6 +208,7 @@ export class GraphBuilder { // Create class nodes with "contains" edges for (const cls of analysis.classes) { const classId = `class:${filePath}:${cls.name}`; + this.nodeIds.add(classId); this.nodes.push({ id: classId, type: "class", @@ -253,8 +259,10 @@ export class GraphBuilder { const lang = detectLanguage(filePath); if (lang !== "unknown") this.languages.add(lang); const name = filePath.split("/").pop() ?? filePath; + const id = `${meta.nodeType ?? "file"}:${filePath}`; + this.nodeIds.add(id); this.nodes.push({ - id: `${meta.nodeType ?? "file"}:${filePath}`, + id, type: meta.nodeType, name, filePath, @@ -268,16 +276,14 @@ export class GraphBuilder { this.addNonCodeFile(filePath, meta); const fileId = `${meta.nodeType ?? "file"}:${filePath}`; - const existingIds = new Set(this.nodes.map(n => n.id)); - // Create child nodes for definitions (tables, schemas, etc.) for (const def of meta.definitions ?? []) { const childId = `${def.kind}:${filePath}:${def.name}`; - if (existingIds.has(childId)) { + if (this.nodeIds.has(childId)) { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } - existingIds.add(childId); + this.nodeIds.add(childId); this.nodes.push({ id: childId, type: this.mapKindToNodeType(def.kind), @@ -294,11 +300,11 @@ export class GraphBuilder { // Create child nodes for services for (const svc of meta.services ?? []) { const childId = `service:${filePath}:${svc.name}`; - if (existingIds.has(childId)) { + if (this.nodeIds.has(childId)) { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } - existingIds.add(childId); + this.nodeIds.add(childId); this.nodes.push({ id: childId, type: "service", @@ -314,11 +320,11 @@ export class GraphBuilder { // Create child nodes for endpoints for (const ep of meta.endpoints ?? []) { const childId = `endpoint:${filePath}:${ep.path}`; - if (existingIds.has(childId)) { + if (this.nodeIds.has(childId)) { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } - existingIds.add(childId); + this.nodeIds.add(childId); this.nodes.push({ id: childId, type: "endpoint", @@ -335,11 +341,11 @@ export class GraphBuilder { // Create child nodes for steps (pipeline/makefile targets) for (const step of meta.steps ?? []) { const childId = `step:${filePath}:${step.name}`; - if (existingIds.has(childId)) { + if (this.nodeIds.has(childId)) { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } - existingIds.add(childId); + this.nodeIds.add(childId); this.nodes.push({ id: childId, type: "pipeline", @@ -356,11 +362,11 @@ export class GraphBuilder { // Create child nodes for resources (Terraform, etc.) for (const res of meta.resources ?? []) { const childId = `resource:${filePath}:${res.name}`; - if (existingIds.has(childId)) { + if (this.nodeIds.has(childId)) { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } - existingIds.add(childId); + this.nodeIds.add(childId); this.nodes.push({ id: childId, type: "resource", From 82b1d06777cd0eca9209a8879034fd0c62842e76 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:35:55 +0300 Subject: [PATCH 3/9] refactor: extract basename helper to replace repeated split/pop pattern The same filePath.split("/").pop() ?? filePath expression appeared three times across addFile, addFileWithAnalysis, and addNonCodeFile. A private static helper centralises the logic and makes call sites easier to read. --- .../packages/core/src/analyzer/graph-builder.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index fdb8f48..d43e9e0 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -135,13 +135,17 @@ export class GraphBuilder { this.gitHash = gitHash; } + private static basename(filePath: string): string { + return GraphBuilder.basename(filePath); + } + addFile(filePath: string, meta: FileMeta): void { const lang = detectLanguage(filePath); if (lang !== "unknown") { this.languages.add(lang); } - const name = filePath.split("/").pop() ?? filePath; + const name = GraphBuilder.basename(filePath); const id = `file:${filePath}`; this.nodeIds.add(id); @@ -166,7 +170,7 @@ export class GraphBuilder { this.languages.add(lang); } - const fileName = filePath.split("/").pop() ?? filePath; + const fileName = GraphBuilder.basename(filePath); const fileId = `file:${filePath}`; // Create the file node @@ -258,7 +262,7 @@ export class GraphBuilder { addNonCodeFile(filePath: string, meta: NonCodeFileMeta): void { const lang = detectLanguage(filePath); if (lang !== "unknown") this.languages.add(lang); - const name = filePath.split("/").pop() ?? filePath; + const name = GraphBuilder.basename(filePath); const id = `${meta.nodeType ?? "file"}:${filePath}`; this.nodeIds.add(id); this.nodes.push({ From 6c55ee303aa9f9c421e7a5278bbce147bbff2054 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:41:12 +0300 Subject: [PATCH 4/9] refactor: return fileId from addNonCodeFile instead of recomputing it addNonCodeFileWithAnalysis was reconstructing the fileId string independently of addNonCodeFile, creating a silent correctness risk if the ID construction logic ever changed. addNonCodeFile now returns the ID it used so the caller cannot go out of sync. --- .../packages/core/src/analyzer/graph-builder.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index d43e9e0..f74cb7e 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -136,7 +136,7 @@ export class GraphBuilder { } private static basename(filePath: string): string { - return GraphBuilder.basename(filePath); + return filePath.split("/").pop() ?? filePath; } addFile(filePath: string, meta: FileMeta): void { @@ -259,7 +259,7 @@ export class GraphBuilder { }); } - addNonCodeFile(filePath: string, meta: NonCodeFileMeta): void { + addNonCodeFile(filePath: string, meta: NonCodeFileMeta): string { const lang = detectLanguage(filePath); if (lang !== "unknown") this.languages.add(lang); const name = GraphBuilder.basename(filePath); @@ -274,11 +274,11 @@ export class GraphBuilder { tags: meta.tags, complexity: meta.complexity, }); + return id; } addNonCodeFileWithAnalysis(filePath: string, meta: NonCodeFileAnalysisMeta): void { - this.addNonCodeFile(filePath, meta); - const fileId = `${meta.nodeType ?? "file"}:${filePath}`; + const fileId = this.addNonCodeFile(filePath, meta); // Create child nodes for definitions (tables, schemas, etc.) for (const def of meta.definitions ?? []) { From 5593af76ca4c39e8091003ce9f16b7452e700bbf Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:44:17 +0300 Subject: [PATCH 5/9] refactor: extract endpoint label into variable to avoid duplicated template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The endpoint name template was evaluated twice — once for name and once for summary. A single const removes the duplication and resolves the nested template literal lint warning. --- .../packages/core/src/analyzer/graph-builder.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index f74cb7e..d77155b 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -328,14 +328,15 @@ export class GraphBuilder { console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); continue; } + const name = `${ep.method ?? ""} ${ep.path}`.trim() this.nodeIds.add(childId); this.nodes.push({ id: childId, type: "endpoint", - name: `${ep.method ?? ""} ${ep.path}`.trim(), + name, filePath, lineRange: ep.lineRange, - summary: `Endpoint: ${ep.method ?? ""} ${ep.path}`.trim(), + summary: `Endpoint: ${name}`, tags: [], complexity: meta.complexity, }); From ef0c65aa17b7a89d883f3102d06df20fd5c36736 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:47:16 +0300 Subject: [PATCH 6/9] refactor: deduplicate import and call edges via edgeKeys set addImportEdge and addCallEdge previously pushed edges unconditionally, allowing duplicate relationships if multiple agents reported the same import or call. A shared edgeKeys set keyed on type|source|target silently skips any edge that has already been recorded. --- .../packages/core/src/analyzer/graph-builder.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index d77155b..e2dd645 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -127,6 +127,7 @@ export class GraphBuilder { private readonly edges: GraphEdge[] = []; private readonly languages = new Set(); private readonly nodeIds = new Set(); + private readonly edgeKeys = new Set(); private readonly projectName: string; private readonly gitHash: string; @@ -235,6 +236,9 @@ export class GraphBuilder { } addImportEdge(fromFile: string, toFile: string): void { + const key = `imports|file:${fromFile}|file:${toFile}`; + if (this.edgeKeys.has(key)) return; + this.edgeKeys.add(key); this.edges.push({ source: `file:${fromFile}`, target: `file:${toFile}`, @@ -250,6 +254,9 @@ export class GraphBuilder { calleeFile: string, calleeFunc: string, ): void { + const key = `calls|function:${callerFile}:${callerFunc}|function:${calleeFile}:${calleeFunc}`; + if (this.edgeKeys.has(key)) return; + this.edgeKeys.add(key); this.edges.push({ source: `function:${callerFile}:${callerFunc}`, target: `function:${calleeFile}:${calleeFunc}`, From e82235e1dfc29bc33a996e844fc0d99d3c9e84e6 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:49:47 +0300 Subject: [PATCH 7/9] refactor: return shallow copies of nodes and edges from build() build() was returning direct references to the builder's internal arrays, allowing callers to mutate graph.nodes or graph.edges and corrupt the builder's state. Spreading into new arrays at build time prevents this at negligible cost. --- .../packages/core/src/analyzer/graph-builder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index e2dd645..8aa73b1 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -412,8 +412,8 @@ export class GraphBuilder { analyzedAt: new Date().toISOString(), gitCommitHash: this.gitHash, }, - nodes: this.nodes, - edges: this.edges, + nodes: [...this.nodes], + edges: [...this.edges], layers: [], tour: [], }; From 45b98716ec03d5c0ab88aab2a6d7bb873c776d0b Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:52:13 +0300 Subject: [PATCH 8/9] fix: use localeCompare in languages sort for reliable alphabetical order Default sort() uses Unicode code point ordering which can produce unexpected results for non-ASCII strings. localeCompare guarantees correct alphabetical ordering regardless of character set. --- .../packages/core/src/analyzer/graph-builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index 8aa73b1..3a35034 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -406,7 +406,7 @@ export class GraphBuilder { version: "1.0.0", project: { name: this.projectName, - languages: [...this.languages].sort(), + languages: [...this.languages].sort((a, b) => a.localeCompare(b)), frameworks: [], description: "", analyzedAt: new Date().toISOString(), From d11a9bb1c98606f8f97f8e2196203fcb2b582f18 Mon Sep 17 00:00:00 2001 From: Nikola Chetelyazov Date: Mon, 13 Apr 2026 09:56:12 +0300 Subject: [PATCH 9/9] refactor: extract addChildNode helper to remove repeated boilerplate All five child-node loops in addNonCodeFileWithAnalysis shared the same four-line pattern: dedup check, nodeIds.add, nodes.push, and a contains edge push. A private addChildNode helper centralises this so each loop only constructs the node object specific to its type. --- .../core/src/analyzer/graph-builder.ts | 77 +++++++------------ 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts index 3a35034..d34d723 100644 --- a/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts +++ b/understand-anything-plugin/packages/core/src/analyzer/graph-builder.ts @@ -289,14 +289,8 @@ export class GraphBuilder { // Create child nodes for definitions (tables, schemas, etc.) for (const def of meta.definitions ?? []) { - const childId = `${def.kind}:${filePath}:${def.name}`; - if (this.nodeIds.has(childId)) { - console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); - continue; - } - this.nodeIds.add(childId); - this.nodes.push({ - id: childId, + this.addChildNode({ + id: `${def.kind}:${filePath}:${def.name}`, type: this.mapKindToNodeType(def.kind), name: def.name, filePath, @@ -304,41 +298,27 @@ export class GraphBuilder { summary: `${def.kind}: ${def.name} (${def.fields.length} fields)`, tags: [], complexity: meta.complexity, - }); - this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 }); + }, fileId); } // Create child nodes for services for (const svc of meta.services ?? []) { - const childId = `service:${filePath}:${svc.name}`; - if (this.nodeIds.has(childId)) { - console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); - continue; - } - this.nodeIds.add(childId); - this.nodes.push({ - id: childId, + this.addChildNode({ + id: `service:${filePath}:${svc.name}`, type: "service", name: svc.name, filePath, summary: `Service ${svc.name}${svc.image ? ` (image: ${svc.image})` : ""}`, tags: [], complexity: meta.complexity, - }); - this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 }); + }, fileId); } // Create child nodes for endpoints for (const ep of meta.endpoints ?? []) { - const childId = `endpoint:${filePath}:${ep.path}`; - if (this.nodeIds.has(childId)) { - console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); - continue; - } - const name = `${ep.method ?? ""} ${ep.path}`.trim() - this.nodeIds.add(childId); - this.nodes.push({ - id: childId, + const name = `${ep.method ?? ""} ${ep.path}`.trim(); + this.addChildNode({ + id: `endpoint:${filePath}:${ep.path}`, type: "endpoint", name, filePath, @@ -346,20 +326,13 @@ export class GraphBuilder { summary: `Endpoint: ${name}`, tags: [], complexity: meta.complexity, - }); - this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 }); + }, fileId); } // Create child nodes for steps (pipeline/makefile targets) for (const step of meta.steps ?? []) { - const childId = `step:${filePath}:${step.name}`; - if (this.nodeIds.has(childId)) { - console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); - continue; - } - this.nodeIds.add(childId); - this.nodes.push({ - id: childId, + this.addChildNode({ + id: `step:${filePath}:${step.name}`, type: "pipeline", name: step.name, filePath, @@ -367,20 +340,13 @@ export class GraphBuilder { summary: `Step: ${step.name}`, tags: [], complexity: meta.complexity, - }); - this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 }); + }, fileId); } // Create child nodes for resources (Terraform, etc.) for (const res of meta.resources ?? []) { - const childId = `resource:${filePath}:${res.name}`; - if (this.nodeIds.has(childId)) { - console.warn(`[GraphBuilder] Duplicate node ID "${childId}" — skipping`); - continue; - } - this.nodeIds.add(childId); - this.nodes.push({ - id: childId, + this.addChildNode({ + id: `resource:${filePath}:${res.name}`, type: "resource", name: res.name, filePath, @@ -388,11 +354,20 @@ export class GraphBuilder { summary: `Resource: ${res.name} (${res.kind})`, tags: [], complexity: meta.complexity, - }); - this.edges.push({ source: fileId, target: childId, type: "contains", direction: "forward", weight: 1 }); + }, fileId); } } + private addChildNode(node: GraphNode, parentId: string): void { + if (this.nodeIds.has(node.id)) { + console.warn(`[GraphBuilder] Duplicate node ID "${node.id}" — skipping`); + return; + } + this.nodeIds.add(node.id); + this.nodes.push(node); + this.edges.push({ source: parentId, target: node.id, type: "contains", direction: "forward", weight: 1 }); + } + private mapKindToNodeType(kind: string): GraphNode["type"] { const mapped = KIND_TO_NODE_TYPE[kind]; if (!mapped) {