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,298 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { execSync } from 'child_process';
4
+ import { analyzePythonCode, AnalysisResult } from './analyzer.js';
5
+ import { depsScan } from './archScanner.js';
6
+
7
+ export interface RepoAnalysisResult {
8
+ score: number; // Release Readiness Score (RRS)
9
+ status: 'Ready for Production' | 'Needs Attention' | 'Not Ready for Deployment';
10
+ findings: {
11
+ title: string;
12
+ file: string;
13
+ line: number;
14
+ type: string;
15
+ category: 'Service Interruption' | 'Scalability' | 'Maintenance' | 'Security';
16
+ evidence: string;
17
+ standard: string;
18
+ impact: string;
19
+ action: string;
20
+ reference: string;
21
+ whenItMatters: string;
22
+ }[];
23
+ metrics: {
24
+ totalFiles: number;
25
+ pythonFiles: number;
26
+ criticalRisks: number;
27
+ operationalGaps: number;
28
+ };
29
+ graphUrl?: string;
30
+ disclosure?: string;
31
+ cta?: string;
32
+ }
33
+
34
+ /**
35
+ * CEO-ready "Business Translation" for technical risks
36
+ */
37
+ /**
38
+ * 3-Tier Business Audit Translation
39
+ */
40
+ export function getAuditDetails(type: string, issue: string): { title: string; category: RepoAnalysisResult['findings'][0]['category']; evidence: string; standard: string; impact: string; action: string; reference: string; whenItMatters: string } {
41
+ if (type === 'SecurityRisk') {
42
+ return {
43
+ title: "보안 취약점 위험 (Security Vulnerability)",
44
+ category: 'Security',
45
+ evidence: issue,
46
+ standard: "OWASP Top 10 A03:2021 – Injection",
47
+ impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 통로가 됩니다. 보안 사고 발생 시 법적 책임 및 서비스 중단이 불가피합니다.",
48
+ action: "해당 코드를 즉시 격리하고 subprocess.run(shell=False) 또는 환경 변수(.env)를 활용하여 민감 정보를 분리하세요.",
49
+ reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
50
+ whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있는 0순위 리스크입니다."
51
+ };
52
+ }
53
+ if (type === 'ProductionRisk') {
54
+ if (issue.includes('테스트')) {
55
+ return {
56
+ title: "자동화 테스트 부재 (Missing Automated Tests)",
57
+ category: 'Service Interruption',
58
+ evidence: "tests/ 디렉토리 또는 pytest/unittest 관련 설정을 찾을 수 없습니다.",
59
+ standard: "Stability Standard: Code Coverage > 70%",
60
+ impact: "수동 테스트에 의존하게 되어 코드 변경 시마다 예상치 못한 장애가 운영 환경에 배포될 위험이 높으며, 장애 복구 시간(MTTR)이 매우 길어집니다.",
61
+ action: "pytest를 설치하고 핵심 로직에 대한 Smoke Test 3개를 먼저 작성하세요.",
62
+ reference: "https://docs.pytest.org/en/7.1.x/getting-started.html",
63
+ whenItMatters: "코드 변경 주기가 빨라지거나 팀원이 2명 이상으로 늘어날 때 병목이 발생합니다."
64
+ };
65
+ }
66
+ if (issue.includes('CI')) {
67
+ return {
68
+ title: "배포 자동화 파이프라인 부재 (Missing CI Pipeline)",
69
+ category: 'Service Interruption',
70
+ evidence: "GitHub Actions 또는 CI 도구 워크플로우 설정을 발견할 수 없습니다.",
71
+ standard: "Modern DevOps Standard: Continuous Integration",
72
+ impact: "코드 머지 전 검증 자동화가 불가능하여, 사람이 개입하는 과정에서 실수가 반복됩니다. 이는 배포 안정성을 근본적으로 파악할 수 없게 만듭니다.",
73
+ action: ".github/workflows/main.yml 파일을 생성하고 자동 테스트 프로세스를 구축하세요.",
74
+ reference: "https://github.com/features/actions",
75
+ whenItMatters: "하루에 2회 이상 배포를 시도할 때 검증 누락으로 인한 사고 확률이 80%를 상회하게 됩니다."
76
+ };
77
+ }
78
+ if (issue.includes('Docker')) {
79
+ return {
80
+ title: "컨테이너화 구성 누락 (Environment Inconsistency)",
81
+ category: 'Service Interruption',
82
+ evidence: "Dockerfile 또는 docker-compose.yml 파일이 존재하지 않습니다.",
83
+ standard: "Cloud Native Standard: Immutable Infrastructure",
84
+ impact: "로컬 개발 환경과 실제 운영 환경의 버전 차이로 인해 '내 컴퓨터에선 되는데 서버에선 안 되는' 예측 불가능한 장애가 발생합니다.",
85
+ action: "Dockerfile을 작성하여 환경 설정을 컨테이너화하세요.",
86
+ reference: "https://docs.docker.com/engine/reference/builder/",
87
+ whenItMatters: "신규 개발자가 팀에 합류하거나 클라우드 인프라 확장이 필요할 때 시간 낭비가 심화됩니다."
88
+ };
89
+ }
90
+ return {
91
+ title: "운영 준비도 미흡 (Production Readiness Gap)",
92
+ category: 'Service Interruption',
93
+ evidence: issue,
94
+ standard: "Production Readiness Checklist v1.0",
95
+ impact: "실제 운영 환경에서 예기치 못한 장애 발생 시 복구가 불가능하거나 매우 지연될 수 있는 핵심 요소입니다.",
96
+ action: "해당 설정 파일을 추가하거나 환경 설정을 고정하여 재현 가능성을 확보하세요.",
97
+ reference: "https://google.github.io/styleguide/pyguide.html",
98
+ whenItMatters: "장애 발생 시 원인 파악을 수분 내에 완료하지 못할 경우 다운타임이 기하급수적으로 늘어납니다."
99
+ };
100
+ }
101
+ if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
102
+ return {
103
+ title: "구조적 의존성 결함 (Structural Dependency Issue)",
104
+ category: 'Scalability',
105
+ evidence: type === 'CircularDependency' ? "모듈 간의 상호 참조 구조가 감지되었습니다." : issue,
106
+ standard: "Clean Architecture: Dependency Rule",
107
+ impact: "모듈 간 결합도가 높아져 성능 저하 및 무한 루프 위험이 있으며, 코드 한 곳을 수정할 때 전혀 다른 곳에서 장애가 발생하는 '기술 부채'의 원인이 됩니다.",
108
+ action: "의존성 방향을 한 방향으로 정리하세요.",
109
+ reference: "https://peps.python.org/pep-0008/#imports",
110
+ whenItMatters: "프로젝트 코드 베이스가 5,000줄 이상으로 늘어날 때 유지보수가 불가능한 시크릿 코드로 변질됩니다."
111
+ };
112
+ }
113
+ return {
114
+ title: "기타 잠재적 리스크 (Other Potential Risks)",
115
+ category: 'Maintenance',
116
+ evidence: issue,
117
+ standard: "General Coding Best Practices",
118
+ impact: "비즈니스 안정성에 위협이 될 수 있습니다.",
119
+ action: "지속적인 모니터링이 필요합니다.",
120
+ reference: "#",
121
+ whenItMatters: "지속적인 관찰 없이는 언젠가 운영 환경에서 비용적 손실로 이어집니다."
122
+ };
123
+ }
124
+
125
+ export async function analyzeRepository(repoPath: string, resultsDir?: string): Promise<RepoAnalysisResult> {
126
+ const files: string[] = [];
127
+ const IGNORE_DIRS = ["node_modules", ".git", "dist", "build", "venv", ".venv", "__pycache__"];
128
+
129
+ function walk(dir: string) {
130
+ let entries;
131
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
132
+ for (const ent of entries) {
133
+ const full = path.join(dir, ent.name);
134
+ if (ent.isDirectory()) {
135
+ if (IGNORE_DIRS.includes(ent.name)) continue;
136
+ walk(full);
137
+ } else if (ent.isFile() && (ent.name.endsWith('.py') || ent.name.endsWith('.ts') || ent.name.endsWith('.js'))) {
138
+ files.push(full);
139
+ }
140
+ }
141
+ }
142
+
143
+ walk(repoPath);
144
+
145
+ const findings: RepoAnalysisResult['findings'] = [];
146
+ let criticalCount = 0;
147
+ let operationalGapCount = 0;
148
+
149
+ // 1. Operational Audit (Critical -30 each)
150
+ const auditChecks = [
151
+ { name: 'tests', type: 'directory', pattern: /tests?|spec/, message: '[서비스 중단 리스크] 테스트 코드가 없습니다. 장애 발생 시 복구 시간(MTTR)이 급증할 위험이 있습니다.' },
152
+ { name: 'CI', type: 'directory', pattern: /\.github\/workflows|\.circleci|jenkins|gitlab/, message: '[서비스 중단 리스크] 자동화된 CI 환경이 없습니다. 배포 안정성을 보장할 수 없습니다.' },
153
+ { name: 'Docker', type: 'file', pattern: /Dockerfile|docker-compose/, message: '[인프라 리스크] 컨테이너 기반 환경 설정이 없습니다. 로컬과 운영 환경 불일치로 인한 장애 발생 가능성이 높습니다.' },
154
+ { name: 'Pinning', type: 'file', pattern: /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/, message: '[인프라 리스크] 종속성 버전 고정 파일이 없습니다. 외부 라이브러리 업데이트 시 서비스가 즉시 중단될 수 있습니다.' },
155
+ { name: 'Env', type: 'file', pattern: /\.env\.example|\.env\.sample/, message: '[유지보수 리스크] 환경 설정 예시 파일(.env.example)이 없습니다. 신규 투입 인력의 셋업 비용이 발생하며 사고 대응이 늦어집니다.' },
156
+ ];
157
+
158
+ for (const check of auditChecks) {
159
+ let found = false;
160
+ try {
161
+ if (check.type === 'directory') {
162
+ const dirs = fs.readdirSync(repoPath, { withFileTypes: true }).filter(d => d.isDirectory());
163
+ found = dirs.some(d => check.pattern.test(d.name.toLowerCase()));
164
+ } else {
165
+ const allFiles = fs.readdirSync(repoPath);
166
+ found = allFiles.some(f => check.pattern.test(f.toLowerCase()));
167
+ }
168
+ } catch (e) { found = false; }
169
+
170
+ if (!found) {
171
+ const details = getAuditDetails('ProductionRisk', check.message);
172
+ findings.push({
173
+ file: 'Repository Root',
174
+ line: 0,
175
+ type: 'ProductionRisk',
176
+ ...details
177
+ });
178
+ operationalGapCount++;
179
+ }
180
+ }
181
+
182
+ // New Ritual Checks: Logging & Git Hygiene
183
+ const hasGitIgnore = fs.existsSync(path.join(repoPath, '.gitignore'));
184
+ if (!hasGitIgnore) {
185
+ const details = getAuditDetails('ProductionRisk', '[운영 리스크] .gitignore 파일이 없습니다. 민감한 정보가 저장소에 포함될 위험이 매우 높습니다.');
186
+ findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
187
+ operationalGapCount++;
188
+ }
189
+
190
+ let hasLogging = false;
191
+
192
+ // 2. Code Level Analysis (Python logic)
193
+ for (const f of files.filter(f => f.endsWith('.py'))) {
194
+ try {
195
+ const code = fs.readFileSync(f, 'utf8');
196
+ const lines = code.split('\n');
197
+ const relativePath = path.relative(repoPath, f);
198
+
199
+ // Logging Check
200
+ if (code.includes('import logging') || code.includes('from loguru import logger')) {
201
+ hasLogging = true;
202
+ }
203
+
204
+ // God Module Check (>500 lines)
205
+ if (lines.length > 500) {
206
+ const details = getAuditDetails('ProductionRisk', `[유지보수 리스크] 거대 모듈(${lines.length}줄)이 감지되었습니다. 단일 파일의 책임이 과도하여 변경 시 장애 파급력이 큽니다.`);
207
+ findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
208
+ operationalGapCount++;
209
+ }
210
+
211
+ const result = await analyzePythonCode(code, relativePath);
212
+
213
+ if (result.hasError) {
214
+ const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
215
+ findings.push({
216
+ file: relativePath,
217
+ line: result.line || 0,
218
+ type: result.type || 'Error',
219
+ ...details
220
+ });
221
+
222
+ if (result.type === 'SecurityRisk') criticalCount++;
223
+ }
224
+ } catch (e) {
225
+ console.error(`Error analyzing ${f}:`, e);
226
+ }
227
+ }
228
+
229
+ if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
230
+ const details = getAuditDetails('ProductionRisk', '[운영 리스크] 로그 시스템 사용이 감지되지 않습니다. 장애 발생 시 원인 파악이 불가능합니다.');
231
+ findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
232
+ operationalGapCount++;
233
+ }
234
+
235
+ // 3. Dependency Scan (Scalability -15)
236
+ const { adj, cycles } = depsScan(repoPath);
237
+ for (const cycle of cycles) {
238
+ const details = getAuditDetails('CircularDependency', '');
239
+ findings.push({
240
+ file: cycle[0],
241
+ line: 0,
242
+ type: 'CircularDependency',
243
+ ...details
244
+ });
245
+ }
246
+
247
+ // 4. Scoring Logic (PRS: Production Readiness Score)
248
+ // Max score is 95. Perfect project doesn't exist.
249
+ let score = 95;
250
+ score -= operationalGapCount * 30; // Critical operational gaps
251
+ score -= criticalCount * 45; // Security risks
252
+ score -= cycles.length * 15; // Structural issues
253
+ score = Math.max(0, score);
254
+
255
+ let status: RepoAnalysisResult['status'] = 'Ready for Production';
256
+ if (score < 70) status = 'Not Ready for Deployment';
257
+ else if (score < 90) status = 'Needs Attention';
258
+
259
+ // 5. Visualization
260
+ let graphUrl = "";
261
+ if (resultsDir && fs.existsSync(resultsDir)) {
262
+ const scanId = `graph_${Date.now()}`;
263
+ const outputImgPath = path.join(resultsDir, `${scanId}.png`);
264
+ const configPath = path.join(resultsDir, `${scanId}.json`);
265
+
266
+ const edges: [string, string][] = [];
267
+ adj.forEach((targets, source) => {
268
+ targets.forEach(target => edges.push([source, target]));
269
+ });
270
+
271
+ const config = { edges, cycles, output_path: outputImgPath };
272
+ fs.writeFileSync(configPath, JSON.stringify(config));
273
+
274
+ try {
275
+ const toolsDir = path.resolve(process.cwd(), "..", "..", "tools");
276
+ const visualizerPath = path.join(toolsDir, "visualizer.py");
277
+ execSync(`python3 ${visualizerPath} ${configPath}`);
278
+ graphUrl = `/results/${scanId}.png`;
279
+ } catch (e: any) {
280
+ console.error("Visualization failed:", e.message);
281
+ }
282
+ }
283
+
284
+ return {
285
+ score,
286
+ status,
287
+ findings,
288
+ metrics: {
289
+ totalFiles: files.length,
290
+ pythonFiles: files.filter(f => f.endsWith('.py')).length,
291
+ criticalRisks: criticalCount,
292
+ operationalGaps: operationalGapCount
293
+ },
294
+ graphUrl,
295
+ disclosure: `배포 전 감사가 완료되었습니다. 발견된 리스크들은 실제 운영 환경에서 예기치 못한 서비스 중단이나 데이터 손실을 야기할 수 있는 항목들입니다.`,
296
+ cta: `배포 루틴 자동화를 위해 GitHub App을 설치하고 지속적인 배포 준비도(Release Readiness)를 관리하세요.`
297
+ };
298
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": [
15
+ "src/**/*"
16
+ ],
17
+ "exclude": [
18
+ "node_modules",
19
+ "dist",
20
+ "**/*.test.ts"
21
+ ]
22
+ }