archrisk-engine 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.
@@ -0,0 +1,101 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runDeepAnalysis = runDeepAnalysis;
37
+ const generative_ai_1 = require("@google/generative-ai");
38
+ const fs = __importStar(require("fs-extra"));
39
+ const path = __importStar(require("path"));
40
+ async function runDeepAnalysis(repoDir, provider, apiKey) {
41
+ if (provider !== 'GEMINI') {
42
+ throw new Error(`Provider ${provider} is not yet supported in Deep Analysis.`);
43
+ }
44
+ const genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
45
+ const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
46
+ // Gather some context (simplified for MVP)
47
+ const files = await getRelevantFiles(repoDir);
48
+ const codeContext = await Promise.all(files.map(async (f) => {
49
+ const content = await fs.readFile(f, 'utf8');
50
+ return `File: ${path.relative(repoDir, f)}\nContent:\n${content.substring(0, 1000)}...`;
51
+ }));
52
+ const prompt = `
53
+ You are an expert Software Architect. Analyze the following project for:
54
+ 1. Code smells and anti-patterns.
55
+ 2. Specific refactoring suggestions.
56
+ 3. Technical debt estimation.
57
+
58
+ Project Context:
59
+ ${codeContext.join('\n\n')}
60
+
61
+ Return JSON format:
62
+ {
63
+ "summary": "High level overview",
64
+ "refactoringGuides": [
65
+ {"file": "filename", "description": "why it needs refactoring", "suggestion": "how to refactor"}
66
+ ],
67
+ "techDebtScore": 0-100
68
+ }
69
+ `;
70
+ const result = await model.generateContent(prompt);
71
+ const response = await result.response;
72
+ const text = response.text();
73
+ try {
74
+ const jsonStr = text.match(/\{[\s\S]*\}/)?.[0] || '{}';
75
+ return JSON.parse(jsonStr);
76
+ }
77
+ catch (e) {
78
+ return {
79
+ summary: "AI analysis completed but failed to parse structured data.",
80
+ refactoringGuides: [],
81
+ techDebtScore: 50
82
+ };
83
+ }
84
+ }
85
+ async function getRelevantFiles(dir) {
86
+ const allFiles = [];
87
+ const items = await fs.readdir(dir);
88
+ for (const item of items) {
89
+ if (item === 'node_modules' || item.startsWith('.'))
90
+ continue;
91
+ const fullPath = path.join(dir, item);
92
+ const stat = await fs.stat(fullPath);
93
+ if (stat.isDirectory()) {
94
+ allFiles.push(...await getRelevantFiles(fullPath));
95
+ }
96
+ else if (item.endsWith('.py') || item.endsWith('.ts') || item.endsWith('.js')) {
97
+ allFiles.push(fullPath);
98
+ }
99
+ }
100
+ return allFiles.slice(0, 5); // MVP: Limit to 5 files for context
101
+ }
@@ -0,0 +1,5 @@
1
+ export * from './analyzer.js';
2
+ export * from './aiDiagnosis.js';
3
+ export * from './archScanner.js';
4
+ export * from './repoAnalyzer.js';
5
+ export * from './deepAnalysis.js';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./analyzer.js"), exports);
18
+ __exportStar(require("./aiDiagnosis.js"), exports);
19
+ __exportStar(require("./archScanner.js"), exports);
20
+ __exportStar(require("./repoAnalyzer.js"), exports);
21
+ __exportStar(require("./deepAnalysis.js"), exports);
@@ -0,0 +1,43 @@
1
+ export interface RepoAnalysisResult {
2
+ score: number;
3
+ status: 'Ready for Production' | 'Needs Attention' | 'Not Ready for Deployment';
4
+ findings: {
5
+ title: string;
6
+ file: string;
7
+ line: number;
8
+ type: string;
9
+ category: 'Service Interruption' | 'Scalability' | 'Maintenance' | 'Security';
10
+ evidence: string;
11
+ standard: string;
12
+ impact: string;
13
+ action: string;
14
+ reference: string;
15
+ whenItMatters: string;
16
+ }[];
17
+ metrics: {
18
+ totalFiles: number;
19
+ pythonFiles: number;
20
+ criticalRisks: number;
21
+ operationalGaps: number;
22
+ };
23
+ graphUrl?: string;
24
+ disclosure?: string;
25
+ cta?: string;
26
+ }
27
+ /**
28
+ * CEO-ready "Business Translation" for technical risks
29
+ */
30
+ /**
31
+ * 3-Tier Business Audit Translation
32
+ */
33
+ export declare function getAuditDetails(type: string, issue: string): {
34
+ title: string;
35
+ category: RepoAnalysisResult['findings'][0]['category'];
36
+ evidence: string;
37
+ standard: string;
38
+ impact: string;
39
+ action: string;
40
+ reference: string;
41
+ whenItMatters: string;
42
+ };
43
+ export declare function analyzeRepository(repoPath: string, resultsDir?: string): Promise<RepoAnalysisResult>;
@@ -0,0 +1,299 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getAuditDetails = getAuditDetails;
37
+ exports.analyzeRepository = analyzeRepository;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ const analyzer_js_1 = require("./analyzer.js");
42
+ const archScanner_js_1 = require("./archScanner.js");
43
+ /**
44
+ * CEO-ready "Business Translation" for technical risks
45
+ */
46
+ /**
47
+ * 3-Tier Business Audit Translation
48
+ */
49
+ function getAuditDetails(type, issue) {
50
+ if (type === 'SecurityRisk') {
51
+ return {
52
+ title: "보안 취약점 위험 (Security Vulnerability)",
53
+ category: 'Security',
54
+ evidence: issue,
55
+ standard: "OWASP Top 10 A03:2021 – Injection",
56
+ impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 통로가 됩니다. 보안 사고 발생 시 법적 책임 및 서비스 중단이 불가피합니다.",
57
+ action: "해당 코드를 즉시 격리하고 subprocess.run(shell=False) 또는 환경 변수(.env)를 활용하여 민감 정보를 분리하세요.",
58
+ reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
59
+ whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있는 0순위 리스크입니다."
60
+ };
61
+ }
62
+ if (type === 'ProductionRisk') {
63
+ if (issue.includes('테스트')) {
64
+ return {
65
+ title: "자동화 테스트 부재 (Missing Automated Tests)",
66
+ category: 'Service Interruption',
67
+ evidence: "tests/ 디렉토리 또는 pytest/unittest 관련 설정을 찾을 수 없습니다.",
68
+ standard: "Stability Standard: Code Coverage > 70%",
69
+ impact: "수동 테스트에 의존하게 되어 코드 변경 시마다 예상치 못한 장애가 운영 환경에 배포될 위험이 높으며, 장애 복구 시간(MTTR)이 매우 길어집니다.",
70
+ action: "pytest를 설치하고 핵심 로직에 대한 Smoke Test 3개를 먼저 작성하세요.",
71
+ reference: "https://docs.pytest.org/en/7.1.x/getting-started.html",
72
+ whenItMatters: "코드 변경 주기가 빨라지거나 팀원이 2명 이상으로 늘어날 때 병목이 발생합니다."
73
+ };
74
+ }
75
+ if (issue.includes('CI')) {
76
+ return {
77
+ title: "배포 자동화 파이프라인 부재 (Missing CI Pipeline)",
78
+ category: 'Service Interruption',
79
+ evidence: "GitHub Actions 또는 CI 도구 워크플로우 설정을 발견할 수 없습니다.",
80
+ standard: "Modern DevOps Standard: Continuous Integration",
81
+ impact: "코드 머지 전 검증 자동화가 불가능하여, 사람이 개입하는 과정에서 실수가 반복됩니다. 이는 배포 안정성을 근본적으로 파악할 수 없게 만듭니다.",
82
+ action: ".github/workflows/main.yml 파일을 생성하고 자동 테스트 프로세스를 구축하세요.",
83
+ reference: "https://github.com/features/actions",
84
+ whenItMatters: "하루에 2회 이상 배포를 시도할 때 검증 누락으로 인한 사고 확률이 80%를 상회하게 됩니다."
85
+ };
86
+ }
87
+ if (issue.includes('Docker')) {
88
+ return {
89
+ title: "컨테이너화 구성 누락 (Environment Inconsistency)",
90
+ category: 'Service Interruption',
91
+ evidence: "Dockerfile 또는 docker-compose.yml 파일이 존재하지 않습니다.",
92
+ standard: "Cloud Native Standard: Immutable Infrastructure",
93
+ impact: "로컬 개발 환경과 실제 운영 환경의 버전 차이로 인해 '내 컴퓨터에선 되는데 서버에선 안 되는' 예측 불가능한 장애가 발생합니다.",
94
+ action: "Dockerfile을 작성하여 환경 설정을 컨테이너화하세요.",
95
+ reference: "https://docs.docker.com/engine/reference/builder/",
96
+ whenItMatters: "신규 개발자가 팀에 합류하거나 클라우드 인프라 확장이 필요할 때 시간 낭비가 심화됩니다."
97
+ };
98
+ }
99
+ return {
100
+ title: "운영 준비도 미흡 (Production Readiness Gap)",
101
+ category: 'Service Interruption',
102
+ evidence: issue,
103
+ standard: "Production Readiness Checklist v1.0",
104
+ impact: "실제 운영 환경에서 예기치 못한 장애 발생 시 복구가 불가능하거나 매우 지연될 수 있는 핵심 요소입니다.",
105
+ action: "해당 설정 파일을 추가하거나 환경 설정을 고정하여 재현 가능성을 확보하세요.",
106
+ reference: "https://google.github.io/styleguide/pyguide.html",
107
+ whenItMatters: "장애 발생 시 원인 파악을 수분 내에 완료하지 못할 경우 다운타임이 기하급수적으로 늘어납니다."
108
+ };
109
+ }
110
+ if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
111
+ return {
112
+ title: "구조적 의존성 결함 (Structural Dependency Issue)",
113
+ category: 'Scalability',
114
+ evidence: type === 'CircularDependency' ? "모듈 간의 상호 참조 구조가 감지되었습니다." : issue,
115
+ standard: "Clean Architecture: Dependency Rule",
116
+ impact: "모듈 간 결합도가 높아져 성능 저하 및 무한 루프 위험이 있으며, 코드 한 곳을 수정할 때 전혀 다른 곳에서 장애가 발생하는 '기술 부채'의 원인이 됩니다.",
117
+ action: "의존성 방향을 한 방향으로 정리하세요.",
118
+ reference: "https://peps.python.org/pep-0008/#imports",
119
+ whenItMatters: "프로젝트 코드 베이스가 5,000줄 이상으로 늘어날 때 유지보수가 불가능한 시크릿 코드로 변질됩니다."
120
+ };
121
+ }
122
+ return {
123
+ title: "기타 잠재적 리스크 (Other Potential Risks)",
124
+ category: 'Maintenance',
125
+ evidence: issue,
126
+ standard: "General Coding Best Practices",
127
+ impact: "비즈니스 안정성에 위협이 될 수 있습니다.",
128
+ action: "지속적인 모니터링이 필요합니다.",
129
+ reference: "#",
130
+ whenItMatters: "지속적인 관찰 없이는 언젠가 운영 환경에서 비용적 손실로 이어집니다."
131
+ };
132
+ }
133
+ async function analyzeRepository(repoPath, resultsDir) {
134
+ const files = [];
135
+ const IGNORE_DIRS = ["node_modules", ".git", "dist", "build", "venv", ".venv", "__pycache__"];
136
+ function walk(dir) {
137
+ let entries;
138
+ try {
139
+ entries = fs.readdirSync(dir, { withFileTypes: true });
140
+ }
141
+ catch {
142
+ return;
143
+ }
144
+ for (const ent of entries) {
145
+ const full = path.join(dir, ent.name);
146
+ if (ent.isDirectory()) {
147
+ if (IGNORE_DIRS.includes(ent.name))
148
+ continue;
149
+ walk(full);
150
+ }
151
+ else if (ent.isFile() && (ent.name.endsWith('.py') || ent.name.endsWith('.ts') || ent.name.endsWith('.js'))) {
152
+ files.push(full);
153
+ }
154
+ }
155
+ }
156
+ walk(repoPath);
157
+ const findings = [];
158
+ let criticalCount = 0;
159
+ let operationalGapCount = 0;
160
+ // 1. Operational Audit (Critical -30 each)
161
+ const auditChecks = [
162
+ { name: 'tests', type: 'directory', pattern: /tests?|spec/, message: '[서비스 중단 리스크] 테스트 코드가 없습니다. 장애 발생 시 복구 시간(MTTR)이 급증할 위험이 있습니다.' },
163
+ { name: 'CI', type: 'directory', pattern: /\.github\/workflows|\.circleci|jenkins|gitlab/, message: '[서비스 중단 리스크] 자동화된 CI 환경이 없습니다. 배포 안정성을 보장할 수 없습니다.' },
164
+ { name: 'Docker', type: 'file', pattern: /Dockerfile|docker-compose/, message: '[인프라 리스크] 컨테이너 기반 환경 설정이 없습니다. 로컬과 운영 환경 불일치로 인한 장애 발생 가능성이 높습니다.' },
165
+ { name: 'Pinning', type: 'file', pattern: /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/, message: '[인프라 리스크] 종속성 버전 고정 파일이 없습니다. 외부 라이브러리 업데이트 시 서비스가 즉시 중단될 수 있습니다.' },
166
+ { name: 'Env', type: 'file', pattern: /\.env\.example|\.env\.sample/, message: '[유지보수 리스크] 환경 설정 예시 파일(.env.example)이 없습니다. 신규 투입 인력의 셋업 비용이 발생하며 사고 대응이 늦어집니다.' },
167
+ ];
168
+ for (const check of auditChecks) {
169
+ let found = false;
170
+ try {
171
+ if (check.type === 'directory') {
172
+ const dirs = fs.readdirSync(repoPath, { withFileTypes: true }).filter(d => d.isDirectory());
173
+ found = dirs.some(d => check.pattern.test(d.name.toLowerCase()));
174
+ }
175
+ else {
176
+ const allFiles = fs.readdirSync(repoPath);
177
+ found = allFiles.some(f => check.pattern.test(f.toLowerCase()));
178
+ }
179
+ }
180
+ catch (e) {
181
+ found = false;
182
+ }
183
+ if (!found) {
184
+ const details = getAuditDetails('ProductionRisk', check.message);
185
+ findings.push({
186
+ file: 'Repository Root',
187
+ line: 0,
188
+ type: 'ProductionRisk',
189
+ ...details
190
+ });
191
+ operationalGapCount++;
192
+ }
193
+ }
194
+ // New Ritual Checks: Logging & Git Hygiene
195
+ const hasGitIgnore = fs.existsSync(path.join(repoPath, '.gitignore'));
196
+ if (!hasGitIgnore) {
197
+ const details = getAuditDetails('ProductionRisk', '[운영 리스크] .gitignore 파일이 없습니다. 민감한 정보가 저장소에 포함될 위험이 매우 높습니다.');
198
+ findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
199
+ operationalGapCount++;
200
+ }
201
+ let hasLogging = false;
202
+ // 2. Code Level Analysis (Python logic)
203
+ for (const f of files.filter(f => f.endsWith('.py'))) {
204
+ try {
205
+ const code = fs.readFileSync(f, 'utf8');
206
+ const lines = code.split('\n');
207
+ const relativePath = path.relative(repoPath, f);
208
+ // Logging Check
209
+ if (code.includes('import logging') || code.includes('from loguru import logger')) {
210
+ hasLogging = true;
211
+ }
212
+ // God Module Check (>500 lines)
213
+ if (lines.length > 500) {
214
+ const details = getAuditDetails('ProductionRisk', `[유지보수 리스크] 거대 모듈(${lines.length}줄)이 감지되었습니다. 단일 파일의 책임이 과도하여 변경 시 장애 파급력이 큽니다.`);
215
+ findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
216
+ operationalGapCount++;
217
+ }
218
+ const result = await (0, analyzer_js_1.analyzePythonCode)(code, relativePath);
219
+ if (result.hasError) {
220
+ const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
221
+ findings.push({
222
+ file: relativePath,
223
+ line: result.line || 0,
224
+ type: result.type || 'Error',
225
+ ...details
226
+ });
227
+ if (result.type === 'SecurityRisk')
228
+ criticalCount++;
229
+ }
230
+ }
231
+ catch (e) {
232
+ console.error(`Error analyzing ${f}:`, e);
233
+ }
234
+ }
235
+ if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
236
+ const details = getAuditDetails('ProductionRisk', '[운영 리스크] 로그 시스템 사용이 감지되지 않습니다. 장애 발생 시 원인 파악이 불가능합니다.');
237
+ findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
238
+ operationalGapCount++;
239
+ }
240
+ // 3. Dependency Scan (Scalability -15)
241
+ const { adj, cycles } = (0, archScanner_js_1.depsScan)(repoPath);
242
+ for (const cycle of cycles) {
243
+ const details = getAuditDetails('CircularDependency', '');
244
+ findings.push({
245
+ file: cycle[0],
246
+ line: 0,
247
+ type: 'CircularDependency',
248
+ ...details
249
+ });
250
+ }
251
+ // 4. Scoring Logic (PRS: Production Readiness Score)
252
+ // Max score is 95. Perfect project doesn't exist.
253
+ let score = 95;
254
+ score -= operationalGapCount * 30; // Critical operational gaps
255
+ score -= criticalCount * 45; // Security risks
256
+ score -= cycles.length * 15; // Structural issues
257
+ score = Math.max(0, score);
258
+ let status = 'Ready for Production';
259
+ if (score < 70)
260
+ status = 'Not Ready for Deployment';
261
+ else if (score < 90)
262
+ status = 'Needs Attention';
263
+ // 5. Visualization
264
+ let graphUrl = "";
265
+ if (resultsDir && fs.existsSync(resultsDir)) {
266
+ const scanId = `graph_${Date.now()}`;
267
+ const outputImgPath = path.join(resultsDir, `${scanId}.png`);
268
+ const configPath = path.join(resultsDir, `${scanId}.json`);
269
+ const edges = [];
270
+ adj.forEach((targets, source) => {
271
+ targets.forEach(target => edges.push([source, target]));
272
+ });
273
+ const config = { edges, cycles, output_path: outputImgPath };
274
+ fs.writeFileSync(configPath, JSON.stringify(config));
275
+ try {
276
+ const toolsDir = path.resolve(process.cwd(), "..", "..", "tools");
277
+ const visualizerPath = path.join(toolsDir, "visualizer.py");
278
+ (0, child_process_1.execSync)(`python3 ${visualizerPath} ${configPath}`);
279
+ graphUrl = `/results/${scanId}.png`;
280
+ }
281
+ catch (e) {
282
+ console.error("Visualization failed:", e.message);
283
+ }
284
+ }
285
+ return {
286
+ score,
287
+ status,
288
+ findings,
289
+ metrics: {
290
+ totalFiles: files.length,
291
+ pythonFiles: files.filter(f => f.endsWith('.py')).length,
292
+ criticalRisks: criticalCount,
293
+ operationalGaps: operationalGapCount
294
+ },
295
+ graphUrl,
296
+ disclosure: `배포 전 감사가 완료되었습니다. 발견된 리스크들은 실제 운영 환경에서 예기치 못한 서비스 중단이나 데이터 손실을 야기할 수 있는 항목들입니다.`,
297
+ cta: `배포 루틴 자동화를 위해 GitHub App을 설치하고 지속적인 배포 준비도(Release Readiness)를 관리하세요.`
298
+ };
299
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "archrisk-engine",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "test": "jest"
9
+ },
10
+ "dependencies": {
11
+ "@google/generative-ai": "^0.21.0",
12
+ "fs-extra": "^11.1.1"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.0.0",
16
+ "@types/fs-extra": "^11.0.1",
17
+ "typescript": "^5.0.0",
18
+ "jest": "^29.0.0",
19
+ "ts-jest": "^29.0.0"
20
+ }
21
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * [The Brain] AI Diagnosis Engine (TypeScript Version)
3
+ *
4
+ * Transforms errors and architecture issues into actionable solutions using Gemini API.
5
+ */
6
+
7
+ import { ArchIssue } from "./archScanner.js";
8
+
9
+ export interface DiagnosisResult {
10
+ severity: 'error' | 'warning';
11
+ issue: string;
12
+ suggestion: string;
13
+ fixedCode: string;
14
+ confidence: number;
15
+ }
16
+
17
+ const diagnosisCache = new Map<string, DiagnosisResult>();
18
+
19
+ /**
20
+ * Call Gemini API with structured prompt
21
+ */
22
+ async function callGeminiAPI(prompt: string): Promise<DiagnosisResult> {
23
+ const apiKey = process.env.GEMINI_API_KEY;
24
+
25
+ if (!apiKey) {
26
+ console.warn('[Brain] Gemini API key not configured. Returning mock diagnosis.');
27
+ return mockDiagnosis();
28
+ }
29
+
30
+ const model = process.env.AI_DIAGNOSIS_MODEL || 'gemini-2.0-flash';
31
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
32
+
33
+ try {
34
+ const response = await fetch(url, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({
38
+ contents: [{
39
+ parts: [{ text: prompt }]
40
+ }],
41
+ generationConfig: {
42
+ temperature: 0.2,
43
+ maxOutputTokens: parseInt(process.env.AI_DIAGNOSIS_MAX_TOKENS || '1024')
44
+ }
45
+ })
46
+ });
47
+
48
+ const data = await response.json();
49
+
50
+ if (data.error) {
51
+ throw new Error(data.error.message);
52
+ }
53
+
54
+ const text = data.candidates[0].content.parts[0].text;
55
+
56
+ const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/) || text.match(/\{[\s\S]*\}/);
57
+ if (jsonMatch) {
58
+ return JSON.parse(jsonMatch[1] || jsonMatch[0]);
59
+ }
60
+
61
+ throw new Error('Failed to parse JSON from Gemini response');
62
+ } catch (error: any) {
63
+ console.error('[Brain] Gemini API error:', error.message);
64
+ return mockDiagnosis('AI Diagnosis limited - using fallback', error.message);
65
+ }
66
+ }
67
+
68
+ function mockDiagnosis(
69
+ issue = 'API key not configured - using mock diagnosis',
70
+ suggestion = 'Set GEMINI_API_KEY in environment variables'
71
+ ): DiagnosisResult {
72
+ return {
73
+ severity: 'error',
74
+ issue: issue,
75
+ suggestion: suggestion,
76
+ fixedCode: '# Gemini API quota exceeded or key missing. Using static fallback analysis.',
77
+ confidence: 0.0
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Diagnose code error using Gemini AI
83
+ */
84
+ export async function diagnoseCodeError(
85
+ file: string,
86
+ line: number,
87
+ errorType: string,
88
+ errorMessage: string,
89
+ codeContext: string
90
+ ): Promise<DiagnosisResult> {
91
+ const cacheKey = `${file}:${line}:${errorType}`;
92
+ if (diagnosisCache.has(cacheKey)) {
93
+ return diagnosisCache.get(cacheKey)!;
94
+ }
95
+
96
+ const prompt = `당신은 AI-to-Job 컴파일러입니다. Python 에러를 실행 가능한 작업 정의로 변환하는 역할입니다.
97
+
98
+ **에러 정보:**
99
+ - 타입: ${errorType}
100
+ - 라인: ${line}
101
+ - 파일: ${file}
102
+ - 에러 메시지: ${errorMessage}
103
+
104
+ **코드 컨텍스트:**
105
+ \`\`\`python
106
+ ${codeContext}
107
+ \`\`\`
108
+
109
+ **작업:**
110
+ 이 에러를 분석하고 다음의 정확한 구조로 JSON 응답만 제공하세요:
111
+ {
112
+ "severity": "error" | "warning",
113
+ "issue": "문제에 대한 간단한 설명 (한글)",
114
+ "suggestion": "실행 가능한 수정 지침 (한글)",
115
+ "fixedCode": "수정된 코드 스니펫",
116
+ "confidence": 0.0 ~ 1.0 사이의 값
117
+ }
118
+
119
+ **중요**: 유효한 JSON만 응답하세요. 설명이나 JSON 블록 밖의 마크다운은 작성하지 마세요.`;
120
+
121
+ const diagnosis = await callGeminiAPI(prompt);
122
+ diagnosisCache.set(cacheKey, diagnosis);
123
+ return diagnosis;
124
+ }
125
+
126
+ /**
127
+ * Diagnose architecture issue using Gemini AI
128
+ */
129
+ export async function diagnoseArchIssue(
130
+ issue: ArchIssue,
131
+ fileContexts: { path: string, content: string }[]
132
+ ): Promise<DiagnosisResult> {
133
+ const contexts = fileContexts.map(f => `--- File: ${f.path} ---\n${f.content.slice(0, 2000)}...`).join('\n\n');
134
+
135
+ const prompt = `당신은 Senior Software Architect AI입니다. 현재 프로젝트의 아키텍처 결함을 분석하고 리팩토링 방안을 제시하는 역할입니다.
136
+
137
+ **탐지된 이슈 정보:**
138
+ - 타입(RuleId): ${issue.ruleId}
139
+ - 제목: ${issue.title}
140
+ - 상세 설명: ${issue.details}
141
+ - 관련 경로: ${issue.relatedPaths?.join(', ') || 'N/A'}
142
+
143
+ **코드 컨텍스트 (일부):**
144
+ ${contexts}
145
+
146
+ **작업:**
147
+ 이 아키텍처 이슈를 분석하고 다음의 정확한 구조로 JSON 응답만 제공하세요:
148
+ {
149
+ "severity": "error" | "warning",
150
+ "issue": "아키텍처 문제에 대한 구조적 분석 (한글)",
151
+ "suggestion": "구체적인 리팩토링 가이드 및 단계별 조치 사항 (한글)",
152
+ "fixedCode": "리팩토링에 도움이 되는 코드 스니펫 또는 인터페이스 설계 예시",
153
+ "confidence": 0.0 ~ 1.0 사이의 값
154
+ }
155
+
156
+ **중요**: 유효한 JSON만 응답하세요. 설명이나 JSON 블록 밖의 마크다운은 작성하지 마세요.`;
157
+
158
+ return await callGeminiAPI(prompt);
159
+ }