@itz4blitz/agentful 0.4.0 → 0.5.1
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 +133 -5
- 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 +232 -9
- 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
package/lib/core/cli.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Codebase Analyzer CLI
|
|
5
|
+
*
|
|
6
|
+
* Simple CLI for testing the analyzer
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node lib/core/cli.js [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --project <path> Project root path (default: current directory)
|
|
13
|
+
* --output <path> Output file path (default: .agentful/architecture.json)
|
|
14
|
+
* --force Force re-analysis even if cache is fresh
|
|
15
|
+
* --verbose Show detailed progress
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { CodebaseAnalyzer } from './analyzer.js';
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
|
|
22
|
+
// Parse CLI arguments
|
|
23
|
+
const options = {
|
|
24
|
+
projectRoot: process.cwd(),
|
|
25
|
+
outputPath: '.agentful/architecture.json',
|
|
26
|
+
force: false,
|
|
27
|
+
verbose: false
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
const arg = args[i];
|
|
32
|
+
|
|
33
|
+
if (arg === '--project' && args[i + 1]) {
|
|
34
|
+
options.projectRoot = args[++i];
|
|
35
|
+
} else if (arg === '--output' && args[i + 1]) {
|
|
36
|
+
options.outputPath = args[++i];
|
|
37
|
+
} else if (arg === '--force') {
|
|
38
|
+
options.force = true;
|
|
39
|
+
} else if (arg === '--verbose') {
|
|
40
|
+
options.verbose = true;
|
|
41
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
42
|
+
console.log(`
|
|
43
|
+
Codebase Analyzer CLI
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
node lib/core/cli.js [options]
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--project <path> Project root path (default: current directory)
|
|
50
|
+
--output <path> Output file path (default: .agentful/architecture.json)
|
|
51
|
+
--force Force re-analysis even if cache is fresh
|
|
52
|
+
--verbose Show detailed progress
|
|
53
|
+
--help, -h Show this help message
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
# Analyze current directory
|
|
57
|
+
node lib/core/cli.js
|
|
58
|
+
|
|
59
|
+
# Analyze specific project
|
|
60
|
+
node lib/core/cli.js --project /path/to/project
|
|
61
|
+
|
|
62
|
+
# Force re-analysis
|
|
63
|
+
node lib/core/cli.js --force --verbose
|
|
64
|
+
`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Create analyzer
|
|
70
|
+
const analyzer = new CodebaseAnalyzer(options);
|
|
71
|
+
|
|
72
|
+
// Setup event listeners
|
|
73
|
+
analyzer.on('start', ({ projectRoot }) => {
|
|
74
|
+
console.log(`\n🔍 Analyzing codebase: ${projectRoot}\n`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
analyzer.on('progress', ({ stage, progress, fileCount, count }) => {
|
|
78
|
+
if (options.verbose) {
|
|
79
|
+
let message = ` ${stage}: ${progress}%`;
|
|
80
|
+
if (fileCount) message += ` (${fileCount} files)`;
|
|
81
|
+
if (count) message += ` (${count} detected)`;
|
|
82
|
+
console.log(message);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
analyzer.on('warning', ({ message }) => {
|
|
87
|
+
if (options.verbose) {
|
|
88
|
+
console.warn(` ⚠️ ${message}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
analyzer.on('written', ({ path }) => {
|
|
93
|
+
console.log(`\n✅ Analysis written to: ${path}\n`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
analyzer.on('complete', ({ duration, analysis }) => {
|
|
97
|
+
console.log(`⏱️ Completed in ${duration}ms\n`);
|
|
98
|
+
|
|
99
|
+
// Print summary
|
|
100
|
+
console.log('📊 Summary:');
|
|
101
|
+
console.log(` Files analyzed: ${analysis.fileCount}`);
|
|
102
|
+
console.log(` Confidence: ${analysis.confidence}%`);
|
|
103
|
+
console.log(` Primary language: ${analysis.primaryLanguage || 'unknown'}`);
|
|
104
|
+
console.log(` Languages: ${analysis.languages.map(l => l.name).join(', ')}`);
|
|
105
|
+
console.log(` Frameworks: ${analysis.frameworks.map(f => f.name).join(', ') || 'none'}`);
|
|
106
|
+
|
|
107
|
+
if (analysis.recommendations.length > 0) {
|
|
108
|
+
console.log(`\n💡 Recommendations:`);
|
|
109
|
+
analysis.recommendations.forEach((rec, i) => {
|
|
110
|
+
console.log(` ${i + 1}. [${rec.priority}] ${rec.message}`);
|
|
111
|
+
console.log(` → ${rec.action}`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log('');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
analyzer.on('error', (error) => {
|
|
119
|
+
console.error(`\n❌ Analysis failed: ${error.message}\n`);
|
|
120
|
+
if (options.verbose) {
|
|
121
|
+
console.error(error.stack);
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Run analysis
|
|
127
|
+
(async () => {
|
|
128
|
+
try {
|
|
129
|
+
if (options.force) {
|
|
130
|
+
await analyzer.analyze();
|
|
131
|
+
} else {
|
|
132
|
+
await analyzer.analyzeWithCache();
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(`\n❌ Unexpected error: ${error.message}\n`);
|
|
136
|
+
if (options.verbose) {
|
|
137
|
+
console.error(error.stack);
|
|
138
|
+
}
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
@@ -0,0 +1,342 @@
|
|
|
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
|
+
};
|