@corbat-tech/coding-standards-mcp 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/LICENSE +21 -0
- package/README.md +371 -0
- package/assets/demo.gif +0 -0
- package/dist/agent.d.ts +53 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +629 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +651 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config.d.ts +73 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +105 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles.d.ts +39 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +526 -0
- package/dist/profiles.js.map +1 -0
- package/dist/prompts-legacy.d.ts +25 -0
- package/dist/prompts-legacy.d.ts.map +1 -0
- package/dist/prompts-legacy.js +600 -0
- package/dist/prompts-legacy.js.map +1 -0
- package/dist/prompts-v2.d.ts +30 -0
- package/dist/prompts-v2.d.ts.map +1 -0
- package/dist/prompts-v2.js +310 -0
- package/dist/prompts-v2.js.map +1 -0
- package/dist/prompts.d.ts +30 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +310 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +18 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +95 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools-legacy.d.ts +196 -0
- package/dist/tools-legacy.d.ts.map +1 -0
- package/dist/tools-legacy.js +1230 -0
- package/dist/tools-legacy.js.map +1 -0
- package/dist/tools-v2.d.ts +92 -0
- package/dist/tools-v2.d.ts.map +1 -0
- package/dist/tools-v2.js +410 -0
- package/dist/tools-v2.js.map +1 -0
- package/dist/tools.d.ts +92 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +410 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +3054 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +515 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/retry.d.ts +44 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +74 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +79 -0
- package/profiles/README.md +199 -0
- package/profiles/custom/.gitkeep +2 -0
- package/profiles/templates/_template.yaml +159 -0
- package/profiles/templates/angular.yaml +494 -0
- package/profiles/templates/java-spring-backend.yaml +512 -0
- package/profiles/templates/minimal.yaml +102 -0
- package/profiles/templates/nodejs.yaml +338 -0
- package/profiles/templates/python.yaml +340 -0
- package/profiles/templates/react.yaml +331 -0
- package/profiles/templates/vue.yaml +598 -0
- package/standards/architecture/ddd.md +173 -0
- package/standards/architecture/hexagonal.md +97 -0
- package/standards/cicd/github-actions.md +567 -0
- package/standards/clean-code/naming.md +175 -0
- package/standards/clean-code/principles.md +179 -0
- package/standards/containerization/dockerfile.md +419 -0
- package/standards/database/selection-guide.md +443 -0
- package/standards/documentation/guidelines.md +189 -0
- package/standards/event-driven/domain-events.md +527 -0
- package/standards/kubernetes/deployment.md +518 -0
- package/standards/observability/guidelines.md +665 -0
- package/standards/project-setup/initialization-checklist.md +650 -0
- package/standards/spring-boot/best-practices.md +598 -0
- package/standards/testing/guidelines.md +559 -0
- package/standards/workflow/llm-development-workflow.md +542 -0
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { access, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { stringify } from 'yaml';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
// Colors for terminal output
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bright: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
};
|
|
21
|
+
const c = colors;
|
|
22
|
+
class CorbatInit {
|
|
23
|
+
rl;
|
|
24
|
+
projectDir;
|
|
25
|
+
detectedInfo = { language: 'unknown' };
|
|
26
|
+
constructor() {
|
|
27
|
+
this.rl = createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout,
|
|
30
|
+
});
|
|
31
|
+
this.projectDir = process.cwd();
|
|
32
|
+
}
|
|
33
|
+
print(text) {
|
|
34
|
+
console.log(text);
|
|
35
|
+
}
|
|
36
|
+
async question(prompt) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
this.rl.question(prompt, (answer) => {
|
|
39
|
+
resolve(answer.trim());
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async select(prompt, options, defaultIndex = 0) {
|
|
44
|
+
this.print(`\n${c.cyan}${prompt}${c.reset}`);
|
|
45
|
+
options.forEach((opt, i) => {
|
|
46
|
+
const marker = i === defaultIndex ? `${c.green}→${c.reset}` : ' ';
|
|
47
|
+
const highlight = i === defaultIndex ? c.bright : c.dim;
|
|
48
|
+
this.print(` ${marker} ${highlight}${i + 1}) ${opt}${c.reset}`);
|
|
49
|
+
});
|
|
50
|
+
const answer = await this.question(`\n${c.yellow}Enter number [${defaultIndex + 1}]: ${c.reset}`);
|
|
51
|
+
const index = answer ? Number.parseInt(answer, 10) - 1 : defaultIndex;
|
|
52
|
+
if (index >= 0 && index < options.length) {
|
|
53
|
+
return options[index];
|
|
54
|
+
}
|
|
55
|
+
return options[defaultIndex];
|
|
56
|
+
}
|
|
57
|
+
async confirm(prompt, defaultYes = true) {
|
|
58
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
59
|
+
const answer = await this.question(`${c.cyan}${prompt}${c.reset} ${c.dim}${hint}${c.reset} `);
|
|
60
|
+
if (!answer)
|
|
61
|
+
return defaultYes;
|
|
62
|
+
return answer.toLowerCase().startsWith('y');
|
|
63
|
+
}
|
|
64
|
+
async input(prompt, defaultValue) {
|
|
65
|
+
const hint = defaultValue ? ` ${c.dim}[${defaultValue}]${c.reset}` : '';
|
|
66
|
+
const answer = await this.question(`${c.cyan}${prompt}${c.reset}${hint}: `);
|
|
67
|
+
return answer || defaultValue || '';
|
|
68
|
+
}
|
|
69
|
+
async detectProject() {
|
|
70
|
+
this.print(`\n${c.blue}🔍 Scanning project...${c.reset}\n`);
|
|
71
|
+
// Check for Java/Maven/Gradle
|
|
72
|
+
try {
|
|
73
|
+
await access(join(this.projectDir, 'pom.xml'));
|
|
74
|
+
this.detectedInfo.language = 'Java';
|
|
75
|
+
this.detectedInfo.buildTool = 'Maven';
|
|
76
|
+
const pomContent = await readFile(join(this.projectDir, 'pom.xml'), 'utf-8');
|
|
77
|
+
// Detect Java version
|
|
78
|
+
const javaMatch = pomContent.match(/<java\.version>(\d+)<\/java\.version>/);
|
|
79
|
+
if (javaMatch)
|
|
80
|
+
this.detectedInfo.javaVersion = javaMatch[1];
|
|
81
|
+
// Detect Spring Boot version
|
|
82
|
+
const springMatch = pomContent.match(/<version>(\d+\.\d+\.\d+)<\/version>[\s\S]*?spring-boot/i);
|
|
83
|
+
const springParentMatch = pomContent.match(/spring-boot-starter-parent[\s\S]*?<version>(\d+\.\d+\.\d+)/);
|
|
84
|
+
if (springParentMatch) {
|
|
85
|
+
this.detectedInfo.framework = 'Spring Boot';
|
|
86
|
+
this.detectedInfo.springVersion = springParentMatch[1];
|
|
87
|
+
}
|
|
88
|
+
else if (springMatch) {
|
|
89
|
+
this.detectedInfo.framework = 'Spring Boot';
|
|
90
|
+
this.detectedInfo.springVersion = springMatch[1];
|
|
91
|
+
}
|
|
92
|
+
this.detectedInfo.testFramework = 'JUnit5';
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Not Maven
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await access(join(this.projectDir, 'build.gradle'));
|
|
99
|
+
this.detectedInfo.language = 'Java';
|
|
100
|
+
this.detectedInfo.buildTool = 'Gradle';
|
|
101
|
+
const gradleContent = await readFile(join(this.projectDir, 'build.gradle'), 'utf-8');
|
|
102
|
+
const javaMatch = gradleContent.match(/sourceCompatibility\s*=\s*['"]?(\d+)/);
|
|
103
|
+
if (javaMatch)
|
|
104
|
+
this.detectedInfo.javaVersion = javaMatch[1];
|
|
105
|
+
if (gradleContent.includes('spring-boot')) {
|
|
106
|
+
this.detectedInfo.framework = 'Spring Boot';
|
|
107
|
+
const springMatch = gradleContent.match(/springBootVersion\s*=\s*['"](\d+\.\d+\.\d+)/);
|
|
108
|
+
if (springMatch)
|
|
109
|
+
this.detectedInfo.springVersion = springMatch[1];
|
|
110
|
+
}
|
|
111
|
+
this.detectedInfo.testFramework = 'JUnit5';
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Not Gradle
|
|
115
|
+
}
|
|
116
|
+
// Check for Node.js/TypeScript
|
|
117
|
+
try {
|
|
118
|
+
await access(join(this.projectDir, 'package.json'));
|
|
119
|
+
const pkgContent = await readFile(join(this.projectDir, 'package.json'), 'utf-8');
|
|
120
|
+
const pkg = JSON.parse(pkgContent);
|
|
121
|
+
if (this.detectedInfo.language === 'unknown') {
|
|
122
|
+
this.detectedInfo.language = pkg.devDependencies?.typescript ? 'TypeScript' : 'JavaScript';
|
|
123
|
+
this.detectedInfo.buildTool = 'npm';
|
|
124
|
+
}
|
|
125
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
126
|
+
if (deps.react)
|
|
127
|
+
this.detectedInfo.framework = 'React';
|
|
128
|
+
else if (deps.vue)
|
|
129
|
+
this.detectedInfo.framework = 'Vue';
|
|
130
|
+
else if (deps['@nestjs/core'])
|
|
131
|
+
this.detectedInfo.framework = 'NestJS';
|
|
132
|
+
else if (deps.express)
|
|
133
|
+
this.detectedInfo.framework = 'Express';
|
|
134
|
+
else if (deps.fastify)
|
|
135
|
+
this.detectedInfo.framework = 'Fastify';
|
|
136
|
+
if (deps.vitest)
|
|
137
|
+
this.detectedInfo.testFramework = 'Vitest';
|
|
138
|
+
else if (deps.jest)
|
|
139
|
+
this.detectedInfo.testFramework = 'Jest';
|
|
140
|
+
else if (deps.mocha)
|
|
141
|
+
this.detectedInfo.testFramework = 'Mocha';
|
|
142
|
+
if (pkg.engines?.node) {
|
|
143
|
+
this.detectedInfo.nodeVersion = pkg.engines.node.replace(/[^\d.]/g, '');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Not Node.js
|
|
148
|
+
}
|
|
149
|
+
// Check for Python
|
|
150
|
+
try {
|
|
151
|
+
await access(join(this.projectDir, 'pyproject.toml'));
|
|
152
|
+
this.detectedInfo.language = 'Python';
|
|
153
|
+
this.detectedInfo.buildTool = 'Poetry';
|
|
154
|
+
const pyContent = await readFile(join(this.projectDir, 'pyproject.toml'), 'utf-8');
|
|
155
|
+
if (pyContent.includes('fastapi'))
|
|
156
|
+
this.detectedInfo.framework = 'FastAPI';
|
|
157
|
+
else if (pyContent.includes('django'))
|
|
158
|
+
this.detectedInfo.framework = 'Django';
|
|
159
|
+
else if (pyContent.includes('flask'))
|
|
160
|
+
this.detectedInfo.framework = 'Flask';
|
|
161
|
+
const pythonMatch = pyContent.match(/python\s*=\s*["'][\^~]?(\d+\.\d+)/);
|
|
162
|
+
if (pythonMatch)
|
|
163
|
+
this.detectedInfo.pythonVersion = pythonMatch[1];
|
|
164
|
+
this.detectedInfo.testFramework = 'pytest';
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Not Python with pyproject.toml
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
await access(join(this.projectDir, 'requirements.txt'));
|
|
171
|
+
if (this.detectedInfo.language === 'unknown') {
|
|
172
|
+
this.detectedInfo.language = 'Python';
|
|
173
|
+
this.detectedInfo.buildTool = 'pip';
|
|
174
|
+
this.detectedInfo.testFramework = 'pytest';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// No requirements.txt
|
|
179
|
+
}
|
|
180
|
+
// Check for Docker/K8s
|
|
181
|
+
try {
|
|
182
|
+
await access(join(this.projectDir, 'Dockerfile'));
|
|
183
|
+
this.detectedInfo.hasDocker = true;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
this.detectedInfo.hasDocker = false;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const files = await readdir(this.projectDir);
|
|
190
|
+
this.detectedInfo.hasKubernetes = files.some((f) => f.includes('k8s') || f.includes('kubernetes') || f.endsWith('.yaml'));
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
this.detectedInfo.hasKubernetes = false;
|
|
194
|
+
}
|
|
195
|
+
// Check for CI/CD
|
|
196
|
+
try {
|
|
197
|
+
await access(join(this.projectDir, '.github/workflows'));
|
|
198
|
+
this.detectedInfo.hasCICD = true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
try {
|
|
202
|
+
await access(join(this.projectDir, '.gitlab-ci.yml'));
|
|
203
|
+
this.detectedInfo.hasCICD = true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
this.detectedInfo.hasCICD = false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
printDetectedInfo() {
|
|
211
|
+
const info = this.detectedInfo;
|
|
212
|
+
this.print(`${c.green}✓ Detected configuration:${c.reset}\n`);
|
|
213
|
+
this.print(` ${c.bright}Language:${c.reset} ${info.language}`);
|
|
214
|
+
if (info.framework) {
|
|
215
|
+
this.print(` ${c.bright}Framework:${c.reset} ${info.framework}${info.springVersion ? ` ${info.springVersion}` : ''}`);
|
|
216
|
+
}
|
|
217
|
+
if (info.buildTool) {
|
|
218
|
+
this.print(` ${c.bright}Build Tool:${c.reset} ${info.buildTool}`);
|
|
219
|
+
}
|
|
220
|
+
if (info.javaVersion) {
|
|
221
|
+
this.print(` ${c.bright}Java Version:${c.reset} ${info.javaVersion}`);
|
|
222
|
+
}
|
|
223
|
+
if (info.nodeVersion) {
|
|
224
|
+
this.print(` ${c.bright}Node Version:${c.reset} ${info.nodeVersion}`);
|
|
225
|
+
}
|
|
226
|
+
if (info.pythonVersion) {
|
|
227
|
+
this.print(` ${c.bright}Python:${c.reset} ${info.pythonVersion}`);
|
|
228
|
+
}
|
|
229
|
+
if (info.testFramework) {
|
|
230
|
+
this.print(` ${c.bright}Tests:${c.reset} ${info.testFramework}`);
|
|
231
|
+
}
|
|
232
|
+
if (info.hasDocker) {
|
|
233
|
+
this.print(` ${c.bright}Docker:${c.reset} Yes`);
|
|
234
|
+
}
|
|
235
|
+
if (info.hasCICD) {
|
|
236
|
+
this.print(` ${c.bright}CI/CD:${c.reset} Yes`);
|
|
237
|
+
}
|
|
238
|
+
this.print('');
|
|
239
|
+
}
|
|
240
|
+
async runWizard() {
|
|
241
|
+
const info = this.detectedInfo;
|
|
242
|
+
// Profile name
|
|
243
|
+
const defaultName = info.framework
|
|
244
|
+
? `${info.framework.toLowerCase().replace(/\s+/g, '-')}-project`
|
|
245
|
+
: `${info.language.toLowerCase()}-project`;
|
|
246
|
+
const name = await this.input('Profile name', defaultName);
|
|
247
|
+
const description = await this.input('Description', `${info.framework || info.language} project standards`);
|
|
248
|
+
// Architecture
|
|
249
|
+
this.print(`\n${c.magenta}━━━ Architecture ━━━${c.reset}`);
|
|
250
|
+
const archType = await this.select('Architecture pattern:', [
|
|
251
|
+
'hexagonal (Ports & Adapters)',
|
|
252
|
+
'clean (Clean Architecture)',
|
|
253
|
+
'layered (Traditional N-Tier)',
|
|
254
|
+
'modular-monolith',
|
|
255
|
+
'microservices',
|
|
256
|
+
], 0);
|
|
257
|
+
const enforceLayerDeps = await this.confirm('Enforce layer dependencies?', true);
|
|
258
|
+
// DDD
|
|
259
|
+
this.print(`\n${c.magenta}━━━ Domain-Driven Design ━━━${c.reset}`);
|
|
260
|
+
const dddEnabled = await this.confirm('Enable DDD patterns?', true);
|
|
261
|
+
const ubiquitousLanguage = dddEnabled ? await this.confirm('Enforce ubiquitous language?', true) : false;
|
|
262
|
+
// CQRS
|
|
263
|
+
this.print(`\n${c.magenta}━━━ CQRS ━━━${c.reset}`);
|
|
264
|
+
const cqrsEnabled = await this.confirm('Enable CQRS?', info.framework === 'Spring Boot');
|
|
265
|
+
const cqrsSeparation = cqrsEnabled ? await this.select('CQRS separation:', ['logical', 'physical'], 0) : 'logical';
|
|
266
|
+
// Event-Driven
|
|
267
|
+
this.print(`\n${c.magenta}━━━ Event-Driven Architecture ━━━${c.reset}`);
|
|
268
|
+
const eventDrivenEnabled = await this.confirm('Enable event-driven patterns?', info.framework === 'Spring Boot');
|
|
269
|
+
const eventApproach = eventDrivenEnabled
|
|
270
|
+
? await this.select('Event approach:', ['domain-events', 'event-sourcing'], 0)
|
|
271
|
+
: 'domain-events';
|
|
272
|
+
// Code Quality
|
|
273
|
+
this.print(`\n${c.magenta}━━━ Code Quality Thresholds ━━━${c.reset}`);
|
|
274
|
+
const maxMethodLinesStr = await this.input('Max lines per method', '20');
|
|
275
|
+
const maxClassLinesStr = await this.input('Max lines per class', '200');
|
|
276
|
+
const maxFileLinesStr = await this.input('Max lines per file', '400');
|
|
277
|
+
const maxParamsStr = await this.input('Max method parameters', '4');
|
|
278
|
+
const minCoverageStr = await this.input('Minimum test coverage %', '80');
|
|
279
|
+
// Testing
|
|
280
|
+
this.print(`\n${c.magenta}━━━ Testing ━━━${c.reset}`);
|
|
281
|
+
let testFramework = info.testFramework || 'JUnit5';
|
|
282
|
+
let assertionLib = 'AssertJ';
|
|
283
|
+
let mockingLib = 'Mockito';
|
|
284
|
+
if (info.language === 'Java') {
|
|
285
|
+
testFramework = await this.select('Test framework:', ['JUnit5', 'JUnit4', 'TestNG'], 0);
|
|
286
|
+
assertionLib = await this.select('Assertion library:', ['AssertJ', 'Hamcrest', 'JUnit assertions'], 0);
|
|
287
|
+
mockingLib = await this.select('Mocking library:', ['Mockito', 'EasyMock', 'JMockit'], 0);
|
|
288
|
+
}
|
|
289
|
+
else if (info.language === 'TypeScript' || info.language === 'JavaScript') {
|
|
290
|
+
testFramework = await this.select('Test framework:', ['Vitest', 'Jest', 'Mocha'], info.testFramework === 'Vitest' ? 0 : 1);
|
|
291
|
+
assertionLib = testFramework === 'Vitest' ? 'Vitest' : 'Jest';
|
|
292
|
+
mockingLib = testFramework === 'Vitest' ? 'Vitest' : 'Jest';
|
|
293
|
+
}
|
|
294
|
+
else if (info.language === 'Python') {
|
|
295
|
+
testFramework = await this.select('Test framework:', ['pytest', 'unittest'], 0);
|
|
296
|
+
assertionLib = 'pytest';
|
|
297
|
+
mockingLib = 'pytest-mock';
|
|
298
|
+
}
|
|
299
|
+
// Technologies
|
|
300
|
+
const technologies = [];
|
|
301
|
+
if (info.language === 'Java' && info.javaVersion) {
|
|
302
|
+
technologies.push({ name: 'Java', version: info.javaVersion });
|
|
303
|
+
}
|
|
304
|
+
if (info.framework) {
|
|
305
|
+
technologies.push({
|
|
306
|
+
name: info.framework,
|
|
307
|
+
version: info.springVersion || undefined,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (info.buildTool) {
|
|
311
|
+
technologies.push({ name: info.buildTool });
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
name,
|
|
315
|
+
description,
|
|
316
|
+
architecture: {
|
|
317
|
+
type: archType.split(' ')[0],
|
|
318
|
+
enforceLayerDependencies: enforceLayerDeps,
|
|
319
|
+
},
|
|
320
|
+
ddd: {
|
|
321
|
+
enabled: dddEnabled,
|
|
322
|
+
ubiquitousLanguageEnforced: ubiquitousLanguage,
|
|
323
|
+
},
|
|
324
|
+
cqrs: {
|
|
325
|
+
enabled: cqrsEnabled,
|
|
326
|
+
separation: cqrsSeparation,
|
|
327
|
+
},
|
|
328
|
+
eventDriven: {
|
|
329
|
+
enabled: eventDrivenEnabled,
|
|
330
|
+
approach: eventApproach,
|
|
331
|
+
},
|
|
332
|
+
codeQuality: {
|
|
333
|
+
maxMethodLines: Number.parseInt(maxMethodLinesStr, 10) || 20,
|
|
334
|
+
maxClassLines: Number.parseInt(maxClassLinesStr, 10) || 200,
|
|
335
|
+
maxFileLines: Number.parseInt(maxFileLinesStr, 10) || 400,
|
|
336
|
+
maxMethodParameters: Number.parseInt(maxParamsStr, 10) || 4,
|
|
337
|
+
minimumTestCoverage: Number.parseInt(minCoverageStr, 10) || 80,
|
|
338
|
+
},
|
|
339
|
+
testing: {
|
|
340
|
+
framework: testFramework,
|
|
341
|
+
assertionLibrary: assertionLib,
|
|
342
|
+
mockingLibrary: mockingLib,
|
|
343
|
+
},
|
|
344
|
+
technologies,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
buildYamlProfile(config) {
|
|
348
|
+
const profile = {
|
|
349
|
+
name: config.name,
|
|
350
|
+
description: config.description,
|
|
351
|
+
architecture: {
|
|
352
|
+
type: config.architecture.type,
|
|
353
|
+
enforceLayerDependencies: config.architecture.enforceLayerDependencies,
|
|
354
|
+
layers: this.getDefaultLayers(config.architecture.type),
|
|
355
|
+
},
|
|
356
|
+
ddd: config.ddd.enabled
|
|
357
|
+
? {
|
|
358
|
+
enabled: true,
|
|
359
|
+
ubiquitousLanguageEnforced: config.ddd.ubiquitousLanguageEnforced,
|
|
360
|
+
patterns: {
|
|
361
|
+
aggregates: true,
|
|
362
|
+
entities: true,
|
|
363
|
+
valueObjects: true,
|
|
364
|
+
domainEvents: config.eventDriven.enabled,
|
|
365
|
+
repositories: true,
|
|
366
|
+
domainServices: true,
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
: { enabled: false },
|
|
370
|
+
cqrs: config.cqrs.enabled
|
|
371
|
+
? {
|
|
372
|
+
enabled: true,
|
|
373
|
+
separation: config.cqrs.separation,
|
|
374
|
+
patterns: {
|
|
375
|
+
commands: { suffix: 'Command', handler: 'CommandHandler' },
|
|
376
|
+
queries: { suffix: 'Query', handler: 'QueryHandler' },
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
: { enabled: false },
|
|
380
|
+
eventDriven: config.eventDriven.enabled
|
|
381
|
+
? {
|
|
382
|
+
enabled: true,
|
|
383
|
+
approach: config.eventDriven.approach,
|
|
384
|
+
patterns: {
|
|
385
|
+
domainEvents: { suffix: 'Event', pastTense: true },
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
: { enabled: false },
|
|
389
|
+
codeQuality: {
|
|
390
|
+
maxMethodLines: config.codeQuality.maxMethodLines,
|
|
391
|
+
maxClassLines: config.codeQuality.maxClassLines,
|
|
392
|
+
maxFileLines: config.codeQuality.maxFileLines,
|
|
393
|
+
maxMethodParameters: config.codeQuality.maxMethodParameters,
|
|
394
|
+
maxCyclomaticComplexity: 10,
|
|
395
|
+
requireDocumentation: true,
|
|
396
|
+
requireTests: true,
|
|
397
|
+
minimumTestCoverage: config.codeQuality.minimumTestCoverage,
|
|
398
|
+
principles: ['SOLID', 'DRY', 'KISS', 'YAGNI'],
|
|
399
|
+
},
|
|
400
|
+
naming: this.getNamingConventions(this.detectedInfo.language),
|
|
401
|
+
testing: {
|
|
402
|
+
framework: config.testing.framework,
|
|
403
|
+
assertionLibrary: config.testing.assertionLibrary,
|
|
404
|
+
mockingLibrary: config.testing.mockingLibrary,
|
|
405
|
+
types: {
|
|
406
|
+
unit: { suffix: 'Test', coverage: config.codeQuality.minimumTestCoverage },
|
|
407
|
+
integration: { suffix: 'IT' },
|
|
408
|
+
architecture: { tool: 'ArchUnit', recommended: true },
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
technologies: config.technologies,
|
|
412
|
+
};
|
|
413
|
+
return stringify(profile, { lineWidth: 120 });
|
|
414
|
+
}
|
|
415
|
+
getDefaultLayers(archType) {
|
|
416
|
+
if (archType === 'hexagonal') {
|
|
417
|
+
return [
|
|
418
|
+
{ name: 'domain', description: 'Core business logic. NO external dependencies.', allowedDependencies: [] },
|
|
419
|
+
{
|
|
420
|
+
name: 'application',
|
|
421
|
+
description: 'Use cases and ports. Depends only on domain.',
|
|
422
|
+
allowedDependencies: ['domain'],
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'infrastructure',
|
|
426
|
+
description: 'Adapters and frameworks.',
|
|
427
|
+
allowedDependencies: ['domain', 'application'],
|
|
428
|
+
},
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
if (archType === 'clean') {
|
|
432
|
+
return [
|
|
433
|
+
{ name: 'entities', description: 'Enterprise business rules.', allowedDependencies: [] },
|
|
434
|
+
{ name: 'usecases', description: 'Application business rules.', allowedDependencies: ['entities'] },
|
|
435
|
+
{ name: 'adapters', description: 'Interface adapters.', allowedDependencies: ['entities', 'usecases'] },
|
|
436
|
+
{
|
|
437
|
+
name: 'frameworks',
|
|
438
|
+
description: 'Frameworks and drivers.',
|
|
439
|
+
allowedDependencies: ['entities', 'usecases', 'adapters'],
|
|
440
|
+
},
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
if (archType === 'layered') {
|
|
444
|
+
return [
|
|
445
|
+
{ name: 'presentation', description: 'UI layer.', allowedDependencies: ['business'] },
|
|
446
|
+
{ name: 'business', description: 'Business logic.', allowedDependencies: ['data'] },
|
|
447
|
+
{ name: 'data', description: 'Data access.', allowedDependencies: [] },
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
getNamingConventions(language) {
|
|
453
|
+
if (language === 'Java') {
|
|
454
|
+
return {
|
|
455
|
+
general: {
|
|
456
|
+
class: 'PascalCase',
|
|
457
|
+
interface: 'PascalCase',
|
|
458
|
+
method: 'camelCase',
|
|
459
|
+
variable: 'camelCase',
|
|
460
|
+
constant: 'SCREAMING_SNAKE_CASE',
|
|
461
|
+
package: 'lowercase.dot.separated',
|
|
462
|
+
},
|
|
463
|
+
suffixes: {
|
|
464
|
+
repository: 'Repository',
|
|
465
|
+
service: 'Service',
|
|
466
|
+
controller: 'Controller',
|
|
467
|
+
entity: 'Entity (or none)',
|
|
468
|
+
},
|
|
469
|
+
testing: {
|
|
470
|
+
unitTest: '*Test',
|
|
471
|
+
integrationTest: '*IT',
|
|
472
|
+
testMethod: 'should_ExpectedBehavior_When_Condition',
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (language === 'TypeScript' || language === 'JavaScript') {
|
|
477
|
+
return {
|
|
478
|
+
general: {
|
|
479
|
+
class: 'PascalCase',
|
|
480
|
+
interface: 'PascalCase (no I prefix)',
|
|
481
|
+
function: 'camelCase',
|
|
482
|
+
variable: 'camelCase',
|
|
483
|
+
constant: 'SCREAMING_SNAKE_CASE',
|
|
484
|
+
file: 'kebab-case',
|
|
485
|
+
},
|
|
486
|
+
suffixes: {
|
|
487
|
+
repository: '.repository',
|
|
488
|
+
service: '.service',
|
|
489
|
+
controller: '.controller',
|
|
490
|
+
types: '.types',
|
|
491
|
+
},
|
|
492
|
+
testing: {
|
|
493
|
+
testFile: '*.test.ts or *.spec.ts',
|
|
494
|
+
testMethod: 'should ... when ...',
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (language === 'Python') {
|
|
499
|
+
return {
|
|
500
|
+
general: {
|
|
501
|
+
class: 'PascalCase',
|
|
502
|
+
function: 'snake_case',
|
|
503
|
+
variable: 'snake_case',
|
|
504
|
+
constant: 'SCREAMING_SNAKE_CASE',
|
|
505
|
+
module: 'snake_case',
|
|
506
|
+
},
|
|
507
|
+
suffixes: {
|
|
508
|
+
repository: '_repository',
|
|
509
|
+
service: '_service',
|
|
510
|
+
controller: '_controller',
|
|
511
|
+
},
|
|
512
|
+
testing: {
|
|
513
|
+
testFile: 'test_*.py',
|
|
514
|
+
testMethod: 'test_should_*',
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
general: {
|
|
520
|
+
class: 'PascalCase',
|
|
521
|
+
method: 'camelCase',
|
|
522
|
+
variable: 'camelCase',
|
|
523
|
+
constant: 'SCREAMING_SNAKE_CASE',
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
printSummary(config) {
|
|
528
|
+
this.print(`\n${c.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
|
|
529
|
+
this.print(`${c.green} PROFILE SUMMARY${c.reset}`);
|
|
530
|
+
this.print(`${c.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}\n`);
|
|
531
|
+
this.print(`${c.bright}Name:${c.reset} ${config.name}`);
|
|
532
|
+
this.print(`${c.bright}Description:${c.reset} ${config.description}`);
|
|
533
|
+
this.print('');
|
|
534
|
+
this.print(`${c.bright}Architecture:${c.reset} ${config.architecture.type}`);
|
|
535
|
+
this.print(`${c.bright}DDD:${c.reset} ${config.ddd.enabled ? 'Enabled' : 'Disabled'}`);
|
|
536
|
+
this.print(`${c.bright}CQRS:${c.reset} ${config.cqrs.enabled ? `Enabled (${config.cqrs.separation})` : 'Disabled'}`);
|
|
537
|
+
this.print(`${c.bright}Event-Driven:${c.reset} ${config.eventDriven.enabled ? `Enabled (${config.eventDriven.approach})` : 'Disabled'}`);
|
|
538
|
+
this.print('');
|
|
539
|
+
this.print(`${c.bright}Code Quality:${c.reset}`);
|
|
540
|
+
this.print(` - Max method lines: ${config.codeQuality.maxMethodLines}`);
|
|
541
|
+
this.print(` - Max class lines: ${config.codeQuality.maxClassLines}`);
|
|
542
|
+
this.print(` - Min test coverage: ${config.codeQuality.minimumTestCoverage}%`);
|
|
543
|
+
this.print('');
|
|
544
|
+
this.print(`${c.bright}Testing:${c.reset} ${config.testing.framework} + ${config.testing.assertionLibrary}`);
|
|
545
|
+
this.print(`\n${c.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}\n`);
|
|
546
|
+
}
|
|
547
|
+
async run() {
|
|
548
|
+
this.print(`
|
|
549
|
+
${c.cyan}╔═══════════════════════════════════════════════════════════╗
|
|
550
|
+
║ ║
|
|
551
|
+
║ ${c.bright}🔧 CORBAT PROFILE GENERATOR${c.reset}${c.cyan} ║
|
|
552
|
+
║ ║
|
|
553
|
+
║ Create a custom coding standards profile for your ║
|
|
554
|
+
║ project in seconds. ║
|
|
555
|
+
║ ║
|
|
556
|
+
╚═══════════════════════════════════════════════════════════╝${c.reset}
|
|
557
|
+
`);
|
|
558
|
+
// Detect project
|
|
559
|
+
await this.detectProject();
|
|
560
|
+
if (this.detectedInfo.language !== 'unknown') {
|
|
561
|
+
this.printDetectedInfo();
|
|
562
|
+
const useDetected = await this.confirm('Use detected configuration as base?', true);
|
|
563
|
+
if (!useDetected) {
|
|
564
|
+
this.detectedInfo = { language: 'unknown' };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
this.print(`${c.yellow}⚠ Could not auto-detect project type.${c.reset}`);
|
|
569
|
+
this.print(`${c.dim} Starting from scratch...${c.reset}\n`);
|
|
570
|
+
const lang = await this.select('Select your primary language:', ['Java', 'TypeScript', 'JavaScript', 'Python', 'Other'], 0);
|
|
571
|
+
this.detectedInfo.language = lang;
|
|
572
|
+
}
|
|
573
|
+
// Run wizard
|
|
574
|
+
const config = await this.runWizard();
|
|
575
|
+
// Show summary
|
|
576
|
+
this.printSummary(config);
|
|
577
|
+
// Confirm and save
|
|
578
|
+
const shouldSave = await this.confirm('Save this profile?', true);
|
|
579
|
+
if (shouldSave) {
|
|
580
|
+
const filename = `${config.name.toLowerCase().replace(/\s+/g, '-')}.yaml`;
|
|
581
|
+
const saveLocation = await this.select('Where to save?', [`profiles/custom/${filename} (corbat-mcp)`, `.corbat/${filename} (project local)`, 'Custom path...'], 0);
|
|
582
|
+
let savePath;
|
|
583
|
+
if (saveLocation.includes('profiles/custom')) {
|
|
584
|
+
// Save to corbat-mcp profiles
|
|
585
|
+
const corbatRoot = join(__dirname, '..', '..');
|
|
586
|
+
savePath = join(corbatRoot, 'profiles', 'custom', filename);
|
|
587
|
+
}
|
|
588
|
+
else if (saveLocation.includes('.corbat')) {
|
|
589
|
+
// Save to project local
|
|
590
|
+
const corbatDir = join(this.projectDir, '.corbat');
|
|
591
|
+
try {
|
|
592
|
+
await access(corbatDir);
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
const { mkdir } = await import('node:fs/promises');
|
|
596
|
+
await mkdir(corbatDir, { recursive: true });
|
|
597
|
+
}
|
|
598
|
+
savePath = join(corbatDir, filename);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// Custom path
|
|
602
|
+
const customPath = await this.input('Enter full path');
|
|
603
|
+
savePath = customPath;
|
|
604
|
+
}
|
|
605
|
+
const yamlContent = this.buildYamlProfile(config);
|
|
606
|
+
await writeFile(savePath, yamlContent, 'utf-8');
|
|
607
|
+
this.print(`\n${c.green}✓ Profile saved to: ${savePath}${c.reset}`);
|
|
608
|
+
// Also create .corbat.json if desired
|
|
609
|
+
const createCorbatJson = await this.confirm('\nCreate .corbat.json in project root?', true);
|
|
610
|
+
if (createCorbatJson) {
|
|
611
|
+
const corbatJson = {
|
|
612
|
+
profile: config.name.toLowerCase().replace(/\s+/g, '-'),
|
|
613
|
+
autoInject: true,
|
|
614
|
+
rules: {
|
|
615
|
+
always: config.ddd.enabled
|
|
616
|
+
? ['Follow DDD patterns', 'Use ubiquitous language']
|
|
617
|
+
: ['Follow clean code principles'],
|
|
618
|
+
},
|
|
619
|
+
decisions: {},
|
|
620
|
+
};
|
|
621
|
+
await writeFile(join(this.projectDir, '.corbat.json'), JSON.stringify(corbatJson, null, 2), 'utf-8');
|
|
622
|
+
this.print(`${c.green}✓ Created .corbat.json${c.reset}`);
|
|
623
|
+
}
|
|
624
|
+
this.print(`
|
|
625
|
+
${c.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
|
|
626
|
+
${c.bright} 🎉 All done!${c.reset}
|
|
627
|
+
|
|
628
|
+
${c.cyan}Next steps:${c.reset}
|
|
629
|
+
|
|
630
|
+
1. Use your profile:
|
|
631
|
+
${c.dim}"Review this code using corbat with profile ${config.name.toLowerCase().replace(/\s+/g, '-')}"${c.reset}
|
|
632
|
+
|
|
633
|
+
2. Or with @corbat shortcut:
|
|
634
|
+
${c.dim}"Create a service @corbat"${c.reset}
|
|
635
|
+
|
|
636
|
+
${c.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
|
|
637
|
+
`);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
this.print(`\n${c.yellow}Profile not saved.${c.reset}`);
|
|
641
|
+
}
|
|
642
|
+
this.rl.close();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// Run if executed directly
|
|
646
|
+
const init = new CorbatInit();
|
|
647
|
+
init.run().catch((error) => {
|
|
648
|
+
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
});
|
|
651
|
+
//# sourceMappingURL=init.js.map
|