codexa 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
  <h1>
3
- <img src="https://github.com/sahitya-chandra/codexa/blob/main/.github/assets/logo.png" alt="Codexa Logo" width="90" align="absmiddle"> Codexa
3
+ Codexa
4
4
  </h1>
5
5
 
6
6
  <p>
@@ -54,6 +54,8 @@
54
54
  - 🔄 **Session Management**: Maintain conversation context across queries
55
55
  - 📊 **Streaming Output**: Real-time response streaming for better UX
56
56
  - 🎨 **Multiple File Types**: Supports TypeScript, JavaScript, Python, Go, Rust, Java, and more
57
+ - 🧠 **Smart Configuration**: Automatically detects project languages and optimizes config
58
+ - 🛡️ **Intelligent Filtering**: Automatically excludes binaries, large files, and build artifacts
57
59
  - ⚙️ **Highly Configurable**: Fine-tune chunking, retrieval, and model parameters
58
60
  - 🚀 **Zero Setup**: Works out of the box with sensible defaults
59
61
 
@@ -239,17 +241,41 @@ Once Codexa is installed and your LLM is configured, you're ready to use it:
239
241
 
240
242
  ### `init`
241
243
 
242
- Creates a `.codexarc.json` configuration file in the current directory with default settings.
244
+ Creates a `.codexarc.json` configuration file optimized for your codebase.
243
245
 
244
246
  ```bash
245
247
  codexa init
246
248
  ```
247
249
 
248
250
  **What it does:**
249
- - Creates `.codexarc.json` in the project root
250
- - Uses sensible defaults for all configuration options
251
+ - **Analyzes your codebase** to detect languages, package managers, and frameworks
252
+ - **Creates optimized config** with language-specific include/exclude patterns
253
+ - **Generates `.codexarc.json`** in the project root with tailored settings
251
254
  - Can be safely run multiple times (won't overwrite existing config)
252
255
 
256
+ **Detection Capabilities:**
257
+ - **Languages**: TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, Scala, C/C++, Ruby, PHP, Swift, Dart, and more
258
+ - **Package Managers**: npm, yarn, pnpm, pip, poetry, go, cargo, maven, gradle, sbt, bundler, composer, and more
259
+ - **Frameworks**: Next.js, React, Django, Flask, Rails, Laravel, Spring, Flutter, and more
260
+
261
+ **Example Output:**
262
+ ```
263
+ Analyzing codebase...
264
+ ✓ Detected: typescript, javascript (npm, yarn)
265
+
266
+ ✓ Created .codexarc.json with optimized settings for your codebase!
267
+
268
+ ┌ 🚀 Setup Complete ──────────────────────────────────────────┐
269
+ │ │
270
+ │ Next Steps: │
271
+ │ │
272
+ │ 1. Review .codexarc.json - Update provider keys if needed │
273
+ │ 2. Run: codexa ingest - Start indexing your codebase │
274
+ │ 3. Run: codexa ask "your question" - Ask questions │
275
+ │ │
276
+ └─────────────────────────────────────────────────────────────┘
277
+ ```
278
+
253
279
  ---
254
280
 
255
281
  ### `ingest`
@@ -274,9 +300,15 @@ codexa ingest --force
274
300
 
275
301
  **What it does:**
276
302
  1. Scans your repository based on `includeGlobs` and `excludeGlobs` patterns
277
- 2. Chunks files into manageable segments
278
- 3. Generates vector embeddings for each chunk
279
- 4. Stores everything in `.codexa/index.db` (SQLite database)
303
+ 2. **Filters files** - Automatically excludes binaries, large files (>5MB), and build artifacts
304
+ 3. Chunks files into manageable segments
305
+ 4. Generates vector embeddings for each chunk
306
+ 5. Stores everything in `.codexa/index.db` (SQLite database)
307
+
308
+ **Smart Filtering:**
309
+ - Automatically skips binary files (executables, images, archives, etc.)
310
+ - Excludes files larger than the configured size limit (default: 5MB)
311
+ - Filters based on file content analysis (not just extensions)
280
312
 
281
313
  **Note:** First ingestion may take a few minutes depending on your codebase size. Subsequent ingestions are faster as they only process changed files.
282
314
 
@@ -332,6 +364,28 @@ Codexa uses a `.codexarc.json` file in your project root for configuration. This
332
364
 
333
365
  **Format:** JSON
334
366
 
367
+ ### Dynamic Configuration Generation
368
+
369
+ When you run `codexa init`, Codexa automatically:
370
+
371
+ 1. **Analyzes your codebase** structure to detect:
372
+ - Languages present (by file extensions)
373
+ - Package managers used (by config files)
374
+ - Frameworks detected (by dependencies and config files)
375
+
376
+ 2. **Generates optimized patterns**:
377
+ - **Include patterns**: Only file extensions relevant to detected languages
378
+ - **Exclude patterns**: Language-specific build artifacts, dependency directories, and cache folders
379
+ - **Smart defaults**: Based on your project type
380
+
381
+ 3. **Applies best practices**:
382
+ - Excludes common build outputs (`dist/`, `build/`, `target/`, etc.)
383
+ - Excludes dependency directories (`node_modules/`, `vendor/`, `.venv/`, etc.)
384
+ - Includes important config files and documentation
385
+ - Filters binaries and large files automatically
386
+
387
+ This means your config is tailored to your project from the start, ensuring optimal indexing performance!
388
+
335
389
  ### Environment Variables
336
390
 
337
391
  Some settings can be configured via environment variables:
@@ -468,6 +522,49 @@ Controls randomness in LLM responses (0.0 = deterministic, 1.0 = creative).
468
522
 
469
523
  Number of code chunks to retrieve and use as context for each question. Higher values provide more context but may include less relevant information.
470
524
 
525
+ #### `maxFileSize`
526
+
527
+ **Type:** `number`
528
+ **Default:** `5242880` (5MB)
529
+
530
+ Maximum file size in bytes. Files larger than this will be excluded from indexing. Helps avoid processing large binary files or generated artifacts.
531
+
532
+ **Example:**
533
+ ```json
534
+ {
535
+ "maxFileSize": 10485760 // 10MB
536
+ }
537
+ ```
538
+
539
+ #### `skipBinaryFiles`
540
+
541
+ **Type:** `boolean`
542
+ **Default:** `true`
543
+
544
+ Whether to automatically skip binary files during indexing. Binary detection uses both file extension and content analysis.
545
+
546
+ **Example:**
547
+ ```json
548
+ {
549
+ "skipBinaryFiles": true
550
+ }
551
+ ```
552
+
553
+ #### `skipLargeFiles`
554
+
555
+ **Type:** `boolean`
556
+ **Default:** `true`
557
+
558
+ Whether to skip files exceeding `maxFileSize` during indexing. Set to `false` if you want to include all files regardless of size.
559
+
560
+ **Example:**
561
+ ```json
562
+ {
563
+ "skipLargeFiles": true,
564
+ "maxFileSize": 10485760 // 10MB
565
+ }
566
+ ```
567
+
471
568
  ### Example Configurations
472
569
 
473
570
  #### Groq Cloud Provider (Default)
@@ -666,10 +763,13 @@ When you run `codexa ask`:
666
763
  **Problem:** First ingestion takes too long.
667
764
 
668
765
  **Solutions:**
669
- 1. Reduce `maxChunkSize` to create more, smaller chunks
670
- 2. Add more patterns to `excludeGlobs` to skip unnecessary files
671
- 3. Be more specific with `includeGlobs` to focus on important files
672
- 4. Use `--force` only when necessary (incremental updates are faster)
766
+ 1. The dynamic config should already optimize patterns - check your `.codexarc.json` was generated correctly
767
+ 2. Reduce `maxFileSize` to exclude more large files
768
+ 3. Reduce `maxChunkSize` to create more, smaller chunks
769
+ 4. Add more patterns to `excludeGlobs` to skip unnecessary files
770
+ 5. Be more specific with `includeGlobs` to focus on important files
771
+ 6. Use `--force` only when necessary (incremental updates are faster)
772
+ 7. Ensure `skipBinaryFiles` and `skipLargeFiles` are enabled (default)
673
773
 
674
774
  ### Poor Quality Answers
675
775
 
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
8
  const ora_1 = __importDefault(require("ora"));
9
+ const chalk_1 = __importDefault(require("chalk"));
9
10
  const config_1 = require("./config");
10
11
  const ingest_1 = require("./ingest");
11
12
  const agent_1 = require("./agent");
@@ -22,14 +23,35 @@ const program = new commander_1.Command();
22
23
  program
23
24
  .name('codexa')
24
25
  .description('Ask questions about any local repository from the command line.')
25
- .version('0.1.0');
26
+ .version('1.2.0')
27
+ .action(() => {
28
+ console.log('\n');
29
+ logger_1.log.box(`${chalk_1.default.bold('Welcome to Codexa!')}\n\n` +
30
+ `${chalk_1.default.dim('Codexa is a CLI tool that helps you understand your codebase using AI.')}\n\n` +
31
+ `${chalk_1.default.bold('Getting Started:')}\n\n` +
32
+ `${chalk_1.default.dim('1.')} ${chalk_1.default.white('Initialize Codexa in your project:')}\n` +
33
+ ` ${chalk_1.default.cyan('codexa init')}\n\n` +
34
+ `${chalk_1.default.dim('2.')} ${chalk_1.default.white('Index your codebase:')}\n` +
35
+ ` ${chalk_1.default.cyan('codexa ingest')}\n\n` +
36
+ `${chalk_1.default.dim('3.')} ${chalk_1.default.white('Ask questions:')}\n` +
37
+ ` ${chalk_1.default.cyan('codexa ask "your question"')}\n\n` +
38
+ `${chalk_1.default.dim('For more help, run:')} ${chalk_1.default.cyan('codexa --help')}`, '🚀 Codexa');
39
+ console.log('\n');
40
+ });
26
41
  program
27
42
  .command('init')
28
43
  .description('Create a local .codexarc.json with sensible defaults.')
29
44
  .action(async () => {
30
45
  const cwd = process.cwd();
31
46
  await (0, config_1.ensureConfig)(cwd);
32
- logger_1.log.success('Created .codexarc.json. Update it with your provider keys if needed.');
47
+ console.log('\n');
48
+ logger_1.log.success('Created .codexarc.json with optimized settings for your codebase!');
49
+ console.log('\n');
50
+ logger_1.log.box(`${chalk_1.default.bold('Next Steps:')}\n\n` +
51
+ `${chalk_1.default.dim('1.')} ${chalk_1.default.white('Review .codexarc.json')} - Update provider keys if needed\n` +
52
+ `${chalk_1.default.dim('2.')} ${chalk_1.default.white('Run:')} ${chalk_1.default.cyan('codexa ingest')} ${chalk_1.default.dim('- Start indexing your codebase')}\n` +
53
+ `${chalk_1.default.dim('3.')} ${chalk_1.default.white('Run:')} ${chalk_1.default.cyan('codexa ask "your question"')} ${chalk_1.default.dim('- Ask questions about your code')}`, '🚀 Setup Complete');
54
+ console.log('\n');
33
55
  });
34
56
  program
35
57
  .command('ingest')
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.analyzeProject = analyzeProject;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const globby_1 = require("globby");
10
+ const LANGUAGE_DEFINITIONS = [
11
+ {
12
+ name: 'typescript',
13
+ extensions: ['.ts', '.tsx', '.d.ts'],
14
+ packageManagers: ['npm', 'yarn', 'pnpm'],
15
+ frameworks: ['nextjs', 'react', 'angular', 'vue', 'nest', 'express'],
16
+ configFiles: ['tsconfig.json', 'next.config.js', 'next.config.ts'],
17
+ },
18
+ {
19
+ name: 'javascript',
20
+ extensions: ['.js', '.jsx', '.mjs', '.cjs'],
21
+ packageManagers: ['npm', 'yarn', 'pnpm'],
22
+ frameworks: ['react', 'vue', 'express', 'nextjs'],
23
+ configFiles: ['package.json', 'webpack.config.js', 'vite.config.js'],
24
+ },
25
+ {
26
+ name: 'python',
27
+ extensions: ['.py', '.pyw', '.pyi'],
28
+ packageManagers: ['pip', 'poetry', 'pipenv', 'conda'],
29
+ frameworks: ['django', 'flask', 'fastapi', 'pytest'],
30
+ configFiles: [
31
+ 'requirements.txt',
32
+ 'setup.py',
33
+ 'pyproject.toml',
34
+ 'Pipfile',
35
+ 'poetry.lock',
36
+ 'manage.py',
37
+ ],
38
+ },
39
+ {
40
+ name: 'go',
41
+ extensions: ['.go'],
42
+ packageManagers: ['go'],
43
+ frameworks: ['gin', 'echo', 'fiber'],
44
+ configFiles: ['go.mod', 'go.sum', 'Gopkg.toml'],
45
+ },
46
+ {
47
+ name: 'rust',
48
+ extensions: ['.rs'],
49
+ packageManagers: ['cargo'],
50
+ frameworks: ['actix', 'rocket', 'axum'],
51
+ configFiles: ['Cargo.toml', 'Cargo.lock'],
52
+ },
53
+ {
54
+ name: 'java',
55
+ extensions: ['.java', '.kt', '.scala'],
56
+ packageManagers: ['maven', 'gradle', 'sbt'],
57
+ frameworks: ['spring', 'javafx', 'android'],
58
+ configFiles: ['pom.xml', 'build.gradle', 'build.sbt', 'settings.gradle'],
59
+ },
60
+ {
61
+ name: 'cpp',
62
+ extensions: ['.cpp', '.cxx', '.cc', '.c++', '.hpp', '.hxx', '.h++'],
63
+ packageManagers: ['cmake', 'conan'],
64
+ frameworks: ['qt', 'boost'],
65
+ configFiles: ['CMakeLists.txt', 'Makefile', 'conanfile.txt'],
66
+ },
67
+ {
68
+ name: 'c',
69
+ extensions: ['.c', '.h'],
70
+ packageManagers: ['cmake', 'make'],
71
+ configFiles: ['CMakeLists.txt', 'Makefile', 'configure'],
72
+ },
73
+ {
74
+ name: 'ruby',
75
+ extensions: ['.rb', '.rake'],
76
+ packageManagers: ['bundler', 'gem'],
77
+ frameworks: ['rails', 'sinatra'],
78
+ configFiles: ['Gemfile', 'Gemfile.lock', 'Rakefile'],
79
+ },
80
+ {
81
+ name: 'php',
82
+ extensions: ['.php', '.phtml'],
83
+ packageManagers: ['composer'],
84
+ frameworks: ['laravel', 'symfony', 'wordpress'],
85
+ configFiles: ['composer.json', 'composer.lock'],
86
+ },
87
+ {
88
+ name: 'swift',
89
+ extensions: ['.swift'],
90
+ packageManagers: ['swiftpm', 'cocoapods'],
91
+ frameworks: ['swiftui', 'uikit'],
92
+ configFiles: ['Package.swift', 'Podfile'],
93
+ },
94
+ {
95
+ name: 'kotlin',
96
+ extensions: ['.kt', '.kts'],
97
+ packageManagers: ['gradle', 'maven'],
98
+ frameworks: ['android', 'spring'],
99
+ configFiles: ['build.gradle.kts', 'pom.xml'],
100
+ },
101
+ {
102
+ name: 'scala',
103
+ extensions: ['.scala', '.sc'],
104
+ packageManagers: ['sbt', 'maven'],
105
+ frameworks: ['play', 'akka'],
106
+ configFiles: ['build.sbt', 'pom.xml'],
107
+ },
108
+ {
109
+ name: 'dart',
110
+ extensions: ['.dart'],
111
+ packageManagers: ['pub'],
112
+ frameworks: ['flutter'],
113
+ configFiles: ['pubspec.yaml', 'pubspec.lock'],
114
+ },
115
+ {
116
+ name: 'html',
117
+ extensions: ['.html', '.htm', '.xhtml'],
118
+ },
119
+ {
120
+ name: 'css',
121
+ extensions: ['.css', '.scss', '.sass', '.less', '.styl'],
122
+ },
123
+ {
124
+ name: 'sql',
125
+ extensions: ['.sql'],
126
+ },
127
+ {
128
+ name: 'shell',
129
+ extensions: ['.sh', '.bash', '.zsh', '.fish'],
130
+ },
131
+ {
132
+ name: 'yaml',
133
+ extensions: ['.yaml', '.yml'],
134
+ },
135
+ {
136
+ name: 'toml',
137
+ extensions: ['.toml'],
138
+ },
139
+ {
140
+ name: 'json',
141
+ extensions: ['.json'],
142
+ },
143
+ {
144
+ name: 'xml',
145
+ extensions: ['.xml'],
146
+ },
147
+ {
148
+ name: 'markdown',
149
+ extensions: ['.md', '.markdown', '.mdx'],
150
+ },
151
+ {
152
+ name: 'docker',
153
+ extensions: ['.dockerfile'],
154
+ configFiles: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'],
155
+ },
156
+ ];
157
+ const FRAMEWORK_INDICATORS = {
158
+ nextjs: ['next.config.js', 'next.config.ts', '.next'],
159
+ react: ['package.json'],
160
+ django: ['manage.py', 'settings.py', 'wsgi.py'],
161
+ flask: ['flask', 'app.py'], // Check for flask import
162
+ rails: ['Gemfile', 'config.ru', 'app/models'],
163
+ laravel: ['artisan', 'app/Http'],
164
+ spring: ['pom.xml', 'application.properties'],
165
+ android: ['AndroidManifest.xml', 'build.gradle'],
166
+ flutter: ['pubspec.yaml', 'lib/main.dart'],
167
+ };
168
+ /**
169
+ * Analyzes codebase to detect languages, package managers, and frameworks
170
+ */
171
+ async function analyzeProject(cwd) {
172
+ const languages = new Set();
173
+ const packageManagers = new Set();
174
+ const frameworks = new Set();
175
+ const detectedConfigFiles = [];
176
+ let hasLargeFiles = false;
177
+ try {
178
+ const allFiles = await (0, globby_1.globby)(['**/*'], {
179
+ cwd,
180
+ gitignore: true,
181
+ ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**', '.codexa/**'],
182
+ onlyFiles: true,
183
+ absolute: false,
184
+ });
185
+ const extensionMap = new Map();
186
+ const LARGE_FILE_THRESHOLD = 5 * 1024 * 1024;
187
+ for (const file of allFiles) {
188
+ const ext = node_path_1.default.extname(file).toLowerCase();
189
+ if (ext) {
190
+ extensionMap.set(ext, (extensionMap.get(ext) || 0) + 1);
191
+ }
192
+ // large file check
193
+ try {
194
+ const filePath = node_path_1.default.join(cwd, file);
195
+ const stats = await fs_extra_1.default.stat(filePath);
196
+ if (stats.size > LARGE_FILE_THRESHOLD) {
197
+ hasLargeFiles = true;
198
+ }
199
+ }
200
+ catch {
201
+ // Ignore errors (file might not exist, permission issues, etc.)
202
+ }
203
+ }
204
+ for (const lang of LANGUAGE_DEFINITIONS) {
205
+ const hasExtension = lang.extensions.some((ext) => extensionMap.has(ext));
206
+ if (hasExtension) {
207
+ languages.add(lang.name);
208
+ if (lang.packageManagers) {
209
+ lang.packageManagers.forEach((pm) => packageManagers.add(pm));
210
+ }
211
+ }
212
+ }
213
+ const configFileChecks = [
214
+ 'package.json', // npm/yarn/pnpm
215
+ 'package-lock.json', // npm
216
+ 'yarn.lock', // yarn
217
+ 'pnpm-lock.yaml', // pnpm
218
+ 'requirements.txt', // pip
219
+ 'pyproject.toml', // poetry
220
+ 'Pipfile', // pipenv
221
+ 'go.mod', // go
222
+ 'Cargo.toml', // cargo
223
+ 'pom.xml', // maven
224
+ 'build.gradle', // gradle
225
+ 'build.sbt', // sbt
226
+ 'Gemfile', // bundler
227
+ 'composer.json', // composer
228
+ 'pubspec.yaml', // pub (dart/flutter)
229
+ 'CMakeLists.txt', // cmake
230
+ 'Makefile', // make
231
+ 'Dockerfile', // docker
232
+ 'docker-compose.yml',
233
+ 'docker-compose.yaml',
234
+ ];
235
+ for (const configFile of configFileChecks) {
236
+ const configPath = node_path_1.default.join(cwd, configFile);
237
+ if (await fs_extra_1.default.pathExists(configPath)) {
238
+ detectedConfigFiles.push(configFile);
239
+ if (configFile === 'package.json' || configFile === 'package-lock.json') {
240
+ packageManagers.add('npm');
241
+ }
242
+ else if (configFile === 'yarn.lock') {
243
+ packageManagers.add('yarn');
244
+ }
245
+ else if (configFile === 'pnpm-lock.yaml') {
246
+ packageManagers.add('pnpm');
247
+ }
248
+ else if (configFile === 'requirements.txt' || configFile === 'setup.py') {
249
+ packageManagers.add('pip');
250
+ }
251
+ else if (configFile === 'pyproject.toml' || configFile === 'poetry.lock') {
252
+ packageManagers.add('poetry');
253
+ }
254
+ else if (configFile === 'Pipfile') {
255
+ packageManagers.add('pipenv');
256
+ }
257
+ else if (configFile === 'go.mod') {
258
+ packageManagers.add('go');
259
+ }
260
+ else if (configFile === 'Cargo.toml') {
261
+ packageManagers.add('cargo');
262
+ }
263
+ else if (configFile === 'pom.xml') {
264
+ packageManagers.add('maven');
265
+ }
266
+ else if (configFile === 'build.gradle' || configFile === 'settings.gradle') {
267
+ packageManagers.add('gradle');
268
+ }
269
+ else if (configFile === 'build.sbt') {
270
+ packageManagers.add('sbt');
271
+ }
272
+ else if (configFile === 'Gemfile') {
273
+ packageManagers.add('bundler');
274
+ }
275
+ else if (configFile === 'composer.json') {
276
+ packageManagers.add('composer');
277
+ }
278
+ else if (configFile === 'pubspec.yaml') {
279
+ packageManagers.add('pub');
280
+ }
281
+ else if (configFile === 'CMakeLists.txt') {
282
+ packageManagers.add('cmake');
283
+ }
284
+ }
285
+ }
286
+ for (const [framework, indicators] of Object.entries(FRAMEWORK_INDICATORS)) {
287
+ for (const indicator of indicators) {
288
+ const indicatorPath = node_path_1.default.join(cwd, indicator);
289
+ if (await fs_extra_1.default.pathExists(indicatorPath)) {
290
+ frameworks.add(framework);
291
+ break;
292
+ }
293
+ }
294
+ }
295
+ // Special case: Check package.json for framework dependencies
296
+ const packageJsonPath = node_path_1.default.join(cwd, 'package.json');
297
+ if (await fs_extra_1.default.pathExists(packageJsonPath)) {
298
+ try {
299
+ const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
300
+ const deps = {
301
+ ...packageJson.dependencies,
302
+ ...packageJson.devDependencies,
303
+ };
304
+ const depNames = Object.keys(deps).map((d) => d.toLowerCase());
305
+ if (depNames.some((d) => d.includes('react'))) {
306
+ frameworks.add('react');
307
+ }
308
+ if (depNames.some((d) => d.includes('next'))) {
309
+ frameworks.add('nextjs');
310
+ }
311
+ if (depNames.some((d) => d.includes('angular'))) {
312
+ frameworks.add('angular');
313
+ }
314
+ if (depNames.some((d) => d.includes('vue'))) {
315
+ frameworks.add('vue');
316
+ }
317
+ if (depNames.some((d) => d.includes('nestjs'))) {
318
+ frameworks.add('nest');
319
+ }
320
+ if (depNames.some((d) => d.includes('express'))) {
321
+ frameworks.add('express');
322
+ }
323
+ }
324
+ catch {
325
+ // Ignore JSON parse err
326
+ }
327
+ }
328
+ }
329
+ catch (error) {
330
+ console.warn(`Project analysis warning: ${error instanceof Error ? error.message : 'Unknown error'}`);
331
+ }
332
+ return {
333
+ languages: Array.from(languages).sort(),
334
+ packageManagers: Array.from(packageManagers).sort(),
335
+ frameworks: Array.from(frameworks).sort(),
336
+ hasLargeFiles,
337
+ detectedConfigFiles: detectedConfigFiles.sort(),
338
+ };
339
+ }