@itz4blitz/agentful 1.2.0 → 1.3.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 +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Convention Detector
|
|
6
|
-
*
|
|
7
|
-
* Extracts coding conventions and style guidelines:
|
|
8
|
-
* - Naming conventions (camelCase, PascalCase, snake_case, kebab-case)
|
|
9
|
-
* - File structure organization (feature-based, layer-based, domain-driven)
|
|
10
|
-
* - Code style (indentation, quotes, semicolons)
|
|
11
|
-
* - Import style (named, default, relative vs absolute)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Detect naming conventions from files
|
|
16
|
-
*
|
|
17
|
-
* @param {string[]} files - Array of file paths
|
|
18
|
-
* @returns {Object} Naming convention analysis
|
|
19
|
-
*/
|
|
20
|
-
export function detectNamingConventions(files) {
|
|
21
|
-
const patterns = {
|
|
22
|
-
camelCase: 0,
|
|
23
|
-
PascalCase: 0,
|
|
24
|
-
'snake_case': 0,
|
|
25
|
-
'kebab-case': 0
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
for (const file of files) {
|
|
29
|
-
const basename = path.basename(file, path.extname(file));
|
|
30
|
-
|
|
31
|
-
if (/^[a-z][a-zA-Z0-9]*$/.test(basename)) {
|
|
32
|
-
patterns.camelCase++;
|
|
33
|
-
} else if (/^[A-Z][a-zA-Z0-9]*$/.test(basename)) {
|
|
34
|
-
patterns.PascalCase++;
|
|
35
|
-
} else if (/^[a-z][a-z0-9_]*$/.test(basename)) {
|
|
36
|
-
patterns['snake_case']++;
|
|
37
|
-
} else if (/^[a-z][a-z0-9-]*$/.test(basename)) {
|
|
38
|
-
patterns['kebab-case']++;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Determine dominant pattern
|
|
43
|
-
const sortedPatterns = Object.entries(patterns)
|
|
44
|
-
.sort(([, a], [, b]) => b - a);
|
|
45
|
-
|
|
46
|
-
const totalFiles = files.length;
|
|
47
|
-
const [dominant, count] = sortedPatterns[0];
|
|
48
|
-
const confidence = totalFiles > 0 ? Math.round((count / totalFiles) * 100) : 0;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
dominant,
|
|
52
|
-
confidence,
|
|
53
|
-
distribution: patterns
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Detect file structure organization
|
|
59
|
-
*
|
|
60
|
-
* @param {string[]} files - Array of file paths
|
|
61
|
-
* @returns {Object} File structure analysis
|
|
62
|
-
*/
|
|
63
|
-
export function detectFileStructure(files) {
|
|
64
|
-
const indicators = {
|
|
65
|
-
'feature-based': 0,
|
|
66
|
-
'layer-based': 0,
|
|
67
|
-
'domain-driven': 0,
|
|
68
|
-
'atomic': 0
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Feature-based indicators
|
|
72
|
-
const hasFeatureDirs = files.some(file =>
|
|
73
|
-
file.includes('/features/') || file.match(/\/[a-z]+-feature\//)
|
|
74
|
-
);
|
|
75
|
-
if (hasFeatureDirs) indicators['feature-based'] += 3;
|
|
76
|
-
|
|
77
|
-
// Layer-based indicators (controllers, models, views pattern)
|
|
78
|
-
const layers = ['controllers', 'models', 'views', 'services', 'repositories'];
|
|
79
|
-
const layerCount = layers.filter(layer =>
|
|
80
|
-
files.some(file => file.includes(`/${layer}/`))
|
|
81
|
-
).length;
|
|
82
|
-
indicators['layer-based'] = layerCount;
|
|
83
|
-
|
|
84
|
-
// Domain-driven indicators
|
|
85
|
-
const hasDomains = files.some(file =>
|
|
86
|
-
file.includes('/domains/') || file.includes('/domain/')
|
|
87
|
-
);
|
|
88
|
-
const hasAggregates = files.some(file =>
|
|
89
|
-
file.includes('/aggregates/') || file.includes('/entities/')
|
|
90
|
-
);
|
|
91
|
-
if (hasDomains || hasAggregates) {
|
|
92
|
-
indicators['domain-driven'] += 2;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Atomic design indicators
|
|
96
|
-
const hasAtoms = files.some(file =>
|
|
97
|
-
file.includes('/atoms/') || file.includes('/molecules/') || file.includes('/organisms/')
|
|
98
|
-
);
|
|
99
|
-
if (hasAtoms) indicators['atomic'] += 3;
|
|
100
|
-
|
|
101
|
-
// Determine dominant structure
|
|
102
|
-
const sortedIndicators = Object.entries(indicators)
|
|
103
|
-
.sort(([, a], [, b]) => b - a);
|
|
104
|
-
|
|
105
|
-
const [structure, score] = sortedIndicators[0];
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
structure: score > 0 ? structure : 'mixed',
|
|
109
|
-
confidence: Math.min(90, score * 20),
|
|
110
|
-
indicators
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Detect code style conventions from file content
|
|
116
|
-
*
|
|
117
|
-
* @param {string} content - File content to analyze
|
|
118
|
-
* @returns {Object} Code style analysis
|
|
119
|
-
*/
|
|
120
|
-
export function detectCodeStyle(content) {
|
|
121
|
-
const style = {
|
|
122
|
-
indentation: 'unknown',
|
|
123
|
-
quotes: 'unknown',
|
|
124
|
-
semicolons: 'unknown',
|
|
125
|
-
trailingCommas: 'unknown'
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Detect indentation
|
|
129
|
-
const lines = content.split('\n');
|
|
130
|
-
let twoSpaces = 0;
|
|
131
|
-
let fourSpaces = 0;
|
|
132
|
-
let tabs = 0;
|
|
133
|
-
|
|
134
|
-
for (const line of lines) {
|
|
135
|
-
const match = line.match(/^(\s+)/);
|
|
136
|
-
if (match) {
|
|
137
|
-
const indent = match[1];
|
|
138
|
-
if (indent === ' ') twoSpaces++;
|
|
139
|
-
else if (indent === ' ') fourSpaces++;
|
|
140
|
-
else if (indent.includes('\t')) tabs++;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (twoSpaces > fourSpaces && twoSpaces > tabs) {
|
|
145
|
-
style.indentation = '2 spaces';
|
|
146
|
-
} else if (fourSpaces > twoSpaces && fourSpaces > tabs) {
|
|
147
|
-
style.indentation = '4 spaces';
|
|
148
|
-
} else if (tabs > twoSpaces && tabs > fourSpaces) {
|
|
149
|
-
style.indentation = 'tabs';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Detect quotes
|
|
153
|
-
const singleQuotes = (content.match(/'\w+'/g) || []).length;
|
|
154
|
-
const doubleQuotes = (content.match(/"\w+"/g) || []).length;
|
|
155
|
-
const backticks = (content.match(/`[^`]+`/g) || []).length;
|
|
156
|
-
|
|
157
|
-
if (singleQuotes > doubleQuotes && singleQuotes > backticks) {
|
|
158
|
-
style.quotes = 'single';
|
|
159
|
-
} else if (doubleQuotes > singleQuotes && doubleQuotes > backticks) {
|
|
160
|
-
style.quotes = 'double';
|
|
161
|
-
} else if (backticks > 0) {
|
|
162
|
-
style.quotes = 'template literals preferred';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Detect semicolons
|
|
166
|
-
const withSemicolon = (content.match(/;$/gm) || []).length;
|
|
167
|
-
const withoutSemicolon = (content.match(/[^;{}\s]$/gm) || []).length;
|
|
168
|
-
|
|
169
|
-
style.semicolons = withSemicolon > withoutSemicolon ? 'required' : 'omitted';
|
|
170
|
-
|
|
171
|
-
// Detect trailing commas
|
|
172
|
-
const trailingCommas = (content.match(/,\s*[\]}]/g) || []).length;
|
|
173
|
-
style.trailingCommas = trailingCommas > 5 ? 'preferred' : 'avoided';
|
|
174
|
-
|
|
175
|
-
return style;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Detect import style conventions
|
|
180
|
-
*
|
|
181
|
-
* @param {string} content - File content to analyze
|
|
182
|
-
* @returns {Object} Import style analysis
|
|
183
|
-
*/
|
|
184
|
-
export function detectImportStyle(content) {
|
|
185
|
-
const style = {
|
|
186
|
-
namedImports: 0,
|
|
187
|
-
defaultImports: 0,
|
|
188
|
-
namespaceImports: 0,
|
|
189
|
-
relativeImports: 0,
|
|
190
|
-
absoluteImports: 0,
|
|
191
|
-
aliasedImports: 0
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const importRegex = /import\s+(?:{[^}]+}|[\w]+|\*\s+as\s+[\w]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
195
|
-
let match;
|
|
196
|
-
|
|
197
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
198
|
-
const importStatement = match[0];
|
|
199
|
-
const importPath = match[1];
|
|
200
|
-
|
|
201
|
-
// Count import types
|
|
202
|
-
if (importStatement.includes('{')) {
|
|
203
|
-
style.namedImports++;
|
|
204
|
-
} else if (importStatement.includes('* as')) {
|
|
205
|
-
style.namespaceImports++;
|
|
206
|
-
} else {
|
|
207
|
-
style.defaultImports++;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Count path types
|
|
211
|
-
if (importPath.startsWith('.')) {
|
|
212
|
-
style.relativeImports++;
|
|
213
|
-
} else if (importPath.startsWith('@/') || importPath.startsWith('~/')) {
|
|
214
|
-
style.aliasedImports++;
|
|
215
|
-
} else {
|
|
216
|
-
style.absoluteImports++;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Determine preferences
|
|
221
|
-
const total = style.namedImports + style.defaultImports + style.namespaceImports;
|
|
222
|
-
const preference = total > 0 ? {
|
|
223
|
-
importType: style.namedImports > style.defaultImports ? 'named' : 'default',
|
|
224
|
-
pathStyle: style.aliasedImports > style.relativeImports ? 'aliased' :
|
|
225
|
-
style.relativeImports > 0 ? 'relative' : 'absolute'
|
|
226
|
-
} : null;
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
...style,
|
|
230
|
-
preference
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Detect ESLint/Prettier configuration
|
|
236
|
-
*
|
|
237
|
-
* @param {string[]} files - Array of file paths
|
|
238
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
239
|
-
* @returns {Promise<Object>} Linting configuration
|
|
240
|
-
*/
|
|
241
|
-
export async function detectLintingConfig(files, projectRoot) {
|
|
242
|
-
const config = {
|
|
243
|
-
eslint: false,
|
|
244
|
-
prettier: false,
|
|
245
|
-
styleGuide: null
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// Check for config files
|
|
249
|
-
const hasEslint = files.some(file =>
|
|
250
|
-
file.endsWith('.eslintrc') ||
|
|
251
|
-
file.endsWith('.eslintrc.js') ||
|
|
252
|
-
file.endsWith('.eslintrc.json') ||
|
|
253
|
-
file.endsWith('eslint.config.js')
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
const hasPrettier = files.some(file =>
|
|
257
|
-
file.endsWith('.prettierrc') ||
|
|
258
|
-
file.endsWith('.prettierrc.js') ||
|
|
259
|
-
file.endsWith('.prettierrc.json') ||
|
|
260
|
-
file.endsWith('prettier.config.js')
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
config.eslint = hasEslint;
|
|
264
|
-
config.prettier = hasPrettier;
|
|
265
|
-
|
|
266
|
-
// Try to detect style guide from package.json
|
|
267
|
-
try {
|
|
268
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
269
|
-
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
270
|
-
const packageJson = JSON.parse(content);
|
|
271
|
-
|
|
272
|
-
const allDeps = {
|
|
273
|
-
...packageJson.dependencies,
|
|
274
|
-
...packageJson.devDependencies
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
if (allDeps['eslint-config-airbnb']) {
|
|
278
|
-
config.styleGuide = 'airbnb';
|
|
279
|
-
} else if (allDeps['eslint-config-standard']) {
|
|
280
|
-
config.styleGuide = 'standard';
|
|
281
|
-
} else if (allDeps['eslint-config-google']) {
|
|
282
|
-
config.styleGuide = 'google';
|
|
283
|
-
} else if (allDeps['@typescript-eslint/eslint-plugin']) {
|
|
284
|
-
config.styleGuide = 'typescript';
|
|
285
|
-
}
|
|
286
|
-
} catch (error) {
|
|
287
|
-
// Can't read package.json, skip style guide detection
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return config;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Detect all conventions in project
|
|
295
|
-
*
|
|
296
|
-
* @param {string[]} files - Array of file paths
|
|
297
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
298
|
-
* @returns {Promise<Object>} All detected conventions
|
|
299
|
-
*/
|
|
300
|
-
export async function detectConventions(files, projectRoot) {
|
|
301
|
-
const naming = detectNamingConventions(files);
|
|
302
|
-
const fileStructure = detectFileStructure(files);
|
|
303
|
-
const linting = await detectLintingConfig(files, projectRoot);
|
|
304
|
-
|
|
305
|
-
// Sample a few files for code style analysis
|
|
306
|
-
let codeStyle = null;
|
|
307
|
-
let importStyle = null;
|
|
308
|
-
|
|
309
|
-
const sampleFiles = files
|
|
310
|
-
.filter(file => /\.(js|ts|jsx|tsx)$/.test(file))
|
|
311
|
-
.slice(0, 3);
|
|
312
|
-
|
|
313
|
-
if (sampleFiles.length > 0) {
|
|
314
|
-
try {
|
|
315
|
-
const samplePath = path.join(projectRoot, sampleFiles[0]);
|
|
316
|
-
const content = await fs.readFile(samplePath, 'utf-8');
|
|
317
|
-
codeStyle = detectCodeStyle(content);
|
|
318
|
-
importStyle = detectImportStyle(content);
|
|
319
|
-
} catch (error) {
|
|
320
|
-
// Can't read sample file, skip style detection
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
naming: naming.dominant,
|
|
326
|
-
namingConfidence: naming.confidence,
|
|
327
|
-
fileStructure: fileStructure.structure,
|
|
328
|
-
structureConfidence: fileStructure.confidence,
|
|
329
|
-
codeStyle,
|
|
330
|
-
importStyle,
|
|
331
|
-
linting
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export default {
|
|
336
|
-
detectConventions,
|
|
337
|
-
detectNamingConventions,
|
|
338
|
-
detectFileStructure,
|
|
339
|
-
detectCodeStyle,
|
|
340
|
-
detectImportStyle,
|
|
341
|
-
detectLintingConfig
|
|
342
|
-
};
|
|
@@ -1,276 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
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';
|