@itz4blitz/agentful 0.4.0 → 1.0.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 +131 -16
- package/bin/cli.js +1031 -47
- package/bin/hooks/README.md +338 -82
- package/bin/hooks/analyze-trigger.js +69 -0
- package/bin/hooks/block-random-docs.js +77 -0
- package/bin/hooks/health-check.js +153 -0
- package/bin/hooks/post-agent.js +101 -0
- package/bin/hooks/post-feature.js +227 -0
- package/bin/hooks/pre-agent.js +118 -0
- package/bin/hooks/pre-feature.js +138 -0
- package/lib/VALIDATION_README.md +455 -0
- package/lib/atomic.js +350 -0
- package/lib/ci/claude-action-integration.js +641 -0
- package/lib/ci/index.js +10 -0
- package/lib/core/CLAUDE_EXECUTOR.md +371 -0
- package/lib/core/README.md +321 -0
- package/lib/core/analyzer.js +497 -0
- package/lib/core/claude-executor.example.js +210 -0
- package/lib/core/claude-executor.js +1046 -0
- package/lib/core/cli.js +141 -0
- package/lib/core/detectors/conventions.js +342 -0
- package/lib/core/detectors/framework.js +276 -0
- package/lib/core/detectors/index.js +15 -0
- package/lib/core/detectors/language.js +199 -0
- package/lib/core/detectors/patterns.js +356 -0
- package/lib/core/generator.js +626 -0
- package/lib/core/index.js +9 -0
- package/lib/core/output-parser.example.js +250 -0
- package/lib/core/output-parser.js +458 -0
- package/lib/core/storage.js +515 -0
- package/lib/core/templates.js +556 -0
- package/lib/index.js +32 -0
- package/lib/init.js +252 -21
- package/lib/pipeline/cli.js +423 -0
- package/lib/pipeline/engine.js +928 -0
- package/lib/pipeline/executor.js +440 -0
- package/lib/pipeline/index.js +33 -0
- package/lib/pipeline/integrations.js +559 -0
- package/lib/pipeline/schemas.js +288 -0
- package/lib/presets.js +207 -0
- package/lib/remote/client.js +361 -0
- package/lib/server/auth.js +286 -0
- package/lib/server/client-example.js +190 -0
- package/lib/server/executor.js +426 -0
- package/lib/server/index.js +469 -0
- package/lib/update-helpers.js +505 -0
- package/lib/validation.js +460 -0
- package/package.json +19 -2
- package/template/.claude/agents/architect.md +260 -0
- package/template/.claude/agents/backend.md +203 -0
- package/template/.claude/agents/fixer.md +244 -0
- package/template/.claude/agents/frontend.md +232 -0
- package/template/.claude/agents/orchestrator.md +528 -0
- package/template/.claude/agents/product-analyzer.md +1130 -0
- package/template/.claude/agents/reviewer.md +229 -0
- package/template/.claude/agents/tester.md +242 -0
- package/{.claude → template/.claude}/commands/agentful-analyze.md +151 -43
- package/template/.claude/commands/agentful-decide.md +470 -0
- package/{.claude → template/.claude}/commands/agentful-product.md +89 -5
- package/template/.claude/commands/agentful-start.md +432 -0
- package/{.claude → template/.claude}/commands/agentful-status.md +88 -3
- package/template/.claude/commands/agentful-update.md +402 -0
- package/template/.claude/commands/agentful-validate.md +369 -0
- package/{.claude → template/.claude}/commands/agentful.md +110 -183
- package/template/.claude/product/EXAMPLES.md +167 -0
- package/{.claude → template/.claude}/settings.json +9 -13
- package/{.claude → template/.claude}/skills/conversation/SKILL.md +13 -7
- package/template/.claude/skills/deployment/SKILL.md +116 -0
- package/template/.claude/skills/product-planning/SKILL.md +463 -0
- package/template/.claude/skills/testing/SKILL.md +228 -0
- package/template/.claude/skills/validation/SKILL.md +650 -0
- package/template/CLAUDE.md +73 -5
- package/template/bin/hooks/block-random-docs.js +121 -0
- package/version.json +1 -1
- package/.claude/agents/architect.md +0 -524
- package/.claude/agents/backend.md +0 -315
- package/.claude/agents/fixer.md +0 -263
- package/.claude/agents/frontend.md +0 -274
- package/.claude/agents/orchestrator.md +0 -283
- package/.claude/agents/product-analyzer.md +0 -792
- package/.claude/agents/reviewer.md +0 -332
- package/.claude/agents/tester.md +0 -410
- package/.claude/commands/agentful-decide.md +0 -214
- package/.claude/commands/agentful-start.md +0 -182
- package/.claude/commands/agentful-validate.md +0 -127
- package/.claude/product/EXAMPLES.md +0 -610
- package/.claude/product/README.md +0 -326
- package/.claude/skills/validation/SKILL.md +0 -271
- package/bin/hooks/analyze-trigger.sh +0 -57
- package/bin/hooks/health-check.sh +0 -36
- /package/{.claude → template/.claude}/commands/agentful-generate.md +0 -0
- /package/{.claude → template/.claude}/product/index.md +0 -0
- /package/{.claude → template/.claude}/skills/product-tracking/SKILL.md +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Framework Detector
|
|
6
|
+
*
|
|
7
|
+
* Detects frameworks and libraries used in the project based on:
|
|
8
|
+
* - package.json dependencies
|
|
9
|
+
* - Import statements
|
|
10
|
+
* - Configuration files
|
|
11
|
+
* - File structure patterns
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Framework detection rules
|
|
16
|
+
* Maps package names to frameworks
|
|
17
|
+
*/
|
|
18
|
+
const FRAMEWORK_PACKAGES = {
|
|
19
|
+
// JavaScript/TypeScript frameworks
|
|
20
|
+
'next': { name: 'Next.js', type: 'framework', category: 'web' },
|
|
21
|
+
'react': { name: 'React', type: 'library', category: 'ui' },
|
|
22
|
+
'vue': { name: 'Vue', type: 'framework', category: 'web' },
|
|
23
|
+
'nuxt': { name: 'Nuxt', type: 'framework', category: 'web' },
|
|
24
|
+
'angular': { name: 'Angular', type: 'framework', category: 'web' },
|
|
25
|
+
'svelte': { name: 'Svelte', type: 'framework', category: 'ui' },
|
|
26
|
+
'express': { name: 'Express', type: 'framework', category: 'backend' },
|
|
27
|
+
'fastify': { name: 'Fastify', type: 'framework', category: 'backend' },
|
|
28
|
+
'nestjs': { name: 'NestJS', type: 'framework', category: 'backend' },
|
|
29
|
+
'koa': { name: 'Koa', type: 'framework', category: 'backend' },
|
|
30
|
+
'hapi': { name: 'Hapi', type: 'framework', category: 'backend' },
|
|
31
|
+
|
|
32
|
+
// Testing frameworks
|
|
33
|
+
'vitest': { name: 'Vitest', type: 'framework', category: 'testing' },
|
|
34
|
+
'jest': { name: 'Jest', type: 'framework', category: 'testing' },
|
|
35
|
+
'mocha': { name: 'Mocha', type: 'framework', category: 'testing' },
|
|
36
|
+
'jasmine': { name: 'Jasmine', type: 'framework', category: 'testing' },
|
|
37
|
+
'cypress': { name: 'Cypress', type: 'framework', category: 'testing' },
|
|
38
|
+
'playwright': { name: 'Playwright', type: 'framework', category: 'testing' },
|
|
39
|
+
|
|
40
|
+
// Build tools
|
|
41
|
+
'vite': { name: 'Vite', type: 'tool', category: 'build' },
|
|
42
|
+
'webpack': { name: 'Webpack', type: 'tool', category: 'build' },
|
|
43
|
+
'rollup': { name: 'Rollup', type: 'tool', category: 'build' },
|
|
44
|
+
'esbuild': { name: 'esbuild', type: 'tool', category: 'build' },
|
|
45
|
+
'parcel': { name: 'Parcel', type: 'tool', category: 'build' },
|
|
46
|
+
|
|
47
|
+
// Database/ORM
|
|
48
|
+
'prisma': { name: 'Prisma', type: 'library', category: 'database' },
|
|
49
|
+
'typeorm': { name: 'TypeORM', type: 'library', category: 'database' },
|
|
50
|
+
'sequelize': { name: 'Sequelize', type: 'library', category: 'database' },
|
|
51
|
+
'mongoose': { name: 'Mongoose', type: 'library', category: 'database' },
|
|
52
|
+
'drizzle-orm': { name: 'Drizzle', type: 'library', category: 'database' },
|
|
53
|
+
|
|
54
|
+
// Styling
|
|
55
|
+
'tailwindcss': { name: 'Tailwind CSS', type: 'library', category: 'styling' },
|
|
56
|
+
'styled-components': { name: 'Styled Components', type: 'library', category: 'styling' },
|
|
57
|
+
'@emotion/react': { name: 'Emotion', type: 'library', category: 'styling' },
|
|
58
|
+
'sass': { name: 'Sass', type: 'tool', category: 'styling' }
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Configuration files that indicate specific frameworks
|
|
63
|
+
*/
|
|
64
|
+
const CONFIG_FILES = {
|
|
65
|
+
'next.config.js': 'Next.js',
|
|
66
|
+
'next.config.mjs': 'Next.js',
|
|
67
|
+
'next.config.ts': 'Next.js',
|
|
68
|
+
'nuxt.config.js': 'Nuxt',
|
|
69
|
+
'nuxt.config.ts': 'Nuxt',
|
|
70
|
+
'vue.config.js': 'Vue',
|
|
71
|
+
'angular.json': 'Angular',
|
|
72
|
+
'svelte.config.js': 'Svelte',
|
|
73
|
+
'vite.config.js': 'Vite',
|
|
74
|
+
'vite.config.ts': 'Vite',
|
|
75
|
+
'vitest.config.js': 'Vitest',
|
|
76
|
+
'vitest.config.ts': 'Vitest',
|
|
77
|
+
'jest.config.js': 'Jest',
|
|
78
|
+
'jest.config.ts': 'Jest',
|
|
79
|
+
'playwright.config.js': 'Playwright',
|
|
80
|
+
'playwright.config.ts': 'Playwright',
|
|
81
|
+
'tailwind.config.js': 'Tailwind CSS',
|
|
82
|
+
'tailwind.config.ts': 'Tailwind CSS'
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* File structure patterns that indicate frameworks
|
|
87
|
+
*/
|
|
88
|
+
const STRUCTURE_PATTERNS = {
|
|
89
|
+
'Next.js': ['pages/', 'app/', 'public/'],
|
|
90
|
+
'Nuxt': ['pages/', 'layouts/', 'nuxt.config'],
|
|
91
|
+
'Angular': ['src/app/', 'angular.json'],
|
|
92
|
+
'Vue': ['src/components/', 'src/views/'],
|
|
93
|
+
'Express': ['routes/', 'middleware/'],
|
|
94
|
+
'NestJS': ['src/modules/', 'src/controllers/']
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect frameworks from package.json
|
|
99
|
+
*
|
|
100
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
101
|
+
* @returns {Promise<Object[]>} Array of detected frameworks
|
|
102
|
+
*/
|
|
103
|
+
export async function detectFromPackageJson(projectRoot) {
|
|
104
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
105
|
+
const frameworks = [];
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
109
|
+
const packageJson = JSON.parse(content);
|
|
110
|
+
|
|
111
|
+
const allDeps = {
|
|
112
|
+
...packageJson.dependencies,
|
|
113
|
+
...packageJson.devDependencies
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
for (const [pkg, version] of Object.entries(allDeps)) {
|
|
117
|
+
const framework = FRAMEWORK_PACKAGES[pkg];
|
|
118
|
+
if (framework) {
|
|
119
|
+
frameworks.push({
|
|
120
|
+
name: framework.name,
|
|
121
|
+
version: version.replace(/[\^~]/g, ''),
|
|
122
|
+
type: framework.type,
|
|
123
|
+
category: framework.category,
|
|
124
|
+
confidence: 95,
|
|
125
|
+
source: 'package.json'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
// package.json not found or invalid - not an error, just no data
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return frameworks;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Detect frameworks from config files
|
|
138
|
+
*
|
|
139
|
+
* @param {string[]} files - Array of file paths
|
|
140
|
+
* @returns {Object[]} Array of detected frameworks
|
|
141
|
+
*/
|
|
142
|
+
export function detectFromConfigFiles(files) {
|
|
143
|
+
const frameworks = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const basename = path.basename(file);
|
|
148
|
+
const frameworkName = CONFIG_FILES[basename];
|
|
149
|
+
|
|
150
|
+
if (frameworkName && !seen.has(frameworkName)) {
|
|
151
|
+
seen.add(frameworkName);
|
|
152
|
+
frameworks.push({
|
|
153
|
+
name: frameworkName,
|
|
154
|
+
confidence: 90,
|
|
155
|
+
source: 'config file',
|
|
156
|
+
configFile: basename
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return frameworks;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Detect frameworks from file structure
|
|
166
|
+
*
|
|
167
|
+
* @param {string[]} files - Array of file paths
|
|
168
|
+
* @returns {Object[]} Array of detected frameworks
|
|
169
|
+
*/
|
|
170
|
+
export function detectFromStructure(files) {
|
|
171
|
+
const frameworks = [];
|
|
172
|
+
|
|
173
|
+
for (const [frameworkName, patterns] of Object.entries(STRUCTURE_PATTERNS)) {
|
|
174
|
+
let matchCount = 0;
|
|
175
|
+
|
|
176
|
+
for (const pattern of patterns) {
|
|
177
|
+
const hasPattern = files.some(file =>
|
|
178
|
+
file.startsWith(pattern) || file.includes(`/${pattern}`)
|
|
179
|
+
);
|
|
180
|
+
if (hasPattern) matchCount++;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (matchCount > 0) {
|
|
184
|
+
const confidence = Math.min(85, 50 + (matchCount / patterns.length) * 35);
|
|
185
|
+
frameworks.push({
|
|
186
|
+
name: frameworkName,
|
|
187
|
+
confidence: Math.round(confidence),
|
|
188
|
+
source: 'file structure',
|
|
189
|
+
patternsMatched: matchCount,
|
|
190
|
+
patternsTotal: patterns.length
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return frameworks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Detect frameworks from import statements
|
|
200
|
+
*
|
|
201
|
+
* @param {string} content - File content to analyze
|
|
202
|
+
* @returns {string[]} Array of imported framework names
|
|
203
|
+
*/
|
|
204
|
+
export function detectFromImports(content) {
|
|
205
|
+
const imports = new Set();
|
|
206
|
+
const importRegex = /(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g;
|
|
207
|
+
|
|
208
|
+
let match;
|
|
209
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
210
|
+
const importPath = match[1];
|
|
211
|
+
const packageName = importPath.startsWith('@')
|
|
212
|
+
? importPath.split('/').slice(0, 2).join('/')
|
|
213
|
+
: importPath.split('/')[0];
|
|
214
|
+
|
|
215
|
+
if (FRAMEWORK_PACKAGES[packageName]) {
|
|
216
|
+
imports.add(FRAMEWORK_PACKAGES[packageName].name);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return Array.from(imports);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Merge and deduplicate framework detections
|
|
225
|
+
*
|
|
226
|
+
* @param {Object[][]} detectionResults - Multiple arrays of detected frameworks
|
|
227
|
+
* @returns {Object[]} Merged and deduplicated frameworks with highest confidence
|
|
228
|
+
*/
|
|
229
|
+
export function mergeFrameworks(...detectionResults) {
|
|
230
|
+
const frameworkMap = new Map();
|
|
231
|
+
|
|
232
|
+
for (const results of detectionResults) {
|
|
233
|
+
for (const framework of results) {
|
|
234
|
+
const existing = frameworkMap.get(framework.name);
|
|
235
|
+
|
|
236
|
+
if (!existing || framework.confidence > existing.confidence) {
|
|
237
|
+
frameworkMap.set(framework.name, framework);
|
|
238
|
+
} else if (existing) {
|
|
239
|
+
// Merge sources
|
|
240
|
+
existing.sources = existing.sources || [existing.source];
|
|
241
|
+
if (!existing.sources.includes(framework.source)) {
|
|
242
|
+
existing.sources.push(framework.source);
|
|
243
|
+
}
|
|
244
|
+
// Boost confidence if detected from multiple sources
|
|
245
|
+
existing.confidence = Math.min(98, existing.confidence + 5);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return Array.from(frameworkMap.values())
|
|
251
|
+
.sort((a, b) => b.confidence - a.confidence);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Detect all frameworks in project
|
|
256
|
+
*
|
|
257
|
+
* @param {string[]} files - Array of file paths
|
|
258
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
259
|
+
* @returns {Promise<Object[]>} Array of detected frameworks
|
|
260
|
+
*/
|
|
261
|
+
export async function detectFrameworks(files, projectRoot) {
|
|
262
|
+
const fromPackage = await detectFromPackageJson(projectRoot);
|
|
263
|
+
const fromConfig = detectFromConfigFiles(files);
|
|
264
|
+
const fromStructure = detectFromStructure(files);
|
|
265
|
+
|
|
266
|
+
return mergeFrameworks(fromPackage, fromConfig, fromStructure);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default {
|
|
270
|
+
detectFrameworks,
|
|
271
|
+
detectFromPackageJson,
|
|
272
|
+
detectFromConfigFiles,
|
|
273
|
+
detectFromStructure,
|
|
274
|
+
detectFromImports,
|
|
275
|
+
mergeFrameworks
|
|
276
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detector Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Centralized exports for all detector modules
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './language.js';
|
|
8
|
+
export * from './framework.js';
|
|
9
|
+
export * from './patterns.js';
|
|
10
|
+
export * from './conventions.js';
|
|
11
|
+
|
|
12
|
+
export { default as languageDetector } from './language.js';
|
|
13
|
+
export { default as frameworkDetector } from './framework.js';
|
|
14
|
+
export { default as patternDetector } from './patterns.js';
|
|
15
|
+
export { default as conventionDetector } from './conventions.js';
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Language Detector
|
|
6
|
+
*
|
|
7
|
+
* Detects programming languages used in the project based on:
|
|
8
|
+
* - File extensions
|
|
9
|
+
* - File content analysis
|
|
10
|
+
* - Configuration files
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Language detection rules
|
|
15
|
+
* Maps file extensions to languages
|
|
16
|
+
*/
|
|
17
|
+
const LANGUAGE_EXTENSIONS = {
|
|
18
|
+
'.js': 'JavaScript',
|
|
19
|
+
'.mjs': 'JavaScript',
|
|
20
|
+
'.cjs': 'JavaScript',
|
|
21
|
+
'.jsx': 'JavaScript',
|
|
22
|
+
'.ts': 'TypeScript',
|
|
23
|
+
'.tsx': 'TypeScript',
|
|
24
|
+
'.py': 'Python',
|
|
25
|
+
'.java': 'Java',
|
|
26
|
+
'.go': 'Go',
|
|
27
|
+
'.rs': 'Rust',
|
|
28
|
+
'.rb': 'Ruby',
|
|
29
|
+
'.php': 'PHP',
|
|
30
|
+
'.cs': 'C#',
|
|
31
|
+
'.cpp': 'C++',
|
|
32
|
+
'.c': 'C',
|
|
33
|
+
'.h': 'C',
|
|
34
|
+
'.hpp': 'C++',
|
|
35
|
+
'.swift': 'Swift',
|
|
36
|
+
'.kt': 'Kotlin',
|
|
37
|
+
'.scala': 'Scala',
|
|
38
|
+
'.r': 'R',
|
|
39
|
+
'.jl': 'Julia',
|
|
40
|
+
'.lua': 'Lua',
|
|
41
|
+
'.sh': 'Shell',
|
|
42
|
+
'.bash': 'Shell',
|
|
43
|
+
'.zsh': 'Shell'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Language detection from config files
|
|
48
|
+
*/
|
|
49
|
+
const CONFIG_FILES = {
|
|
50
|
+
'tsconfig.json': 'TypeScript',
|
|
51
|
+
'jsconfig.json': 'JavaScript',
|
|
52
|
+
'package.json': 'JavaScript',
|
|
53
|
+
'requirements.txt': 'Python',
|
|
54
|
+
'setup.py': 'Python',
|
|
55
|
+
'Pipfile': 'Python',
|
|
56
|
+
'pyproject.toml': 'Python',
|
|
57
|
+
'go.mod': 'Go',
|
|
58
|
+
'Cargo.toml': 'Rust',
|
|
59
|
+
'pom.xml': 'Java',
|
|
60
|
+
'build.gradle': 'Java',
|
|
61
|
+
'Gemfile': 'Ruby',
|
|
62
|
+
'composer.json': 'PHP'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detect languages from file list
|
|
67
|
+
*
|
|
68
|
+
* @param {string[]} files - Array of file paths relative to project root
|
|
69
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
70
|
+
* @returns {Promise<Object[]>} Array of detected languages with confidence scores
|
|
71
|
+
*/
|
|
72
|
+
export async function detectLanguages(files, projectRoot) {
|
|
73
|
+
const languageCounts = {};
|
|
74
|
+
const configEvidence = {};
|
|
75
|
+
|
|
76
|
+
// Count files by extension
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const ext = path.extname(file).toLowerCase();
|
|
79
|
+
const language = LANGUAGE_EXTENSIONS[ext];
|
|
80
|
+
|
|
81
|
+
if (language) {
|
|
82
|
+
if (!languageCounts[language]) {
|
|
83
|
+
languageCounts[language] = { files: 0, extensions: new Set() };
|
|
84
|
+
}
|
|
85
|
+
languageCounts[language].files++;
|
|
86
|
+
languageCounts[language].extensions.add(ext);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for config files
|
|
90
|
+
const basename = path.basename(file);
|
|
91
|
+
const configLang = CONFIG_FILES[basename];
|
|
92
|
+
if (configLang) {
|
|
93
|
+
configEvidence[configLang] = (configEvidence[configLang] || 0) + 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Calculate confidence scores
|
|
98
|
+
const totalFiles = files.length;
|
|
99
|
+
const languages = [];
|
|
100
|
+
|
|
101
|
+
for (const [language, data] of Object.entries(languageCounts)) {
|
|
102
|
+
const filePercentage = (data.files / totalFiles) * 100;
|
|
103
|
+
const configBonus = configEvidence[language] ? 10 : 0;
|
|
104
|
+
const extensionDiversity = data.extensions.size > 1 ? 5 : 0;
|
|
105
|
+
|
|
106
|
+
let confidence = Math.min(95, Math.round(filePercentage * 0.8 + configBonus + extensionDiversity));
|
|
107
|
+
|
|
108
|
+
// TypeScript bonus: if tsconfig.json exists
|
|
109
|
+
if (language === 'TypeScript' && configEvidence['TypeScript']) {
|
|
110
|
+
confidence = Math.min(95, confidence + 10);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
languages.push({
|
|
114
|
+
name: language,
|
|
115
|
+
confidence,
|
|
116
|
+
files: data.files,
|
|
117
|
+
percentage: Math.round(filePercentage * 10) / 10,
|
|
118
|
+
extensions: Array.from(data.extensions)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Sort by file count descending
|
|
123
|
+
languages.sort((a, b) => b.files - a.files);
|
|
124
|
+
|
|
125
|
+
return languages;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect primary language (highest confidence)
|
|
130
|
+
*
|
|
131
|
+
* @param {Object[]} languages - Array of detected languages
|
|
132
|
+
* @returns {Object|null} Primary language or null if none detected
|
|
133
|
+
*/
|
|
134
|
+
export function getPrimaryLanguage(languages) {
|
|
135
|
+
if (languages.length === 0) return null;
|
|
136
|
+
|
|
137
|
+
// Return language with highest confidence
|
|
138
|
+
return languages.reduce((prev, current) =>
|
|
139
|
+
current.confidence > prev.confidence ? current : prev
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Analyze language-specific patterns in content
|
|
145
|
+
*
|
|
146
|
+
* @param {string} content - File content to analyze
|
|
147
|
+
* @param {string} language - Language to check for
|
|
148
|
+
* @returns {Object} Pattern detection result
|
|
149
|
+
*/
|
|
150
|
+
export function analyzeLanguagePatterns(content, language) {
|
|
151
|
+
const patterns = {
|
|
152
|
+
TypeScript: {
|
|
153
|
+
features: [
|
|
154
|
+
{ name: 'interfaces', regex: /interface\s+\w+/g },
|
|
155
|
+
{ name: 'types', regex: /type\s+\w+\s*=/g },
|
|
156
|
+
{ name: 'generics', regex: /<[A-Z]\w*>/g },
|
|
157
|
+
{ name: 'decorators', regex: /@\w+/g }
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
JavaScript: {
|
|
161
|
+
features: [
|
|
162
|
+
{ name: 'async/await', regex: /async\s+function|await\s+/g },
|
|
163
|
+
{ name: 'arrow functions', regex: /=>\s*{|=>\s*\w+/g },
|
|
164
|
+
{ name: 'destructuring', regex: /const\s*{[^}]+}\s*=/g },
|
|
165
|
+
{ name: 'template literals', regex: /`[^`]*\${[^}]*}[^`]*`/g }
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
Python: {
|
|
169
|
+
features: [
|
|
170
|
+
{ name: 'classes', regex: /class\s+\w+:/g },
|
|
171
|
+
{ name: 'decorators', regex: /@\w+/g },
|
|
172
|
+
{ name: 'list comprehensions', regex: /\[[^\]]+for\s+\w+\s+in\s+[^\]]+\]/g },
|
|
173
|
+
{ name: 'type hints', regex: /:\s*\w+\s*(?:=|\))/g }
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const langPatterns = patterns[language];
|
|
179
|
+
if (!langPatterns) {
|
|
180
|
+
return { detected: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const detected = {};
|
|
184
|
+
for (const { name, regex } of langPatterns.features) {
|
|
185
|
+
const matches = content.match(regex);
|
|
186
|
+
detected[name] = matches ? matches.length : 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
detected: true,
|
|
191
|
+
features: detected
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default {
|
|
196
|
+
detectLanguages,
|
|
197
|
+
getPrimaryLanguage,
|
|
198
|
+
analyzeLanguagePatterns
|
|
199
|
+
};
|