@mandujs/core 0.9.31 → 0.9.37
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/package.json +3 -1
- package/src/brain/architecture/analyzer.ts +15 -7
- package/src/bundler/dev.ts +35 -27
- package/src/guard/analyzer.ts +350 -0
- package/src/guard/ast-analyzer.ts +806 -0
- package/src/guard/index.ts +174 -0
- package/src/guard/presets/atomic.ts +70 -0
- package/src/guard/presets/clean.ts +77 -0
- package/src/guard/presets/fsd.ts +79 -0
- package/src/guard/presets/hexagonal.ts +68 -0
- package/src/guard/presets/index.ts +155 -0
- package/src/guard/reporter.ts +445 -0
- package/src/guard/statistics.ts +572 -0
- package/src/guard/suggestions.ts +345 -0
- package/src/guard/types.ts +331 -0
- package/src/guard/validator.ts +683 -0
- package/src/guard/watcher.ts +376 -0
- package/src/index.ts +1 -0
- package/src/router/fs-patterns.ts +380 -0
- package/src/router/fs-routes.ts +389 -0
- package/src/router/fs-scanner.ts +513 -0
- package/src/router/fs-types.ts +278 -0
- package/src/router/index.ts +81 -0
- package/src/runtime/boundary.tsx +232 -0
- package/src/runtime/index.ts +1 -0
- package/src/runtime/router.test.ts +53 -0
- package/src/runtime/router.ts +143 -46
- package/src/runtime/server.ts +233 -2
- package/src/spec/schema.ts +11 -0
- package/src/watcher/rules.ts +4 -4
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Guard Reporter
|
|
3
|
+
*
|
|
4
|
+
* 에이전트 친화적 경고 출력
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
Violation,
|
|
9
|
+
ViolationReport,
|
|
10
|
+
Severity,
|
|
11
|
+
LayerDefinition,
|
|
12
|
+
GuardPreset,
|
|
13
|
+
} from "./types";
|
|
14
|
+
import {
|
|
15
|
+
getDocumentationLink,
|
|
16
|
+
toAgentFormat,
|
|
17
|
+
type AgentViolationFormat,
|
|
18
|
+
} from "./suggestions";
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// ANSI Colors
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
const colors = {
|
|
25
|
+
reset: "\x1b[0m",
|
|
26
|
+
bold: "\x1b[1m",
|
|
27
|
+
dim: "\x1b[2m",
|
|
28
|
+
red: "\x1b[31m",
|
|
29
|
+
green: "\x1b[32m",
|
|
30
|
+
yellow: "\x1b[33m",
|
|
31
|
+
blue: "\x1b[34m",
|
|
32
|
+
magenta: "\x1b[35m",
|
|
33
|
+
cyan: "\x1b[36m",
|
|
34
|
+
white: "\x1b[37m",
|
|
35
|
+
bgRed: "\x1b[41m",
|
|
36
|
+
bgYellow: "\x1b[43m",
|
|
37
|
+
bgBlue: "\x1b[44m",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
// Formatting Helpers
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
const SEPARATOR = "━".repeat(60);
|
|
45
|
+
|
|
46
|
+
function getSeverityIcon(severity: Severity): string {
|
|
47
|
+
switch (severity) {
|
|
48
|
+
case "error":
|
|
49
|
+
return "🚨";
|
|
50
|
+
case "warn":
|
|
51
|
+
return "⚠️";
|
|
52
|
+
case "info":
|
|
53
|
+
return "ℹ️";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getSeverityColor(severity: Severity): string {
|
|
58
|
+
switch (severity) {
|
|
59
|
+
case "error":
|
|
60
|
+
return colors.red;
|
|
61
|
+
case "warn":
|
|
62
|
+
return colors.yellow;
|
|
63
|
+
case "info":
|
|
64
|
+
return colors.blue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getSeverityLabel(severity: Severity): string {
|
|
69
|
+
switch (severity) {
|
|
70
|
+
case "error":
|
|
71
|
+
return "ERROR";
|
|
72
|
+
case "warn":
|
|
73
|
+
return "WARNING";
|
|
74
|
+
case "info":
|
|
75
|
+
return "INFO";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
// Violation Formatting
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 단일 위반 포맷팅 (에이전트 친화적)
|
|
85
|
+
*/
|
|
86
|
+
export function formatViolation(
|
|
87
|
+
violation: Violation,
|
|
88
|
+
hierarchy?: string[]
|
|
89
|
+
): string {
|
|
90
|
+
const icon = getSeverityIcon(violation.severity);
|
|
91
|
+
const color = getSeverityColor(violation.severity);
|
|
92
|
+
const label = getSeverityLabel(violation.severity);
|
|
93
|
+
|
|
94
|
+
const lines: string[] = [
|
|
95
|
+
"",
|
|
96
|
+
`${color}${SEPARATOR}${colors.reset}`,
|
|
97
|
+
`${icon} ${color}${colors.bold}ARCHITECTURE VIOLATION DETECTED${colors.reset}`,
|
|
98
|
+
`${color}${SEPARATOR}${colors.reset}`,
|
|
99
|
+
"",
|
|
100
|
+
`${colors.dim}📁 File:${colors.reset} ${violation.filePath}`,
|
|
101
|
+
`${colors.dim}📍 Line:${colors.reset} ${violation.line}, ${colors.dim}Column:${colors.reset} ${violation.column}`,
|
|
102
|
+
`${colors.red}❌ Violation:${colors.reset} ${violation.importStatement}`,
|
|
103
|
+
"",
|
|
104
|
+
`${color}🔴 Rule:${colors.reset} ${violation.ruleName}`,
|
|
105
|
+
` ${violation.ruleDescription}`,
|
|
106
|
+
"",
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// 레이어 계층 시각화
|
|
110
|
+
if (hierarchy && hierarchy.length > 0) {
|
|
111
|
+
lines.push(`${colors.cyan}📊 Layer Hierarchy:${colors.reset}`);
|
|
112
|
+
lines.push(formatHierarchy(hierarchy, violation.fromLayer, violation.toLayer));
|
|
113
|
+
lines.push("");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 허용된 레이어
|
|
117
|
+
if (violation.allowedLayers.length > 0) {
|
|
118
|
+
lines.push(`${colors.green}✅ Allowed imports from "${violation.fromLayer}":${colors.reset}`);
|
|
119
|
+
for (const layer of violation.allowedLayers) {
|
|
120
|
+
lines.push(` • @/${layer}/*`);
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 해결 제안
|
|
126
|
+
if (violation.suggestions.length > 0) {
|
|
127
|
+
lines.push(`${colors.magenta}💡 Suggestions:${colors.reset}`);
|
|
128
|
+
violation.suggestions.forEach((suggestion, i) => {
|
|
129
|
+
lines.push(` ${i + 1}. ${suggestion}`);
|
|
130
|
+
});
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
lines.push(`${color}${SEPARATOR}${colors.reset}`);
|
|
135
|
+
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 레이어 계층 시각화
|
|
141
|
+
*/
|
|
142
|
+
function formatHierarchy(
|
|
143
|
+
hierarchy: string[],
|
|
144
|
+
fromLayer: string,
|
|
145
|
+
toLayer: string
|
|
146
|
+
): string {
|
|
147
|
+
const fromIndex = hierarchy.indexOf(fromLayer);
|
|
148
|
+
const toIndex = hierarchy.indexOf(toLayer);
|
|
149
|
+
|
|
150
|
+
// 계층 화살표 생성
|
|
151
|
+
const layerLine = hierarchy.join(" → ");
|
|
152
|
+
let visualization = ` ${layerLine}`;
|
|
153
|
+
|
|
154
|
+
// 위반 방향 표시
|
|
155
|
+
if (fromIndex !== -1 && toIndex !== -1) {
|
|
156
|
+
if (fromIndex > toIndex) {
|
|
157
|
+
visualization += `\n ${" ".repeat(getPositionOffset(hierarchy, toLayer))}↑`;
|
|
158
|
+
visualization += ` ${" ".repeat(getPositionOffset(hierarchy, fromLayer) - getPositionOffset(hierarchy, toLayer) - 2)}↓`;
|
|
159
|
+
visualization += `\n ${colors.red}(violation: ${fromLayer} importing UP)${colors.reset}`;
|
|
160
|
+
} else {
|
|
161
|
+
visualization += `\n ${colors.red}(violation: importing restricted layer)${colors.reset}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return visualization;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 레이어 위치 오프셋 계산
|
|
170
|
+
*/
|
|
171
|
+
function getPositionOffset(hierarchy: string[], layer: string): number {
|
|
172
|
+
let offset = 0;
|
|
173
|
+
for (const l of hierarchy) {
|
|
174
|
+
if (l === layer) break;
|
|
175
|
+
offset += l.length + 4; // " → " = 4 chars
|
|
176
|
+
}
|
|
177
|
+
return offset;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
+
// Report Formatting
|
|
182
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 전체 리포트 포맷팅
|
|
186
|
+
*/
|
|
187
|
+
export function formatReport(
|
|
188
|
+
report: ViolationReport,
|
|
189
|
+
hierarchy?: string[]
|
|
190
|
+
): string {
|
|
191
|
+
const lines: string[] = [];
|
|
192
|
+
|
|
193
|
+
// 헤더
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push(`${colors.bold}${colors.cyan}╔══════════════════════════════════════════════════════════╗${colors.reset}`);
|
|
196
|
+
lines.push(`${colors.bold}${colors.cyan}║${colors.reset} 🛡️ Mandu Guard Report ${colors.bold}${colors.cyan}║${colors.reset}`);
|
|
197
|
+
lines.push(`${colors.bold}${colors.cyan}╚══════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
198
|
+
lines.push("");
|
|
199
|
+
|
|
200
|
+
// 요약
|
|
201
|
+
lines.push(`${colors.dim}📊 Summary:${colors.reset}`);
|
|
202
|
+
lines.push(` Files analyzed: ${report.filesAnalyzed}`);
|
|
203
|
+
lines.push(` Analysis time: ${report.analysisTime}ms`);
|
|
204
|
+
lines.push(` Total violations: ${report.totalViolations}`);
|
|
205
|
+
lines.push("");
|
|
206
|
+
|
|
207
|
+
// 심각도별 카운트
|
|
208
|
+
if (report.totalViolations > 0) {
|
|
209
|
+
lines.push(`${colors.dim}📈 By Severity:${colors.reset}`);
|
|
210
|
+
if (report.bySeverity.error > 0) {
|
|
211
|
+
lines.push(` ${colors.red}🚨 Errors: ${report.bySeverity.error}${colors.reset}`);
|
|
212
|
+
}
|
|
213
|
+
if (report.bySeverity.warn > 0) {
|
|
214
|
+
lines.push(` ${colors.yellow}⚠️ Warnings: ${report.bySeverity.warn}${colors.reset}`);
|
|
215
|
+
}
|
|
216
|
+
if (report.bySeverity.info > 0) {
|
|
217
|
+
lines.push(` ${colors.blue}ℹ️ Info: ${report.bySeverity.info}${colors.reset}`);
|
|
218
|
+
}
|
|
219
|
+
lines.push("");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 각 위반 출력
|
|
223
|
+
for (const violation of report.violations) {
|
|
224
|
+
lines.push(formatViolation(violation, hierarchy));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 결과
|
|
228
|
+
if (report.totalViolations === 0) {
|
|
229
|
+
lines.push(`${colors.green}✅ No architecture violations found!${colors.reset}`);
|
|
230
|
+
} else {
|
|
231
|
+
lines.push(`${colors.red}❌ ${report.totalViolations} violation(s) found. Please fix them.${colors.reset}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
lines.push("");
|
|
235
|
+
|
|
236
|
+
return lines.join("\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 간략한 위반 요약 (한 줄)
|
|
241
|
+
*/
|
|
242
|
+
export function formatViolationSummary(violation: Violation): string {
|
|
243
|
+
const icon = getSeverityIcon(violation.severity);
|
|
244
|
+
return `${icon} ${violation.filePath}:${violation.line} - ${violation.fromLayer} → ${violation.toLayer} (${violation.ruleName})`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
|
+
// Console Output
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 위반 콘솔 출력
|
|
253
|
+
*/
|
|
254
|
+
export function printViolation(
|
|
255
|
+
violation: Violation,
|
|
256
|
+
hierarchy?: string[]
|
|
257
|
+
): void {
|
|
258
|
+
console.log(formatViolation(violation, hierarchy));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 리포트 콘솔 출력
|
|
263
|
+
*/
|
|
264
|
+
export function printReport(
|
|
265
|
+
report: ViolationReport,
|
|
266
|
+
hierarchy?: string[]
|
|
267
|
+
): void {
|
|
268
|
+
console.log(formatReport(report, hierarchy));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 실시간 위반 알림 (짧은 형식)
|
|
273
|
+
*/
|
|
274
|
+
export function printRealtimeViolation(violation: Violation): void {
|
|
275
|
+
const icon = getSeverityIcon(violation.severity);
|
|
276
|
+
const color = getSeverityColor(violation.severity);
|
|
277
|
+
|
|
278
|
+
console.log("");
|
|
279
|
+
console.log(`${color}${SEPARATOR}${colors.reset}`);
|
|
280
|
+
console.log(`${icon} ${color}${colors.bold}ARCHITECTURE VIOLATION${colors.reset}`);
|
|
281
|
+
console.log(`${colors.dim}File:${colors.reset} ${violation.filePath}:${violation.line}`);
|
|
282
|
+
console.log(`${colors.red}${violation.fromLayer} → ${violation.toLayer}${colors.reset} (not allowed)`);
|
|
283
|
+
console.log(`${colors.green}Allowed:${colors.reset} ${violation.allowedLayers.join(", ") || "none"}`);
|
|
284
|
+
if (violation.suggestions.length > 0) {
|
|
285
|
+
console.log(`${colors.magenta}Fix:${colors.reset} ${violation.suggestions[0]}`);
|
|
286
|
+
}
|
|
287
|
+
console.log(`${color}${SEPARATOR}${colors.reset}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
291
|
+
// JSON Output (CI/CD)
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* JSON 형식 리포트
|
|
296
|
+
*/
|
|
297
|
+
export function formatReportAsJSON(report: ViolationReport): string {
|
|
298
|
+
return JSON.stringify(report, null, 2);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 위반을 GitHub Actions 형식으로 출력
|
|
303
|
+
*/
|
|
304
|
+
export function formatForGitHubActions(violation: Violation): string {
|
|
305
|
+
const level = violation.severity === "error" ? "error" : "warning";
|
|
306
|
+
return `::${level} file=${violation.filePath},line=${violation.line},col=${violation.column}::${violation.ruleName}: ${violation.ruleDescription}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// Agent-Optimized Output
|
|
311
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 에이전트 최적화 형식으로 위반 출력
|
|
315
|
+
*
|
|
316
|
+
* AI Agent가 파싱하고 자동 수정하기 쉬운 형식
|
|
317
|
+
*/
|
|
318
|
+
export function formatViolationForAgent(
|
|
319
|
+
violation: Violation,
|
|
320
|
+
preset?: GuardPreset
|
|
321
|
+
): string {
|
|
322
|
+
const agentFormat = toAgentFormat(violation, preset);
|
|
323
|
+
const lines: string[] = [];
|
|
324
|
+
|
|
325
|
+
lines.push("");
|
|
326
|
+
lines.push(`${colors.cyan}┌─ GUARD VIOLATION ──────────────────────────────────────────┐${colors.reset}`);
|
|
327
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
328
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.red}[${agentFormat.severity.toUpperCase()}]${colors.reset} ${agentFormat.rule.name}`);
|
|
329
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
330
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.dim}FILE:${colors.reset} ${agentFormat.location.file}:${agentFormat.location.line}:${agentFormat.location.column}`);
|
|
331
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.dim}RULE:${colors.reset} ${agentFormat.violation.fromLayer} → ${agentFormat.violation.toLayer} (NOT ALLOWED)`);
|
|
332
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
333
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.yellow}VIOLATION:${colors.reset}`);
|
|
334
|
+
lines.push(`${colors.cyan}│${colors.reset} ${agentFormat.violation.importStatement}`);
|
|
335
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
336
|
+
|
|
337
|
+
// 수정 방법
|
|
338
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.green}FIX:${colors.reset}`);
|
|
339
|
+
lines.push(`${colors.cyan}│${colors.reset} ${agentFormat.fix.primary}`);
|
|
340
|
+
|
|
341
|
+
if (agentFormat.fix.codeChange) {
|
|
342
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
343
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.magenta}CODE CHANGE:${colors.reset}`);
|
|
344
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.red}- ${agentFormat.fix.codeChange.before}${colors.reset}`);
|
|
345
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.green}+ ${agentFormat.fix.codeChange.after}${colors.reset}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 허용된 import
|
|
349
|
+
if (agentFormat.allowed.length > 0) {
|
|
350
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
351
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.blue}ALLOWED:${colors.reset} ${agentFormat.allowed.join(", ")}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 문서 링크
|
|
355
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
356
|
+
lines.push(`${colors.cyan}│${colors.reset} ${colors.dim}DOCS:${colors.reset} ${agentFormat.rule.documentation}`);
|
|
357
|
+
lines.push(`${colors.cyan}│${colors.reset}`);
|
|
358
|
+
lines.push(`${colors.cyan}└────────────────────────────────────────────────────────────┘${colors.reset}`);
|
|
359
|
+
|
|
360
|
+
return lines.join("\n");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 에이전트용 JSON 포맷
|
|
365
|
+
*/
|
|
366
|
+
export function formatViolationAsAgentJSON(
|
|
367
|
+
violation: Violation,
|
|
368
|
+
preset?: GuardPreset
|
|
369
|
+
): string {
|
|
370
|
+
return JSON.stringify(toAgentFormat(violation, preset), null, 2);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 여러 위반을 에이전트 형식으로 출력
|
|
375
|
+
*/
|
|
376
|
+
export function formatReportForAgent(
|
|
377
|
+
report: ViolationReport,
|
|
378
|
+
preset?: GuardPreset
|
|
379
|
+
): string {
|
|
380
|
+
const lines: string[] = [];
|
|
381
|
+
|
|
382
|
+
lines.push("");
|
|
383
|
+
lines.push(`${colors.bold}${colors.cyan}╔══════════════════════════════════════════════════════════════╗${colors.reset}`);
|
|
384
|
+
lines.push(`${colors.bold}${colors.cyan}║${colors.reset} 🛡️ MANDU GUARD ANALYSIS ${colors.bold}${colors.cyan}║${colors.reset}`);
|
|
385
|
+
lines.push(`${colors.bold}${colors.cyan}╚══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
386
|
+
lines.push("");
|
|
387
|
+
|
|
388
|
+
// 요약 (에이전트가 빠르게 파악할 수 있도록)
|
|
389
|
+
lines.push(`${colors.dim}SUMMARY:${colors.reset}`);
|
|
390
|
+
lines.push(` files_analyzed: ${report.filesAnalyzed}`);
|
|
391
|
+
lines.push(` total_violations: ${report.totalViolations}`);
|
|
392
|
+
lines.push(` errors: ${report.bySeverity.error}`);
|
|
393
|
+
lines.push(` warnings: ${report.bySeverity.warn}`);
|
|
394
|
+
lines.push(` info: ${report.bySeverity.info}`);
|
|
395
|
+
lines.push("");
|
|
396
|
+
|
|
397
|
+
if (report.totalViolations === 0) {
|
|
398
|
+
lines.push(`${colors.green}✅ ALL CLEAR - No architecture violations detected${colors.reset}`);
|
|
399
|
+
lines.push("");
|
|
400
|
+
return lines.join("\n");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 위반별 상세
|
|
404
|
+
lines.push(`${colors.yellow}VIOLATIONS:${colors.reset}`);
|
|
405
|
+
lines.push("");
|
|
406
|
+
|
|
407
|
+
for (const violation of report.violations) {
|
|
408
|
+
lines.push(formatViolationForAgent(violation, preset));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 액션 요약
|
|
412
|
+
lines.push("");
|
|
413
|
+
lines.push(`${colors.bold}ACTION REQUIRED:${colors.reset}`);
|
|
414
|
+
if (report.bySeverity.error > 0) {
|
|
415
|
+
lines.push(` ${colors.red}• Fix ${report.bySeverity.error} error(s) before continuing${colors.reset}`);
|
|
416
|
+
}
|
|
417
|
+
if (report.bySeverity.warn > 0) {
|
|
418
|
+
lines.push(` ${colors.yellow}• Consider fixing ${report.bySeverity.warn} warning(s)${colors.reset}`);
|
|
419
|
+
}
|
|
420
|
+
lines.push("");
|
|
421
|
+
|
|
422
|
+
return lines.join("\n");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 전체 리포트를 에이전트 JSON으로
|
|
427
|
+
*/
|
|
428
|
+
export function formatReportAsAgentJSON(
|
|
429
|
+
report: ViolationReport,
|
|
430
|
+
preset?: GuardPreset
|
|
431
|
+
): string {
|
|
432
|
+
const agentReport = {
|
|
433
|
+
summary: {
|
|
434
|
+
filesAnalyzed: report.filesAnalyzed,
|
|
435
|
+
totalViolations: report.totalViolations,
|
|
436
|
+
analysisTime: report.analysisTime,
|
|
437
|
+
bySeverity: report.bySeverity,
|
|
438
|
+
byType: report.byType,
|
|
439
|
+
},
|
|
440
|
+
violations: report.violations.map((v) => toAgentFormat(v, preset)),
|
|
441
|
+
actionRequired: report.bySeverity.error > 0,
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
return JSON.stringify(agentReport, null, 2);
|
|
445
|
+
}
|