diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5478724..8e7ae1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,21 +7,47 @@ on: branches: [master] jobs: - test: + quality: + name: Quality Checks runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2 with: version: 10 - - uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm' + - run: pnpm install --frozen-lockfile + + - name: Lint & Format + run: | + pnpm lint + pnpm format:check + - name: Type Check + run: pnpm type-check + + - name: Security Audit + run: pnpm audit + + test: + name: Functional Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' - run: pnpm install --frozen-lockfile - - run: pnpm audit - - run: pnpm build - - run: pnpm test + + - name: Build + run: pnpm build + + - name: Run Tests + run: pnpm test diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8867f14 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2 +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f384bd2..942aaaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,9 @@ ```bash git clone https://github.com/PatrickSys/codebase-context.git cd codebase-context -npm install -npm run build +pnpm install +pnpm build + ``` ## Using the Package @@ -37,7 +38,8 @@ src/ **Better search ranking** - The hybrid search in `src/core/search.ts` could use tuning. Currently uses RRF to combine semantic and keyword scores. -**Tests** - There are none. Any test coverage would be an improvement. +**Tests** - Run `pnpm test`. We use Vitest for unit and smoke testing. + ## Adding a Framework Analyzer @@ -59,7 +61,7 @@ interface FrameworkAnalyzer { ## Running Locally ```bash -npm run build +pnpm build node dist/index.js /path/to/test/project ``` @@ -68,7 +70,7 @@ The server logs to stderr, so you can see what it's doing. ## Pull Requests - Fork, branch, make changes -- Run `npm run build` to make sure it compiles +- Run `pnpm build` to make sure it compiles - Test on an actual project - Open PR with what you changed and why diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..f381ac8 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,24 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; + +export default tseslint.config( + { ignores: ['dist', 'node_modules', 'coverage'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier], + files: ['src/**/*.ts', 'tests/**/*.ts'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.node, + }, + plugins: { + // import plugin is handled via recommended usually, but kept simple for now + }, + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_', 'varsIgnorePattern': '^_', 'caughtErrorsIgnorePattern': '^_' }], + 'no-console': ['warn', { 'allow': ['warn', 'error'] }], + }, + }, +); diff --git a/package.json b/package.json index f9280f0..26ae9f7 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,11 @@ "dev": "ts-node src/index.ts", "watch": "tsc -w", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "lint": "eslint \"src/**/*.ts\"", + "format": "prettier --write \"src/**/*.ts\"", + "format:check": "prettier --check \"src/**/*.ts\"", + "type-check": "tsc --noEmit" }, "dependencies": { "@lancedb/lancedb": "^0.4.0", @@ -94,10 +98,26 @@ "zod": "^4.3.4" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@types/glob": "^8.1.0", "@types/node": "^20.11.24", "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^8.51.0", + "@typescript-eslint/parser": "^8.51.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "globals": "^17.0.0", + "prettier": "^3.7.4", "ts-node": "^10.9.2", + "typescript-eslint": "^8.51.0", "vitest": "^4.0.16" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild", + "protobufjs", + "sharp" + ] } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 021d9ce..2473de6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: specifier: ^4.3.4 version: 4.3.4 devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 '@types/glob': specifier: ^8.1.0 version: 8.1.0 @@ -48,9 +51,33 @@ importers: '@types/uuid': specifier: ^9.0.8 version: 9.0.8 + '@typescript-eslint/eslint-plugin': + specifier: ^8.51.0 + version: 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.51.0 + version: 8.51.0(eslint@9.39.2)(typescript@5.9.3) + eslint: + specifier: ^9.39.2 + version: 9.39.2 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.2) + eslint-plugin-import: + specifier: ^2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2) + globals: + specifier: ^17.0.0 + version: 17.0.0 + prettier: + specifier: ^3.7.4 + version: 3.7.4 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + typescript-eslint: + specifier: ^8.51.0 + version: 8.51.0(eslint@9.39.2)(typescript@5.9.3) vitest: specifier: ^4.0.16 version: 4.0.16(@types/node@20.19.25) @@ -217,6 +244,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hono/node-server@1.19.7': resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==} engines: {node: '>=18.14.1'} @@ -227,6 +292,22 @@ packages: resolution: {integrity: sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==} engines: {node: '>=18'} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -443,6 +524,9 @@ packages: cpu: [x64] os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -479,6 +563,12 @@ packages: '@types/glob@8.1.0': resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -497,10 +587,52 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@typescript-eslint/eslint-plugin@8.51.0': + resolution: {integrity: sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.51.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.51.0': + resolution: {integrity: sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.51.0': + resolution: {integrity: sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.51.0': + resolution: {integrity: sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.51.0': + resolution: {integrity: sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.51.0': + resolution: {integrity: sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@7.18.0': resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.51.0': + resolution: {integrity: sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -510,10 +642,27 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.51.0': + resolution: {integrity: sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.51.0': + resolution: {integrity: sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.51.0': + resolution: {integrity: sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} @@ -554,6 +703,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -575,6 +729,9 @@ packages: ajv: optional: true + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -601,6 +758,9 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-back@3.1.0: resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} engines: {node: '>=6'} @@ -609,17 +769,49 @@ packages: resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} engines: {node: '>=12.17'} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + b4a@1.7.3: resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} peerDependencies: @@ -679,6 +871,9 @@ packages: resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} engines: {node: '>=18'} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -697,10 +892,18 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -742,6 +945,9 @@ packages: resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} engines: {node: '>=12.20.0'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -769,6 +975,26 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -786,6 +1012,17 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -806,6 +1043,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -829,6 +1070,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -848,6 +1093,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -856,13 +1109,95 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -910,6 +1245,12 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -925,6 +1266,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -937,12 +1282,27 @@ packages: resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} engines: {node: '>=4.0.0'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatbuffers@1.12.0: resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==} flatbuffers@23.5.26: resolution: {integrity: sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -977,10 +1337,21 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + fuse.js@7.1.0: resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} engines: {node: '>=10'} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -989,6 +1360,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -996,10 +1371,26 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.0.0: + resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1011,10 +1402,21 @@ packages: guid-typescript@1.0.9: resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1053,31 +1455,99 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.3.4: resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1085,6 +1555,45 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1094,19 +1603,50 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + json-bignum@0.0.3: resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==} engines: {node: '>=0.8'} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} @@ -1159,6 +1699,9 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1184,6 +1727,9 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -1217,6 +1763,26 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -1252,17 +1818,44 @@ packages: zod: optional: true + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -1295,6 +1888,10 @@ packages: platform@1.3.6: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1304,6 +1901,15 @@ packages: engines: {node: '>=10'} hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + protobufjs@6.11.4: resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} hasBin: true @@ -1315,6 +1921,10 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -1338,10 +1948,27 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1358,12 +1985,28 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -1377,6 +2020,18 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -1446,6 +2101,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -1457,6 +2116,18 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -1468,14 +2139,26 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + table-layout@4.1.1: resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} engines: {node: '>=12.17'} @@ -1528,6 +2211,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.3.0: + resolution: {integrity: sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -1542,16 +2231,46 @@ packages: '@swc/wasm': optional: true + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.51.0: + resolution: {integrity: sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1565,6 +2284,10 @@ packages: resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} engines: {node: '>=12.17'} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -1575,6 +2298,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1673,6 +2399,22 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1683,6 +2425,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wordwrapjs@5.1.1: resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==} engines: {node: '>=12.17'} @@ -1702,6 +2448,10 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -1794,12 +2544,69 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@hono/node-server@1.19.7(hono@4.11.3)': dependencies: hono: 4.11.3 '@huggingface/jinja@0.2.2': {} + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -1974,6 +2781,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true + '@rtsao/scc@1.1.0': {} + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.17': @@ -2006,6 +2815,10 @@ snapshots: '@types/minimatch': 5.1.2 '@types/node': 20.19.25 + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + '@types/long@4.0.2': {} '@types/minimatch@5.1.2': {} @@ -2025,8 +2838,68 @@ snapshots: '@types/uuid@9.0.8': {} + '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/type-utils': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.51.0 + eslint: 9.39.2 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.51.0 + debug: 4.4.3 + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.51.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.51.0': + dependencies: + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/visitor-keys': 8.51.0 + + '@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.51.0(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2 + ts-api-utils: 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.51.0': {} + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 7.18.0 @@ -2042,11 +2915,42 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.51.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/visitor-keys': 8.51.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.51.0(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@7.18.0': dependencies: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.51.0': + dependencies: + '@typescript-eslint/types': 8.51.0 + eslint-visitor-keys: 4.2.1 + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.1.0 @@ -2107,6 +3011,10 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-walk@8.3.4: dependencies: acorn: 8.15.0 @@ -2121,6 +3029,13 @@ snapshots: optionalDependencies: ajv: 8.17.1 + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -2152,16 +3067,74 @@ snapshots: arg@4.1.3: {} + argparse@2.0.1: {} + array-back@3.1.0: {} array-back@6.2.2: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + array-union@2.1.0: {} + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + async-function@1.0.0: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + b4a@1.7.3: {} balanced-match@1.0.2: {} @@ -2225,6 +3198,11 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -2245,11 +3223,20 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + chai@6.2.2: {} chalk-template@0.4.0: @@ -2297,6 +3284,8 @@ snapshots: table-layout: 4.1.1 typical: 7.3.0 + concat-map@0.0.1: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -2318,6 +3307,28 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2328,6 +3339,20 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -2340,6 +3365,10 @@ snapshots: dependencies: path-type: 4.0.0 + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2360,6 +3389,63 @@ snapshots: dependencies: once: 1.4.0 + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2377,6 +3463,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -2408,12 +3504,129 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.39.2): + dependencies: + eslint: 9.39.2 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + etag@1.8.1: {} event-target-shim@5.0.1: {} @@ -2483,6 +3696,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} fastq@1.19.1: @@ -2493,6 +3710,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -2512,10 +3733,26 @@ snapshots: dependencies: array-back: 3.1.0 - flatbuffers@1.12.0: {} + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatbuffers@1.12.0: {} flatbuffers@23.5.26: {} + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -2547,8 +3784,21 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + fuse.js@7.1.0: {} + generator-function@2.0.1: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2567,12 +3817,22 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + github-from-package@0.0.0: {} glob-parent@5.1.2: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -2582,6 +3842,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + globals@14.0.0: {} + + globals@17.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -2595,8 +3864,18 @@ snapshots: guid-typescript@1.0.9: {} + has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -2637,26 +3916,143 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} ini@1.3.8: {} + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-arrayish@0.3.4: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} is-promise@4.0.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} jackspeak@3.4.3: @@ -2667,14 +4063,43 @@ snapshots: jose@6.1.3: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + json-bignum@0.0.3: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + lodash.merge@4.6.2: {} + long@4.0.0: {} lru-cache@10.4.3: {} @@ -2712,6 +4137,10 @@ snapshots: mimic-response@3.1.0: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -2728,6 +4157,8 @@ snapshots: napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + negotiator@1.0.0: {} node-abi@3.85.0: @@ -2746,6 +4177,37 @@ snapshots: object-inspect@1.13.4: {} + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + obug@2.1.1: {} on-finished@2.4.1: @@ -2790,12 +4252,43 @@ snapshots: transitivePeerDependencies: - encoding + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -2817,6 +4310,8 @@ snapshots: platform@1.3.6: {} + possible-typed-array-names@1.1.0: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -2838,6 +4333,10 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} + + prettier@3.7.4: {} + protobufjs@6.11.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -2864,6 +4363,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode@2.3.1: {} + qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -2892,8 +4393,36 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} rollup@4.54.0: @@ -2938,10 +4467,31 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + safer-buffer@2.1.2: {} + semver@6.3.1: {} + semver@7.7.3: {} send@1.2.1: @@ -2969,6 +4519,28 @@ snapshots: transitivePeerDependencies: - supports-color + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} sharp@0.32.6: @@ -3048,6 +4620,11 @@ snapshots: std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -3069,6 +4646,29 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -3081,12 +4681,18 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + table-layout@4.1.1: dependencies: array-back: 6.2.2 @@ -3157,6 +4763,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.3.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -3175,30 +4785,96 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-is@2.0.1: dependencies: content-type: 1.0.5 media-typer: 1.1.0 mime-types: 3.0.2 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.51.0(eslint@9.39.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} typical@4.0.0: {} typical@7.3.0: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@5.26.5: {} undici-types@6.21.0: {} unpipe@1.0.0: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + util-deprecate@1.0.2: {} uuid@9.0.1: {} @@ -3265,6 +4941,47 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3274,6 +4991,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + wordwrapjs@5.1.1: {} wrap-ansi@7.0.0: @@ -3292,6 +5011,8 @@ snapshots: yn@3.1.1: {} + yocto-queue@0.1.0: {} + zod-to-json-schema@3.25.1(zod@4.3.4): dependencies: zod: 4.3.4 diff --git a/src/analyzers/angular/index.ts b/src/analyzers/angular/index.ts index 9e4a87b..53b5050 100644 --- a/src/analyzers/angular/index.ts +++ b/src/analyzers/angular/index.ts @@ -4,8 +4,8 @@ * Detects state management patterns, architectural layers, and Angular-specific patterns */ -import { promises as fs } from "fs"; -import path from "path"; +import { promises as fs } from 'fs'; +import path from 'path'; import { FrameworkAnalyzer, AnalysisResult, @@ -14,25 +14,15 @@ import { CodeComponent, ImportStatement, ExportStatement, - Dependency, - FrameworkInfo, - ArchitecturalLayer, -} from "../../types/index.js"; -import { parse } from "@typescript-eslint/typescript-estree"; -import { createChunksFromCode } from "../../utils/chunking.js"; + ArchitecturalLayer +} from '../../types/index.js'; +import { parse } from '@typescript-eslint/typescript-estree'; +import { createChunksFromCode } from '../../utils/chunking.js'; export class AngularAnalyzer implements FrameworkAnalyzer { - readonly name = "angular"; - readonly version = "1.0.0"; - readonly supportedExtensions = [ - ".ts", - ".js", - ".html", - ".scss", - ".css", - ".sass", - ".less", - ]; + readonly name = 'angular'; + readonly version = '1.0.0'; + readonly supportedExtensions = ['.ts', '.js', '.html', '.scss', '.css', '.sass', '.less']; readonly priority = 100; // Highest priority for Angular files private angularPatterns = { @@ -44,19 +34,17 @@ export class AngularAnalyzer implements FrameworkAnalyzer { // Guards: Check for interface implementation OR method signature OR functional guard guard: /(?:implements\s+(?:CanActivate|CanDeactivate|CanLoad|CanMatch)|canActivate\s*\(|canDeactivate\s*\(|canLoad\s*\(|canMatch\s*\(|CanActivateFn|CanDeactivateFn|CanMatchFn)/, - interceptor: - /(?:implements\s+HttpInterceptor|intercept\s*\(|HttpInterceptorFn)/, + interceptor: /(?:implements\s+HttpInterceptor|intercept\s*\(|HttpInterceptorFn)/, resolver: /(?:implements\s+Resolve|resolve\s*\(|ResolveFn)/, - validator: /(?:implements\s+(?:Validator|AsyncValidator)|validate\s*\()/, + validator: /(?:implements\s+(?:Validator|AsyncValidator)|validate\s*\()/ }; private stateManagementPatterns = { ngrx: /@ngrx\/store|createAction|createReducer|createSelector/, akita: /@datorama\/akita|Query|Store\.update/, elf: /@ngneat\/elf|createStore|withEntities/, - signals: - /\bsignal\s*[<(]|\bcomputed\s*[<(]|\beffect\s*\(|\blinkedSignal\s*[<(]/, - rxjsState: /BehaviorSubject|ReplaySubject|shareReplay/, + signals: /\bsignal\s*[<(]|\bcomputed\s*[<(]|\beffect\s*\(|\blinkedSignal\s*[<(]/, + rxjsState: /BehaviorSubject|ReplaySubject|shareReplay/ }; private modernAngularPatterns = { @@ -71,7 +59,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { controlFlowFor: /@for\s*\(/, controlFlowSwitch: /@switch\s*\(/, controlFlowDefer: /@defer\s*[({]/, - injectFunction: /\binject\s*[<(]/, + injectFunction: /\binject\s*[<(]/ }; canAnalyze(filePath: string, content?: string): boolean { @@ -81,16 +69,14 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } // For TypeScript files, check if it contains Angular decorators - if (ext === ".ts" && content) { - return Object.values(this.angularPatterns).some((pattern) => - pattern.test(content) - ); + if (ext === '.ts' && content) { + return Object.values(this.angularPatterns).some((pattern) => pattern.test(content)); } // Angular component templates and styles - if ([".html", ".scss", ".css", ".sass", ".less"].includes(ext)) { + if (['.html', '.scss', '.css', '.sass', '.less'].includes(ext)) { // Check if there's a corresponding .ts file - const baseName = filePath.replace(/\.(html|scss|css|sass|less)$/, ""); + // const baseName = filePath.replace(/\.(html|scss|css|sass|less)$/, ''); return true; // We'll verify during analysis } @@ -101,25 +87,25 @@ export class AngularAnalyzer implements FrameworkAnalyzer { const ext = path.extname(filePath).toLowerCase(); const relativePath = path.relative(process.cwd(), filePath); - if (ext === ".ts") { + if (ext === '.ts') { return this.analyzeTypeScriptFile(filePath, content, relativePath); - } else if (ext === ".html") { + } else if (ext === '.html') { return this.analyzeTemplateFile(filePath, content, relativePath); - } else if ([".scss", ".css", ".sass", ".less"].includes(ext)) { + } else if (['.scss', '.css', '.sass', '.less'].includes(ext)) { return this.analyzeStyleFile(filePath, content, relativePath); } // Fallback return { filePath, - language: "unknown", - framework: "angular", + language: 'unknown', + framework: 'angular', components: [], imports: [], exports: [], dependencies: [], metadata: {}, - chunks: [], + chunks: [] }; } @@ -137,44 +123,39 @@ export class AngularAnalyzer implements FrameworkAnalyzer { const ast = parse(content, { loc: true, range: true, - comment: true, + comment: true }); // Extract imports for (const node of ast.body) { - if (node.type === "ImportDeclaration" && node.source.value) { + if (node.type === 'ImportDeclaration' && node.source.value) { const source = node.source.value as string; imports.push({ source, imports: node.specifiers.map((s: any) => { - if (s.type === "ImportDefaultSpecifier") return "default"; - if (s.type === "ImportNamespaceSpecifier") return "*"; + if (s.type === 'ImportDefaultSpecifier') return 'default'; + if (s.type === 'ImportNamespaceSpecifier') return '*'; return s.imported?.name || s.local.name; }), - isDefault: node.specifiers.some( - (s: any) => s.type === "ImportDefaultSpecifier" - ), + isDefault: node.specifiers.some((s: any) => s.type === 'ImportDefaultSpecifier'), isDynamic: false, - line: node.loc?.start.line, + line: node.loc?.start.line }); // Track dependencies - if (!source.startsWith(".") && !source.startsWith("/")) { - dependencies.push(source.split("/")[0]); + if (!source.startsWith('.') && !source.startsWith('/')) { + dependencies.push(source.split('/')[0]); } } // Extract class declarations with decorators if ( - node.type === "ExportNamedDeclaration" && - node.declaration?.type === "ClassDeclaration" + node.type === 'ExportNamedDeclaration' && + node.declaration?.type === 'ClassDeclaration' ) { const classNode = node.declaration; if (classNode.id && classNode.decorators) { - const component = await this.extractAngularComponent( - classNode, - content - ); + const component = await this.extractAngularComponent(classNode, content); if (component) { components.push(component); } @@ -182,7 +163,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } // Handle direct class exports - if (node.type === "ClassDeclaration" && node.id && node.decorators) { + if (node.type === 'ClassDeclaration' && node.id && node.decorators) { const component = await this.extractAngularComponent(node, content); if (component) { components.push(component); @@ -190,38 +171,29 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } // Extract exports - if (node.type === "ExportNamedDeclaration") { + if (node.type === 'ExportNamedDeclaration') { if (node.declaration) { - if ( - node.declaration.type === "ClassDeclaration" && - node.declaration.id - ) { + if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) { exports.push({ name: node.declaration.id.name, isDefault: false, - type: "class", + type: 'class' }); } } } - if (node.type === "ExportDefaultDeclaration") { - const name = - node.declaration.type === "Identifier" - ? node.declaration.name - : "default"; + if (node.type === 'ExportDefaultDeclaration') { + const name = node.declaration.type === 'Identifier' ? node.declaration.name : 'default'; exports.push({ name, isDefault: true, - type: "default", + type: 'default' }); } } } catch (error) { - console.warn( - `Failed to parse Angular TypeScript file ${filePath}:`, - error - ); + console.warn(`Failed to parse Angular TypeScript file ${filePath}:`, error); } // Detect state management @@ -238,14 +210,14 @@ export class AngularAnalyzer implements FrameworkAnalyzer { content, filePath, relativePath, - "typescript", + 'typescript', components, { - framework: "angular", + framework: 'angular', layer, statePattern, dependencies, - modernPatterns, + modernPatterns } ); @@ -253,27 +225,30 @@ export class AngularAnalyzer implements FrameworkAnalyzer { const detectedPatterns: Array<{ category: string; name: string }> = []; // Dependency Injection pattern - if (modernPatterns.includes("injectFunction")) { - detectedPatterns.push({ category: "dependencyInjection", name: "inject() function" }); - } else if (content.includes("constructor(") && content.includes("private") && - (relativePath.endsWith(".service.ts") || relativePath.endsWith(".component.ts"))) { - detectedPatterns.push({ category: "dependencyInjection", name: "Constructor injection" }); + if (modernPatterns.includes('injectFunction')) { + detectedPatterns.push({ category: 'dependencyInjection', name: 'inject() function' }); + } else if ( + content.includes('constructor(') && + content.includes('private') && + (relativePath.endsWith('.service.ts') || relativePath.endsWith('.component.ts')) + ) { + detectedPatterns.push({ category: 'dependencyInjection', name: 'Constructor injection' }); } // State Management pattern if (/BehaviorSubject|ReplaySubject|Subject|Observable/.test(content)) { - detectedPatterns.push({ category: "stateManagement", name: "RxJS" }); + detectedPatterns.push({ category: 'stateManagement', name: 'RxJS' }); } - if (modernPatterns.some((p) => p.startsWith("signal"))) { - detectedPatterns.push({ category: "stateManagement", name: "Signals" }); + if (modernPatterns.some((p) => p.startsWith('signal'))) { + detectedPatterns.push({ category: 'stateManagement', name: 'Signals' }); } // Reactivity patterns if (/\beffect\s*\(/.test(content)) { - detectedPatterns.push({ category: "reactivity", name: "Effect" }); + detectedPatterns.push({ category: 'reactivity', name: 'Effect' }); } if (/\bcomputed\s*[<(]/.test(content)) { - detectedPatterns.push({ category: "reactivity", name: "Computed" }); + detectedPatterns.push({ category: 'reactivity', name: 'Computed' }); } // Component Style pattern detection @@ -281,41 +256,46 @@ export class AngularAnalyzer implements FrameworkAnalyzer { // explicit standalone: false → NgModule-based // no explicit flag + uses modern patterns (inject, signals) → likely Standalone (Angular v19+ default) // no explicit flag + no modern patterns → ambiguous, don't classify - const hasExplicitStandalone = content.includes("standalone: true"); - const hasExplicitNgModule = content.includes("standalone: false"); - const usesModernPatterns = modernPatterns.includes("injectFunction") || - modernPatterns.some(p => p.startsWith("signal")); + const hasExplicitStandalone = content.includes('standalone: true'); + const hasExplicitNgModule = content.includes('standalone: false'); + const usesModernPatterns = + modernPatterns.includes('injectFunction') || + modernPatterns.some((p) => p.startsWith('signal')); - if (relativePath.endsWith("component.ts") || relativePath.endsWith("directive.ts") || relativePath.endsWith("pipe.ts")) { + if ( + relativePath.endsWith('component.ts') || + relativePath.endsWith('directive.ts') || + relativePath.endsWith('pipe.ts') + ) { if (hasExplicitStandalone) { - detectedPatterns.push({ category: "componentStyle", name: "Standalone" }); + detectedPatterns.push({ category: 'componentStyle', name: 'Standalone' }); } else if (hasExplicitNgModule) { - detectedPatterns.push({ category: "componentStyle", name: "NgModule-based" }); + detectedPatterns.push({ category: 'componentStyle', name: 'NgModule-based' }); } else if (usesModernPatterns) { // No explicit flag but uses modern patterns → likely v19+ standalone default - detectedPatterns.push({ category: "componentStyle", name: "Standalone" }); + detectedPatterns.push({ category: 'componentStyle', name: 'Standalone' }); } // If no explicit flag and no modern patterns, don't classify (ambiguous) } // Input style pattern - if (modernPatterns.includes("signalInput")) { - detectedPatterns.push({ category: "componentInputs", name: "Signal-based inputs" }); - } else if (content.includes("@Input()")) { - detectedPatterns.push({ category: "componentInputs", name: "Decorator-based @Input" }); + if (modernPatterns.includes('signalInput')) { + detectedPatterns.push({ category: 'componentInputs', name: 'Signal-based inputs' }); + } else if (content.includes('@Input()')) { + detectedPatterns.push({ category: 'componentInputs', name: 'Decorator-based @Input' }); } return { filePath, - language: "typescript", - framework: "angular", + language: 'typescript', + framework: 'angular', components, imports, exports, dependencies: dependencies.map((name) => ({ name, category: this.categorizeDependency(name), - layer, + layer })), metadata: { analyzer: this.name, @@ -323,26 +303,24 @@ export class AngularAnalyzer implements FrameworkAnalyzer { statePattern, modernPatterns, // isStandalone: true if explicit standalone: true, or if uses modern patterns (implying v19+ default) - isStandalone: content.includes("standalone: true") || - (!content.includes("standalone: false") && - (modernPatterns.includes("injectFunction") || modernPatterns.some(p => p.startsWith("signal")))), - hasRoutes: - content.includes("RouterModule") || content.includes("routes"), + isStandalone: + content.includes('standalone: true') || + (!content.includes('standalone: false') && + (modernPatterns.includes('injectFunction') || + modernPatterns.some((p) => p.startsWith('signal')))), + hasRoutes: content.includes('RouterModule') || content.includes('routes'), usesSignals: - modernPatterns.length > 0 && - modernPatterns.some((p) => p.startsWith("signal")), - usesControlFlow: modernPatterns.some((p) => - p.startsWith("controlFlow") - ), - usesInject: modernPatterns.includes("injectFunction"), + modernPatterns.length > 0 && modernPatterns.some((p) => p.startsWith('signal')), + usesControlFlow: modernPatterns.some((p) => p.startsWith('controlFlow')), + usesInject: modernPatterns.includes('injectFunction'), usesRxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/.test(content), usesEffect: /\beffect\s*\(/.test(content), usesComputed: /\bcomputed\s*[<(]/.test(content), componentType: components.length > 0 ? components[0].metadata.angularType : undefined, // NEW: Patterns for the indexer to forward generically - detectedPatterns, + detectedPatterns }, - chunks, + chunks }; } @@ -352,9 +330,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { private detectModernAngularPatterns(content: string): string[] { const detected: string[] = []; - for (const [patternName, regex] of Object.entries( - this.modernAngularPatterns - )) { + for (const [patternName, regex] of Object.entries(this.modernAngularPatterns)) { if (regex.test(content)) { detected.push(patternName); } @@ -372,71 +348,64 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } const decorator = classNode.decorators[0]; - const decoratorName = - decorator.expression.callee?.name || decorator.expression.name; + const decoratorName = decorator.expression.callee?.name || decorator.expression.name; let componentType: string | undefined; let angularType: string | undefined; // Determine Angular component type - if (decoratorName === "Component") { - componentType = "component"; - angularType = "component"; - } else if (decoratorName === "Directive") { - componentType = "directive"; - angularType = "directive"; - } else if (decoratorName === "Pipe") { - componentType = "pipe"; - angularType = "pipe"; - } else if (decoratorName === "NgModule") { - componentType = "module"; - angularType = "module"; - } else if (decoratorName === "Injectable") { + if (decoratorName === 'Component') { + componentType = 'component'; + angularType = 'component'; + } else if (decoratorName === 'Directive') { + componentType = 'directive'; + angularType = 'directive'; + } else if (decoratorName === 'Pipe') { + componentType = 'pipe'; + angularType = 'pipe'; + } else if (decoratorName === 'NgModule') { + componentType = 'module'; + angularType = 'module'; + } else if (decoratorName === 'Injectable') { // For @Injectable, check if it's actually a guard/interceptor/resolver/validator // before defaulting to 'service' - const classContent = content.substring( - classNode.range[0], - classNode.range[1] - ); + const classContent = content.substring(classNode.range[0], classNode.range[1]); if (this.angularPatterns.guard.test(classContent)) { - componentType = "guard"; - angularType = "guard"; + componentType = 'guard'; + angularType = 'guard'; } else if (this.angularPatterns.interceptor.test(classContent)) { - componentType = "interceptor"; - angularType = "interceptor"; + componentType = 'interceptor'; + angularType = 'interceptor'; } else if (this.angularPatterns.resolver.test(classContent)) { - componentType = "resolver"; - angularType = "resolver"; + componentType = 'resolver'; + angularType = 'resolver'; } else if (this.angularPatterns.validator.test(classContent)) { - componentType = "validator"; - angularType = "validator"; + componentType = 'validator'; + angularType = 'validator'; } else { // Default to service if no specific pattern matches - componentType = "service"; - angularType = "service"; + componentType = 'service'; + angularType = 'service'; } } // If still no type, check patterns one more time (for classes without decorators) if (!componentType) { - const classContent = content.substring( - classNode.range[0], - classNode.range[1] - ); + const classContent = content.substring(classNode.range[0], classNode.range[1]); if (this.angularPatterns.guard.test(classContent)) { - componentType = "guard"; - angularType = "guard"; + componentType = 'guard'; + angularType = 'guard'; } else if (this.angularPatterns.interceptor.test(classContent)) { - componentType = "interceptor"; - angularType = "interceptor"; + componentType = 'interceptor'; + angularType = 'interceptor'; } else if (this.angularPatterns.resolver.test(classContent)) { - componentType = "resolver"; - angularType = "resolver"; + componentType = 'resolver'; + angularType = 'resolver'; } else if (this.angularPatterns.validator.test(classContent)) { - componentType = "validator"; - angularType = "validator"; + componentType = 'validator'; + angularType = 'validator'; } } @@ -455,15 +424,15 @@ export class AngularAnalyzer implements FrameworkAnalyzer { return { name: classNode.id.name, - type: "class", + type: 'class', componentType, startLine: classNode.loc.start.line, endLine: classNode.loc.end.line, decorators: [ { name: decoratorName, - properties: decoratorMetadata, - }, + properties: decoratorMetadata + } ], lifecycle, dependencies: injectedServices, @@ -476,8 +445,8 @@ export class AngularAnalyzer implements FrameworkAnalyzer { templateUrl: decoratorMetadata.templateUrl, styleUrls: decoratorMetadata.styleUrls, inputs: inputs.map((i) => i.name), - outputs: outputs.map((o) => o.name), - }, + outputs: outputs.map((o) => o.name) + } }; } @@ -488,18 +457,18 @@ export class AngularAnalyzer implements FrameworkAnalyzer { if (decorator.expression.arguments && decorator.expression.arguments[0]) { const arg = decorator.expression.arguments[0]; - if (arg.type === "ObjectExpression") { + if (arg.type === 'ObjectExpression') { for (const prop of arg.properties) { if (prop.key && prop.value) { const key = prop.key.name || prop.key.value; - if (prop.value.type === "Literal") { + if (prop.value.type === 'Literal') { metadata[key] = prop.value.value; - } else if (prop.value.type === "ArrayExpression") { + } else if (prop.value.type === 'ArrayExpression') { metadata[key] = prop.value.elements - .map((el: any) => (el.type === "Literal" ? el.value : null)) + .map((el: any) => (el.type === 'Literal' ? el.value : null)) .filter(Boolean); - } else if (prop.value.type === "Identifier") { + } else if (prop.value.type === 'Identifier') { metadata[key] = prop.value.name; } } @@ -507,7 +476,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } } } catch (error) { - console.warn("Failed to extract decorator metadata:", error); + console.warn('Failed to extract decorator metadata:', error); } return metadata; @@ -516,19 +485,19 @@ export class AngularAnalyzer implements FrameworkAnalyzer { private extractLifecycleHooks(classNode: any): string[] { const hooks: string[] = []; const lifecycleHooks = [ - "ngOnChanges", - "ngOnInit", - "ngDoCheck", - "ngAfterContentInit", - "ngAfterContentChecked", - "ngAfterViewInit", - "ngAfterViewChecked", - "ngOnDestroy", + 'ngOnChanges', + 'ngOnInit', + 'ngDoCheck', + 'ngAfterContentInit', + 'ngAfterContentChecked', + 'ngAfterViewInit', + 'ngAfterViewChecked', + 'ngOnDestroy' ]; if (classNode.body && classNode.body.body) { for (const member of classNode.body.body) { - if (member.type === "MethodDefinition" && member.key) { + if (member.type === 'MethodDefinition' && member.key) { const methodName = member.key.name; if (lifecycleHooks.includes(methodName)) { hooks.push(methodName); @@ -546,16 +515,11 @@ export class AngularAnalyzer implements FrameworkAnalyzer { // Look for constructor parameters if (classNode.body && classNode.body.body) { for (const member of classNode.body.body) { - if ( - member.type === "MethodDefinition" && - member.kind === "constructor" - ) { + if (member.type === 'MethodDefinition' && member.kind === 'constructor') { if (member.value.params) { for (const param of member.value.params) { if (param.typeAnnotation?.typeAnnotation?.typeName) { - services.push( - param.typeAnnotation.typeAnnotation.typeName.name - ); + services.push(param.typeAnnotation.typeAnnotation.typeName.name); } } } @@ -571,20 +535,18 @@ export class AngularAnalyzer implements FrameworkAnalyzer { if (classNode.body && classNode.body.body) { for (const member of classNode.body.body) { - if (member.type === "PropertyDefinition") { + if (member.type === 'PropertyDefinition') { // Check for decorator-based @Input() if (member.decorators) { const hasInput = member.decorators.some( - (d: any) => - d.expression?.callee?.name === "Input" || - d.expression?.name === "Input" + (d: any) => d.expression?.callee?.name === 'Input' || d.expression?.name === 'Input' ); if (hasInput && member.key) { inputs.push({ name: member.key.name, - type: member.typeAnnotation?.typeAnnotation?.type || "any", - style: "decorator", + type: member.typeAnnotation?.typeAnnotation?.type || 'any', + style: 'decorator' }); } } @@ -592,16 +554,16 @@ export class AngularAnalyzer implements FrameworkAnalyzer { // Check for signal-based input() (Angular v17.1+) if (member.value && member.key) { const valueStr = - member.value.type === "CallExpression" + member.value.type === 'CallExpression' ? member.value.callee?.name || member.value.callee?.object?.name : null; - if (valueStr === "input") { + if (valueStr === 'input') { inputs.push({ name: member.key.name, - type: "InputSignal", - style: "signal", - required: member.value.callee?.property?.name === "required", + type: 'InputSignal', + style: 'signal', + required: member.value.callee?.property?.name === 'required' }); } } @@ -617,20 +579,18 @@ export class AngularAnalyzer implements FrameworkAnalyzer { if (classNode.body && classNode.body.body) { for (const member of classNode.body.body) { - if (member.type === "PropertyDefinition") { + if (member.type === 'PropertyDefinition') { // Check for decorator-based @Output() if (member.decorators) { const hasOutput = member.decorators.some( - (d: any) => - d.expression?.callee?.name === "Output" || - d.expression?.name === "Output" + (d: any) => d.expression?.callee?.name === 'Output' || d.expression?.name === 'Output' ); if (hasOutput && member.key) { outputs.push({ name: member.key.name, - type: "EventEmitter", - style: "decorator", + type: 'EventEmitter', + style: 'decorator' }); } } @@ -638,15 +598,13 @@ export class AngularAnalyzer implements FrameworkAnalyzer { // Check for signal-based output() (Angular v17.1+) if (member.value && member.key) { const valueStr = - member.value.type === "CallExpression" - ? member.value.callee?.name - : null; + member.value.type === 'CallExpression' ? member.value.callee?.name : null; - if (valueStr === "output") { + if (valueStr === 'output') { outputs.push({ name: member.key.name, - type: "OutputEmitterRef", - style: "signal", + type: 'OutputEmitterRef', + style: 'signal' }); } } @@ -663,38 +621,30 @@ export class AngularAnalyzer implements FrameworkAnalyzer { relativePath: string ): Promise { // Find corresponding component file - const componentPath = filePath.replace(/\.html$/, ".ts"); + const componentPath = filePath.replace(/\.html$/, '.ts'); // Detect legacy vs modern control flow const hasLegacyDirectives = /\*ng(?:If|For|Switch)/.test(content); - const hasModernControlFlow = /@(?:if|for|switch|defer)\s*[({]/.test( - content - ); + const hasModernControlFlow = /@(?:if|for|switch|defer)\s*[({]/.test(content); return { filePath, - language: "html", - framework: "angular", + language: 'html', + framework: 'angular', components: [], imports: [], exports: [], dependencies: [], metadata: { analyzer: this.name, - type: "template", + type: 'template', componentPath, hasLegacyDirectives, hasModernControlFlow, hasBindings: /\[|\(|{{/.test(content), - hasDefer: /@defer\s*[({]/.test(content), + hasDefer: /@defer\s*[({]/.test(content) }, - chunks: await createChunksFromCode( - content, - filePath, - relativePath, - "html", - [] - ), + chunks: await createChunksFromCode(content, filePath, relativePath, 'html', []) }; } @@ -709,29 +659,21 @@ export class AngularAnalyzer implements FrameworkAnalyzer { return { filePath, language, - framework: "angular", + framework: 'angular', components: [], imports: [], exports: [], dependencies: [], metadata: { analyzer: this.name, - type: "style", + type: 'style' }, - chunks: await createChunksFromCode( - content, - filePath, - relativePath, - language, - [] - ), + chunks: await createChunksFromCode(content, filePath, relativePath, language, []) }; } private detectStateManagement(content: string): string | undefined { - for (const [pattern, regex] of Object.entries( - this.stateManagementPatterns - )) { + for (const [pattern, regex] of Object.entries(this.stateManagementPatterns)) { if (regex.test(content)) { return pattern; } @@ -739,105 +681,89 @@ export class AngularAnalyzer implements FrameworkAnalyzer { return undefined; } - private determineLayer( - filePath: string, - components: CodeComponent[] - ): ArchitecturalLayer { + private determineLayer(filePath: string, components: CodeComponent[]): ArchitecturalLayer { const lowerPath = filePath.toLowerCase(); // Check path-based patterns if ( - lowerPath.includes("/component") || - lowerPath.includes("/view") || - lowerPath.includes("/page") + lowerPath.includes('/component') || + lowerPath.includes('/view') || + lowerPath.includes('/page') ) { - return "presentation"; + return 'presentation'; } - if (lowerPath.includes("/service")) { - return "business"; + if (lowerPath.includes('/service')) { + return 'business'; } if ( - lowerPath.includes("/data") || - lowerPath.includes("/repository") || - lowerPath.includes("/api") + lowerPath.includes('/data') || + lowerPath.includes('/repository') || + lowerPath.includes('/api') ) { - return "data"; + return 'data'; } if ( - lowerPath.includes("/store") || - lowerPath.includes("/state") || - lowerPath.includes("/ngrx") + lowerPath.includes('/store') || + lowerPath.includes('/state') || + lowerPath.includes('/ngrx') ) { - return "state"; + return 'state'; } - if (lowerPath.includes("/core")) { - return "core"; + if (lowerPath.includes('/core')) { + return 'core'; } - if (lowerPath.includes("/shared")) { - return "shared"; + if (lowerPath.includes('/shared')) { + return 'shared'; } - if (lowerPath.includes("/feature")) { - return "feature"; + if (lowerPath.includes('/feature')) { + return 'feature'; } // Check component types for (const component of components) { if ( - component.componentType === "component" || - component.componentType === "directive" || - component.componentType === "pipe" + component.componentType === 'component' || + component.componentType === 'directive' || + component.componentType === 'pipe' ) { - return "presentation"; + return 'presentation'; } - if (component.componentType === "service") { - return lowerPath.includes("http") || lowerPath.includes("api") - ? "data" - : "business"; + if (component.componentType === 'service') { + return lowerPath.includes('http') || lowerPath.includes('api') ? 'data' : 'business'; } - if ( - component.componentType === "guard" || - component.componentType === "interceptor" - ) { - return "core"; + if (component.componentType === 'guard' || component.componentType === 'interceptor') { + return 'core'; } } - return "unknown"; + return 'unknown'; } private categorizeDependency(name: string): any { - if (name.startsWith("@angular/")) { - return "framework"; + if (name.startsWith('@angular/')) { + return 'framework'; } - if ( - name.includes("ngrx") || - name.includes("akita") || - name.includes("elf") - ) { - return "state"; + if (name.includes('ngrx') || name.includes('akita') || name.includes('elf')) { + return 'state'; } - if ( - name.includes("material") || - name.includes("primeng") || - name.includes("ng-bootstrap") - ) { - return "ui"; + if (name.includes('material') || name.includes('primeng') || name.includes('ng-bootstrap')) { + return 'ui'; } - if (name.includes("router")) { - return "routing"; + if (name.includes('router')) { + return 'routing'; } - if (name.includes("http") || name.includes("common/http")) { - return "http"; + if (name.includes('http') || name.includes('common/http')) { + return 'http'; } if ( - name.includes("test") || - name.includes("jest") || - name.includes("jasmine") || - name.includes("karma") + name.includes('test') || + name.includes('jest') || + name.includes('jasmine') || + name.includes('karma') ) { - return "testing"; + return 'testing'; } - return "other"; + return 'other'; } async detectCodebaseMetadata(rootPath: string): Promise { @@ -847,7 +773,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { languages: [], dependencies: [], architecture: { - type: "feature-based", + type: 'feature-based', layers: { presentation: 0, business: 0, @@ -857,14 +783,14 @@ export class AngularAnalyzer implements FrameworkAnalyzer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, + unknown: 0 }, - patterns: [], + patterns: [] }, styleGuides: [], documentation: [], projectStructure: { - type: "single-app", + type: 'single-app' }, statistics: { totalFiles: 0, @@ -880,85 +806,75 @@ export class AngularAnalyzer implements FrameworkAnalyzer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, - }, + unknown: 0 + } }, - customMetadata: {}, + customMetadata: {} }; try { // Read package.json - const packageJsonPath = path.join(rootPath, "package.json"); - const packageJson = JSON.parse( - await fs.readFile(packageJsonPath, "utf-8") - ); + const packageJsonPath = path.join(rootPath, 'package.json'); + const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); metadata.name = packageJson.name || metadata.name; // Extract Angular version and dependencies const allDeps = { ...packageJson.dependencies, - ...packageJson.devDependencies, + ...packageJson.devDependencies }; - const angularVersion = - allDeps["@angular/core"]?.replace(/[\^~]/, "") || "unknown"; + const angularVersion = allDeps['@angular/core']?.replace(/[\^~]/, '') || 'unknown'; // Detect state management const stateManagement: string[] = []; - if (allDeps["@ngrx/store"]) stateManagement.push("ngrx"); - if (allDeps["@datorama/akita"]) stateManagement.push("akita"); - if (allDeps["@ngneat/elf"]) stateManagement.push("elf"); + if (allDeps['@ngrx/store']) stateManagement.push('ngrx'); + if (allDeps['@datorama/akita']) stateManagement.push('akita'); + if (allDeps['@ngneat/elf']) stateManagement.push('elf'); // Detect UI libraries const uiLibraries: string[] = []; - if (allDeps["@angular/material"]) uiLibraries.push("Angular Material"); - if (allDeps["primeng"]) uiLibraries.push("PrimeNG"); - if (allDeps["@ng-bootstrap/ng-bootstrap"]) - uiLibraries.push("ng-bootstrap"); + if (allDeps['@angular/material']) uiLibraries.push('Angular Material'); + if (allDeps['primeng']) uiLibraries.push('PrimeNG'); + if (allDeps['@ng-bootstrap/ng-bootstrap']) uiLibraries.push('ng-bootstrap'); // Detect testing frameworks const testingFrameworks: string[] = []; - if (allDeps["jasmine-core"]) testingFrameworks.push("Jasmine"); - if (allDeps["karma"]) testingFrameworks.push("Karma"); - if (allDeps["jest"]) testingFrameworks.push("Jest"); + if (allDeps['jasmine-core']) testingFrameworks.push('Jasmine'); + if (allDeps['karma']) testingFrameworks.push('Karma'); + if (allDeps['jest']) testingFrameworks.push('Jest'); metadata.framework = { - name: "Angular", + name: 'Angular', version: angularVersion, - type: "angular", - variant: "unknown", // Will be determined during analysis + type: 'angular', + variant: 'unknown', // Will be determined during analysis stateManagement, uiLibraries, - testingFrameworks, + testingFrameworks }; // Convert dependencies - metadata.dependencies = Object.entries(allDeps).map( - ([name, version]) => ({ - name, - version: version as string, - category: this.categorizeDependency(name), - }) - ); + metadata.dependencies = Object.entries(allDeps).map(([name, version]) => ({ + name, + version: version as string, + category: this.categorizeDependency(name) + })); } catch (error) { - console.warn("Failed to read Angular project metadata:", error); + console.warn('Failed to read Angular project metadata:', error); } // Calculate statistics from existing index if available try { - const indexPath = path.join(rootPath, ".codebase-index.json"); - const indexContent = await fs.readFile(indexPath, "utf-8"); + const indexPath = path.join(rootPath, '.codebase-index.json'); + const indexContent = await fs.readFile(indexPath, 'utf-8'); const chunks = JSON.parse(indexContent); - console.error( - `Loading statistics from ${indexPath}: ${chunks.length} chunks` - ); + console.error(`Loading statistics from ${indexPath}: ${chunks.length} chunks`); if (Array.isArray(chunks) && chunks.length > 0) { - metadata.statistics.totalFiles = new Set( - chunks.map((c: any) => c.filePath) - ).size; + metadata.statistics.totalFiles = new Set(chunks.map((c: any) => c.filePath)).size; metadata.statistics.totalLines = chunks.reduce( (sum: number, c: any) => sum + (c.endLine - c.startLine + 1), 0 @@ -975,13 +891,12 @@ export class AngularAnalyzer implements FrameworkAnalyzer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, + unknown: 0 }; for (const chunk of chunks) { if (chunk.componentType) { - componentCounts[chunk.componentType] = - (componentCounts[chunk.componentType] || 0) + 1; + componentCounts[chunk.componentType] = (componentCounts[chunk.componentType] || 0) + 1; metadata.statistics.totalComponents++; } @@ -997,7 +912,7 @@ export class AngularAnalyzer implements FrameworkAnalyzer { } } catch (error) { // Index doesn't exist yet, keep statistics at 0 - console.warn("Failed to calculate statistics from index:", error); + console.warn('Failed to calculate statistics from index:', error); } return metadata; @@ -1015,45 +930,52 @@ export class AngularAnalyzer implements FrameworkAnalyzer { const className = classMatch ? classMatch[1] : fileName; switch (componentType) { - case "component": - const selector = metadata.decorator?.selector || "unknown"; + case 'component': { + const selector = metadata.decorator?.selector || 'unknown'; const inputs = metadata.decorator?.inputs?.length || 0; const outputs = metadata.decorator?.outputs?.length || 0; const lifecycle = this.extractLifecycleMethods(content); - return `Angular component '${className}' (selector: ${selector})${lifecycle ? ` with ${lifecycle}` : "" - }${inputs ? `, ${inputs} inputs` : ""}${outputs ? `, ${outputs} outputs` : "" - }.`; + return `Angular component '${className}' (selector: ${selector})${ + lifecycle ? ` with ${lifecycle}` : '' + }${inputs ? `, ${inputs} inputs` : ''}${outputs ? `, ${outputs} outputs` : ''}.`; + } - case "service": - const providedIn = metadata.decorator?.providedIn || "unknown"; + case 'service': { + const providedIn = metadata.decorator?.providedIn || 'unknown'; const methods = this.extractPublicMethods(content); - return `Angular service '${className}' (providedIn: ${providedIn})${methods ? ` providing ${methods}` : "" - }.`; + return `Angular service '${className}' (providedIn: ${providedIn})${ + methods ? ` providing ${methods}` : '' + }.`; + } - case "guard": + case 'guard': { const guardType = this.detectGuardType(content); return `Angular ${guardType} guard '${className}' protecting routes.`; + } - case "directive": - const directiveSelector = metadata.decorator?.selector || "unknown"; + case 'directive': { + const directiveSelector = metadata.decorator?.selector || 'unknown'; return `Angular directive '${className}' (selector: ${directiveSelector}).`; + } - case "pipe": - const pipeName = metadata.decorator?.name || "unknown"; + case 'pipe': { + const pipeName = metadata.decorator?.name || 'unknown'; return `Angular pipe '${className}' (name: ${pipeName}) for data transformation.`; + } - case "module": + case 'module': { const imports = metadata.decorator?.imports?.length || 0; const declarations = metadata.decorator?.declarations?.length || 0; return `Angular module '${className}' with ${declarations} declarations and ${imports} imports.`; + } - case "interceptor": + case 'interceptor': return `Angular HTTP interceptor '${className}' modifying HTTP requests/responses.`; - case "resolver": + case 'resolver': return `Angular resolver '${className}' pre-fetching route data.`; - case "validator": + case 'validator': return `Angular validator '${className}' for form validation.`; default: @@ -1061,72 +983,74 @@ export class AngularAnalyzer implements FrameworkAnalyzer { if (className && className !== fileName) { // Check for common patterns if ( - content.includes("signal(") || - content.includes("computed(") || - content.includes("effect(") + content.includes('signal(') || + content.includes('computed(') || + content.includes('effect(') ) { return `Angular code '${className}' using signals.`; } - if (content.includes("inject(")) { + if (content.includes('inject(')) { return `Angular code '${className}' using dependency injection.`; } - if (content.includes("Observable") || content.includes("Subject")) { + if (content.includes('Observable') || content.includes('Subject')) { return `Angular code '${className}' with reactive streams.`; } return `Angular code '${className}' in ${fileName}.`; } - // Extract first meaningful export or declaration - const exportMatch = content.match( - /export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/ - ); - if (exportMatch) { - return `Exports '${exportMatch[1]}' from ${fileName}.`; - } + { + // Extract first meaningful export or declaration + const exportMatch = content.match( + /export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/ + ); + if (exportMatch) { + return `Exports '${exportMatch[1]}' from ${fileName}.`; + } - return `Angular code in ${fileName}.`; + return `Angular code in ${fileName}.`; + } } } private extractLifecycleMethods(content: string): string { const lifecycles = [ - "ngOnInit", - "ngOnChanges", - "ngOnDestroy", - "ngAfterViewInit", - "ngAfterContentInit", + 'ngOnInit', + 'ngOnChanges', + 'ngOnDestroy', + 'ngAfterViewInit', + 'ngAfterContentInit' ]; const found = lifecycles.filter((method) => content.includes(method)); - return found.length > 0 ? found.join(", ") : ""; + return found.length > 0 ? found.join(', ') : ''; } private extractPublicMethods(content: string): string { const methodMatches = content.match(/public\s+(\w+)\s*\(/g); - if (!methodMatches || methodMatches.length === 0) return ""; + if (!methodMatches || methodMatches.length === 0) return ''; const methods = methodMatches .slice(0, 3) .map((m) => m.match(/public\s+(\w+)/)?.[1]) .filter(Boolean); - return methods.length > 0 ? `methods: ${methods.join(", ")}` : ""; + return methods.length > 0 ? `methods: ${methods.join(', ')}` : ''; } private detectGuardType(content: string): string { - if (content.includes("CanActivate")) return "CanActivate"; - if (content.includes("CanDeactivate")) return "CanDeactivate"; - if (content.includes("CanLoad")) return "CanLoad"; - if (content.includes("CanMatch")) return "CanMatch"; - return "route"; + if (content.includes('CanActivate')) return 'CanActivate'; + if (content.includes('CanDeactivate')) return 'CanDeactivate'; + if (content.includes('CanLoad')) return 'CanLoad'; + if (content.includes('CanMatch')) return 'CanMatch'; + return 'route'; } private extractFirstComment(content: string): string { const commentMatch = content.match(/\/\*\*\s*\n?\s*\*\s*(.+?)(?:\n|\*\/)/); - return commentMatch ? commentMatch[1].trim() : ""; + return commentMatch ? commentMatch[1].trim() : ''; } private extractFirstLine(content: string): string { const firstLine = content - .split("\n") - .find((line) => line.trim() && !line.trim().startsWith("import")); - return firstLine ? firstLine.trim().slice(0, 60) + "..." : ""; + .split('\n') + .find((line) => line.trim() && !line.trim().startsWith('import')); + return firstLine ? firstLine.trim().slice(0, 60) + '...' : ''; } } diff --git a/src/analyzers/generic/index.ts b/src/analyzers/generic/index.ts index 411ff59..03f1ead 100644 --- a/src/analyzers/generic/index.ts +++ b/src/analyzers/generic/index.ts @@ -13,7 +13,7 @@ import { CodeComponent, ImportStatement, ExportStatement, - Dependency, + Dependency } from '../../types/index.js'; import { createChunksFromCode } from '../../utils/chunking.js'; import { detectLanguage } from '../../utils/language-detection.js'; @@ -29,13 +29,26 @@ export class GenericAnalyzer implements FrameworkAnalyzer { readonly version = '1.0.0'; readonly supportedExtensions = [ // JavaScript/TypeScript - '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', + '.js', + '.jsx', + '.ts', + '.tsx', + '.mjs', + '.cjs', // Python - '.py', '.pyi', + '.py', + '.pyi', // Java/Kotlin - '.java', '.kt', '.kts', + '.java', + '.kt', + '.kts', // C/C++ - '.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', + '.c', + '.cpp', + '.cc', + '.cxx', + '.h', + '.hpp', // C# '.cs', // Go @@ -51,17 +64,29 @@ export class GenericAnalyzer implements FrameworkAnalyzer { // Scala '.scala', // Shell - '.sh', '.bash', '.zsh', + '.sh', + '.bash', + '.zsh', // Config - '.json', '.yaml', '.yml', '.toml', '.xml', + '.json', + '.yaml', + '.yml', + '.toml', + '.xml', // Markup - '.html', '.htm', '.md', '.mdx', + '.html', + '.htm', + '.md', + '.mdx', // Styles - '.css', '.scss', '.sass', '.less', + '.css', + '.scss', + '.sass', + '.less' ]; readonly priority = 10; // Low priority - fallback analyzer - canAnalyze(filePath: string, content?: string): boolean { + canAnalyze(filePath: string, _content?: string): boolean { const ext = path.extname(filePath).toLowerCase(); return this.supportedExtensions.includes(ext); } @@ -108,14 +133,13 @@ export class GenericAnalyzer implements FrameworkAnalyzer { metadata: { analyzer: this.name, fileSize: content.length, - lineCount: content.split('\n').length, + lineCount: content.split('\n').length }, - chunks, + chunks }; } async detectCodebaseMetadata(rootPath: string): Promise { - const packageJsonPath = path.join(rootPath, 'package.json'); let projectName = path.basename(rootPath); let dependencies: Dependency[] = []; @@ -124,9 +148,8 @@ export class GenericAnalyzer implements FrameworkAnalyzer { try { workspaceType = await detectWorkspaceType(rootPath); - workspacePackages = workspaceType !== 'single' - ? await scanWorkspacePackageJsons(rootPath) - : []; + workspacePackages = + workspaceType !== 'single' ? await scanWorkspacePackageJsons(rootPath) : []; const pkgPath = path.join(rootPath, 'package.json'); let packageJson: any = {}; @@ -137,16 +160,17 @@ export class GenericAnalyzer implements FrameworkAnalyzer { // no root package.json } - const rawDeps = workspaceType !== 'single' - ? aggregateWorkspaceDependencies(workspacePackages) - : { ...packageJson.dependencies, ...packageJson.devDependencies }; + const rawDeps = + workspaceType !== 'single' + ? aggregateWorkspaceDependencies(workspacePackages) + : { ...packageJson.dependencies, ...packageJson.devDependencies }; dependencies = Object.entries(rawDeps).map(([name, version]) => ({ name, version: version as string, - category: categorizeDependency(name), + category: categorizeDependency(name) })); - } catch (error) { + } catch (_error) { // skip } @@ -166,20 +190,20 @@ export class GenericAnalyzer implements FrameworkAnalyzer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, + unknown: 0 }, - patterns: [], + patterns: [] }, styleGuides: [], documentation: [], projectStructure: { type: workspaceType === 'single' ? 'single-app' : 'monorepo', - packages: workspacePackages.map(p => ({ + packages: workspacePackages.map((p) => ({ name: p.name || path.basename(path.dirname(p.filePath)), path: path.relative(rootPath, path.dirname(p.filePath)), - type: 'app', // default to app + type: 'app' // default to app })), - workspaces: workspaceType !== 'single' ? [workspaceType] : undefined, + workspaces: workspaceType !== 'single' ? [workspaceType] : undefined }, statistics: { totalFiles: 0, @@ -195,12 +219,12 @@ export class GenericAnalyzer implements FrameworkAnalyzer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, - }, + unknown: 0 + } }, customMetadata: { - monorepoType: workspaceType !== 'single' ? workspaceType : undefined, - }, + monorepoType: workspaceType !== 'single' ? workspaceType : undefined + } }; return metadata; @@ -209,7 +233,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { private async parseJSTSFile( filePath: string, content: string, - language: 'typescript' | 'javascript' + _language: 'typescript' | 'javascript' ): Promise<{ components: CodeComponent[]; imports: ImportStatement[]; @@ -226,7 +250,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { loc: true, range: true, comment: true, - jsx: filePath.endsWith('x'), + jsx: filePath.endsWith('x') }); // Extract imports @@ -241,7 +265,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { }), isDefault: node.specifiers.some((s: any) => s.type === 'ImportDefaultSpecifier'), isDynamic: false, - line: node.loc?.start.line, + line: node.loc?.start.line }); } @@ -252,7 +276,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { type: 'class', startLine: node.loc!.start.line, endLine: node.loc!.end.line, - metadata: {}, + metadata: {} }); } @@ -262,7 +286,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { type: 'function', startLine: node.loc!.start.line, endLine: node.loc!.end.line, - metadata: {}, + metadata: {} }); } @@ -270,7 +294,8 @@ export class GenericAnalyzer implements FrameworkAnalyzer { for (const decl of node.declarations) { if (decl.id.type === 'Identifier') { // Check if it's an arrow function or function expression - const isFunction = decl.init && + const isFunction = + decl.init && (decl.init.type === 'ArrowFunctionExpression' || decl.init.type === 'FunctionExpression'); @@ -279,7 +304,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { type: isFunction ? 'function' : 'variable', startLine: decl.loc!.start.line, endLine: decl.loc!.end.line, - metadata: {}, + metadata: {} }); } } @@ -294,7 +319,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { exports.push({ name: decl.id.name, isDefault: false, - type: 'named', + type: 'named' }); } } @@ -302,7 +327,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { exports.push({ name: (node.declaration.id as any).name, isDefault: false, - type: 'named', + type: 'named' }); } } @@ -313,7 +338,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { exports.push({ name: spec.exported.name, isDefault: false, - type: 'named', + type: 'named' }); } } @@ -321,13 +346,11 @@ export class GenericAnalyzer implements FrameworkAnalyzer { } if (node.type === 'ExportDefaultDeclaration') { - const name = node.declaration.type === 'Identifier' - ? node.declaration.name - : 'default'; + const name = node.declaration.type === 'Identifier' ? node.declaration.name : 'default'; exports.push({ name, isDefault: true, - type: 'default', + type: 'default' }); } } @@ -349,7 +372,10 @@ export class GenericAnalyzer implements FrameworkAnalyzer { // Classes: class, struct { regex: /(?:^|\s)(?:class|struct|interface|trait)\s+(\w+)/i, type: 'class' }, // Methods: pub fn, pub fn, private func - { regex: /(?:pub|public|private|protected)?\s*(?:fn|func|function|def|method)\s+(\w+)/i, type: 'method' }, + { + regex: /(?:pub|public|private|protected)?\s*(?:fn|func|function|def|method)\s+(\w+)/i, + type: 'method' + } ]; lines.forEach((line, index) => { @@ -361,7 +387,7 @@ export class GenericAnalyzer implements FrameworkAnalyzer { type: pattern.type, startLine: index + 1, endLine: index + 1, // Will be updated if we find end - metadata: {}, + metadata: {} }); } } @@ -412,7 +438,9 @@ export class GenericAnalyzer implements FrameworkAnalyzer { // Fallback to first meaningful line const firstLine = content .split('\n') - .find(line => line.trim() && !line.trim().startsWith('import') && !line.trim().startsWith('//')); + .find( + (line) => line.trim() && !line.trim().startsWith('import') && !line.trim().startsWith('//') + ); return `${language} code in ${fileName}: ${firstLine ? firstLine.trim().slice(0, 60) + '...' : 'code definition'}`; } diff --git a/src/core/analyzer-registry.ts b/src/core/analyzer-registry.ts index f687686..d600422 100644 --- a/src/core/analyzer-registry.ts +++ b/src/core/analyzer-registry.ts @@ -13,20 +13,24 @@ export class AnalyzerRegistry { this.analyzers.set(analyzer.name, analyzer); // Re-sort by priority (highest first) - this.sortedAnalyzers = Array.from(this.analyzers.values()) - .sort((a, b) => b.priority - a.priority); + this.sortedAnalyzers = Array.from(this.analyzers.values()).sort( + (a, b) => b.priority - a.priority + ); // Debug logging guarded by env var - avoids stderr output during MCP STDIO handshake if (process.env.CODEBASE_CONTEXT_DEBUG) { - console.error(`[DEBUG] Registered analyzer: ${analyzer.name} (priority: ${analyzer.priority})`); + console.error( + `[DEBUG] Registered analyzer: ${analyzer.name} (priority: ${analyzer.priority})` + ); } } unregister(name: string): boolean { const deleted = this.analyzers.delete(name); if (deleted) { - this.sortedAnalyzers = Array.from(this.analyzers.values()) - .sort((a, b) => b.priority - a.priority); + this.sortedAnalyzers = Array.from(this.analyzers.values()).sort( + (a, b) => b.priority - a.priority + ); } return deleted; } @@ -56,9 +60,7 @@ export class AnalyzerRegistry { * Find all analyzers that can handle a file */ findAllAnalyzers(filePath: string, content?: string): FrameworkAnalyzer[] { - return this.sortedAnalyzers.filter(analyzer => - analyzer.canAnalyze(filePath, content) - ); + return this.sortedAnalyzers.filter((analyzer) => analyzer.canAnalyze(filePath, content)); } /** @@ -86,10 +88,10 @@ export class AnalyzerRegistry { * Get analyzer statistics */ getStats(): { name: string; priority: number; extensions: string[] }[] { - return this.sortedAnalyzers.map(analyzer => ({ + return this.sortedAnalyzers.map((analyzer) => ({ name: analyzer.name, priority: analyzer.priority, - extensions: analyzer.supportedExtensions, + extensions: analyzer.supportedExtensions })); } } diff --git a/src/core/indexer.ts b/src/core/indexer.ts index fb548e0..35cb8cf 100644 --- a/src/core/indexer.ts +++ b/src/core/indexer.ts @@ -3,32 +3,30 @@ * Scans files, delegates to analyzers, creates embeddings, stores in vector DB */ -import { promises as fs } from "fs"; -import path from "path"; -import { glob } from "glob"; -import ignore from "ignore"; +import { promises as fs } from 'fs'; +import path from 'path'; +import { glob } from 'glob'; +import ignore from 'ignore'; import { CodebaseMetadata, CodeChunk, IndexingProgress, IndexingStats, IndexingPhase, - CodebaseConfig, - AnalysisResult, -} from "../types/index.js"; -import { analyzerRegistry } from "./analyzer-registry.js"; -import { isCodeFile, isBinaryFile } from "../utils/language-detection.js"; + CodebaseConfig +} from '../types/index.js'; +import { analyzerRegistry } from './analyzer-registry.js'; +import { isCodeFile, isBinaryFile } from '../utils/language-detection.js'; +import { getEmbeddingProvider } from '../embeddings/index.js'; +import { getStorageProvider, CodeChunkWithEmbedding } from '../storage/index.js'; import { - getEmbeddingProvider, - EmbeddingProvider, -} from "../embeddings/index.js"; -import { - getStorageProvider, - VectorStorageProvider, - CodeChunkWithEmbedding, -} from "../storage/index.js"; -import { LibraryUsageTracker, PatternDetector, ImportGraph, InternalFileGraph, FileExport } from "../utils/usage-tracker.js"; -import { getFileCommitDates } from "../utils/git-dates.js"; + LibraryUsageTracker, + PatternDetector, + ImportGraph, + InternalFileGraph, + FileExport +} from '../utils/usage-tracker.js'; +import { getFileCommitDates } from '../utils/git-dates.js'; export interface IndexerOptions { rootPath: string; @@ -48,13 +46,13 @@ export class CodebaseIndexer { this.onProgressCallback = options.onProgress; this.progress = { - phase: "initializing", + phase: 'initializing', percentage: 0, filesProcessed: 0, totalFiles: 0, chunksCreated: 0, errors: [], - startedAt: new Date(), + startedAt: new Date() }; } @@ -64,44 +62,38 @@ export class CodebaseIndexer { angular: { enabled: true, priority: 100 }, react: { enabled: false, priority: 90 }, vue: { enabled: false, priority: 90 }, - generic: { enabled: true, priority: 10 }, + generic: { enabled: true, priority: 10 } }, - include: ["**/*.{ts,tsx,js,jsx,html,css,scss,sass,less}"], - exclude: [ - "node_modules/**", - "dist/**", - "build/**", - ".git/**", - "coverage/**", - ], + include: ['**/*.{ts,tsx,js,jsx,html,css,scss,sass,less}'], + exclude: ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**'], respectGitignore: true, parsing: { maxFileSize: 1048576, // 1MB chunkSize: 100, chunkOverlap: 10, parseTests: true, - parseNodeModules: false, + parseNodeModules: false }, styleGuides: { autoDetect: true, - paths: ["STYLE_GUIDE.md", "docs/style-guide.md", "ARCHITECTURE.md"], - parseMarkdown: true, + paths: ['STYLE_GUIDE.md', 'docs/style-guide.md', 'ARCHITECTURE.md'], + parseMarkdown: true }, documentation: { autoDetect: true, includeReadmes: true, - includeChangelogs: false, + includeChangelogs: false }, embedding: { - provider: "transformers", - model: "Xenova/bge-base-en-v1.5", - batchSize: 100, + provider: 'transformers', + model: 'Xenova/bge-base-en-v1.5', + batchSize: 100 }, skipEmbedding: false, storage: { - provider: "lancedb", - path: "./codebase-index", - }, + provider: 'lancedb', + path: './codebase-index' + } }; return { @@ -112,10 +104,10 @@ export class CodebaseIndexer { styleGuides: { ...defaultConfig.styleGuides, ...userConfig?.styleGuides }, documentation: { ...defaultConfig.documentation, - ...userConfig?.documentation, + ...userConfig?.documentation }, embedding: { ...defaultConfig.embedding, ...userConfig?.embedding }, - storage: { ...defaultConfig.storage, ...userConfig?.storage }, + storage: { ...defaultConfig.storage, ...userConfig?.storage } }; } @@ -139,15 +131,15 @@ export class CodebaseIndexer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, + unknown: 0 }, errors: [], - startedAt: new Date(), + startedAt: new Date() }; try { // Phase 1: Scanning - this.updateProgress("scanning", 0); + this.updateProgress('scanning', 0); let files = await this.scanFiles(); // Memory safety: limit total files to prevent heap exhaustion @@ -168,7 +160,7 @@ export class CodebaseIndexer { console.error(`Found ${files.length} files to index`); // Phase 2: Analyzing & Parsing - this.updateProgress("analyzing", 0); + this.updateProgress('analyzing', 0); const allChunks: CodeChunk[] = []; const libraryTracker = new LibraryUsageTracker(); const patternDetector = new PatternDetector(); @@ -186,14 +178,14 @@ export class CodebaseIndexer { try { // Normalize line endings to \n for consistent cross-platform output - const rawContent = await fs.readFile(file, "utf-8"); - const content = rawContent.replace(/\r\n/g, "\n"); + const rawContent = await fs.readFile(file, 'utf-8'); + const content = rawContent.replace(/\r\n/g, '\n'); const result = await analyzerRegistry.analyzeFile(file, content); if (result) { allChunks.push(...result.chunks); stats.indexedFiles++; - stats.totalLines += content.split("\n").length; + stats.totalLines += content.split('\n').length; // Track library usage AND import graph from imports for (const imp of result.imports) { @@ -223,9 +215,9 @@ export class CodebaseIndexer { // Track exports for unused export detection if (result.exports && result.exports.length > 0) { - const fileExports: FileExport[] = result.exports.map(exp => ({ + const fileExports: FileExport[] = result.exports.map((exp) => ({ name: exp.name, - type: exp.isDefault ? 'default' : (exp.type as FileExport['type']) || 'other', + type: exp.isDefault ? 'default' : (exp.type as FileExport['type']) || 'other' })); internalFileGraph.trackExports(file, fileExports); } @@ -234,7 +226,11 @@ export class CodebaseIndexer { patternDetector.detectFromCode(content, file); // Helper to extract code snippet around a pattern - const extractSnippet = (pattern: RegExp, linesBefore = 1, linesAfter = 3): string | undefined => { + const extractSnippet = ( + pattern: RegExp, + linesBefore = 1, + linesAfter = 3 + ): string | undefined => { const match = content.match(pattern); if (!match) return undefined; const lines = content.split('\n'); @@ -259,25 +255,30 @@ export class CodebaseIndexer { // Try to extract a relevant snippet for the pattern const snippetPattern = this.getSnippetPatternFor(pattern.category, pattern.name); const snippet = snippetPattern ? extractSnippet(snippetPattern) : undefined; - patternDetector.track(pattern.category, pattern.name, - snippet ? { file: relPath, snippet } : undefined, fileDate); + patternDetector.track( + pattern.category, + pattern.name, + snippet ? { file: relPath, snippet } : undefined, + fileDate + ); } } // Track file for Golden File scoring (framework-agnostic based on patterns) const detectedPatterns = result.metadata?.detectedPatterns || []; const hasPattern = (category: string, name: string) => - detectedPatterns.some((p: { category: string; name: string }) => - p.category === category && p.name === name); + detectedPatterns.some( + (p: { category: string; name: string }) => + p.category === category && p.name === name + ); - const patternScore = ( + const patternScore = (hasPattern('dependencyInjection', 'inject() function') ? 1 : 0) + (hasPattern('stateManagement', 'Signals') ? 1 : 0) + (hasPattern('reactivity', 'Computed') ? 1 : 0) + (hasPattern('reactivity', 'Effect') ? 1 : 0) + (hasPattern('componentStyle', 'Standalone') ? 1 : 0) + - (hasPattern('componentInputs', 'Signal-based inputs') ? 1 : 0) - ); + (hasPattern('componentInputs', 'Signal-based inputs') ? 1 : 0); if (patternScore >= 3) { patternDetector.trackGoldenFile(relPath, patternScore, { inject: hasPattern('dependencyInjection', 'inject() function'), @@ -285,7 +286,7 @@ export class CodebaseIndexer { computed: hasPattern('reactivity', 'Computed'), effect: hasPattern('reactivity', 'Effect'), standalone: hasPattern('componentStyle', 'Standalone'), - signalInputs: hasPattern('componentInputs', 'Signal-based inputs'), + signalInputs: hasPattern('componentInputs', 'Signal-based inputs') }); } @@ -307,8 +308,8 @@ export class CodebaseIndexer { stats.errors.push({ filePath: file, error: error instanceof Error ? error.message : String(error), - phase: "analyzing", - timestamp: new Date(), + phase: 'analyzing', + timestamp: new Date() }); } @@ -320,10 +321,7 @@ export class CodebaseIndexer { stats.totalChunks = allChunks.length; stats.avgChunkSize = allChunks.length > 0 - ? Math.round( - allChunks.reduce((sum, c) => sum + c.content.length, 0) / - allChunks.length - ) + ? Math.round(allChunks.reduce((sum, c) => sum + c.content.length, 0) / allChunks.length) : 0; // Memory safety: limit chunks to prevent embedding memory issues @@ -337,10 +335,10 @@ export class CodebaseIndexer { } // Phase 3: Embedding - let chunksWithEmbeddings: CodeChunkWithEmbedding[] = []; + const chunksWithEmbeddings: CodeChunkWithEmbedding[] = []; if (!this.config.skipEmbedding) { - this.updateProgress("embedding", 50); + this.updateProgress('embedding', 50); console.error(`Creating embeddings for ${chunksToEmbed.length} chunks...`); // Initialize embedding provider @@ -360,7 +358,7 @@ export class CodebaseIndexer { if (chunk.componentType) { parts.unshift(`Type: ${chunk.componentType}`); } - return parts.join("\n"); + return parts.join('\n'); }); const embeddings = await embeddingProvider.embedBatch(texts); @@ -368,62 +366,61 @@ export class CodebaseIndexer { for (let j = 0; j < batch.length; j++) { chunksWithEmbeddings.push({ ...batch[j], - embedding: embeddings[j], + embedding: embeddings[j] }); } // Update progress - const embeddingProgress = - 50 + Math.round((i / chunksToEmbed.length) * 25); - this.updateProgress("embedding", embeddingProgress); - - if ( - (i + batchSize) % 100 === 0 || - i + batchSize >= chunksToEmbed.length - ) { + const embeddingProgress = 50 + Math.round((i / chunksToEmbed.length) * 25); + this.updateProgress('embedding', embeddingProgress); + + if ((i + batchSize) % 100 === 0 || i + batchSize >= chunksToEmbed.length) { console.error( - `Embedded ${Math.min(i + batchSize, chunksToEmbed.length)}/${chunksToEmbed.length + `Embedded ${Math.min(i + batchSize, chunksToEmbed.length)}/${ + chunksToEmbed.length } chunks` ); } } } else { - console.error("Skipping embedding generation (skipEmbedding=true)"); + console.error('Skipping embedding generation (skipEmbedding=true)'); } // Phase 4: Storing - this.updateProgress("storing", 75); + this.updateProgress('storing', 75); if (!this.config.skipEmbedding) { console.error(`Storing ${chunksToEmbed.length} chunks...`); // Store in LanceDB for vector search - const storagePath = path.join(this.rootPath, ".codebase-index"); + const storagePath = path.join(this.rootPath, '.codebase-index'); const storageProvider = await getStorageProvider({ path: storagePath }); await storageProvider.clear(); // Clear existing index await storageProvider.store(chunksWithEmbeddings); } // Also save JSON for keyword search (Fuse.js) - use chunksToEmbed for consistency - const indexPath = path.join(this.rootPath, ".codebase-index.json"); + const indexPath = path.join(this.rootPath, '.codebase-index.json'); // Write without pretty-printing to save memory await fs.writeFile(indexPath, JSON.stringify(chunksToEmbed)); // Save library usage and pattern stats - const intelligencePath = path.join(this.rootPath, ".codebase-intelligence.json"); + const intelligencePath = path.join(this.rootPath, '.codebase-intelligence.json'); const libraryStats = libraryTracker.getStats(); // Extract tsconfig paths for AI to understand import aliases let tsconfigPaths: Record | undefined; try { - const tsconfigPath = path.join(this.rootPath, "tsconfig.json"); - const tsconfigContent = await fs.readFile(tsconfigPath, "utf-8"); + const tsconfigPath = path.join(this.rootPath, 'tsconfig.json'); + const tsconfigContent = await fs.readFile(tsconfigPath, 'utf-8'); const tsconfig = JSON.parse(tsconfigContent); if (tsconfig.compilerOptions?.paths) { tsconfigPaths = tsconfig.compilerOptions.paths; - console.error(`Found ${Object.keys(tsconfigPaths!).length} path aliases in tsconfig.json`); + console.error( + `Found ${Object.keys(tsconfigPaths!).length} path aliases in tsconfig.json` + ); } - } catch (error) { + } catch (_error) { // No tsconfig.json or no paths defined } @@ -436,33 +433,31 @@ export class CodebaseIndexer { tsconfigPaths, importGraph: { usages: importGraph.getAllUsages(), - topUsed: importGraph.getTopUsed(30), + topUsed: importGraph.getTopUsed(30) }, // Internal file graph for circular dependency and unused export detection internalFileGraph: internalFileGraph.toJSON(), - generatedAt: new Date().toISOString(), + generatedAt: new Date().toISOString() }; await fs.writeFile(intelligencePath, JSON.stringify(intelligence, null, 2)); // Phase 5: Complete - this.updateProgress("complete", 100); + this.updateProgress('complete', 100); stats.duration = Date.now() - startTime; stats.completedAt = new Date(); console.error(`Indexing complete in ${stats.duration}ms`); - console.error( - `Indexed ${stats.indexedFiles} files, ${stats.totalChunks} chunks` - ); + console.error(`Indexed ${stats.indexedFiles} files, ${stats.totalChunks} chunks`); return stats; } catch (error) { - this.progress.phase = "error"; + this.progress.phase = 'error'; stats.errors.push({ filePath: this.rootPath, error: error instanceof Error ? error.message : String(error), phase: this.progress.phase, - timestamp: new Date(), + timestamp: new Date() }); throw error; } @@ -475,16 +470,16 @@ export class CodebaseIndexer { let ig: ReturnType | null = null; if (this.config.respectGitignore) { try { - const gitignorePath = path.join(this.rootPath, ".gitignore"); - const gitignoreContent = await fs.readFile(gitignorePath, "utf-8"); + const gitignorePath = path.join(this.rootPath, '.gitignore'); + const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8'); ig = ignore.default().add(gitignoreContent); - } catch (error) { + } catch (_error) { // No .gitignore or couldn't read it } } // Scan with glob - const includePatterns = this.config.include || ["**/*"]; + const includePatterns = this.config.include || ['**/*']; const excludePatterns = this.config.exclude || []; for (const pattern of includePatterns) { @@ -492,7 +487,7 @@ export class CodebaseIndexer { cwd: this.rootPath, absolute: true, ignore: excludePatterns, - nodir: true, + nodir: true }); for (const file of matches) { @@ -515,7 +510,7 @@ export class CodebaseIndexer { console.warn(`Skipping large file: ${file} (${stats.size} bytes)`); continue; } - } catch (error) { + } catch (_error) { continue; } @@ -546,7 +541,7 @@ export class CodebaseIndexer { languages: [], dependencies: [], architecture: { - type: "mixed", + type: 'mixed', layers: { presentation: 0, business: 0, @@ -556,14 +551,14 @@ export class CodebaseIndexer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, + unknown: 0 }, - patterns: [], + patterns: [] }, styleGuides: [], documentation: [], projectStructure: { - type: "single-app", + type: 'single-app' }, statistics: { totalFiles: 0, @@ -579,10 +574,10 @@ export class CodebaseIndexer { shared: 0, feature: 0, infrastructure: 0, - unknown: 0, - }, + unknown: 0 + } }, - customMetadata: {}, + customMetadata: {} }; // Loop through all analyzers (highest priority first) and merge their metadata @@ -599,17 +594,17 @@ export class CodebaseIndexer { // Load intelligence data if available try { - const intelligencePath = path.join(this.rootPath, ".codebase-intelligence.json"); - const intelligenceContent = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(this.rootPath, '.codebase-intelligence.json'); + const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(intelligenceContent); metadata.customMetadata = { ...metadata.customMetadata, libraryUsage: intelligence.libraryUsage, patterns: intelligence.patterns, - intelligenceGeneratedAt: intelligence.generatedAt, + intelligenceGeneratedAt: intelligence.generatedAt }; - } catch (error) { + } catch (_error) { // Intelligence file doesn't exist yet (indexing not run) } @@ -630,20 +625,26 @@ export class CodebaseIndexer { architecture: { type: incoming.architecture?.type || base.architecture.type, layers: this.mergeLayers(base.architecture.layers, incoming.architecture?.layers), - patterns: [...new Set([...(base.architecture.patterns || []), ...(incoming.architecture?.patterns || [])])], // Merge and deduplicate + patterns: [ + ...new Set([ + ...(base.architecture.patterns || []), + ...(incoming.architecture?.patterns || []) + ]) + ] // Merge and deduplicate }, styleGuides: [...new Set([...base.styleGuides, ...incoming.styleGuides])], // Merge and deduplicate documentation: [...new Set([...base.documentation, ...incoming.documentation])], // Merge and deduplicate - projectStructure: incoming.projectStructure?.type !== 'single-app' - ? incoming.projectStructure - : base.projectStructure, + projectStructure: + incoming.projectStructure?.type !== 'single-app' + ? incoming.projectStructure + : base.projectStructure, statistics: this.mergeStatistics(base.statistics, incoming.statistics), - customMetadata: { ...base.customMetadata, ...incoming.customMetadata }, + customMetadata: { ...base.customMetadata, ...incoming.customMetadata } }; } private mergeDependencies(base: any[], incoming: any[]): any[] { - const seen = new Set(base.map(d => d.name)); + const seen = new Set(base.map((d) => d.name)); const result = [...base]; for (const dep of incoming) { if (!seen.has(dep.name)) { @@ -665,7 +666,7 @@ export class CodebaseIndexer { shared: Math.max(base.shared || 0, incoming.shared || 0), feature: Math.max(base.feature || 0, incoming.feature || 0), infrastructure: Math.max(base.infrastructure || 0, incoming.infrastructure || 0), - unknown: Math.max(base.unknown || 0, incoming.unknown || 0), + unknown: Math.max(base.unknown || 0, incoming.unknown || 0) }; } @@ -675,11 +676,10 @@ export class CodebaseIndexer { totalLines: Math.max(base.totalLines || 0, incoming.totalLines || 0), totalComponents: Math.max(base.totalComponents || 0, incoming.totalComponents || 0), componentsByType: { ...base.componentsByType, ...incoming.componentsByType }, - componentsByLayer: this.mergeLayers(base.componentsByLayer, incoming.componentsByLayer), + componentsByLayer: this.mergeLayers(base.componentsByLayer, incoming.componentsByLayer) }; } - /** * Get regex pattern for extracting code snippets based on pattern category and name * This maps abstract pattern names to actual code patterns @@ -688,24 +688,24 @@ export class CodebaseIndexer { const patterns: Record> = { dependencyInjection: { 'inject() function': /\binject\s*[<(]/, - 'Constructor injection': /constructor\s*\(/, + 'Constructor injection': /constructor\s*\(/ }, stateManagement: { - 'RxJS': /BehaviorSubject|ReplaySubject|Subject|Observable/, - 'Signals': /\bsignal\s*[<(]/, + RxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/, + Signals: /\bsignal\s*[<(]/ }, reactivity: { - 'Effect': /\beffect\s*\(/, - 'Computed': /\bcomputed\s*[<(]/, + Effect: /\beffect\s*\(/, + Computed: /\bcomputed\s*[<(]/ }, componentStyle: { - 'Standalone': /standalone\s*:\s*true/, - 'NgModule-based': /@(?:Component|Directive|Pipe)\s*\(/, + Standalone: /standalone\s*:\s*true/, + 'NgModule-based': /@(?:Component|Directive|Pipe)\s*\(/ }, componentInputs: { 'Signal-based inputs': /\binput\s*[<(]/, - 'Decorator-based @Input': /@Input\(\)/, - }, + 'Decorator-based @Input': /@Input\(\)/ + } }; return patterns[category]?.[name] || null; } diff --git a/src/core/search.ts b/src/core/search.ts index e009de4..0edb119 100644 --- a/src/core/search.ts +++ b/src/core/search.ts @@ -2,16 +2,13 @@ * Hybrid search combining semantic vector search with keyword matching */ -import Fuse from "fuse.js"; -import path from "path"; -import { promises as fs } from "fs"; -import { CodeChunk, SearchResult, SearchFilters } from "../types/index.js"; -import { - EmbeddingProvider, - getEmbeddingProvider, -} from "../embeddings/index.js"; -import { VectorStorageProvider, getStorageProvider } from "../storage/index.js"; -import { analyzerRegistry } from "./analyzer-registry.js"; +import Fuse from 'fuse.js'; +import path from 'path'; +import { promises as fs } from 'fs'; +import { CodeChunk, SearchResult, SearchFilters } from '../types/index.js'; +import { EmbeddingProvider, getEmbeddingProvider } from '../embeddings/index.js'; +import { VectorStorageProvider, getStorageProvider } from '../storage/index.js'; +import { analyzerRegistry } from './analyzer-registry.js'; export interface SearchOptions { useSemanticSearch?: boolean; @@ -24,7 +21,7 @@ const DEFAULT_SEARCH_OPTIONS: SearchOptions = { useSemanticSearch: true, useKeywordSearch: true, semanticWeight: 0.7, - keywordWeight: 0.3, + keywordWeight: 0.3 }; export class CodebaseSearcher { @@ -48,7 +45,7 @@ export class CodebaseSearcher { constructor(rootPath: string) { this.rootPath = rootPath; - this.storagePath = path.join(rootPath, ".codebase-index"); + this.storagePath = path.join(rootPath, '.codebase-index'); } async initialize(): Promise { @@ -60,39 +57,39 @@ export class CodebaseSearcher { this.embeddingProvider = await getEmbeddingProvider(); this.storageProvider = await getStorageProvider({ - path: this.storagePath, + path: this.storagePath }); this.initialized = true; } catch (error) { - console.warn("Partial initialization (keyword search only):", error); + console.warn('Partial initialization (keyword search only):', error); this.initialized = true; } } private async loadKeywordIndex(): Promise { try { - const indexPath = path.join(this.rootPath, ".codebase-index.json"); - const content = await fs.readFile(indexPath, "utf-8"); + const indexPath = path.join(this.rootPath, '.codebase-index.json'); + const content = await fs.readFile(indexPath, 'utf-8'); this.chunks = JSON.parse(content); this.fuseIndex = new Fuse(this.chunks, { keys: [ - { name: "content", weight: 0.4 }, - { name: "metadata.componentName", weight: 0.25 }, - { name: "filePath", weight: 0.15 }, - { name: "relativePath", weight: 0.15 }, - { name: "componentType", weight: 0.15 }, - { name: "layer", weight: 0.1 }, - { name: "tags", weight: 0.15 }, + { name: 'content', weight: 0.4 }, + { name: 'metadata.componentName', weight: 0.25 }, + { name: 'filePath', weight: 0.15 }, + { name: 'relativePath', weight: 0.15 }, + { name: 'componentType', weight: 0.15 }, + { name: 'layer', weight: 0.1 }, + { name: 'tags', weight: 0.15 } ], includeScore: true, threshold: 0.4, useExtendedSearch: true, - ignoreLocation: true, + ignoreLocation: true }); } catch (error) { - console.warn("Keyword index load failed:", error); + console.warn('Keyword index load failed:', error); this.chunks = []; this.fuseIndex = null; } @@ -103,8 +100,8 @@ export class CodebaseSearcher { */ private async loadPatternIntelligence(): Promise { try { - const intelligencePath = path.join(this.rootPath, ".codebase-intelligence.json"); - const content = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(this.rootPath, '.codebase-intelligence.json'); + const content = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(content); const decliningPatterns = new Set(); @@ -113,7 +110,7 @@ export class CodebaseSearcher { // Extract pattern indicators from intelligence data if (intelligence.patterns) { - for (const [category, data] of Object.entries(intelligence.patterns)) { + for (const [_category, data] of Object.entries(intelligence.patterns)) { const patternData = data as any; // Track primary pattern @@ -139,9 +136,14 @@ export class CodebaseSearcher { } this.patternIntelligence = { decliningPatterns, risingPatterns, patternWarnings }; - console.error(`[search] Loaded pattern intelligence: ${decliningPatterns.size} declining, ${risingPatterns.size} rising patterns`); + console.error( + `[search] Loaded pattern intelligence: ${decliningPatterns.size} declining, ${risingPatterns.size} rising patterns` + ); } catch (error) { - console.warn("Pattern intelligence load failed (will proceed without trend detection):", error); + console.warn( + 'Pattern intelligence load failed (will proceed without trend detection):', + error + ); this.patternIntelligence = null; } } @@ -149,7 +151,10 @@ export class CodebaseSearcher { /** * v1.2: Detect pattern trend from chunk content */ - private detectChunkTrend(chunk: CodeChunk): { trend: 'Rising' | 'Stable' | 'Declining' | undefined; warning?: string } { + private detectChunkTrend(chunk: CodeChunk): { + trend: 'Rising' | 'Stable' | 'Declining' | undefined; + warning?: string; + } { if (!this.patternIntelligence) { return { trend: undefined }; } @@ -187,26 +192,16 @@ export class CodebaseSearcher { await this.initialize(); } - const { - useSemanticSearch, - useKeywordSearch, - semanticWeight, - keywordWeight, - } = { + const { useSemanticSearch, useKeywordSearch, semanticWeight, keywordWeight } = { ...DEFAULT_SEARCH_OPTIONS, - ...options, + ...options }; - const results: Map = - new Map(); + const results: Map = new Map(); if (useSemanticSearch && this.embeddingProvider && this.storageProvider) { try { - const vectorResults = await this.semanticSearch( - query, - limit * 2, - filters - ); + const vectorResults = await this.semanticSearch(query, limit * 2, filters); vectorResults.forEach((result) => { const id = result.chunk.id; @@ -217,22 +212,18 @@ export class CodebaseSearcher { } else { results.set(id, { chunk: result.chunk, - scores: [result.score * (semanticWeight || 0.7)], + scores: [result.score * (semanticWeight || 0.7)] }); } }); } catch (error) { - console.warn("Semantic search failed:", error); + console.warn('Semantic search failed:', error); } } if (useKeywordSearch && this.fuseIndex) { try { - const keywordResults = await this.keywordSearch( - query, - limit * 2, - filters - ); + const keywordResults = await this.keywordSearch(query, limit * 2, filters); keywordResults.forEach((result) => { const id = result.chunk.id; @@ -243,17 +234,17 @@ export class CodebaseSearcher { } else { results.set(id, { chunk: result.chunk, - scores: [result.score * (keywordWeight || 0.3)], + scores: [result.score * (keywordWeight || 0.3)] }); } }); } catch (error) { - console.warn("Keyword search failed:", error); + console.warn('Keyword search failed:', error); } } const combinedResults: SearchResult[] = Array.from(results.entries()) - .map(([id, { chunk, scores }]) => { + .map(([_id, { chunk, scores }]) => { // Calculate base combined score let combinedScore = scores.reduce((sum, score) => sum + score, 0); @@ -262,12 +253,12 @@ export class CodebaseSearcher { combinedScore = Math.min(1.0, combinedScore); // Boost scores for Angular components with proper detection - if (chunk.componentType && chunk.framework === "angular") { + if (chunk.componentType && chunk.framework === 'angular') { combinedScore = Math.min(1.0, combinedScore * 1.3); } // Boost if layer is detected - if (chunk.layer && chunk.layer !== "unknown") { + if (chunk.layer && chunk.layer !== 'unknown') { combinedScore = Math.min(1.0, combinedScore * 1.1); } @@ -276,7 +267,7 @@ export class CodebaseSearcher { if (trend === 'Rising') { combinedScore = Math.min(1.0, combinedScore * 1.15); // +15% for modern patterns } else if (trend === 'Declining') { - combinedScore = combinedScore * 0.90; // -10% for legacy patterns + combinedScore = combinedScore * 0.9; // -10% for legacy patterns } const summary = this.generateSummary(chunk); @@ -297,7 +288,7 @@ export class CodebaseSearcher { metadata: chunk.metadata, // v1.2: Pattern momentum awareness trend, - patternWarning: warning, + patternWarning: warning } as SearchResult; }) .sort((a, b) => b.score - a.score) @@ -307,23 +298,17 @@ export class CodebaseSearcher { } private generateSummary(chunk: CodeChunk): string { - const analyzer = chunk.framework - ? analyzerRegistry.get(chunk.framework) - : null; + const analyzer = chunk.framework ? analyzerRegistry.get(chunk.framework) : null; if (analyzer && analyzer.summarize) { try { const summary = analyzer.summarize(chunk); // Only use analyzer summary if it's meaningful (not the generic fallback) - if ( - summary && - !summary.startsWith("Code in ") && - !summary.includes(": lines ") - ) { + if (summary && !summary.startsWith('Code in ') && !summary.includes(': lines ')) { return summary; } } catch (error) { - console.warn("Analyzer summary failed:", error); + console.warn('Analyzer summary failed:', error); } } @@ -339,35 +324,35 @@ export class CodebaseSearcher { const name = componentName || (classMatch ? classMatch[1] : null); if (name && componentType) { - return `${componentType.charAt(0).toUpperCase() + componentType.slice(1) - } '${name}' in ${fileName}.`; + return `${ + componentType.charAt(0).toUpperCase() + componentType.slice(1) + } '${name}' in ${fileName}.`; } else if (name) { return `'${name}' defined in ${fileName}.`; } else if (componentType) { - return `${componentType.charAt(0).toUpperCase() + componentType.slice(1) - } in ${fileName}.`; + return `${componentType.charAt(0).toUpperCase() + componentType.slice(1)} in ${fileName}.`; } // Last resort: describe the file type const ext = path.extname(fileName).slice(1); const langMap: Record = { - ts: "TypeScript", - js: "JavaScript", - html: "HTML template", - scss: "SCSS styles", - css: "CSS styles", - json: "JSON config", + ts: 'TypeScript', + js: 'JavaScript', + html: 'HTML template', + scss: 'SCSS styles', + css: 'CSS styles', + json: 'JSON config' }; return `${langMap[ext] || ext.toUpperCase()} in ${fileName}.`; } private generateSnippet(content: string, maxLines: number = 100): string { - const lines = content.split("\n"); + const lines = content.split('\n'); if (lines.length <= maxLines) { return content; } - const snippet = lines.slice(0, maxLines).join("\n"); + const snippet = lines.slice(0, maxLines).join('\n'); const remaining = lines.length - maxLines; return `${snippet}\n\n... [${remaining} more lines]`; } @@ -383,15 +368,11 @@ export class CodebaseSearcher { const queryVector = await this.embeddingProvider.embed(query); - const results = await this.storageProvider.search( - queryVector, - limit, - filters - ); + const results = await this.storageProvider.search(queryVector, limit, filters); return results.map((r) => ({ chunk: r.chunk, - score: r.score, + score: r.score })); } @@ -410,10 +391,7 @@ export class CodebaseSearcher { fuseResults = fuseResults.filter((r) => { const chunk = r.item; - if ( - filters.componentType && - chunk.componentType !== filters.componentType - ) { + if (filters.componentType && chunk.componentType !== filters.componentType) { return false; } if (filters.layer && chunk.layer !== filters.layer) { @@ -444,7 +422,7 @@ export class CodebaseSearcher { const queryLower = query.toLowerCase(); const fileName = path.basename(chunk.filePath).toLowerCase(); const relativePathLower = chunk.relativePath.toLowerCase(); - const componentName = chunk.metadata?.componentName?.toLowerCase() || ""; + const componentName = chunk.metadata?.componentName?.toLowerCase() || ''; // Exact class name match if (componentName && queryLower === componentName) { @@ -454,7 +432,7 @@ export class CodebaseSearcher { // Exact file name match if ( fileName === queryLower || - fileName.replace(/\.ts$/, "") === queryLower.replace(/\.ts$/, "") + fileName.replace(/\.ts$/, '') === queryLower.replace(/\.ts$/, '') ) { score = Math.min(1.0, score + 0.2); } @@ -469,7 +447,7 @@ export class CodebaseSearcher { return { chunk, - score, + score }; }); } @@ -486,14 +464,12 @@ export class CodebaseSearcher { } const queryLower = query.toLowerCase(); - const matchingTags = (chunk.tags || []).filter((tag) => - queryLower.includes(tag.toLowerCase()) - ); + const matchingTags = (chunk.tags || []).filter((tag) => queryLower.includes(tag.toLowerCase())); if (matchingTags.length > 0) { - reasons.push(`tags: ${matchingTags.join(", ")}`); + reasons.push(`tags: ${matchingTags.join(', ')}`); } - return reasons.length > 0 ? reasons.join("; ") : "content match"; + return reasons.length > 0 ? reasons.join('; ') : 'content match'; } async getChunkCount(): Promise { diff --git a/src/embeddings/index.ts b/src/embeddings/index.ts index aeed9e1..c7ed0c8 100644 --- a/src/embeddings/index.ts +++ b/src/embeddings/index.ts @@ -45,4 +45,3 @@ export async function getEmbeddingProvider( return provider; } - diff --git a/src/embeddings/openai.ts b/src/embeddings/openai.ts index 2855943..bc418a7 100644 --- a/src/embeddings/openai.ts +++ b/src/embeddings/openai.ts @@ -1,4 +1,3 @@ - import { EmbeddingProvider } from './types.js'; /** @@ -7,61 +6,61 @@ import { EmbeddingProvider } from './types.js'; * Minimal implementation focusing on high ROI and low bloat. */ export class OpenAIEmbeddingProvider implements EmbeddingProvider { - readonly name = 'openai'; - readonly dimensions = 1536; // Default for text-embedding-3-small + readonly name = 'openai'; + readonly dimensions = 1536; // Default for text-embedding-3-small - constructor( - readonly modelName: string = 'text-embedding-3-small', - private apiKey?: string, - private apiEndpoint: string = 'https://api.openai.com/v1' - ) { } + constructor( + readonly modelName: string = 'text-embedding-3-small', + private apiKey?: string, + private apiEndpoint: string = 'https://api.openai.com/v1' + ) {} - async initialize(): Promise { - if (!this.apiKey) { - throw new Error( - 'OpenAI API key is missing. Set OPENAI_API_KEY environment variable or configure it in the MCP settings.' - ); - } + async initialize(): Promise { + if (!this.apiKey) { + throw new Error( + 'OpenAI API key is missing. Set OPENAI_API_KEY environment variable or configure it in the MCP settings.' + ); } + } - isReady(): boolean { - return !!this.apiKey; - } + isReady(): boolean { + return !!this.apiKey; + } - async embed(text: string): Promise { - const batch = await this.embedBatch([text]); - return batch[0]; - } + async embed(text: string): Promise { + const batch = await this.embedBatch([text]); + return batch[0]; + } - async embedBatch(texts: string[]): Promise { - if (!texts.length) return []; + async embedBatch(texts: string[]): Promise { + if (!texts.length) return []; - try { - const response = await fetch(`${this.apiEndpoint}/embeddings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.apiKey}` - }, - body: JSON.stringify({ - model: this.modelName, - input: texts, - encoding_format: 'float' - }) - }); + try { + const response = await fetch(`${this.apiEndpoint}/embeddings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.apiKey}` + }, + body: JSON.stringify({ + model: this.modelName, + input: texts, + encoding_format: 'float' + }) + }); - if (!response.ok) { - const error = await response.text(); - throw new Error(`OpenAI API Error ${response.status}: ${error}`); - } + if (!response.ok) { + const error = await response.text(); + throw new Error(`OpenAI API Error ${response.status}: ${error}`); + } - const data = await response.json() as any; + const data = (await response.json()) as any; - // OpenAI guarantees order matches input - return data.data.map((item: any) => item.embedding); - } catch (error) { - console.error('OpenAI Embedding Failed:', error); - throw error; - } + // OpenAI guarantees order matches input + return data.data.map((item: any) => item.embedding); + } catch (error) { + console.error('OpenAI Embedding Failed:', error); + throw error; } + } } diff --git a/src/embeddings/transformers.ts b/src/embeddings/transformers.ts index e356734..ffd371e 100644 --- a/src/embeddings/transformers.ts +++ b/src/embeddings/transformers.ts @@ -1,13 +1,13 @@ -import { EmbeddingProvider, DEFAULT_MODEL } from "./types.js"; +import { EmbeddingProvider, DEFAULT_MODEL } from './types.js'; const MODEL_CONFIGS: Record = { - "Xenova/bge-small-en-v1.5": { dimensions: 384 }, - "Xenova/all-MiniLM-L6-v2": { dimensions: 384 }, - "Xenova/bge-base-en-v1.5": { dimensions: 768 }, + 'Xenova/bge-small-en-v1.5': { dimensions: 384 }, + 'Xenova/all-MiniLM-L6-v2': { dimensions: 384 }, + 'Xenova/bge-base-en-v1.5': { dimensions: 768 } }; export class TransformersEmbeddingProvider implements EmbeddingProvider { - readonly name = "transformers"; + readonly name = 'transformers'; readonly modelName: string; readonly dimensions: number; @@ -31,18 +31,18 @@ export class TransformersEmbeddingProvider implements EmbeddingProvider { private async _initialize(): Promise { try { console.error(`Loading embedding model: ${this.modelName}`); - console.error("(First run will download ~130MB model)"); + console.error('(First run will download ~130MB model)'); - const { pipeline } = await import("@xenova/transformers"); + const { pipeline } = await import('@xenova/transformers'); - this.pipeline = await pipeline("feature-extraction", this.modelName, { - quantized: true, + this.pipeline = await pipeline('feature-extraction', this.modelName, { + quantized: true }); this.ready = true; console.error(`Model loaded successfully: ${this.modelName}`); } catch (error) { - console.error("Failed to initialize embedding model:", error); + console.error('Failed to initialize embedding model:', error); throw error; } } @@ -54,13 +54,13 @@ export class TransformersEmbeddingProvider implements EmbeddingProvider { try { const output = await this.pipeline(text, { - pooling: "mean", - normalize: true, + pooling: 'mean', + normalize: true }); return Array.from(output.data); } catch (error) { - console.error("Failed to generate embedding:", error); + console.error('Failed to generate embedding:', error); throw error; } } @@ -75,16 +75,12 @@ export class TransformersEmbeddingProvider implements EmbeddingProvider { for (let i = 0; i < texts.length; i += batchSize) { const batch = texts.slice(i, i + batchSize); - const batchEmbeddings = await Promise.all( - batch.map((text) => this.embed(text)) - ); + const batchEmbeddings = await Promise.all(batch.map((text) => this.embed(text))); embeddings.push(...batchEmbeddings); if (texts.length > 100 && (i + batchSize) % 100 === 0) { - console.error( - `Embedded ${Math.min(i + batchSize, texts.length)}/${texts.length} chunks` - ); + console.error(`Embedded ${Math.min(i + batchSize, texts.length)}/${texts.length} chunks`); } } @@ -103,4 +99,3 @@ export async function createEmbeddingProvider( await provider.initialize(); return provider; } - diff --git a/src/embeddings/types.ts b/src/embeddings/types.ts index 44981d3..45c80c5 100644 --- a/src/embeddings/types.ts +++ b/src/embeddings/types.ts @@ -10,7 +10,7 @@ export interface EmbeddingProvider { } export interface EmbeddingConfig { - provider: "transformers" | "ollama" | "openai" | "custom"; + provider: 'transformers' | 'ollama' | 'openai' | 'custom'; model?: string; batchSize?: number; maxRetries?: number; @@ -18,13 +18,12 @@ export interface EmbeddingConfig { apiEndpoint?: string; } -export const DEFAULT_MODEL = process.env.EMBEDDING_MODEL || "Xenova/bge-small-en-v1.5"; +export const DEFAULT_MODEL = process.env.EMBEDDING_MODEL || 'Xenova/bge-small-en-v1.5'; export const DEFAULT_EMBEDDING_CONFIG: EmbeddingConfig = { - provider: (process.env.EMBEDDING_PROVIDER as any) || "transformers", + provider: (process.env.EMBEDDING_PROVIDER as any) || 'transformers', model: DEFAULT_MODEL, batchSize: 32, maxRetries: 3, - apiKey: process.env.OPENAI_API_KEY, + apiKey: process.env.OPENAI_API_KEY }; - diff --git a/src/index.ts b/src/index.ts index 75bc200..189cfe7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,28 +5,27 @@ * Provides codebase indexing and semantic search capabilities */ -import { promises as fs } from "fs"; +import { promises as fs } from 'fs'; -import path from "path"; -import { glob } from "glob"; -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import path from 'path'; +import { glob } from 'glob'; +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, Tool, - Resource, -} from "@modelcontextprotocol/sdk/types.js"; -import { CodebaseIndexer } from "./core/indexer.js"; -import { IndexingStats } from "./types/index.js"; -import { CodebaseSearcher } from "./core/search.js"; -import { analyzerRegistry } from "./core/analyzer-registry.js"; -import { AngularAnalyzer } from "./analyzers/angular/index.js"; -import { GenericAnalyzer } from "./analyzers/generic/index.js"; -import { InternalFileGraph } from "./utils/usage-tracker.js"; - + Resource +} from '@modelcontextprotocol/sdk/types.js'; +import { CodebaseIndexer } from './core/indexer.js'; +import { IndexingStats } from './types/index.js'; +import { CodebaseSearcher } from './core/search.js'; +import { analyzerRegistry } from './core/analyzer-registry.js'; +import { AngularAnalyzer } from './analyzers/angular/index.js'; +import { GenericAnalyzer } from './analyzers/generic/index.js'; +import { InternalFileGraph } from './utils/usage-tracker.js'; analyzerRegistry.register(new AngularAnalyzer()); analyzerRegistry.register(new GenericAnalyzer()); @@ -42,12 +41,8 @@ function resolveRootPath(): string { // Warn if using cwd as fallback (guarded to avoid stderr during MCP STDIO handshake) if (!arg && !envPath && process.env.CODEBASE_CONTEXT_DEBUG) { - console.error( - `[DEBUG] No project path specified. Using current directory: ${rootPath}` - ); - console.error( - `[DEBUG] Hint: Specify path as CLI argument or set CODEBASE_ROOT env var` - ); + console.error(`[DEBUG] No project path specified. Using current directory: ${rootPath}`); + console.error(`[DEBUG] Hint: Specify path as CLI argument or set CODEBASE_ROOT env var`); } return rootPath; @@ -56,7 +51,7 @@ function resolveRootPath(): string { const ROOT_PATH = resolveRootPath(); export interface IndexState { - status: "idle" | "indexing" | "ready" | "error"; + status: 'idle' | 'indexing' | 'ready' | 'error'; lastIndexed?: Date; stats?: IndexingStats; error?: string; @@ -64,188 +59,187 @@ export interface IndexState { } const indexState: IndexState = { - status: "idle", + status: 'idle' }; const server: Server = new Server( { - name: "codebase-context", - version: "1.3.0", + name: 'codebase-context', + version: '1.3.0' }, { capabilities: { tools: {}, - resources: {}, - }, + resources: {} + } } ); const TOOLS: Tool[] = [ { - name: "search_codebase", + name: 'search_codebase', description: - "Search the indexed codebase using natural language queries. Returns code summaries with file locations. " + - "Supports framework-specific queries and architectural layer filtering. " + - "Use the returned filePath with other tools to read complete file contents.", + 'Search the indexed codebase using natural language queries. Returns code summaries with file locations. ' + + 'Supports framework-specific queries and architectural layer filtering. ' + + 'Use the returned filePath with other tools to read complete file contents.', inputSchema: { - type: "object", + type: 'object', properties: { query: { - type: "string", - description: "Natural language search query", + type: 'string', + description: 'Natural language search query' }, limit: { - type: "number", - description: "Maximum number of results to return (default: 5)", - default: 5, + type: 'number', + description: 'Maximum number of results to return (default: 5)', + default: 5 }, filters: { - type: "object", - description: "Optional filters", + type: 'object', + description: 'Optional filters', properties: { framework: { - type: "string", - description: "Filter by framework (angular, react, vue)", + type: 'string', + description: 'Filter by framework (angular, react, vue)' }, language: { - type: "string", - description: "Filter by programming language", + type: 'string', + description: 'Filter by programming language' }, componentType: { - type: "string", - description: - "Filter by component type (component, service, directive, etc.)", + type: 'string', + description: 'Filter by component type (component, service, directive, etc.)' }, layer: { - type: "string", + type: 'string', description: - "Filter by architectural layer (presentation, business, data, state, core, shared)", + 'Filter by architectural layer (presentation, business, data, state, core, shared)' }, tags: { - type: "array", - items: { type: "string" }, - description: "Filter by tags", - }, - }, - }, + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags' + } + } + } }, - required: ["query"], - }, + required: ['query'] + } }, { - name: "get_codebase_metadata", + name: 'get_codebase_metadata', description: - "Get codebase metadata including framework information, dependencies, architecture patterns, " + - "and project statistics.", + 'Get codebase metadata including framework information, dependencies, architecture patterns, ' + + 'and project statistics.', inputSchema: { - type: "object", - properties: {}, - }, + type: 'object', + properties: {} + } }, { - name: "get_indexing_status", + name: 'get_indexing_status', description: - "Get current indexing status: state, statistics, and progress. " + - "Use refresh_index to manually trigger re-indexing when needed.", + 'Get current indexing status: state, statistics, and progress. ' + + 'Use refresh_index to manually trigger re-indexing when needed.', inputSchema: { - type: "object", - properties: {}, - }, + type: 'object', + properties: {} + } }, { - name: "refresh_index", + name: 'refresh_index', description: - "Re-index the codebase. Supports full re-index or incremental mode. " + - "Use incrementalOnly=true to only process files changed since last index.", + 'Re-index the codebase. Supports full re-index or incremental mode. ' + + 'Use incrementalOnly=true to only process files changed since last index.', inputSchema: { - type: "object", + type: 'object', properties: { reason: { - type: "string", - description: "Reason for refreshing the index (for logging)", + type: 'string', + description: 'Reason for refreshing the index (for logging)' }, incrementalOnly: { - type: "boolean", - description: "If true, only re-index files changed since last full index (faster). Default: false (full re-index)", - }, - }, - }, + type: 'boolean', + description: + 'If true, only re-index files changed since last full index (faster). Default: false (full re-index)' + } + } + } }, { - name: "get_style_guide", - description: - "Query style guide rules and architectural patterns from project documentation.", + name: 'get_style_guide', + description: 'Query style guide rules and architectural patterns from project documentation.', inputSchema: { - type: "object", + type: 'object', properties: { query: { - type: "string", + type: 'string', description: - 'Query for specific style guide rules (e.g., "component naming", "service patterns")', + 'Query for specific style guide rules (e.g., "component naming", "service patterns")' }, category: { - type: "string", - description: - "Filter by category (naming, structure, patterns, testing)", - }, + type: 'string', + description: 'Filter by category (naming, structure, patterns, testing)' + } }, - required: ["query"], - }, + required: ['query'] + } }, { - name: "get_team_patterns", + name: 'get_team_patterns', description: - "Get actionable team pattern recommendations based on codebase analysis. " + - "Returns consensus patterns for DI, state management, testing, library wrappers, etc.", + 'Get actionable team pattern recommendations based on codebase analysis. ' + + 'Returns consensus patterns for DI, state management, testing, library wrappers, etc.', inputSchema: { - type: "object", + type: 'object', properties: { category: { - type: "string", - description: "Pattern category to retrieve", - enum: ["all", "di", "state", "testing", "libraries"], - }, - }, - }, + type: 'string', + description: 'Pattern category to retrieve', + enum: ['all', 'di', 'state', 'testing', 'libraries'] + } + } + } }, { - name: "get_component_usage", + name: 'get_component_usage', description: - "Find WHERE a library or component is used in the codebase. " + + 'Find WHERE a library or component is used in the codebase. ' + "This is 'Find Usages' - returns all files that import a given package/module. " + "Example: get_component_usage('@mycompany/utils') → shows all 34 files using it.", inputSchema: { - type: "object", + type: 'object', properties: { name: { - type: "string", - description: "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')", - }, + type: 'string', + description: + "Import source to find usages for (e.g., 'primeng/table', '@mycompany/ui/button', 'lodash')" + } }, - required: ["name"], - }, + required: ['name'] + } }, { - name: "detect_circular_dependencies", + name: 'detect_circular_dependencies', description: - "Analyze the import graph to detect circular dependencies between files. " + - "Circular dependencies can cause initialization issues, tight coupling, and maintenance problems. " + - "Returns all detected cycles sorted by length (shorter cycles are often more problematic).", + 'Analyze the import graph to detect circular dependencies between files. ' + + 'Circular dependencies can cause initialization issues, tight coupling, and maintenance problems. ' + + 'Returns all detected cycles sorted by length (shorter cycles are often more problematic).', inputSchema: { - type: "object", + type: 'object', properties: { scope: { - type: "string", - description: "Optional path prefix to limit analysis (e.g., 'src/features', 'libs/shared')", - }, - }, - }, - }, + type: 'string', + description: + "Optional path prefix to limit analysis (e.g., 'src/features', 'libs/shared')" + } + } + } + } ]; - server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS }; }); @@ -253,13 +247,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { // MCP Resources - Proactive context injection const RESOURCES: Resource[] = [ { - uri: "codebase://context", - name: "Codebase Intelligence", + uri: 'codebase://context', + name: 'Codebase Intelligence', description: - "Automatic codebase context: libraries used, team patterns, and conventions. " + - "Read this BEFORE generating code to follow team standards.", - mimeType: "text/plain", - }, + 'Automatic codebase context: libraries used, team patterns, and conventions. ' + + 'Read this BEFORE generating code to follow team standards.', + mimeType: 'text/plain' + } ]; server.setRequestHandler(ListResourcesRequestSchema, async () => { @@ -267,59 +261,57 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => { }); async function generateCodebaseContext(): Promise { - const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json"); + const intelligencePath = path.join(ROOT_PATH, '.codebase-intelligence.json'); try { - const content = await fs.readFile(intelligencePath, "utf-8"); + const content = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(content); const lines: string[] = []; - lines.push("# Codebase Intelligence"); - lines.push(""); + lines.push('# Codebase Intelligence'); + lines.push(''); lines.push( - "⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations." + '⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations.' ); - lines.push( - "These are FACTS from analyzing your code, not best practices from the internet." - ); - lines.push(""); + lines.push('These are FACTS from analyzing your code, not best practices from the internet.'); + lines.push(''); // Library usage - sorted by count const libraryEntries = Object.entries(intelligence.libraryUsage || {}) .map(([lib, data]: [string, any]) => ({ lib, - count: data.count, + count: data.count })) .sort((a, b) => b.count - a.count); if (libraryEntries.length > 0) { - lines.push("## Libraries Actually Used (Top 15)"); - lines.push(""); + lines.push('## Libraries Actually Used (Top 15)'); + lines.push(''); for (const { lib, count } of libraryEntries.slice(0, 15)) { lines.push(`- **${lib}** (${count} uses)`); } - lines.push(""); + lines.push(''); } // Show tsconfig paths if available (helps AI understand internal imports) if (intelligence.tsconfigPaths && Object.keys(intelligence.tsconfigPaths).length > 0) { - lines.push("## Import Aliases (from tsconfig.json)"); - lines.push(""); - lines.push("These path aliases map to internal project code:"); + lines.push('## Import Aliases (from tsconfig.json)'); + lines.push(''); + lines.push('These path aliases map to internal project code:'); for (const [alias, paths] of Object.entries(intelligence.tsconfigPaths)) { - lines.push(`- \`${alias}\` → ${(paths as string[]).join(", ")}`); + lines.push(`- \`${alias}\` → ${(paths as string[]).join(', ')}`); } - lines.push(""); + lines.push(''); } // Pattern consensus if (intelligence.patterns && Object.keys(intelligence.patterns).length > 0) { lines.push("## YOUR Codebase's Actual Patterns (Not Generic Best Practices)"); - lines.push(""); - lines.push("These patterns were detected by analyzing your actual code."); - lines.push("This is what YOUR team does in practice, not what tutorials recommend."); - lines.push(""); + lines.push(''); + lines.push('These patterns were detected by analyzing your actual code.'); + lines.push('This is what YOUR team does in practice, not what tutorials recommend.'); + lines.push(''); for (const [category, data] of Object.entries(intelligence.patterns)) { const patternData: any = data; @@ -329,7 +321,7 @@ async function generateCodebaseContext(): Promise { const percentage = parseInt(primary.frequency); const categoryName = category - .replace(/([A-Z])/g, " $1") + .replace(/([A-Z])/g, ' $1') .trim() .replace(/^./, (str: string) => str.toUpperCase()); @@ -337,11 +329,15 @@ async function generateCodebaseContext(): Promise { lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - unanimous)`); lines.push(` → Your codebase is 100% consistent - ALWAYS use ${primary.name}`); } else if (percentage >= 80) { - lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - strong consensus)`); + lines.push( + `### ${categoryName}: **${primary.name}** (${primary.frequency} - strong consensus)` + ); lines.push(` → Your team strongly prefers ${primary.name}`); if (patternData.alsoDetected?.length) { const alt = patternData.alsoDetected[0]; - lines.push(` → Minority pattern: ${alt.name} (${alt.frequency}) - avoid for new code`); + lines.push( + ` → Minority pattern: ${alt.name} (${alt.frequency}) - avoid for new code` + ); } } else if (percentage >= 60) { lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - majority)`); @@ -363,20 +359,18 @@ async function generateCodebaseContext(): Promise { } lines.push(` → ASK the team which approach to use for new features`); } - lines.push(""); + lines.push(''); } } - lines.push("---"); - lines.push( - `Generated: ${intelligence.generatedAt || new Date().toISOString()}` - ); + lines.push('---'); + lines.push(`Generated: ${intelligence.generatedAt || new Date().toISOString()}`); - return lines.join("\n"); + return lines.join('\n'); } catch (error) { return ( - "# Codebase Intelligence\n\n" + - "Intelligence data not yet generated. Run indexing first.\n" + + '# Codebase Intelligence\n\n' + + 'Intelligence data not yet generated. Run indexing first.\n' + `Error: ${error instanceof Error ? error.message : String(error)}` ); } @@ -385,17 +379,17 @@ async function generateCodebaseContext(): Promise { server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; - if (uri === "codebase://context") { + if (uri === 'codebase://context') { const content = await generateCodebaseContext(); return { contents: [ { uri, - mimeType: "text/plain", - text: content, - }, - ], + mimeType: 'text/plain', + text: content + } + ] }; } @@ -403,11 +397,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { }); async function performIndexing(): Promise { - indexState.status = "indexing"; + indexState.status = 'indexing'; console.error(`Indexing: ${ROOT_PATH}`); try { - let lastLoggedProgress = { phase: "", percentage: -1 }; + let lastLoggedProgress = { phase: '', percentage: -1 }; const indexer = new CodebaseIndexer({ rootPath: ROOT_PATH, onProgress: (progress) => { @@ -420,13 +414,13 @@ async function performIndexing(): Promise { console.error(`[${progress.phase}] ${progress.percentage}%`); lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage }; } - }, + } }); indexState.indexer = indexer; const stats = await indexer.index(); - indexState.status = "ready"; + indexState.status = 'ready'; indexState.lastIndexed = new Date(); indexState.stats = stats; @@ -436,14 +430,14 @@ async function performIndexing(): Promise { ).toFixed(2)}s` ); } catch (error) { - indexState.status = "error"; + indexState.status = 'error'; indexState.error = error instanceof Error ? error.message : String(error); - console.error("Indexing failed:", indexState.error); + console.error('Indexing failed:', indexState.error); } } async function shouldReindex(): Promise { - const indexPath = path.join(ROOT_PATH, ".codebase-index.json"); + const indexPath = path.join(ROOT_PATH, '.codebase-index.json'); try { await fs.access(indexPath); return false; @@ -457,43 +451,43 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { try { switch (name) { - case "search_codebase": { + case 'search_codebase': { const { query, limit, filters } = args as any; - if (indexState.status === "indexing") { + if (indexState.status === 'indexing') { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "indexing", - message: "Index is still being built. Retry in a moment.", - progress: indexState.indexer?.getProgress(), + status: 'indexing', + message: 'Index is still being built. Retry in a moment.', + progress: indexState.indexer?.getProgress() }, null, 2 - ), - }, - ], + ) + } + ] }; } - if (indexState.status === "error") { + if (indexState.status === 'error') { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "error", - message: `Indexing failed: ${indexState.error}`, + status: 'error', + message: `Indexing failed: ${indexState.error}` }, null, 2 - ), - }, - ], + ) + } + ] }; } @@ -503,10 +497,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "success", + status: 'success', results: results.map((r) => ({ summary: r.summary, snippet: r.snippet, @@ -518,25 +512,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { framework: r.framework, // v1.2: Pattern momentum awareness trend: r.trend, - patternWarning: r.patternWarning, + patternWarning: r.patternWarning })), - totalResults: results.length, + totalResults: results.length }, null, 2 - ), - }, - ], + ) + } + ] }; } - case "get_indexing_status": { + case 'get_indexing_status': { const progress = indexState.indexer?.getProgress(); return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { status: indexState.status, @@ -547,7 +541,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { totalFiles: indexState.stats.totalFiles, indexedFiles: indexState.stats.indexedFiles, totalChunks: indexState.stats.totalChunks, - duration: `${(indexState.stats.duration / 1000).toFixed(2)}s`, + duration: `${(indexState.stats.duration / 1000).toFixed(2)}s` } : undefined, progress: progress @@ -555,25 +549,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { phase: progress.phase, percentage: progress.percentage, filesProcessed: progress.filesProcessed, - totalFiles: progress.totalFiles, + totalFiles: progress.totalFiles } : undefined, error: indexState.error, - hint: "Use refresh_index to manually trigger re-indexing when needed.", + hint: 'Use refresh_index to manually trigger re-indexing when needed.' }, null, 2 - ), - }, - ], + ) + } + ] }; } - case "refresh_index": { + case 'refresh_index': { const { reason, incrementalOnly } = args as { reason?: string; incrementalOnly?: boolean }; - const mode = incrementalOnly ? "incremental" : "full"; - console.error(`Refresh requested (${mode}): ${reason || "Manual trigger"}`); + const mode = incrementalOnly ? 'incremental' : 'full'; + console.error(`Refresh requested (${mode}): ${reason || 'Manual trigger'}`); // TODO: When incremental indexing is implemented (Phase 2), // use `incrementalOnly` to only re-index changed files. @@ -583,56 +577,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "started", + status: 'started', mode, message: incrementalOnly - ? "Incremental re-indexing requested. Check status with get_indexing_status." - : "Full re-indexing started. Check status with get_indexing_status.", + ? 'Incremental re-indexing requested. Check status with get_indexing_status.' + : 'Full re-indexing started. Check status with get_indexing_status.', reason, note: incrementalOnly - ? "Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2." - : undefined, + ? 'Incremental mode requested. Full re-index for now; true incremental indexing coming in Phase 2.' + : undefined }, null, 2 - ), - }, - ], + ) + } + ] }; } - case "get_codebase_metadata": { + case 'get_codebase_metadata': { const indexer = new CodebaseIndexer({ rootPath: ROOT_PATH }); const metadata = await indexer.detectMetadata(); // Load team patterns from intelligence file let teamPatterns = {}; try { - const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json"); - const intelligenceContent = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(ROOT_PATH, '.codebase-intelligence.json'); + const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(intelligenceContent); if (intelligence.patterns) { teamPatterns = { dependencyInjection: intelligence.patterns.dependencyInjection, stateManagement: intelligence.patterns.stateManagement, - componentInputs: intelligence.patterns.componentInputs, + componentInputs: intelligence.patterns.componentInputs }; } - } catch (error) { + } catch (_error) { // No intelligence file or parsing error } return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "success", + status: 'success', metadata: { name: metadata.name, framework: metadata.framework, @@ -641,31 +635,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { architecture: metadata.architecture, projectStructure: metadata.projectStructure, statistics: metadata.statistics, - teamPatterns, - }, + teamPatterns + } }, null, 2 - ), - }, - ], + ) + } + ] }; } - case "get_style_guide": { + case 'get_style_guide': { const { query, category } = args as { query: string; category?: string; }; const styleGuidePatterns = [ - "STYLE_GUIDE.md", - "CODING_STYLE.md", - "ARCHITECTURE.md", - "CONTRIBUTING.md", - "docs/style-guide.md", - "docs/coding-style.md", - "docs/ARCHITECTURE.md", + 'STYLE_GUIDE.md', + 'CODING_STYLE.md', + 'ARCHITECTURE.md', + 'CONTRIBUTING.md', + 'docs/style-guide.md', + 'docs/coding-style.md', + 'docs/ARCHITECTURE.md' ]; const foundGuides: Array<{ @@ -680,13 +674,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const files = await glob(pattern, { cwd: ROOT_PATH, - absolute: true, + absolute: true }); for (const file of files) { try { // Normalize line endings to \n for consistent output - const rawContent = await fs.readFile(file, "utf-8"); - const content = rawContent.replace(/\r\n/g, "\n"); + const rawContent = await fs.readFile(file, 'utf-8'); + const content = rawContent.replace(/\r\n/g, '\n'); const relativePath = path.relative(ROOT_PATH, file); // Find relevant sections based on query @@ -695,18 +689,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { for (const section of sections) { const sectionLower = section.toLowerCase(); - const isRelevant = queryTerms.some((term) => - sectionLower.includes(term) - ); + const isRelevant = queryTerms.some((term) => sectionLower.includes(term)); if (isRelevant) { // Limit section size to ~500 words const words = section.split(/\s+/); - const truncated = words.slice(0, 500).join(" "); + const truncated = words.slice(0, 500).join(' '); relevantSections.push( - "## " + - (words.length > 500 - ? truncated + "..." - : section.trim()) + '## ' + (words.length > 500 ? truncated + '...' : section.trim()) ); } } @@ -714,15 +703,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (relevantSections.length > 0) { foundGuides.push({ file: relativePath, - content: content.slice(0, 200) + "...", - relevantSections: relevantSections.slice(0, 3), // Max 3 sections per file + content: content.slice(0, 200) + '...', + relevantSections: relevantSections.slice(0, 3) // Max 3 sections per file }); } - } catch (e) { + } catch (_e) { // Skip unreadable files } } - } catch (e) { + } catch (_e) { // Pattern didn't match, continue } } @@ -731,65 +720,65 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "no_results", + status: 'no_results', message: `No style guide content found matching: ${query}`, searchedPatterns: styleGuidePatterns, - hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'", + hint: "Try broader terms like 'naming', 'patterns', 'testing', 'components'" }, null, 2 - ), - }, - ], + ) + } + ] }; } return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - status: "success", + status: 'success', query, category, results: foundGuides, - totalFiles: foundGuides.length, + totalFiles: foundGuides.length }, null, 2 - ), - }, - ], + ) + } + ] }; } - case "get_team_patterns": { + case 'get_team_patterns': { const { category } = args as { category?: string }; try { - const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json"); - const content = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(ROOT_PATH, '.codebase-intelligence.json'); + const content = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(content); - const result: any = { status: "success" }; + const result: any = { status: 'success' }; - if (category === "all" || !category) { + if (category === 'all' || !category) { result.patterns = intelligence.patterns || {}; result.goldenFiles = intelligence.goldenFiles || []; if (intelligence.tsconfigPaths) { result.tsconfigPaths = intelligence.tsconfigPaths; } - } else if (category === "di") { + } else if (category === 'di') { result.dependencyInjection = intelligence.patterns?.dependencyInjection; - } else if (category === "state") { + } else if (category === 'state') { result.stateManagement = intelligence.patterns?.stateManagement; - } else if (category === "testing") { + } else if (category === 'testing') { result.testingFramework = intelligence.patterns?.testingFramework; result.testMocking = intelligence.patterns?.testMocking; - } else if (category === "libraries") { + } else if (category === 'libraries') { result.topUsed = intelligence.importGraph?.topUsed || []; if (intelligence.tsconfigPaths) { result.tsconfigPaths = intelligence.tsconfigPaths; @@ -797,30 +786,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { return { content: [ { - type: "text", - text: JSON.stringify({ - status: "error", - message: "Failed to load team patterns", - error: error instanceof Error ? error.message : String(error), - }, null, 2), - }, - ], + type: 'text', + text: JSON.stringify( + { + status: 'error', + message: 'Failed to load team patterns', + error: error instanceof Error ? error.message : String(error) + }, + null, + 2 + ) + } + ] }; } } - case "get_component_usage": { + case 'get_component_usage': { const { name: componentName } = args as { name: string }; try { - const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json"); - const content = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(ROOT_PATH, '.codebase-intelligence.json'); + const content = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(content); const importGraph = intelligence.importGraph || {}; @@ -831,8 +824,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { // Try partial match if exact match not found if (!matchedUsage) { - const matchingKeys = Object.keys(usages).filter(key => - key.includes(componentName) || componentName.includes(key) + const matchingKeys = Object.keys(usages).filter( + (key) => key.includes(componentName) || componentName.includes(key) ); if (matchingKeys.length > 0) { matchedUsage = usages[matchingKeys[0]]; @@ -841,62 +834,87 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (matchedUsage) { return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "success", - component: componentName, - usageCount: matchedUsage.usageCount, - usedIn: matchedUsage.usedIn, - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'success', + component: componentName, + usageCount: matchedUsage.usageCount, + usedIn: matchedUsage.usedIn + }, + null, + 2 + ) + } + ] }; } else { // Show top used as alternatives const topUsed = importGraph.topUsed || []; return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "not_found", - component: componentName, - message: `No usages found for '${componentName}'.`, - suggestions: topUsed.slice(0, 10), - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'not_found', + component: componentName, + message: `No usages found for '${componentName}'.`, + suggestions: topUsed.slice(0, 10) + }, + null, + 2 + ) + } + ] }; } } catch (error) { return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "error", - message: "Failed to get component usage. Run indexing first.", - error: error instanceof Error ? error.message : String(error), - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'error', + message: 'Failed to get component usage. Run indexing first.', + error: error instanceof Error ? error.message : String(error) + }, + null, + 2 + ) + } + ] }; } } - case "detect_circular_dependencies": { + case 'detect_circular_dependencies': { const { scope } = args as { scope?: string }; try { - const intelligencePath = path.join(ROOT_PATH, ".codebase-intelligence.json"); - const content = await fs.readFile(intelligencePath, "utf-8"); + const intelligencePath = path.join(ROOT_PATH, '.codebase-intelligence.json'); + const content = await fs.readFile(intelligencePath, 'utf-8'); const intelligence = JSON.parse(content); if (!intelligence.internalFileGraph) { return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "error", - message: "Internal file graph not found. Please run refresh_index to rebuild the index with cycle detection support.", - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'error', + message: + 'Internal file graph not found. Please run refresh_index to rebuild the index with cycle detection support.' + }, + null, + 2 + ) + } + ] }; } @@ -907,48 +925,67 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (cycles.length === 0) { return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "success", - message: scope - ? `No circular dependencies detected in scope: ${scope}` - : "No circular dependencies detected in the codebase.", - scope, - graphStats, - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'success', + message: scope + ? `No circular dependencies detected in scope: ${scope}` + : 'No circular dependencies detected in the codebase.', + scope, + graphStats + }, + null, + 2 + ) + } + ] }; } return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "warning", - message: `Found ${cycles.length} circular dependency cycle(s).`, - scope, - cycles: cycles.map(c => ({ - files: c.files, - length: c.length, - severity: c.length === 2 ? "high" : c.length <= 3 ? "medium" : "low", - })), - count: cycles.length, - graphStats, - advice: "Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.", - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'warning', + message: `Found ${cycles.length} circular dependency cycle(s).`, + scope, + cycles: cycles.map((c) => ({ + files: c.files, + length: c.length, + severity: c.length === 2 ? 'high' : c.length <= 3 ? 'medium' : 'low' + })), + count: cycles.length, + graphStats, + advice: + 'Shorter cycles (length 2-3) are typically more problematic. Consider breaking the cycle by extracting shared dependencies.' + }, + null, + 2 + ) + } + ] }; } catch (error) { return { - content: [{ - type: "text", - text: JSON.stringify({ - status: "error", - message: "Failed to detect circular dependencies. Run indexing first.", - error: error instanceof Error ? error.message : String(error), - }, null, 2), - }], + content: [ + { + type: 'text', + text: JSON.stringify( + { + status: 'error', + message: 'Failed to detect circular dependencies. Run indexing first.', + error: error instanceof Error ? error.message : String(error) + }, + null, + 2 + ) + } + ] }; } } @@ -957,35 +994,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { - error: `Unknown tool: ${name}`, + error: `Unknown tool: ${name}` }, null, 2 - ), - }, + ) + } ], - isError: true, + isError: true }; } } catch (error) { return { content: [ { - type: "text", + type: 'text', text: JSON.stringify( { error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, + stack: error instanceof Error ? error.stack : undefined }, null, 2 - ), - }, + ) + } ], - isError: true, + isError: true }; } }); @@ -993,13 +1030,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { async function main() { // Server startup banner (guarded to avoid stderr during MCP STDIO handshake) if (process.env.CODEBASE_CONTEXT_DEBUG) { - console.error("[DEBUG] Codebase Context MCP Server"); + console.error('[DEBUG] Codebase Context MCP Server'); console.error(`[DEBUG] Root: ${ROOT_PATH}`); console.error( `[DEBUG] Analyzers: ${analyzerRegistry .getAll() .map((a) => a.name) - .join(", ")}` + .join(', ')}` ); } @@ -1011,7 +1048,7 @@ async function main() { console.error(`Please specify a valid project directory.`); process.exit(1); } - } catch (error) { + } catch (_error) { console.error(`ERROR: Root path does not exist: ${ROOT_PATH}`); console.error(`Please specify a valid project directory.`); process.exit(1); @@ -1020,30 +1057,28 @@ async function main() { // Check for package.json to confirm it's a project root (guarded to avoid stderr during handshake) if (process.env.CODEBASE_CONTEXT_DEBUG) { try { - await fs.access(path.join(ROOT_PATH, "package.json")); + await fs.access(path.join(ROOT_PATH, 'package.json')); console.error(`[DEBUG] Project detected: ${path.basename(ROOT_PATH)}`); } catch { - console.error( - `[DEBUG] WARNING: No package.json found. This may not be a project root.` - ); + console.error(`[DEBUG] WARNING: No package.json found. This may not be a project root.`); } } const needsIndex = await shouldReindex(); if (needsIndex) { - if (process.env.CODEBASE_CONTEXT_DEBUG) console.error("[DEBUG] Starting indexing..."); + if (process.env.CODEBASE_CONTEXT_DEBUG) console.error('[DEBUG] Starting indexing...'); performIndexing(); } else { - if (process.env.CODEBASE_CONTEXT_DEBUG) console.error("[DEBUG] Index found. Ready."); - indexState.status = "ready"; + if (process.env.CODEBASE_CONTEXT_DEBUG) console.error('[DEBUG] Index found. Ready.'); + indexState.status = 'ready'; indexState.lastIndexed = new Date(); } const transport = new StdioServerTransport(); await server.connect(transport); - if (process.env.CODEBASE_CONTEXT_DEBUG) console.error("[DEBUG] Server ready"); + if (process.env.CODEBASE_CONTEXT_DEBUG) console.error('[DEBUG] Server ready'); } // Export server components for programmatic use @@ -1052,12 +1087,12 @@ export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS }; // Only auto-start when run directly as CLI (not when imported as module) // Check if this module is the entry point const isDirectRun = - process.argv[1]?.replace(/\\/g, "/").endsWith("index.js") || - process.argv[1]?.replace(/\\/g, "/").endsWith("index.ts"); + process.argv[1]?.replace(/\\/g, '/').endsWith('index.js') || + process.argv[1]?.replace(/\\/g, '/').endsWith('index.ts'); if (isDirectRun) { main().catch((error) => { - console.error("Fatal:", error); + console.error('Fatal:', error); process.exit(1); }); } diff --git a/src/lib.ts b/src/lib.ts index 543b926..ab6194b 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -32,13 +32,12 @@ */ // Core classes -export { CodebaseIndexer, type IndexerOptions } from "./core/indexer.js"; -export { CodebaseSearcher, type SearchOptions } from "./core/search.js"; -export { - AnalyzerRegistry, - analyzerRegistry, -} from "./core/analyzer-registry.js"; - +export { CodebaseIndexer, type IndexerOptions } from './core/indexer.js'; +import { CodebaseIndexer } from './core/indexer.js'; +export { CodebaseSearcher, type SearchOptions } from './core/search.js'; +import { CodebaseSearcher } from './core/search.js'; +export { AnalyzerRegistry, analyzerRegistry } from './core/analyzer-registry.js'; +import { analyzerRegistry } from './core/analyzer-registry.js'; // Embedding providers export { @@ -46,8 +45,8 @@ export { TransformersEmbeddingProvider, type EmbeddingProvider, type EmbeddingConfig, - DEFAULT_EMBEDDING_CONFIG, -} from "./embeddings/index.js"; + DEFAULT_EMBEDDING_CONFIG +} from './embeddings/index.js'; // Storage providers export { @@ -56,12 +55,14 @@ export { type VectorStorageProvider, type StorageConfig, type CodeChunkWithEmbedding, - DEFAULT_STORAGE_CONFIG, -} from "./storage/index.js"; + DEFAULT_STORAGE_CONFIG +} from './storage/index.js'; // Framework analyzers -export { AngularAnalyzer } from "./analyzers/angular/index.js"; -export { GenericAnalyzer } from "./analyzers/generic/index.js"; +export { AngularAnalyzer } from './analyzers/angular/index.js'; +import { AngularAnalyzer } from './analyzers/angular/index.js'; +export { GenericAnalyzer } from './analyzers/generic/index.js'; +import { GenericAnalyzer } from './analyzers/generic/index.js'; // Utilities export { @@ -70,14 +71,14 @@ export { detectLanguage, isTestFile, isDocumentationFile, - getSupportedExtensions, -} from "./utils/language-detection.js"; + getSupportedExtensions +} from './utils/language-detection.js'; export { createChunksFromCode, calculateComplexity, mergeSmallChunks, - type ChunkingOptions, -} from "./utils/chunking.js"; + type ChunkingOptions +} from './utils/chunking.js'; // All types export type { @@ -134,8 +135,8 @@ export type { Decorator, Property, Method, - Parameter, -} from "./types/index.js"; + Parameter +} from './types/index.js'; /** * Convenience function to create a fully configured indexer with default analyzers @@ -146,26 +147,19 @@ export type { */ export function createIndexer( rootPath: string, - options?: Partial< - Omit - > -): import("./core/indexer.js").CodebaseIndexer { - const { CodebaseIndexer } = require("./core/indexer.js"); - const { AngularAnalyzer } = require("./analyzers/angular/index.js"); - const { GenericAnalyzer } = require("./analyzers/generic/index.js"); - const { analyzerRegistry } = require("./core/analyzer-registry.js"); - + options?: Partial> +): import('./core/indexer.js').CodebaseIndexer { // Register default analyzers if not already registered - if (!analyzerRegistry.get("angular")) { + if (!analyzerRegistry.get('angular')) { analyzerRegistry.register(new AngularAnalyzer()); } - if (!analyzerRegistry.get("generic")) { + if (!analyzerRegistry.get('generic')) { analyzerRegistry.register(new GenericAnalyzer()); } return new CodebaseIndexer({ rootPath, - ...options, + ...options }); } @@ -175,9 +169,6 @@ export function createIndexer( * @param rootPath - Path to the indexed project root * @returns A configured CodebaseSearcher instance */ -export function createSearcher( - rootPath: string -): import("./core/search.js").CodebaseSearcher { - const { CodebaseSearcher } = require("./core/search.js"); +export function createSearcher(rootPath: string): import('./core/search.js').CodebaseSearcher { return new CodebaseSearcher(rootPath); } diff --git a/src/storage/lancedb.ts b/src/storage/lancedb.ts index c19df88..468f988 100644 --- a/src/storage/lancedb.ts +++ b/src/storage/lancedb.ts @@ -3,13 +3,8 @@ * Embedded vector database for storing and searching code embeddings */ -import path from 'path'; import { promises as fs } from 'fs'; -import { - VectorStorageProvider, - CodeChunkWithEmbedding, - VectorSearchResult, -} from './types.js'; +import { VectorStorageProvider, CodeChunkWithEmbedding, VectorSearchResult } from './types.js'; import { CodeChunk, SearchFilters } from '../types/index.js'; export class LanceDBStorageProvider implements VectorStorageProvider { @@ -52,7 +47,7 @@ export class LanceDBStorageProvider implements VectorStorageProvider { } else { console.error('Opened existing LanceDB table'); } - } catch (schemaError) { + } catch (_schemaError) { // If schema check fails, table is likely corrupted - drop and rebuild console.error('Failed to validate table schema, rebuilding index...'); await this.db.dropTable('code_chunks'); @@ -77,7 +72,7 @@ export class LanceDBStorageProvider implements VectorStorageProvider { try { // Convert chunks to LanceDB format - const records = chunks.map(chunk => ({ + const records = chunks.map((chunk) => ({ id: chunk.id, vector: chunk.embedding, content: chunk.content, @@ -93,7 +88,7 @@ export class LanceDBStorageProvider implements VectorStorageProvider { imports: JSON.stringify(chunk.imports), exports: JSON.stringify(chunk.exports), tags: JSON.stringify(chunk.tags), - metadata: JSON.stringify(chunk.metadata), + metadata: JSON.stringify(chunk.metadata) })); // Create or overwrite table @@ -103,7 +98,7 @@ export class LanceDBStorageProvider implements VectorStorageProvider { } else { // Create new table this.table = await this.db.createTable('code_chunks', records, { - mode: 'overwrite', + mode: 'overwrite' }); } @@ -169,10 +164,10 @@ export class LanceDBStorageProvider implements VectorStorageProvider { imports: JSON.parse(result.imports || '[]'), exports: JSON.parse(result.exports || '[]'), tags: JSON.parse(result.tags || '[]'), - metadata: JSON.parse(result.metadata || '{}'), + metadata: JSON.parse(result.metadata || '{}') } as CodeChunk, score: 1 - (result._distance || 0), // Convert distance to similarity - distance: result._distance || 0, + distance: result._distance || 0 })); } catch (error) { console.error('Failed to search:', error); @@ -220,9 +215,7 @@ export class LanceDBStorageProvider implements VectorStorageProvider { /** * Create a LanceDB storage provider */ -export async function createLanceDBStorage( - storagePath: string -): Promise { +export async function createLanceDBStorage(storagePath: string): Promise { const provider = new LanceDBStorageProvider(); await provider.initialize(storagePath); return provider; diff --git a/src/storage/types.ts b/src/storage/types.ts index 791a2a9..80ecd4e 100644 --- a/src/storage/types.ts +++ b/src/storage/types.ts @@ -59,5 +59,5 @@ export interface StorageConfig { export const DEFAULT_STORAGE_CONFIG: StorageConfig = { provider: 'lancedb', - path: '.codebase-index', + path: '.codebase-index' }; diff --git a/src/types/index.ts b/src/types/index.ts index ceff4ec..9cade54 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -89,24 +89,24 @@ export interface Dependency { } export type DependencyCategory = - | 'framework' // Angular, React, Vue core - | 'state' // State management (NgRx, Redux, Pinia) - | 'ui' // UI libraries (Material, Ant Design) - | 'routing' // Routing libraries - | 'http' // HTTP clients - | 'testing' // Testing frameworks - | 'utility' // Utility libraries - | 'build' // Build tools + | 'framework' // Angular, React, Vue core + | 'state' // State management (NgRx, Redux, Pinia) + | 'ui' // UI libraries (Material, Ant Design) + | 'routing' // Routing libraries + | 'http' // HTTP clients + | 'testing' // Testing frameworks + | 'utility' // Utility libraries + | 'build' // Build tools | 'other'; export type ArchitecturalLayer = - | 'presentation' // UI components, views - | 'business' // Business logic, services - | 'data' // Data access, API calls - | 'state' // State management - | 'core' // Core services, guards - | 'shared' // Shared utilities - | 'feature' // Feature modules + | 'presentation' // UI components, views + | 'business' // Business logic, services + | 'data' // Data access, API calls + | 'state' // State management + | 'core' // Core services, guards + | 'shared' // Shared utilities + | 'feature' // Feature modules | 'infrastructure' // Infrastructure code | 'unknown'; diff --git a/src/utils/chunking.ts b/src/utils/chunking.ts index 51f38be..bc28754 100644 --- a/src/utils/chunking.ts +++ b/src/utils/chunking.ts @@ -7,15 +7,15 @@ import { v4 as uuidv4 } from 'uuid'; import { CodeChunk, CodeComponent } from '../types/index.js'; export interface ChunkingOptions { - maxChunkSize?: number; // Max lines per chunk - overlapSize?: number; // Lines of overlap between chunks + maxChunkSize?: number; // Max lines per chunk + overlapSize?: number; // Lines of overlap between chunks preserveBoundaries?: boolean; // Try to chunk at function/class boundaries } const DEFAULT_OPTIONS: ChunkingOptions = { maxChunkSize: 100, overlapSize: 10, - preserveBoundaries: true, + preserveBoundaries: true }; /** @@ -30,11 +30,13 @@ export async function createChunksFromCode( metadata?: Record ): Promise { const chunks: CodeChunk[] = []; - const lines = content.split('\n'); + const _lines = content.split('\n'); // If we have components, create component-aware chunks if (components.length > 0) { - chunks.push(...createComponentChunks(content, filePath, relativePath, language, components, metadata)); + chunks.push( + ...createComponentChunks(content, filePath, relativePath, language, components, metadata) + ); } else { // Fall back to line-based chunking chunks.push(...createLineChunks(content, filePath, relativePath, language, metadata)); @@ -109,8 +111,8 @@ function createComponentChunks( componentType: component.componentType, complexity, importSection, // Keep imports as context in metadata - lifecycle: component.lifecycle, - }, + lifecycle: component.lifecycle + } }); } @@ -157,8 +159,8 @@ function createLineChunks( tags: [], metadata: { ...metadata, - complexity, - }, + complexity + } }); // Move to next chunk with overlap @@ -185,9 +187,9 @@ export function calculateComplexity(code: string): number { /\bwhile\s*\(/g, /\bcase\s+/g, /\bcatch\s*\(/g, - /\b\?\s*[^:]/g, // Ternary operator + /\b\?\s*[^:]/g, // Ternary operator /&&/g, - /\|\|/g, + /\|\|/g ]; for (const pattern of patterns) { @@ -274,8 +276,8 @@ export function mergeSmallChunks(chunks: CodeChunk[], minSize: number = 20): Cod endLine: next.endLine, metadata: { ...current.metadata, - merged: true, - }, + merged: true + } }; } else { merged.push(current); diff --git a/src/utils/dependency-detection.ts b/src/utils/dependency-detection.ts index 29941f9..7ed81af 100644 --- a/src/utils/dependency-detection.ts +++ b/src/utils/dependency-detection.ts @@ -4,9 +4,9 @@ import path from 'path'; import { Dependency as GlobalDependency } from '../types/index.js'; export interface Dependency { - name: string; - version: string; - category: GlobalDependency['category']; + name: string; + version: string; + category: GlobalDependency['category']; } /** @@ -14,110 +14,109 @@ export interface Dependency { * This centralized list helps different analyzers agree on what a library "is". */ export const LIBRARY_CATEGORIES: Record = { - // Frameworks - 'react': 'framework', - 'vue': 'framework', - 'angular': 'framework', - 'svelte': 'framework', - 'next': 'framework', - 'nuxt': 'framework', + // Frameworks + react: 'framework', + vue: 'framework', + angular: 'framework', + svelte: 'framework', + next: 'framework', + nuxt: 'framework', - // UI & Styling - 'tailwindcss': 'ui', - '@mui/material': 'ui', - 'styled-components': 'ui', - '@emotion/react': 'ui', - 'framer-motion': 'ui', - 'lucide-react': 'ui', - '@radix-ui/react-slot': 'ui', - 'class-variance-authority': 'ui', - 'clsx': 'ui', - 'tailwind-merge': 'ui', + // UI & Styling + tailwindcss: 'ui', + '@mui/material': 'ui', + 'styled-components': 'ui', + '@emotion/react': 'ui', + 'framer-motion': 'ui', + 'lucide-react': 'ui', + '@radix-ui/react-slot': 'ui', + 'class-variance-authority': 'ui', + clsx: 'ui', + 'tailwind-merge': 'ui', - // State Management - 'redux': 'state', - '@reduxjs/toolkit': 'state', - 'zustand': 'state', - 'jotai': 'state', - 'recoil': 'state', - 'mobx': 'state', - '@tanstack/react-query': 'state', - 'swr': 'state', + // State Management + redux: 'state', + '@reduxjs/toolkit': 'state', + zustand: 'state', + jotai: 'state', + recoil: 'state', + mobx: 'state', + '@tanstack/react-query': 'state', + swr: 'state', - // Forms & Validation - 'react-hook-form': 'ui', // Often considers UI logic - 'formik': 'ui', - 'zod': 'utility', - 'yup': 'utility', - 'valibot': 'utility', + // Forms & Validation + 'react-hook-form': 'ui', // Often considers UI logic + formik: 'ui', + zod: 'utility', + yup: 'utility', + valibot: 'utility', - // Backend / API - 'express': 'other', - 'fastify': 'other', - 'nest.js': 'other', - 'prisma': 'other', - 'mongoose': 'other', + // Backend / API + express: 'other', + fastify: 'other', + 'nest.js': 'other', + prisma: 'other', + mongoose: 'other', - // Testing - 'jest': 'testing', - 'vitest': 'testing', - 'cypress': 'testing', - '@playwright/test': 'testing', - '@testing-library/react': 'testing', + // Testing + jest: 'testing', + vitest: 'testing', + cypress: 'testing', + '@playwright/test': 'testing', + '@testing-library/react': 'testing', - // Build & Tools - 'typescript': 'build', - 'vite': 'build', - 'webpack': 'build', - 'esbuild': 'build', - 'eslint': 'build', - 'prettier': 'build', - 'nx': 'build', - 'turbo': 'build', + // Build & Tools + typescript: 'build', + vite: 'build', + webpack: 'build', + esbuild: 'build', + eslint: 'build', + prettier: 'build', + nx: 'build', + turbo: 'build' }; export async function readPackageJson(rootPath: string): Promise> { - try { - const packageJsonPath = path.join(rootPath, 'package.json'); - const content = await fs.readFile(packageJsonPath, 'utf-8'); - const pkg = JSON.parse(content); - return { - ...(pkg.dependencies || {}), - ...(pkg.devDependencies || {}), - ...(pkg.peerDependencies || {}), - }; - } catch (error) { - // If no package.json, return empty deps - return {}; - } + try { + const packageJsonPath = path.join(rootPath, 'package.json'); + const content = await fs.readFile(packageJsonPath, 'utf-8'); + const pkg = JSON.parse(content); + return { + ...(pkg.dependencies || {}), + ...(pkg.devDependencies || {}), + ...(pkg.peerDependencies || {}) + }; + } catch (_error) { + // If no package.json, return empty deps + return {}; + } } /** * categorize a dependency based on the known list */ export function categorizeDependency(name: string): Dependency['category'] { - // Exact match - if (LIBRARY_CATEGORIES[name]) { - return LIBRARY_CATEGORIES[name]; - } + // Exact match + if (LIBRARY_CATEGORIES[name]) { + return LIBRARY_CATEGORIES[name]; + } - // Prefix matching (e.g. @angular/core -> framework) - if (name.startsWith('@angular/')) return 'framework'; - if (name.startsWith('@nestjs/')) return 'other'; // No backend category in global types - if (name.startsWith('@nx/')) return 'build'; - if (name.startsWith('@nrwl/')) return 'build'; - if (name.startsWith('@types/')) return 'build'; + // Prefix matching (e.g. @angular/core -> framework) + if (name.startsWith('@angular/')) return 'framework'; + if (name.startsWith('@nestjs/')) return 'other'; // No backend category in global types + if (name.startsWith('@nx/')) return 'build'; + if (name.startsWith('@nrwl/')) return 'build'; + if (name.startsWith('@types/')) return 'build'; - return 'other'; + return 'other'; } // Re-export workspace detection utilities for unified API export { - scanWorkspacePackageJsons, - detectWorkspaceType, - aggregateWorkspaceDependencies, - normalizePackageVersion, - WorkspacePackageJson, - WorkspaceType, + scanWorkspacePackageJsons, + detectWorkspaceType, + aggregateWorkspaceDependencies, + normalizePackageVersion, + WorkspacePackageJson, + WorkspaceType } from './workspace-detection.js'; - diff --git a/src/utils/git-dates.ts b/src/utils/git-dates.ts index 2cebee8..e14ab44 100644 --- a/src/utils/git-dates.ts +++ b/src/utils/git-dates.ts @@ -3,80 +3,78 @@ * Extracts file commit dates from git history for pattern momentum analysis */ -import { exec } from "child_process"; -import { promisify } from "util"; -import path from "path"; +import { exec } from 'child_process'; +import { promisify } from 'util'; const execAsync = promisify(exec); /** * Get the last commit date for each file in the repository. * Uses a single git command to efficiently extract all file dates. - * + * * @param rootPath - Root path of the git repository * @returns Map of relative file paths to their last commit date */ export async function getFileCommitDates(rootPath: string): Promise> { - const fileDates = new Map(); + const fileDates = new Map(); - try { - // Single git command to get all file dates - // Format: ":::ISO_DATE" followed by affected files on new lines - const { stdout } = await execAsync( - 'git log --format=":::%cd" --name-only --date=iso-strict', - { - cwd: rootPath, - maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large repos - } - ); + try { + // Single git command to get all file dates + // Format: ":::ISO_DATE" followed by affected files on new lines + const { stdout } = await execAsync('git log --format=":::%cd" --name-only --date=iso-strict', { + cwd: rootPath, + maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large repos + }); - let currentDate: Date | null = null; + let currentDate: Date | null = null; - for (const line of stdout.split('\n')) { - const trimmed = line.trim(); + for (const line of stdout.split('\n')) { + const trimmed = line.trim(); - if (!trimmed) continue; + if (!trimmed) continue; - if (trimmed.startsWith(':::')) { - // New commit date marker - const dateStr = trimmed.slice(3); - currentDate = new Date(dateStr); - } else if (currentDate) { - // File path - only store if we don't already have a date (first occurrence = most recent) - const normalizedPath = trimmed.replace(/\\/g, '/'); - if (!fileDates.has(normalizedPath)) { - fileDates.set(normalizedPath, currentDate); - } - } + if (trimmed.startsWith(':::')) { + // New commit date marker + const dateStr = trimmed.slice(3); + currentDate = new Date(dateStr); + } else if (currentDate) { + // File path - only store if we don't already have a date (first occurrence = most recent) + const normalizedPath = trimmed.replace(/\\/g, '/'); + if (!fileDates.has(normalizedPath)) { + fileDates.set(normalizedPath, currentDate); } + } + } - console.error(`[git-dates] Loaded commit dates for ${fileDates.size} files`); - } catch (error) { - // Not a git repo or git not available - graceful fallback - const message = error instanceof Error ? error.message : String(error); - if (message.includes('not a git repository') || message.includes('ENOENT')) { - console.error('[git-dates] Not a git repository, skipping temporal analysis'); - } else { - console.error(`[git-dates] Failed to get git dates: ${message}`); - } + console.error(`[git-dates] Loaded commit dates for ${fileDates.size} files`); + } catch (error) { + // Not a git repo or git not available - graceful fallback + const message = error instanceof Error ? error.message : String(error); + if (message.includes('not a git repository') || message.includes('ENOENT')) { + console.error('[git-dates] Not a git repository, skipping temporal analysis'); + } else { + console.error(`[git-dates] Failed to get git dates: ${message}`); } + } - return fileDates; + return fileDates; } /** * Calculate pattern trend based on file date. - * + * * @param newestDate - The most recent file date using this pattern * @returns Trend classification */ -export function calculateTrend(newestDate: Date | undefined): 'Rising' | 'Declining' | 'Stable' | undefined { - if (!newestDate) return undefined; +export function calculateTrend( + newestDate: Date | undefined +): 'Rising' | 'Declining' | 'Stable' | undefined { + if (!newestDate) return undefined; - const now = new Date(); - const daysDiff = Math.floor((now.getTime() - newestDate.getTime()) / (1000 * 60 * 60 * 24)); + const now = new Date(); + const daysDiff = Math.floor((now.getTime() - newestDate.getTime()) / (1000 * 60 * 60 * 24)); - if (daysDiff <= 60) return 'Rising'; - if (daysDiff >= 180) return 'Declining'; - return 'Stable'; + if (daysDiff <= 60) return 'Rising'; + if (daysDiff >= 180) return 'Declining'; + return 'Stable'; } diff --git a/src/utils/language-detection.ts b/src/utils/language-detection.ts index ea54c31..4e69e28 100644 --- a/src/utils/language-detection.ts +++ b/src/utils/language-detection.ts @@ -53,31 +53,83 @@ const extensionToLanguage: Record = { '.c': 'c', '.cpp': 'cpp', '.h': 'c', - '.hpp': 'cpp', + '.hpp': 'cpp' }; // Binary file extensions to skip const binaryExtensions = new Set([ - '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.webp', '.bmp', - '.woff', '.woff2', '.ttf', '.eot', '.otf', - '.mp3', '.mp4', '.wav', '.avi', '.mov', '.webm', - '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', - '.zip', '.tar', '.gz', '.rar', '.7z', - '.exe', '.dll', '.so', '.dylib', - '.lock', '.map', + '.png', + '.jpg', + '.jpeg', + '.gif', + '.ico', + '.svg', + '.webp', + '.bmp', + '.woff', + '.woff2', + '.ttf', + '.eot', + '.otf', + '.mp3', + '.mp4', + '.wav', + '.avi', + '.mov', + '.webm', + '.pdf', + '.doc', + '.docx', + '.xls', + '.xlsx', + '.ppt', + '.pptx', + '.zip', + '.tar', + '.gz', + '.rar', + '.7z', + '.exe', + '.dll', + '.so', + '.dylib', + '.lock', + '.map' ]); // Code file extensions const codeExtensions = new Set([ - '.js', '.mjs', '.cjs', '.jsx', - '.ts', '.tsx', '.mts', '.cts', - '.html', '.htm', - '.css', '.scss', '.sass', '.less', - '.json', '.jsonc', - '.yaml', '.yml', - '.md', '.mdx', - '.graphql', '.gql', - '.py', '.rb', '.java', '.go', '.rs', '.c', '.cpp', '.h', '.hpp', + '.js', + '.mjs', + '.cjs', + '.jsx', + '.ts', + '.tsx', + '.mts', + '.cts', + '.html', + '.htm', + '.css', + '.scss', + '.sass', + '.less', + '.json', + '.jsonc', + '.yaml', + '.yml', + '.md', + '.mdx', + '.graphql', + '.gql', + '.py', + '.rb', + '.java', + '.go', + '.rs', + '.c', + '.cpp', + '.h', + '.hpp' ]); /** diff --git a/src/utils/usage-tracker.ts b/src/utils/usage-tracker.ts index 295fb1a..06935e8 100644 --- a/src/utils/usage-tracker.ts +++ b/src/utils/usage-tracker.ts @@ -48,7 +48,6 @@ export interface ComponentUsageInfo { usageCount: number; } - export class ImportGraph { // Map: importSource -> files that import it private usages: Map = new Map(); @@ -64,7 +63,7 @@ export class ImportGraph { const relPath = this.toRelativePath(importingFile); // Avoid duplicates - if (!existing.some(u => u.file === relPath && u.line === line)) { + if (!existing.some((u) => u.file === relPath && u.line === line)) { existing.push({ file: relPath, line }); this.usages.set(normalized, existing); } @@ -101,7 +100,7 @@ export class ImportGraph { return { usedIn: usages, - usageCount: usages.length, + usageCount: usages.length }; } @@ -114,7 +113,7 @@ export class ImportGraph { for (const [source, usages] of this.usages.entries()) { result[source] = { usedIn: usages.slice(0, 10), // Top 10 usages - usageCount: usages.length, + usageCount: usages.length }; } @@ -128,7 +127,7 @@ export class ImportGraph { return Array.from(this.usages.entries()) .map(([source, usages]) => ({ source, - count: usages.length, + count: usages.length })) .sort((a, b) => b.count - a.count) .slice(0, n); @@ -162,14 +161,13 @@ export class LibraryUsageTracker { return source; } - getStats(): LibraryUsageStats { const stats: LibraryUsageStats = {}; for (const [lib, data] of this.usage.entries()) { stats[lib] = { count: data.count, - examples: Array.from(data.examples).slice(0, 3), + examples: Array.from(data.examples).slice(0, 3) }; } @@ -206,23 +204,53 @@ export interface GoldenFile { const DEFAULT_TEST_FRAMEWORK_CONFIGS: TestFrameworkConfig[] = [ // E2E - { name: 'Playwright', type: 'e2e', indicators: ['@playwright/test', 'page.goto(', 'page.locator('], priority: 100 }, - { name: 'Cypress', type: 'e2e', indicators: ['cy.visit(', 'cy.get(', 'cy.request(', 'cy.window('], priority: 100 }, - { name: 'Puppeteer', type: 'e2e', indicators: ['puppeteer.launch(', 'page.goto(', 'page.locator('], priority: 100 }, + { + name: 'Playwright', + type: 'e2e', + indicators: ['@playwright/test', 'page.goto(', 'page.locator('], + priority: 100 + }, + { + name: 'Cypress', + type: 'e2e', + indicators: ['cy.visit(', 'cy.get(', 'cy.request(', 'cy.window('], + priority: 100 + }, + { + name: 'Puppeteer', + type: 'e2e', + indicators: ['puppeteer.launch(', 'page.goto(', 'page.locator('], + priority: 100 + }, // Unit - specific patterns - { name: 'Jest', type: 'unit', indicators: ['jest.mock(', 'jest.fn(', 'jest.spyOn(', '@jest/globals', 'types/jest'], priority: 100 }, + { + name: 'Jest', + type: 'unit', + indicators: ['jest.mock(', 'jest.fn(', 'jest.spyOn(', '@jest/globals', 'types/jest'], + priority: 100 + }, { name: 'Vitest', type: 'unit', indicators: ['vi.mock(', 'vi.fn(', '@vitest'], priority: 100 }, - { name: 'Jasmine', type: 'unit', indicators: ['jasmine.createSpy', 'jasmine.createSpyObj'], priority: 100 }, + { + name: 'Jasmine', + type: 'unit', + indicators: ['jasmine.createSpy', 'jasmine.createSpyObj'], + priority: 100 + }, // Angular TestBed - { name: 'Angular TestBed', type: 'unit', indicators: ['TestBed.configureTestingModule'], priority: 50 }, + { + name: 'Angular TestBed', + type: 'unit', + indicators: ['TestBed.configureTestingModule'], + priority: 50 + }, // Generic fallback - { name: 'Generic Test', type: 'unit', indicators: ['describe(', 'it(', 'expect('], priority: 10 }, + { name: 'Generic Test', type: 'unit', indicators: ['describe(', 'it(', 'expect('], priority: 10 } ]; -import { calculateTrend } from "./git-dates.js"; +import { calculateTrend } from './git-dates.js'; export class PatternDetector { private patterns: Map> = new Map(); @@ -235,7 +263,12 @@ export class PatternDetector { this.testFrameworkConfigs = customConfigs || DEFAULT_TEST_FRAMEWORK_CONFIGS; } - track(category: string, patternName: string, example?: { file: string; snippet: string }, fileDate?: Date): void { + track( + category: string, + patternName: string, + example?: { file: string; snippet: string }, + fileDate?: Date + ): void { if (!this.patterns.has(category)) { this.patterns.set(category, new Map()); } @@ -290,7 +323,7 @@ export class PatternDetector { */ trackGoldenFile(file: string, score: number, patterns: GoldenFile['patterns']): void { // Check if already tracked - const existing = this.goldenFiles.find(gf => gf.file === file); + const existing = this.goldenFiles.find((gf) => gf.file === file); if (existing) { if (score > existing.score) { existing.score = score; @@ -305,15 +338,13 @@ export class PatternDetector { * Get top N Golden Files - files that best demonstrate all modern patterns together */ getGoldenFiles(n: number = 5): GoldenFile[] { - return this.goldenFiles - .sort((a, b) => b.score - a.score) - .slice(0, n); + return this.goldenFiles.sort((a, b) => b.score - a.score).slice(0, n); } /** * Generate actionable guidance from percentage + trend. * This is what AI agents can directly consume. - * + * * Examples: * - "USE: inject() – 97% adoption, stable" * - "CAUTION: constructor DI – 3%, declining" @@ -341,11 +372,12 @@ export class PatternDetector { // Primary pattern with high adoption if (!isAlternative && percentage >= 80) { // If primary is declining, downgrade to PREFER - if (trend === 'Declining') return `PREFER: ${patternName} – ${percentage}% adoption, declining`; + if (trend === 'Declining') + return `PREFER: ${patternName} – ${percentage}% adoption, declining`; return `USE: ${patternName} – ${percentage}% adoption${trendLabel}`; } - // Primary pattern with moderate adoption + // Primary pattern with moderate adoption if (!isAlternative && percentage >= 50) { return `PREFER: ${patternName} – ${percentage}% adoption${trendLabel}`; } @@ -365,7 +397,6 @@ export class PatternDetector { return `${patternName} – ${percentage}%${trendLabel}`; } - /** * Get robust date for a pattern (P90 percentile) to avoid "single file edit" skew * This means we take the date of the 10th percentile newest file. @@ -385,7 +416,7 @@ export class PatternDetector { // Use 90th percentile (exclude top 10% outliers) // For 100 files, P90 is index 10 (11th newest file) // This allows ~10% of legacy files to be edited without resetting the trend - const p90Index = Math.floor(dates.length * 0.10); + const p90Index = Math.floor(dates.length * 0.1); return new Date(dates[p90Index]); } @@ -426,7 +457,13 @@ export class PatternDetector { } // Generate actionable guidance from percentage + trend, now aware of rising alternatives - const primaryGuidance = this.generateGuidance(primaryName, primaryFreq, primaryTrend, false, hasRisingAlternative); + const primaryGuidance = this.generateGuidance( + primaryName, + primaryFreq, + primaryTrend, + false, + hasRisingAlternative + ); const result: PatternUsageStats[string] = { primary: { @@ -437,25 +474,24 @@ export class PatternDetector { canonicalExample: canonicalExample, newestFileDate: primaryDate?.toISOString(), trend: primaryTrend, - guidance: primaryGuidance, - }, + guidance: primaryGuidance + } }; if (alternatives.length > 0) { - result.alsoDetected = alternatives.map(alt => ({ + result.alsoDetected = alternatives.map((alt) => ({ name: alt.name, count: alt.count, frequency: `${alt.frequency}%`, newestFileDate: alt.date?.toISOString(), trend: alt.trend, - guidance: this.generateGuidance(alt.name, alt.frequency, alt.trend, true), + guidance: this.generateGuidance(alt.name, alt.frequency, alt.trend, true) })); } return result; } - getAllPatterns(): PatternUsageStats { const stats: PatternUsageStats = {}; @@ -473,11 +509,11 @@ export class PatternDetector { * Detect test framework from content using config-driven matching * Returns detected framework with confidence based on priority scoring */ - private detectTestFramework(content: string, filePath: string): { unit?: string; e2e?: string } { + private detectTestFramework(content: string, _filePath: string): { unit?: string; e2e?: string } { const results: { type: 'unit' | 'e2e'; name: string; priority: number }[] = []; for (const config of this.testFrameworkConfigs) { - const matched = config.indicators.some(indicator => content.includes(indicator)); + const matched = config.indicators.some((indicator) => content.includes(indicator)); if (matched) { results.push({ type: config.type, name: config.name, priority: config.priority }); } @@ -486,8 +522,12 @@ export class PatternDetector { if (results.length === 0) return {}; // Find highest priority match for each type - const unitMatches = results.filter(r => r.type === 'unit').sort((a, b) => b.priority - a.priority); - const e2eMatches = results.filter(r => r.type === 'e2e').sort((a, b) => b.priority - a.priority); + const unitMatches = results + .filter((r) => r.type === 'unit') + .sort((a, b) => b.priority - a.priority); + const e2eMatches = results + .filter((r) => r.type === 'e2e') + .sort((a, b) => b.priority - a.priority); const detected: { unit?: string; e2e?: string } = {}; @@ -572,13 +612,12 @@ export class PatternDetector { } } - /** * InternalFileGraph - Tracks file-to-file import relationships for internal files only. * Used for: * 1. Circular dependency detection (toxic coupling) * 2. Unused export detection (dead code identification) - * + * * Unlike ImportGraph which tracks external package usage, this tracks the internal * dependency graph between project files. */ @@ -674,7 +713,7 @@ export class InternalFileGraph { /** * Find all circular dependencies in the graph using DFS with recursion stack. * Returns unique cycles (avoids duplicates like A->B->A and B->A->B). - * + * * @param scope Optional path prefix to limit analysis (e.g., 'src/features') */ findCycles(scope?: string): CyclePath[] { @@ -686,7 +725,7 @@ export class InternalFileGraph { // Get all files to check let filesToCheck = Array.from(this.imports.keys()); if (scope) { - filesToCheck = filesToCheck.filter(f => f.startsWith(scope)); + filesToCheck = filesToCheck.filter((f) => f.startsWith(scope)); } const dfs = (node: string, path: string[]): void => { @@ -714,7 +753,7 @@ export class InternalFileGraph { cycleSignatures.add(signature); cycles.push({ files: cyclePath, - length: cyclePath.length - 1, // -1 because last element = first element + length: cyclePath.length - 1 // -1 because last element = first element }); } } @@ -740,7 +779,7 @@ export class InternalFileGraph { /** * Find exports that are never imported anywhere in the codebase. * These may indicate dead code or forgotten APIs. - * + * * @param scope Optional path prefix to limit analysis */ findUnusedExports(scope?: string): UnusedExport[] { @@ -790,7 +829,7 @@ export class InternalFileGraph { return { files, edges, - avgDependencies: files > 0 ? Math.round((edges / files) * 10) / 10 : 0, + avgDependencies: files > 0 ? Math.round((edges / files) * 10) / 10 : 0 }; } @@ -818,10 +857,13 @@ export class InternalFileGraph { /** * Restore from JSON (for loading from .codebase-intelligence.json) */ - static fromJSON(data: { - imports?: Record; - exports?: Record; - }, rootPath: string): InternalFileGraph { + static fromJSON( + data: { + imports?: Record; + exports?: Record; + }, + rootPath: string + ): InternalFileGraph { const graph = new InternalFileGraph(rootPath); if (data.imports) { @@ -839,4 +881,3 @@ export class InternalFileGraph { return graph; } } - diff --git a/src/utils/workspace-detection.ts b/src/utils/workspace-detection.ts index 09eecc5..aa5ffc1 100644 --- a/src/utils/workspace-detection.ts +++ b/src/utils/workspace-detection.ts @@ -1,6 +1,6 @@ /** * Workspace Detection Utilities - * + * * Scans monorepo workspace structures and detects ecosystem configuration. * Supports Nx, Turborepo, Lerna, pnpm, and npm workspaces. */ @@ -10,11 +10,11 @@ import path from 'path'; import { glob } from 'glob'; export interface WorkspacePackageJson { - filePath: string; - name?: string; - dependencies?: Record; - devDependencies?: Record; - peerDependencies?: Record; + filePath: string; + name?: string; + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; } export type WorkspaceType = 'nx' | 'turborepo' | 'lerna' | 'pnpm' | 'npm' | 'single'; @@ -24,102 +24,105 @@ export type WorkspaceType = 'nx' | 'turborepo' | 'lerna' | 'pnpm' | 'npm' | 'sin * Handles Nx, Turborepo, Lerna, and pnpm workspace structures. */ export async function scanWorkspacePackageJsons(rootPath: string): Promise { - const patterns = [ - 'package.json', - 'apps/*/package.json', - 'packages/*/package.json', - 'libs/*/package.json', // Nx convention - ]; + const patterns = [ + 'package.json', + 'apps/*/package.json', + 'packages/*/package.json', + 'libs/*/package.json' // Nx convention + ]; - const matches = await glob(patterns, { - cwd: rootPath, - absolute: true, - ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**'], - nodir: true, - }); + const matches = await glob(patterns, { + cwd: rootPath, + absolute: true, + ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**'], + nodir: true + }); - const results: WorkspacePackageJson[] = []; - for (const filePath of [...new Set(matches)]) { - try { - const content = await fs.readFile(filePath, 'utf-8'); - const pkg = JSON.parse(content); - results.push({ - filePath, - name: pkg.name, - dependencies: pkg.dependencies, - devDependencies: pkg.devDependencies, - peerDependencies: pkg.peerDependencies, - }); - } catch { - // skip - } + const results: WorkspacePackageJson[] = []; + for (const filePath of [...new Set(matches)]) { + try { + const content = await fs.readFile(filePath, 'utf-8'); + const pkg = JSON.parse(content); + results.push({ + filePath, + name: pkg.name, + dependencies: pkg.dependencies, + devDependencies: pkg.devDependencies, + peerDependencies: pkg.peerDependencies + }); + } catch { + // skip } - return results; + } + return results; } /** * Detect the workspace/monorepo type based on config files and dependencies. */ export async function detectWorkspaceType(rootPath: string): Promise { - // Check for config files - const checks = [ - { file: 'nx.json', type: 'nx' as WorkspaceType }, - { file: 'turbo.json', type: 'turborepo' as WorkspaceType }, - { file: 'lerna.json', type: 'lerna' as WorkspaceType }, - { file: 'pnpm-workspace.yaml', type: 'pnpm' as WorkspaceType }, - ]; - - for (const { file, type } of checks) { - try { - await fs.access(path.join(rootPath, file)); - return type; - } catch { - // not found - } - } + // Check for config files + const checks = [ + { file: 'nx.json', type: 'nx' as WorkspaceType }, + { file: 'turbo.json', type: 'turborepo' as WorkspaceType }, + { file: 'lerna.json', type: 'lerna' as WorkspaceType }, + { file: 'pnpm-workspace.yaml', type: 'pnpm' as WorkspaceType } + ]; - // Check for Nx via dependencies + for (const { file, type } of checks) { try { - const pkgPath = path.join(rootPath, 'package.json'); - const content = await fs.readFile(pkgPath, 'utf-8'); - const pkg = JSON.parse(content); - const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; - - if (allDeps['nx'] || Object.keys(allDeps).some(d => d.startsWith('@nx/') || d.startsWith('@nrwl/'))) { - return 'nx'; - } + await fs.access(path.join(rootPath, file)); + return type; } catch { - // no package.json + // not found } + } + + // Check for Nx via dependencies + try { + const pkgPath = path.join(rootPath, 'package.json'); + const content = await fs.readFile(pkgPath, 'utf-8'); + const pkg = JSON.parse(content); + const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; - // Check if there are multiple package.json files (npm workspaces) - const packages = await scanWorkspacePackageJsons(rootPath); - if (packages.length > 1) { - return 'npm'; + if ( + allDeps['nx'] || + Object.keys(allDeps).some((d) => d.startsWith('@nx/') || d.startsWith('@nrwl/')) + ) { + return 'nx'; } + } catch { + // no package.json + } - return 'single'; + // Check if there are multiple package.json files (npm workspaces) + const packages = await scanWorkspacePackageJsons(rootPath); + if (packages.length > 1) { + return 'npm'; + } + + return 'single'; } /** * Aggregate dependencies from all workspace packages. */ export function aggregateWorkspaceDependencies( - packages: WorkspacePackageJson[] + packages: WorkspacePackageJson[] ): Record { - const allDeps: Record = {}; - for (const pkg of packages) { - Object.assign(allDeps, pkg.dependencies || {}); - Object.assign(allDeps, pkg.devDependencies || {}); - Object.assign(allDeps, pkg.peerDependencies || {}); - } - return allDeps; + const allDeps: Record = {}; + for (const pkg of packages) { + Object.assign(allDeps, pkg.dependencies || {}); + Object.assign(allDeps, pkg.devDependencies || {}); + Object.assign(allDeps, pkg.peerDependencies || {}); + } + return allDeps; } /** * Normalize package version by stripping ^ and ~ prefixes. */ export function normalizePackageVersion(version: string | undefined): string | undefined { - if (!version) return undefined; - return version.replace(/^[~^]/, ''); + if (!version) return undefined; + return version.replace(/^[~^]/, ''); }