vibe-arch 0.1.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 +472 -0
- package/dist/analyzer/index.js +265 -0
- package/dist/analyzer/validator.js +121 -0
- package/dist/commands/daemon.js +240 -0
- package/dist/commands/init.js +269 -0
- package/dist/commands/validate.js +80 -0
- package/dist/generator/index.js +359 -0
- package/dist/index.js +53804 -0
- package/dist/types.js +2 -0
- package/dist/watcher/index.js +194 -0
- package/package.json +59 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.initCommand = initCommand;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const inquirer = __importStar(require("inquirer"));
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const analyzer_1 = require("../analyzer");
|
|
45
|
+
const generator_1 = require("../generator");
|
|
46
|
+
async function initCommand(targetDir, options = {}) {
|
|
47
|
+
const rootDir = path.resolve(targetDir);
|
|
48
|
+
const isNonInteractive = !!(options.yes || !process.stdin.isTTY);
|
|
49
|
+
console.log(chalk_1.default.blue(`\n[vibe-arch] Analyzing Project: ${rootDir}\n`));
|
|
50
|
+
const context = (0, analyzer_1.getProjectContext)(rootDir);
|
|
51
|
+
let selectedArchitecture;
|
|
52
|
+
if (!isNonInteractive) {
|
|
53
|
+
// 아키텍처 선택 옵션
|
|
54
|
+
const { archChoice } = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: "list",
|
|
57
|
+
name: "archChoice",
|
|
58
|
+
message: "How would you like to define the architecture pattern?",
|
|
59
|
+
choices: [
|
|
60
|
+
{
|
|
61
|
+
name: "🤖 Let AI recommend (requires OPENAI_API_KEY)",
|
|
62
|
+
value: "ai",
|
|
63
|
+
},
|
|
64
|
+
{ name: "📋 Choose manually", value: "manual" },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
if (archChoice === "ai") {
|
|
69
|
+
console.log(chalk_1.default.blue("\n[vibe-arch] Calling AI for architecture recommendation...\n"));
|
|
70
|
+
const recommendation = await (0, analyzer_1.getAiArchitectureRecommendation)(context);
|
|
71
|
+
if (recommendation) {
|
|
72
|
+
console.log(chalk_1.default.green(`✅ AI recommends: ${chalk_1.default.bold(recommendation)}\n`));
|
|
73
|
+
const { confirmArch } = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: "confirm",
|
|
76
|
+
name: "confirmArch",
|
|
77
|
+
message: `Use ${recommendation} architecture?`,
|
|
78
|
+
default: true,
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
if (confirmArch) {
|
|
82
|
+
selectedArchitecture = recommendation;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(chalk_1.default.yellow("\n[INFO] Could not get AI recommendation. Falling back to manual selection...\n"));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// AI 실패 시 또는 수동 선택 시
|
|
90
|
+
if (!selectedArchitecture) {
|
|
91
|
+
const { architecture } = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: "list",
|
|
94
|
+
name: "architecture",
|
|
95
|
+
message: "Select the architecture pattern:",
|
|
96
|
+
choices: [
|
|
97
|
+
{ name: "🔷 Hexagonal (Ports & Adapters)", value: "hexagonal" },
|
|
98
|
+
{ name: "🧹 Clean Architecture", value: "clean" },
|
|
99
|
+
{ name: "🎬 MVC (Model-View-Controller)", value: "mvc" },
|
|
100
|
+
{ name: "📚 Layered (3-tier)", value: "layered" },
|
|
101
|
+
{ name: "🧩 Modular (Feature-based)", value: "modular" },
|
|
102
|
+
],
|
|
103
|
+
default: "layered",
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
selectedArchitecture = architecture;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// 비대화형 모드: --arch 플래그가 있으면 사용, 없으면 AI 추천 시도
|
|
111
|
+
if (options.arch) {
|
|
112
|
+
selectedArchitecture = options.arch;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log(chalk_1.default.blue("[vibe-arch] Analyzing project for optimal architecture..."));
|
|
116
|
+
const recommendation = await (0, analyzer_1.getAiArchitectureRecommendation)(context);
|
|
117
|
+
if (recommendation) {
|
|
118
|
+
selectedArchitecture = recommendation;
|
|
119
|
+
console.log(chalk_1.default.green(`[vibe-arch] Selected: ${recommendation}\n`));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// AI 실패 시 지능형 기본값 선택
|
|
123
|
+
selectedArchitecture = getSmartDefaultArchitecture(context);
|
|
124
|
+
console.log(chalk_1.default.green(`[vibe-arch] Selected: ${selectedArchitecture}\n`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let injectionMode = "inline";
|
|
129
|
+
if (!isNonInteractive) {
|
|
130
|
+
const { mode } = await inquirer.prompt([
|
|
131
|
+
{
|
|
132
|
+
type: "list",
|
|
133
|
+
name: "mode",
|
|
134
|
+
message: "Where should architecture metadata be stored?",
|
|
135
|
+
choices: [
|
|
136
|
+
{
|
|
137
|
+
name: "📝 Inline (at the top of each source file)",
|
|
138
|
+
value: "inline",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "📁 Sidecar (mirroring structure in /arch directory)",
|
|
142
|
+
value: "sidecar",
|
|
143
|
+
},
|
|
144
|
+
{ name: "🚫 Disabled (no per-file metadata)", value: "disabled" },
|
|
145
|
+
],
|
|
146
|
+
default: "inline",
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
injectionMode = mode;
|
|
150
|
+
}
|
|
151
|
+
else if (options.injection) {
|
|
152
|
+
// 비대화형 모드에서 --injection 옵션이 있으면 사용
|
|
153
|
+
const valid = ["inline", "sidecar", "disabled"];
|
|
154
|
+
if (valid.includes(options.injection)) {
|
|
155
|
+
injectionMode = options.injection;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(chalk_1.default.yellow(`[WARN] Invalid injection mode: ${options.injection}. Using default: inline\n`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const spec = {
|
|
162
|
+
architecture: selectedArchitecture,
|
|
163
|
+
language: options.lang || context.language || "typescript",
|
|
164
|
+
root: rootDir,
|
|
165
|
+
bounded_contexts: [],
|
|
166
|
+
layers: getDefaultLayers(selectedArchitecture),
|
|
167
|
+
comment_injection: injectionMode,
|
|
168
|
+
ai_targets: ["CLAUDE.md", "GEMINI.md", "AI.md"],
|
|
169
|
+
};
|
|
170
|
+
saveSpec(rootDir, spec);
|
|
171
|
+
console.log(chalk_1.default.green(`\n✅ [SUCCESS] Initialized with ${chalk_1.default.bold(selectedArchitecture)} architecture!`));
|
|
172
|
+
}
|
|
173
|
+
function saveSpec(rootDir, spec) {
|
|
174
|
+
const specJsonPath = path.join(rootDir, ".arch-spec.json");
|
|
175
|
+
const specYamlPath = path.join(rootDir, ".arch-spec.yaml");
|
|
176
|
+
fs.writeFileSync(specJsonPath, JSON.stringify(spec, null, 2), "utf-8");
|
|
177
|
+
fs.writeFileSync(specYamlPath, (0, generator_1.generateArchSpec)(spec), "utf-8");
|
|
178
|
+
(0, generator_1.updateAiContexts)(rootDir, spec);
|
|
179
|
+
}
|
|
180
|
+
function getDefaultLayers(architecture) {
|
|
181
|
+
const templates = {
|
|
182
|
+
hexagonal: {
|
|
183
|
+
core: {
|
|
184
|
+
patterns: ["domain", "port", "model", "entity"],
|
|
185
|
+
forbidden_deps: ["application", "infrastructure"],
|
|
186
|
+
allowed_deps: [],
|
|
187
|
+
},
|
|
188
|
+
application: {
|
|
189
|
+
patterns: ["service", "usecase", "handler", "command", "query"],
|
|
190
|
+
forbidden_deps: ["infrastructure"],
|
|
191
|
+
allowed_deps: ["core"],
|
|
192
|
+
},
|
|
193
|
+
infrastructure: {
|
|
194
|
+
patterns: [
|
|
195
|
+
"adapter",
|
|
196
|
+
"config",
|
|
197
|
+
"repository",
|
|
198
|
+
"persistence",
|
|
199
|
+
"web",
|
|
200
|
+
"external",
|
|
201
|
+
],
|
|
202
|
+
forbidden_deps: [],
|
|
203
|
+
allowed_deps: ["core", "application"],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
modular: {
|
|
207
|
+
app: {
|
|
208
|
+
patterns: ["app", "index", "main"],
|
|
209
|
+
forbidden_deps: [],
|
|
210
|
+
allowed_deps: ["module", "shared"],
|
|
211
|
+
},
|
|
212
|
+
module: {
|
|
213
|
+
patterns: ["feature", "module"],
|
|
214
|
+
forbidden_deps: ["app"],
|
|
215
|
+
allowed_deps: ["shared", "module"],
|
|
216
|
+
},
|
|
217
|
+
shared: {
|
|
218
|
+
patterns: ["shared", "common", "types"],
|
|
219
|
+
forbidden_deps: ["app", "module"],
|
|
220
|
+
allowed_deps: [],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
layered: {
|
|
224
|
+
presentation: {
|
|
225
|
+
patterns: ["controller", "ui"],
|
|
226
|
+
forbidden_deps: ["persistence"],
|
|
227
|
+
allowed_deps: ["business"],
|
|
228
|
+
},
|
|
229
|
+
business: {
|
|
230
|
+
patterns: ["service", "logic"],
|
|
231
|
+
forbidden_deps: [],
|
|
232
|
+
allowed_deps: ["persistence"],
|
|
233
|
+
},
|
|
234
|
+
persistence: {
|
|
235
|
+
patterns: ["repository", "dao"],
|
|
236
|
+
forbidden_deps: ["presentation"],
|
|
237
|
+
allowed_deps: [],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
return templates[architecture] || templates["layered"];
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 프로젝트 구조를 분석하여 최적의 아키텍처를 추천합니다 (AI 없을 때 사용).
|
|
245
|
+
*/
|
|
246
|
+
function getSmartDefaultArchitecture(context) {
|
|
247
|
+
const deps = context.packageJson?.dependencies || {};
|
|
248
|
+
const tree = context.tree.map((p) => p.toLowerCase());
|
|
249
|
+
// React/Vue 감지 (프론트엔드 = modular)
|
|
250
|
+
if (deps.react || deps.vue || deps.svelte || deps.angular) {
|
|
251
|
+
return "modular";
|
|
252
|
+
}
|
|
253
|
+
// Express/Fastify/Spring 감지 (백엔드 = layered)
|
|
254
|
+
if (deps.express || deps.fastify || deps.koa || deps.spring) {
|
|
255
|
+
return "layered";
|
|
256
|
+
}
|
|
257
|
+
// 마이크로서비스 구조 감지 (여러 도메인 폴더 = hexagonal)
|
|
258
|
+
const domainFolders = tree.filter((p) => p.includes("/src/") &&
|
|
259
|
+
["api", "domain", "service", "adapter", "port"].some((keyword) => p.includes(keyword)));
|
|
260
|
+
if (domainFolders.length > 5) {
|
|
261
|
+
return "hexagonal";
|
|
262
|
+
}
|
|
263
|
+
// feature/ module/ 폴더 감지 (feature-based = modular)
|
|
264
|
+
if (tree.some((p) => p.includes("feature/") || p.includes("module/"))) {
|
|
265
|
+
return "modular";
|
|
266
|
+
}
|
|
267
|
+
// 기본값: layered
|
|
268
|
+
return "layered";
|
|
269
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.validateCommand = validateCommand;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const validator_1 = require("../analyzer/validator");
|
|
44
|
+
async function validateCommand(targetDir) {
|
|
45
|
+
const rootDir = path.resolve(targetDir);
|
|
46
|
+
const specPath = path.join(rootDir, '.arch-spec.json');
|
|
47
|
+
if (!fs.existsSync(specPath)) {
|
|
48
|
+
console.error(chalk_1.default.red('\n[ERROR] .arch-spec.json not found. Run: vibe-arch init\n'));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
|
|
53
|
+
console.log(chalk_1.default.blue(`\n🔍 Scanning architecture violations... (${spec.architecture})\n`));
|
|
54
|
+
const violations = (0, validator_1.validateProject)(rootDir, spec);
|
|
55
|
+
if (violations.length === 0) {
|
|
56
|
+
console.log(chalk_1.default.green('✅ No violations found. Project is clean!\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// 레이어별 그룹화
|
|
60
|
+
const byLayer = {};
|
|
61
|
+
for (const v of violations) {
|
|
62
|
+
if (!byLayer[v.from])
|
|
63
|
+
byLayer[v.from] = [];
|
|
64
|
+
byLayer[v.from].push(v);
|
|
65
|
+
}
|
|
66
|
+
for (const [layer, vs] of Object.entries(byLayer)) {
|
|
67
|
+
console.log(chalk_1.default.yellow(`⚠️ Layer '${layer}': ${vs.length} violation(s)`));
|
|
68
|
+
for (const v of vs) {
|
|
69
|
+
const relPath = path.relative(rootDir, v.filePath);
|
|
70
|
+
console.log(` - ${chalk_1.default.gray(relPath)}`);
|
|
71
|
+
console.log(` ${chalk_1.default.red(v.message)}`);
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
console.log(chalk_1.default.bold.red(`Total ${violations.length} violation(s) detected.\n`));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(chalk_1.default.red(`[ERROR] Failed to validate: ${err.message}`));
|
|
79
|
+
}
|
|
80
|
+
}
|