@codexa/cli 8.5.0 → 8.6.9

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.
@@ -1,424 +1,424 @@
1
- /**
2
- * Python Ecosystem Detector
3
- *
4
- * Detects Python projects including:
5
- * - Frameworks: Django, Flask, FastAPI, Starlette
6
- * - ORMs: SQLAlchemy, Django ORM, Tortoise, Peewee
7
- * - Testing: pytest, unittest, nose
8
- * - Package managers: pip, poetry, pipenv, uv
9
- */
10
-
11
- import { join } from "path";
12
- import {
13
- registerDetector,
14
- Detector,
15
- DetectorResult,
16
- DetectedTechnology,
17
- fileExists,
18
- dirExists,
19
- findFiles,
20
- readText,
21
- parseToml,
22
- } from "./index";
23
-
24
- interface Dependency {
25
- name: string;
26
- version?: string;
27
- }
28
-
29
- function parseRequirements(content: string): Dependency[] {
30
- const deps: Dependency[] = [];
31
- for (const line of content.split("\n")) {
32
- const trimmed = line.trim();
33
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
34
-
35
- // Parse formats: package==1.0.0, package>=1.0.0, package~=1.0.0, package
36
- const match = trimmed.match(/^([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:([<>=!~]+)(.+))?/);
37
- if (match) {
38
- deps.push({
39
- name: match[1].toLowerCase(),
40
- version: match[3],
41
- });
42
- }
43
- }
44
- return deps;
45
- }
46
-
47
- function parsePyproject(content: string): Dependency[] {
48
- const deps: Dependency[] = [];
49
-
50
- try {
51
- const toml = parseToml(content);
52
-
53
- // Poetry format
54
- const poetryDeps = toml?.tool?.poetry?.dependencies || {};
55
- const poetryDevDeps = toml?.tool?.poetry?.["dev-dependencies"] || {};
56
-
57
- for (const [name, value] of Object.entries({ ...poetryDeps, ...poetryDevDeps })) {
58
- if (name === "python") continue;
59
- deps.push({
60
- name: name.toLowerCase(),
61
- version: typeof value === "string" ? value : undefined,
62
- });
63
- }
64
-
65
- // PEP 621 format (project.dependencies)
66
- const projectDeps = toml?.project?.dependencies || [];
67
- const optionalDeps = Object.values(toml?.project?.["optional-dependencies"] || {}).flat();
68
-
69
- for (const dep of [...projectDeps, ...optionalDeps]) {
70
- if (typeof dep === "string") {
71
- const match = dep.match(/^([a-zA-Z0-9_-]+)/);
72
- if (match) {
73
- deps.push({ name: match[1].toLowerCase() });
74
- }
75
- }
76
- }
77
- } catch {
78
- // Fallback to regex parsing
79
- const depMatches = content.matchAll(/^\s*"?([a-zA-Z0-9_-]+)"?\s*[=<>]/gm);
80
- for (const match of depMatches) {
81
- deps.push({ name: match[1].toLowerCase() });
82
- }
83
- }
84
-
85
- return deps;
86
- }
87
-
88
- const pythonDetector: Detector = {
89
- name: "python",
90
- ecosystem: "python",
91
- priority: 85,
92
- markers: [
93
- { type: "file", pattern: "pyproject.toml", weight: 1.0 },
94
- { type: "file", pattern: "setup.py", weight: 0.9 },
95
- { type: "file", pattern: "setup.cfg", weight: 0.9 },
96
- { type: "file", pattern: "requirements.txt", weight: 0.8 },
97
- { type: "file", pattern: "Pipfile", weight: 0.9 },
98
- { type: "file", pattern: "poetry.lock", weight: 1.0 },
99
- { type: "file", pattern: "Pipfile.lock", weight: 0.9 },
100
- { type: "file", pattern: "uv.lock", weight: 1.0 },
101
- { type: "directory", pattern: "__pycache__", weight: 0.4 },
102
- { type: "directory", pattern: ".venv", weight: 0.5 },
103
- { type: "directory", pattern: "venv", weight: 0.5 },
104
- { type: "glob", pattern: "**/*.py", weight: 0.6 },
105
- ],
106
-
107
- async detect(cwd: string): Promise<DetectorResult | null> {
108
- const technologies: DetectedTechnology[] = [];
109
- const structure: Record<string, string> = {};
110
- const configFiles: string[] = [];
111
-
112
- // Check for Python files
113
- const pyFiles = findFiles(cwd, "**/*.py");
114
- if (pyFiles.length === 0) {
115
- // Check for config files even without .py files
116
- const hasPyproject = fileExists(join(cwd, "pyproject.toml"));
117
- const hasRequirements = fileExists(join(cwd, "requirements.txt"));
118
-
119
- if (!hasPyproject && !hasRequirements) {
120
- return null;
121
- }
122
- }
123
-
124
- // Collect all dependencies
125
- const allDeps = new Map<string, string | undefined>();
126
-
127
- // Parse requirements.txt
128
- if (fileExists(join(cwd, "requirements.txt"))) {
129
- configFiles.push("requirements.txt");
130
- const content = readText(join(cwd, "requirements.txt"));
131
- if (content) {
132
- for (const dep of parseRequirements(content)) {
133
- allDeps.set(dep.name, dep.version);
134
- }
135
- }
136
- }
137
-
138
- // Also check for dev requirements
139
- const devReqFiles = ["requirements-dev.txt", "requirements.dev.txt", "dev-requirements.txt"];
140
- for (const f of devReqFiles) {
141
- if (fileExists(join(cwd, f))) {
142
- configFiles.push(f);
143
- const content = readText(join(cwd, f));
144
- if (content) {
145
- for (const dep of parseRequirements(content)) {
146
- allDeps.set(dep.name, dep.version);
147
- }
148
- }
149
- }
150
- }
151
-
152
- // Parse pyproject.toml
153
- if (fileExists(join(cwd, "pyproject.toml"))) {
154
- configFiles.push("pyproject.toml");
155
- const content = readText(join(cwd, "pyproject.toml"));
156
- if (content) {
157
- for (const dep of parsePyproject(content)) {
158
- allDeps.set(dep.name, dep.version);
159
- }
160
-
161
- // Detect package manager from pyproject.toml
162
- if (content.includes("[tool.poetry]")) {
163
- technologies.push({
164
- name: "Poetry",
165
- confidence: 1.0,
166
- source: "pyproject.toml [tool.poetry]",
167
- category: "build",
168
- });
169
- } else if (content.includes("[tool.pdm]")) {
170
- technologies.push({
171
- name: "PDM",
172
- confidence: 1.0,
173
- source: "pyproject.toml [tool.pdm]",
174
- category: "build",
175
- });
176
- } else if (content.includes("[tool.hatch]")) {
177
- technologies.push({
178
- name: "Hatch",
179
- confidence: 1.0,
180
- source: "pyproject.toml [tool.hatch]",
181
- category: "build",
182
- });
183
- }
184
- }
185
- }
186
-
187
- // Package manager detection from lock files
188
- if (fileExists(join(cwd, "poetry.lock"))) {
189
- technologies.push({
190
- name: "Poetry",
191
- confidence: 1.0,
192
- source: "poetry.lock",
193
- category: "build",
194
- });
195
- configFiles.push("poetry.lock");
196
- } else if (fileExists(join(cwd, "Pipfile.lock"))) {
197
- technologies.push({
198
- name: "Pipenv",
199
- confidence: 1.0,
200
- source: "Pipfile.lock",
201
- category: "build",
202
- });
203
- configFiles.push("Pipfile.lock");
204
- } else if (fileExists(join(cwd, "uv.lock"))) {
205
- technologies.push({
206
- name: "uv",
207
- confidence: 1.0,
208
- source: "uv.lock",
209
- category: "build",
210
- });
211
- configFiles.push("uv.lock");
212
- }
213
-
214
- // Add Python runtime
215
- technologies.push({
216
- name: "Python",
217
- confidence: 1.0,
218
- source: "Project files",
219
- category: "runtime",
220
- });
221
-
222
- // Web Framework Detection
223
- const webFrameworks = [
224
- { deps: ["django"], name: "Django", confidence: 1.0 },
225
- { deps: ["fastapi"], name: "FastAPI", confidence: 1.0 },
226
- { deps: ["flask"], name: "Flask", confidence: 1.0 },
227
- { deps: ["starlette"], name: "Starlette", confidence: 0.95 },
228
- { deps: ["litestar"], name: "Litestar", confidence: 1.0 },
229
- { deps: ["tornado"], name: "Tornado", confidence: 0.95 },
230
- { deps: ["pyramid"], name: "Pyramid", confidence: 0.95 },
231
- { deps: ["aiohttp"], name: "aiohttp", confidence: 0.9 },
232
- { deps: ["sanic"], name: "Sanic", confidence: 0.95 },
233
- { deps: ["quart"], name: "Quart", confidence: 0.95 },
234
- { deps: ["falcon"], name: "Falcon", confidence: 0.95 },
235
- { deps: ["blacksheep"], name: "BlackSheep", confidence: 0.95 },
236
- ];
237
-
238
- for (const fw of webFrameworks) {
239
- const found = fw.deps.find(d => allDeps.has(d));
240
- if (found) {
241
- technologies.push({
242
- name: fw.name,
243
- version: allDeps.get(found),
244
- confidence: fw.confidence,
245
- source: `Dependency: ${found}`,
246
- category: "backend",
247
- });
248
- }
249
- }
250
-
251
- // ORM Detection
252
- const orms = [
253
- { deps: ["sqlalchemy", "sqlmodel"], name: "SQLAlchemy", confidence: 1.0 },
254
- { deps: ["tortoise-orm"], name: "Tortoise ORM", confidence: 1.0 },
255
- { deps: ["peewee"], name: "Peewee", confidence: 1.0 },
256
- { deps: ["sqlmodel"], name: "SQLModel", confidence: 1.0 },
257
- { deps: ["piccolo"], name: "Piccolo", confidence: 1.0 },
258
- { deps: ["ormar"], name: "Ormar", confidence: 1.0 },
259
- { deps: ["odmantic"], name: "ODMantic", confidence: 1.0 },
260
- { deps: ["mongoengine"], name: "MongoEngine", confidence: 1.0 },
261
- { deps: ["beanie"], name: "Beanie", confidence: 1.0 },
262
- ];
263
-
264
- for (const orm of orms) {
265
- const found = orm.deps.find(d => allDeps.has(d));
266
- if (found) {
267
- technologies.push({
268
- name: orm.name,
269
- version: allDeps.get(found),
270
- confidence: orm.confidence,
271
- source: `Dependency: ${found}`,
272
- category: "orm",
273
- });
274
- }
275
- }
276
-
277
- // Database Detection
278
- const databases = [
279
- { deps: ["psycopg2", "psycopg2-binary", "asyncpg", "psycopg"], name: "PostgreSQL", confidence: 0.9 },
280
- { deps: ["pymysql", "mysqlclient", "aiomysql"], name: "MySQL", confidence: 0.9 },
281
- { deps: ["pymongo", "motor"], name: "MongoDB", confidence: 0.9 },
282
- { deps: ["redis", "aioredis"], name: "Redis", confidence: 0.9 },
283
- { deps: ["elasticsearch", "elasticsearch-dsl"], name: "Elasticsearch", confidence: 0.9 },
284
- { deps: ["cassandra-driver"], name: "Cassandra", confidence: 0.9 },
285
- ];
286
-
287
- const detectedDbs = new Set<string>();
288
- for (const db of databases) {
289
- const found = db.deps.find(d => allDeps.has(d));
290
- if (found && !detectedDbs.has(db.name)) {
291
- technologies.push({
292
- name: db.name,
293
- confidence: db.confidence,
294
- source: `Dependency: ${found}`,
295
- category: "database",
296
- });
297
- detectedDbs.add(db.name);
298
- }
299
- }
300
-
301
- // Testing Detection
302
- const testingTools = [
303
- { deps: ["pytest"], name: "pytest", confidence: 1.0 },
304
- { deps: ["unittest"], name: "unittest", confidence: 0.8 },
305
- { deps: ["nose2"], name: "nose2", confidence: 0.9 },
306
- { deps: ["hypothesis"], name: "Hypothesis", confidence: 0.9 },
307
- { deps: ["locust"], name: "Locust", confidence: 0.9 },
308
- { deps: ["behave"], name: "Behave (BDD)", confidence: 0.9 },
309
- { deps: ["pytest-asyncio"], name: "pytest-asyncio", confidence: 0.9 },
310
- ];
311
-
312
- for (const test of testingTools) {
313
- const found = test.deps.find(d => allDeps.has(d));
314
- if (found) {
315
- technologies.push({
316
- name: test.name,
317
- version: allDeps.get(found),
318
- confidence: test.confidence,
319
- source: `Dependency: ${found}`,
320
- category: "testing",
321
- });
322
- }
323
- }
324
-
325
- // ML/Data Science (common in Python)
326
- const mlTools = [
327
- { deps: ["tensorflow", "keras"], name: "TensorFlow", confidence: 1.0 },
328
- { deps: ["torch", "pytorch"], name: "PyTorch", confidence: 1.0 },
329
- { deps: ["scikit-learn", "sklearn"], name: "scikit-learn", confidence: 1.0 },
330
- { deps: ["pandas"], name: "Pandas", confidence: 0.9 },
331
- { deps: ["numpy"], name: "NumPy", confidence: 0.8 },
332
- { deps: ["transformers"], name: "Hugging Face Transformers", confidence: 1.0 },
333
- { deps: ["langchain"], name: "LangChain", confidence: 1.0 },
334
- { deps: ["openai"], name: "OpenAI SDK", confidence: 0.9 },
335
- ];
336
-
337
- for (const ml of mlTools) {
338
- const found = ml.deps.find(d => allDeps.has(d));
339
- if (found) {
340
- technologies.push({
341
- name: ml.name,
342
- version: allDeps.get(found),
343
- confidence: ml.confidence,
344
- source: `Dependency: ${found}`,
345
- category: "backend",
346
- metadata: { type: "ml/data-science" },
347
- });
348
- }
349
- }
350
-
351
- // Structure detection
352
- const structurePaths = [
353
- { key: "api", paths: ["api", "app/api", "src/api", "app/routers", "routers"] },
354
- { key: "models", paths: ["models", "app/models", "src/models"] },
355
- { key: "services", paths: ["services", "app/services", "src/services"] },
356
- { key: "schemas", paths: ["schemas", "app/schemas", "src/schemas"] },
357
- { key: "tests", paths: ["tests", "test", "app/tests"] },
358
- { key: "migrations", paths: ["migrations", "alembic", "app/migrations"] },
359
- { key: "static", paths: ["static", "app/static"] },
360
- { key: "templates", paths: ["templates", "app/templates"] },
361
- ];
362
-
363
- for (const { key, paths } of structurePaths) {
364
- for (const p of paths) {
365
- if (dirExists(join(cwd, p))) {
366
- structure[key] = p;
367
- break;
368
- }
369
- }
370
- }
371
-
372
- // Django specific
373
- if (allDeps.has("django")) {
374
- const djangoStructure = [
375
- { key: "apps", paths: ["apps", "src/apps"] },
376
- { key: "settings", paths: ["config", "settings", "core/settings"] },
377
- ];
378
- for (const { key, paths } of djangoStructure) {
379
- for (const p of paths) {
380
- if (dirExists(join(cwd, p))) {
381
- structure[key] = p;
382
- break;
383
- }
384
- }
385
- }
386
- }
387
-
388
- // Config files
389
- const configPatterns = [
390
- "setup.py",
391
- "setup.cfg",
392
- "Pipfile",
393
- "tox.ini",
394
- ".flake8",
395
- ".pylintrc",
396
- "mypy.ini",
397
- ".mypy.ini",
398
- "pytest.ini",
399
- "conftest.py",
400
- "alembic.ini",
401
- ".pre-commit-config.yaml",
402
- ".python-version",
403
- "runtime.txt",
404
- ];
405
-
406
- for (const pattern of configPatterns) {
407
- if (fileExists(join(cwd, pattern))) {
408
- configFiles.push(pattern);
409
- }
410
- }
411
-
412
- return {
413
- ecosystem: "python",
414
- technologies,
415
- structure,
416
- configFiles: [...new Set(configFiles)],
417
- };
418
- },
419
- };
420
-
421
- // Register the detector
422
- registerDetector(pythonDetector);
423
-
1
+ /**
2
+ * Python Ecosystem Detector
3
+ *
4
+ * Detects Python projects including:
5
+ * - Frameworks: Django, Flask, FastAPI, Starlette
6
+ * - ORMs: SQLAlchemy, Django ORM, Tortoise, Peewee
7
+ * - Testing: pytest, unittest, nose
8
+ * - Package managers: pip, poetry, pipenv, uv
9
+ */
10
+
11
+ import { join } from "path";
12
+ import {
13
+ registerDetector,
14
+ Detector,
15
+ DetectorResult,
16
+ DetectedTechnology,
17
+ fileExists,
18
+ dirExists,
19
+ findFiles,
20
+ readText,
21
+ parseToml,
22
+ } from "./index";
23
+
24
+ interface Dependency {
25
+ name: string;
26
+ version?: string;
27
+ }
28
+
29
+ function parseRequirements(content: string): Dependency[] {
30
+ const deps: Dependency[] = [];
31
+ for (const line of content.split("\n")) {
32
+ const trimmed = line.trim();
33
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
34
+
35
+ // Parse formats: package==1.0.0, package>=1.0.0, package~=1.0.0, package
36
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:([<>=!~]+)(.+))?/);
37
+ if (match) {
38
+ deps.push({
39
+ name: match[1].toLowerCase(),
40
+ version: match[3],
41
+ });
42
+ }
43
+ }
44
+ return deps;
45
+ }
46
+
47
+ function parsePyproject(content: string): Dependency[] {
48
+ const deps: Dependency[] = [];
49
+
50
+ try {
51
+ const toml = parseToml(content);
52
+
53
+ // Poetry format
54
+ const poetryDeps = toml?.tool?.poetry?.dependencies || {};
55
+ const poetryDevDeps = toml?.tool?.poetry?.["dev-dependencies"] || {};
56
+
57
+ for (const [name, value] of Object.entries({ ...poetryDeps, ...poetryDevDeps })) {
58
+ if (name === "python") continue;
59
+ deps.push({
60
+ name: name.toLowerCase(),
61
+ version: typeof value === "string" ? value : undefined,
62
+ });
63
+ }
64
+
65
+ // PEP 621 format (project.dependencies)
66
+ const projectDeps = toml?.project?.dependencies || [];
67
+ const optionalDeps = Object.values(toml?.project?.["optional-dependencies"] || {}).flat();
68
+
69
+ for (const dep of [...projectDeps, ...optionalDeps]) {
70
+ if (typeof dep === "string") {
71
+ const match = dep.match(/^([a-zA-Z0-9_-]+)/);
72
+ if (match) {
73
+ deps.push({ name: match[1].toLowerCase() });
74
+ }
75
+ }
76
+ }
77
+ } catch {
78
+ // Fallback to regex parsing
79
+ const depMatches = content.matchAll(/^\s*"?([a-zA-Z0-9_-]+)"?\s*[=<>]/gm);
80
+ for (const match of depMatches) {
81
+ deps.push({ name: match[1].toLowerCase() });
82
+ }
83
+ }
84
+
85
+ return deps;
86
+ }
87
+
88
+ const pythonDetector: Detector = {
89
+ name: "python",
90
+ ecosystem: "python",
91
+ priority: 85,
92
+ markers: [
93
+ { type: "file", pattern: "pyproject.toml", weight: 1.0 },
94
+ { type: "file", pattern: "setup.py", weight: 0.9 },
95
+ { type: "file", pattern: "setup.cfg", weight: 0.9 },
96
+ { type: "file", pattern: "requirements.txt", weight: 0.8 },
97
+ { type: "file", pattern: "Pipfile", weight: 0.9 },
98
+ { type: "file", pattern: "poetry.lock", weight: 1.0 },
99
+ { type: "file", pattern: "Pipfile.lock", weight: 0.9 },
100
+ { type: "file", pattern: "uv.lock", weight: 1.0 },
101
+ { type: "directory", pattern: "__pycache__", weight: 0.4 },
102
+ { type: "directory", pattern: ".venv", weight: 0.5 },
103
+ { type: "directory", pattern: "venv", weight: 0.5 },
104
+ { type: "glob", pattern: "**/*.py", weight: 0.6 },
105
+ ],
106
+
107
+ async detect(cwd: string): Promise<DetectorResult | null> {
108
+ const technologies: DetectedTechnology[] = [];
109
+ const structure: Record<string, string> = {};
110
+ const configFiles: string[] = [];
111
+
112
+ // Check for Python files
113
+ const pyFiles = findFiles(cwd, "**/*.py");
114
+ if (pyFiles.length === 0) {
115
+ // Check for config files even without .py files
116
+ const hasPyproject = fileExists(join(cwd, "pyproject.toml"));
117
+ const hasRequirements = fileExists(join(cwd, "requirements.txt"));
118
+
119
+ if (!hasPyproject && !hasRequirements) {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // Collect all dependencies
125
+ const allDeps = new Map<string, string | undefined>();
126
+
127
+ // Parse requirements.txt
128
+ if (fileExists(join(cwd, "requirements.txt"))) {
129
+ configFiles.push("requirements.txt");
130
+ const content = readText(join(cwd, "requirements.txt"));
131
+ if (content) {
132
+ for (const dep of parseRequirements(content)) {
133
+ allDeps.set(dep.name, dep.version);
134
+ }
135
+ }
136
+ }
137
+
138
+ // Also check for dev requirements
139
+ const devReqFiles = ["requirements-dev.txt", "requirements.dev.txt", "dev-requirements.txt"];
140
+ for (const f of devReqFiles) {
141
+ if (fileExists(join(cwd, f))) {
142
+ configFiles.push(f);
143
+ const content = readText(join(cwd, f));
144
+ if (content) {
145
+ for (const dep of parseRequirements(content)) {
146
+ allDeps.set(dep.name, dep.version);
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ // Parse pyproject.toml
153
+ if (fileExists(join(cwd, "pyproject.toml"))) {
154
+ configFiles.push("pyproject.toml");
155
+ const content = readText(join(cwd, "pyproject.toml"));
156
+ if (content) {
157
+ for (const dep of parsePyproject(content)) {
158
+ allDeps.set(dep.name, dep.version);
159
+ }
160
+
161
+ // Detect package manager from pyproject.toml
162
+ if (content.includes("[tool.poetry]")) {
163
+ technologies.push({
164
+ name: "Poetry",
165
+ confidence: 1.0,
166
+ source: "pyproject.toml [tool.poetry]",
167
+ category: "build",
168
+ });
169
+ } else if (content.includes("[tool.pdm]")) {
170
+ technologies.push({
171
+ name: "PDM",
172
+ confidence: 1.0,
173
+ source: "pyproject.toml [tool.pdm]",
174
+ category: "build",
175
+ });
176
+ } else if (content.includes("[tool.hatch]")) {
177
+ technologies.push({
178
+ name: "Hatch",
179
+ confidence: 1.0,
180
+ source: "pyproject.toml [tool.hatch]",
181
+ category: "build",
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ // Package manager detection from lock files
188
+ if (fileExists(join(cwd, "poetry.lock"))) {
189
+ technologies.push({
190
+ name: "Poetry",
191
+ confidence: 1.0,
192
+ source: "poetry.lock",
193
+ category: "build",
194
+ });
195
+ configFiles.push("poetry.lock");
196
+ } else if (fileExists(join(cwd, "Pipfile.lock"))) {
197
+ technologies.push({
198
+ name: "Pipenv",
199
+ confidence: 1.0,
200
+ source: "Pipfile.lock",
201
+ category: "build",
202
+ });
203
+ configFiles.push("Pipfile.lock");
204
+ } else if (fileExists(join(cwd, "uv.lock"))) {
205
+ technologies.push({
206
+ name: "uv",
207
+ confidence: 1.0,
208
+ source: "uv.lock",
209
+ category: "build",
210
+ });
211
+ configFiles.push("uv.lock");
212
+ }
213
+
214
+ // Add Python runtime
215
+ technologies.push({
216
+ name: "Python",
217
+ confidence: 1.0,
218
+ source: "Project files",
219
+ category: "runtime",
220
+ });
221
+
222
+ // Web Framework Detection
223
+ const webFrameworks = [
224
+ { deps: ["django"], name: "Django", confidence: 1.0 },
225
+ { deps: ["fastapi"], name: "FastAPI", confidence: 1.0 },
226
+ { deps: ["flask"], name: "Flask", confidence: 1.0 },
227
+ { deps: ["starlette"], name: "Starlette", confidence: 0.95 },
228
+ { deps: ["litestar"], name: "Litestar", confidence: 1.0 },
229
+ { deps: ["tornado"], name: "Tornado", confidence: 0.95 },
230
+ { deps: ["pyramid"], name: "Pyramid", confidence: 0.95 },
231
+ { deps: ["aiohttp"], name: "aiohttp", confidence: 0.9 },
232
+ { deps: ["sanic"], name: "Sanic", confidence: 0.95 },
233
+ { deps: ["quart"], name: "Quart", confidence: 0.95 },
234
+ { deps: ["falcon"], name: "Falcon", confidence: 0.95 },
235
+ { deps: ["blacksheep"], name: "BlackSheep", confidence: 0.95 },
236
+ ];
237
+
238
+ for (const fw of webFrameworks) {
239
+ const found = fw.deps.find(d => allDeps.has(d));
240
+ if (found) {
241
+ technologies.push({
242
+ name: fw.name,
243
+ version: allDeps.get(found),
244
+ confidence: fw.confidence,
245
+ source: `Dependency: ${found}`,
246
+ category: "backend",
247
+ });
248
+ }
249
+ }
250
+
251
+ // ORM Detection
252
+ const orms = [
253
+ { deps: ["sqlalchemy", "sqlmodel"], name: "SQLAlchemy", confidence: 1.0 },
254
+ { deps: ["tortoise-orm"], name: "Tortoise ORM", confidence: 1.0 },
255
+ { deps: ["peewee"], name: "Peewee", confidence: 1.0 },
256
+ { deps: ["sqlmodel"], name: "SQLModel", confidence: 1.0 },
257
+ { deps: ["piccolo"], name: "Piccolo", confidence: 1.0 },
258
+ { deps: ["ormar"], name: "Ormar", confidence: 1.0 },
259
+ { deps: ["odmantic"], name: "ODMantic", confidence: 1.0 },
260
+ { deps: ["mongoengine"], name: "MongoEngine", confidence: 1.0 },
261
+ { deps: ["beanie"], name: "Beanie", confidence: 1.0 },
262
+ ];
263
+
264
+ for (const orm of orms) {
265
+ const found = orm.deps.find(d => allDeps.has(d));
266
+ if (found) {
267
+ technologies.push({
268
+ name: orm.name,
269
+ version: allDeps.get(found),
270
+ confidence: orm.confidence,
271
+ source: `Dependency: ${found}`,
272
+ category: "orm",
273
+ });
274
+ }
275
+ }
276
+
277
+ // Database Detection
278
+ const databases = [
279
+ { deps: ["psycopg2", "psycopg2-binary", "asyncpg", "psycopg"], name: "PostgreSQL", confidence: 0.9 },
280
+ { deps: ["pymysql", "mysqlclient", "aiomysql"], name: "MySQL", confidence: 0.9 },
281
+ { deps: ["pymongo", "motor"], name: "MongoDB", confidence: 0.9 },
282
+ { deps: ["redis", "aioredis"], name: "Redis", confidence: 0.9 },
283
+ { deps: ["elasticsearch", "elasticsearch-dsl"], name: "Elasticsearch", confidence: 0.9 },
284
+ { deps: ["cassandra-driver"], name: "Cassandra", confidence: 0.9 },
285
+ ];
286
+
287
+ const detectedDbs = new Set<string>();
288
+ for (const db of databases) {
289
+ const found = db.deps.find(d => allDeps.has(d));
290
+ if (found && !detectedDbs.has(db.name)) {
291
+ technologies.push({
292
+ name: db.name,
293
+ confidence: db.confidence,
294
+ source: `Dependency: ${found}`,
295
+ category: "database",
296
+ });
297
+ detectedDbs.add(db.name);
298
+ }
299
+ }
300
+
301
+ // Testing Detection
302
+ const testingTools = [
303
+ { deps: ["pytest"], name: "pytest", confidence: 1.0 },
304
+ { deps: ["unittest"], name: "unittest", confidence: 0.8 },
305
+ { deps: ["nose2"], name: "nose2", confidence: 0.9 },
306
+ { deps: ["hypothesis"], name: "Hypothesis", confidence: 0.9 },
307
+ { deps: ["locust"], name: "Locust", confidence: 0.9 },
308
+ { deps: ["behave"], name: "Behave (BDD)", confidence: 0.9 },
309
+ { deps: ["pytest-asyncio"], name: "pytest-asyncio", confidence: 0.9 },
310
+ ];
311
+
312
+ for (const test of testingTools) {
313
+ const found = test.deps.find(d => allDeps.has(d));
314
+ if (found) {
315
+ technologies.push({
316
+ name: test.name,
317
+ version: allDeps.get(found),
318
+ confidence: test.confidence,
319
+ source: `Dependency: ${found}`,
320
+ category: "testing",
321
+ });
322
+ }
323
+ }
324
+
325
+ // ML/Data Science (common in Python)
326
+ const mlTools = [
327
+ { deps: ["tensorflow", "keras"], name: "TensorFlow", confidence: 1.0 },
328
+ { deps: ["torch", "pytorch"], name: "PyTorch", confidence: 1.0 },
329
+ { deps: ["scikit-learn", "sklearn"], name: "scikit-learn", confidence: 1.0 },
330
+ { deps: ["pandas"], name: "Pandas", confidence: 0.9 },
331
+ { deps: ["numpy"], name: "NumPy", confidence: 0.8 },
332
+ { deps: ["transformers"], name: "Hugging Face Transformers", confidence: 1.0 },
333
+ { deps: ["langchain"], name: "LangChain", confidence: 1.0 },
334
+ { deps: ["openai"], name: "OpenAI SDK", confidence: 0.9 },
335
+ ];
336
+
337
+ for (const ml of mlTools) {
338
+ const found = ml.deps.find(d => allDeps.has(d));
339
+ if (found) {
340
+ technologies.push({
341
+ name: ml.name,
342
+ version: allDeps.get(found),
343
+ confidence: ml.confidence,
344
+ source: `Dependency: ${found}`,
345
+ category: "backend",
346
+ metadata: { type: "ml/data-science" },
347
+ });
348
+ }
349
+ }
350
+
351
+ // Structure detection
352
+ const structurePaths = [
353
+ { key: "api", paths: ["api", "app/api", "src/api", "app/routers", "routers"] },
354
+ { key: "models", paths: ["models", "app/models", "src/models"] },
355
+ { key: "services", paths: ["services", "app/services", "src/services"] },
356
+ { key: "schemas", paths: ["schemas", "app/schemas", "src/schemas"] },
357
+ { key: "tests", paths: ["tests", "test", "app/tests"] },
358
+ { key: "migrations", paths: ["migrations", "alembic", "app/migrations"] },
359
+ { key: "static", paths: ["static", "app/static"] },
360
+ { key: "templates", paths: ["templates", "app/templates"] },
361
+ ];
362
+
363
+ for (const { key, paths } of structurePaths) {
364
+ for (const p of paths) {
365
+ if (dirExists(join(cwd, p))) {
366
+ structure[key] = p;
367
+ break;
368
+ }
369
+ }
370
+ }
371
+
372
+ // Django specific
373
+ if (allDeps.has("django")) {
374
+ const djangoStructure = [
375
+ { key: "apps", paths: ["apps", "src/apps"] },
376
+ { key: "settings", paths: ["config", "settings", "core/settings"] },
377
+ ];
378
+ for (const { key, paths } of djangoStructure) {
379
+ for (const p of paths) {
380
+ if (dirExists(join(cwd, p))) {
381
+ structure[key] = p;
382
+ break;
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ // Config files
389
+ const configPatterns = [
390
+ "setup.py",
391
+ "setup.cfg",
392
+ "Pipfile",
393
+ "tox.ini",
394
+ ".flake8",
395
+ ".pylintrc",
396
+ "mypy.ini",
397
+ ".mypy.ini",
398
+ "pytest.ini",
399
+ "conftest.py",
400
+ "alembic.ini",
401
+ ".pre-commit-config.yaml",
402
+ ".python-version",
403
+ "runtime.txt",
404
+ ];
405
+
406
+ for (const pattern of configPatterns) {
407
+ if (fileExists(join(cwd, pattern))) {
408
+ configFiles.push(pattern);
409
+ }
410
+ }
411
+
412
+ return {
413
+ ecosystem: "python",
414
+ technologies,
415
+ structure,
416
+ configFiles: [...new Set(configFiles)],
417
+ };
418
+ },
419
+ };
420
+
421
+ // Register the detector
422
+ registerDetector(pythonDetector);
423
+
424
424
  export default pythonDetector;