archrisk-engine 1.0.0 → 1.0.1

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.
@@ -2,6 +2,7 @@ export interface RepoAnalysisResult {
2
2
  score: number;
3
3
  status: 'Ready for Production' | 'Needs Attention' | 'Not Ready for Deployment';
4
4
  findings: {
5
+ id: string;
5
6
  title: string;
6
7
  file: string;
7
8
  line: number;
@@ -30,7 +31,11 @@ export interface RepoAnalysisResult {
30
31
  /**
31
32
  * 3-Tier Business Audit Translation
32
33
  */
33
- export declare function getAuditDetails(type: string, issue: string): {
34
+ /**
35
+ * 3-Tier Business Audit Translation with Standard IDs
36
+ */
37
+ export declare function getAuditDetails(id: string, type: string, issue: string): {
38
+ id: string;
34
39
  title: string;
35
40
  category: RepoAnalysisResult['findings'][0]['category'];
36
41
  evidence: string;
@@ -46,88 +46,144 @@ const archScanner_js_1 = require("./archScanner.js");
46
46
  /**
47
47
  * 3-Tier Business Audit Translation
48
48
  */
49
- function getAuditDetails(type, issue) {
50
- if (type === 'SecurityRisk') {
49
+ /**
50
+ * 3-Tier Business Audit Translation with Standard IDs
51
+ */
52
+ function getAuditDetails(id, type, issue) {
53
+ const commonFields = { id };
54
+ if (id === 'RR-SEC-001' || type === 'SecurityRisk') {
51
55
  return {
56
+ ...commonFields,
52
57
  title: "보안 취약점 위험 (Security Vulnerability)",
53
58
  category: 'Security',
54
59
  evidence: issue,
55
60
  standard: "OWASP Top 10 A03:2021 – Injection",
56
- impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 통로가 됩니다. 보안 사고 발생 시 법적 책임 및 서비스 중단이 불가피합니다.",
57
- action: "해당 코드를 즉시 격리하고 subprocess.run(shell=False) 또는 환경 변수(.env)를 활용하여 민감 정보를 분리하세요.",
61
+ impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 조건이 형성됩니다.",
62
+ action: `
63
+ # Action: 격리 및 환경변수 사용
64
+ subprocess.run(..., shell=False) # 권장
65
+ # 또는 .env 파일 사용
66
+ import os
67
+ SECRET = os.getenv('MY_SECRET')
68
+ `,
58
69
  reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
59
- whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있는 0순위 리스크입니다."
70
+ whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있습니다."
60
71
  };
61
72
  }
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
- }
73
+ if (id === 'RR-TEST-001') {
99
74
  return {
100
- title: "운영 준비도 미흡 (Production Readiness Gap)",
75
+ ...commonFields,
76
+ title: "자동화 테스트 부재 (Missing Automated Tests)",
101
77
  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: "장애 발생 시 원인 파악을 수분 내에 완료하지 못할 경우 다운타임이 기하급수적으로 늘어납니다."
78
+ evidence: "tests/ 디렉토리 또는 pytest/unittest 관련 설정을 찾을 수 없습니다.",
79
+ standard: "pytest Framework Documentation",
80
+ impact: "코드 변경 기존 기능이 파괴되었는지 확인할 방법이 없어, 배포 장애 발생 확률이 높아집니다.",
81
+ action: `
82
+ # Action: Create tests/test_smoke.py
83
+ def test_health_check():
84
+ assert True # Basic sanity check
85
+ `,
86
+ reference: "https://docs.pytest.org/",
87
+ whenItMatters: "팀원이 2명 이상으로 늘어나거나 배포 주기가 빨라질 때."
108
88
  };
109
89
  }
110
- if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
90
+ if (id === 'RR-CI-001') {
111
91
  return {
92
+ ...commonFields,
93
+ title: "배포 자동화 파이프라인 부재 (Missing CI Pipeline)",
94
+ category: 'Service Interruption',
95
+ evidence: "GitHub Actions (.github/workflows/*.yml) 또는 CI 설정 파일이 없습니다.",
96
+ standard: "GitHub Actions Documentation",
97
+ impact: "사람의 수동 배포 과정에서 실수가 발생할 수 있으며, 일관된 배포 상태를 보장할 수 없습니다.",
98
+ action: `
99
+ # Action: Create .github/workflows/ci.yml
100
+ name: CI
101
+ on: [push]
102
+ jobs:
103
+ test:
104
+ runs-on: ubuntu-latest
105
+ steps:
106
+ - uses: actions/checkout@v3
107
+ - run: npm test
108
+ `,
109
+ reference: "https://docs.github.com/en/actions",
110
+ whenItMatters: "배포 빈도가 주 2회 이상으로 증가할 때."
111
+ };
112
+ }
113
+ if (id === 'RR-OPS-001') {
114
+ return {
115
+ ...commonFields,
116
+ title: "운영 기본 위생 체크 실패 (Project Hygiene)",
117
+ category: 'Service Interruption',
118
+ evidence: issue, // Consolidated list will be passed here
119
+ standard: "12-Factor App / Docker Documentation",
120
+ impact: "개발 환경과 운영 환경의 불일치로 인해 '내 컴퓨터에서는 되는데 서버에서는 안 되는' 문제가 발생합니다.",
121
+ action: `
122
+ # Checklist to Fix:
123
+ 1. Create 'Dockerfile'
124
+ 2. Create '.gitignore' (use gitignore.io)
125
+ 3. Create 'requirements.txt' or 'package.json'
126
+ 4. Create '.env.example'
127
+ `,
128
+ reference: "https://12factor.net/",
129
+ whenItMatters: "신규 입사자 온보딩 또는 서버 이관 시."
130
+ };
131
+ }
132
+ if (id === 'RR-LOG-001') {
133
+ return {
134
+ ...commonFields,
135
+ title: "로깅 설정 미흡 (Insufficient Logging)",
136
+ category: 'Maintenance',
137
+ evidence: "코드 내에서 로깅 설정(logging, loguru 등)이 발견되지 않았습니다.",
138
+ standard: "Python Logging Cookbook",
139
+ impact: "장애 발생 시 원인을 추적할 수 있는 데이터가 없어 해결 시간이 길어집니다.",
140
+ action: `
141
+ # Action: Python Logging Setup
142
+ import logging
143
+ logging.basicConfig(level=logging.INFO)
144
+ logger = logging.getLogger(__name__)
145
+ logger.info("Server started")
146
+ `,
147
+ reference: "https://docs.python.org/3/howto/logging-cookbook.html",
148
+ whenItMatters: "운영 중 알 수 없는 500 에러가 발생했을 때."
149
+ };
150
+ }
151
+ if (id === 'RR-DEP-001' || type === 'CircularDependency') {
152
+ return {
153
+ ...commonFields,
112
154
  title: "구조적 의존성 결함 (Structural Dependency Issue)",
113
155
  category: 'Scalability',
114
- evidence: type === 'CircularDependency' ? "모듈 간의 상호 참조 구조가 감지되었습니다." : issue,
156
+ evidence: issue,
115
157
  standard: "Clean Architecture: Dependency Rule",
116
- impact: "모듈 간 결합도가 높아져 성능 저하 무한 루프 위험이 있으며, 코드 한 곳을 수정할 때 전혀 다른 곳에서 장애가 발생하는 '기술 부채'의 원인이 됩니다.",
117
- action: "의존성 방향을 방향으로 정리하세요.",
118
- reference: "https://peps.python.org/pep-0008/#imports",
119
- whenItMatters: "프로젝트 코드 베이스가 5,000줄 이상으로 늘어날 때 유지보수가 불가능한 시크릿 코드로 변질됩니다."
158
+ impact: "모듈 간 결합도가 높아져 유지보수가 어려워지고, 사이드 이펙트가 발생하기 쉽습니다.",
159
+ action: "상호 참조하는 모듈을 분리하거나 공통 모듈로 추출하세요.",
160
+ reference: "https://refactoring.guru/design-patterns",
161
+ whenItMatters: "프로젝트 규모가 커질수록 리팩토링 비용이 기하급수적으로 증가합니다."
162
+ };
163
+ }
164
+ if (id === 'RR-LINT-001' || type === 'GodModule') {
165
+ return {
166
+ ...commonFields,
167
+ title: "거대 모듈 감지 (God Module)",
168
+ category: 'Maintenance',
169
+ evidence: issue,
170
+ standard: "Clean Code: Functions",
171
+ impact: "단일 파일의 책임이 과도하여 변경 시 영향 범위를 예측하기 어렵습니다.",
172
+ action: "책임에 따라 파일을 분리하세요 (Separation of Concerns).",
173
+ reference: "https://pypi.org/project/flake8/",
174
+ whenItMatters: "기능 추가 시마다 버그가 발생할 때."
120
175
  };
121
176
  }
122
177
  return {
178
+ ...commonFields,
123
179
  title: "기타 잠재적 리스크 (Other Potential Risks)",
124
180
  category: 'Maintenance',
125
181
  evidence: issue,
126
182
  standard: "General Coding Best Practices",
127
- impact: "비즈니스 안정성에 위협이 수 있습니다.",
128
- action: "지속적인 모니터링이 필요합니다.",
183
+ impact: "잠재적인 버그나 유지보수 어려움이 있을 수 있습니다.",
184
+ action: "해당 코드를 리뷰하고 리팩토링을 고려하세요.",
129
185
  reference: "#",
130
- whenItMatters: "지속적인 관찰 없이는 언젠가 운영 환경에서 비용적 손실로 이어집니다."
186
+ whenItMatters: "지속적인 코드 품질 저하가 우려될 때."
131
187
  };
132
188
  }
133
189
  async function analyzeRepository(repoPath, resultsDir) {
@@ -158,44 +214,61 @@ async function analyzeRepository(repoPath, resultsDir) {
158
214
  let criticalCount = 0;
159
215
  let operationalGapCount = 0;
160
216
  // 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
- }
217
+ // 1. Operational Audit & Consolidation
218
+ const hygieneMissing = [];
219
+ // Check Tests
220
+ const hasTests = fs.readdirSync(repoPath, { withFileTypes: true })
221
+ .some(d => d.isDirectory() && /tests?|spec/i.test(d.name));
222
+ if (!hasTests) {
223
+ const details = getAuditDetails('RR-TEST-001', 'ProductionRisk', '');
224
+ findings.push({
225
+ file: 'Repository Root',
226
+ line: 0,
227
+ type: 'ProductionRisk',
228
+ ...details
229
+ });
230
+ operationalGapCount++;
193
231
  }
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 });
232
+ // Check CI
233
+ const hasCI = fs.readdirSync(repoPath, { withFileTypes: true })
234
+ .some(d => d.isDirectory() && /(\.github|\.circleci|jenkins|gitlab)/i.test(d.name));
235
+ if (!hasCI) {
236
+ const details = getAuditDetails('RR-CI-001', 'ProductionRisk', '');
237
+ findings.push({
238
+ file: 'Repository Root',
239
+ line: 0,
240
+ type: 'ProductionRisk',
241
+ ...details
242
+ });
243
+ operationalGapCount++;
244
+ }
245
+ // Check Hygiene (Docker, Pinning, Env, GitIgnore)
246
+ const allFiles = fs.readdirSync(repoPath);
247
+ // Docker
248
+ if (!allFiles.some(f => /Dockerfile|docker-compose/i.test(f))) {
249
+ hygieneMissing.push("✘ Dockerfile or docker-compose.yml missing");
250
+ }
251
+ // Pinning
252
+ if (!allFiles.some(f => /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/i.test(f))) {
253
+ hygieneMissing.push("✘ Dependency Lockfile (requirements.txt, package-lock.json, etc) missing");
254
+ }
255
+ // Env
256
+ if (!allFiles.some(f => /\.env\.example|\.env\.sample/i.test(f))) {
257
+ hygieneMissing.push("✘ .env.example (Safe environment template) missing");
258
+ }
259
+ // GitIgnore
260
+ if (!fs.existsSync(path.join(repoPath, '.gitignore'))) {
261
+ hygieneMissing.push("✘ .gitignore missing");
262
+ }
263
+ if (hygieneMissing.length > 0) {
264
+ const evidenceBlock = "\n" + hygieneMissing.join("\n");
265
+ const details = getAuditDetails('RR-OPS-001', 'ProductionRisk', evidenceBlock);
266
+ findings.push({
267
+ file: 'Repository Root',
268
+ line: 0,
269
+ type: 'ProductionRisk',
270
+ ...details
271
+ });
199
272
  operationalGapCount++;
200
273
  }
201
274
  let hasLogging = false;
@@ -211,13 +284,13 @@ async function analyzeRepository(repoPath, resultsDir) {
211
284
  }
212
285
  // God Module Check (>500 lines)
213
286
  if (lines.length > 500) {
214
- const details = getAuditDetails('ProductionRisk', `[유지보수 리스크] 거대 모듈(${lines.length}줄)이 감지되었습니다. 단일 파일의 책임이 과도하여 변경 시 장애 파급력이 큽니다.`);
287
+ const details = getAuditDetails('RR-LINT-001', 'GodModule', `File length: ${lines.length} lines`);
215
288
  findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
216
289
  operationalGapCount++;
217
290
  }
218
291
  const result = await (0, analyzer_js_1.analyzePythonCode)(code, relativePath);
219
292
  if (result.hasError) {
220
- const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
293
+ const details = getAuditDetails('RR-SEC-001', result.type || 'Error', result.error || 'Unknown Issue');
221
294
  findings.push({
222
295
  file: relativePath,
223
296
  line: result.line || 0,
@@ -233,14 +306,14 @@ async function analyzeRepository(repoPath, resultsDir) {
233
306
  }
234
307
  }
235
308
  if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
236
- const details = getAuditDetails('ProductionRisk', '[운영 리스크] 로그 시스템 사용이 감지되지 않습니다. 장애 발생 시 원인 파악이 불가능합니다.');
309
+ const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
237
310
  findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
238
311
  operationalGapCount++;
239
312
  }
240
313
  // 3. Dependency Scan (Scalability -15)
241
314
  const { adj, cycles } = (0, archScanner_js_1.depsScan)(repoPath);
242
315
  for (const cycle of cycles) {
243
- const details = getAuditDetails('CircularDependency', '');
316
+ const details = getAuditDetails('RR-DEP-001', 'CircularDependency', `Cycle: ${cycle.join(' -> ')}`);
244
317
  findings.push({
245
318
  file: cycle[0],
246
319
  line: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archrisk-engine",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -8,6 +8,7 @@ export interface RepoAnalysisResult {
8
8
  score: number; // Release Readiness Score (RRS)
9
9
  status: 'Ready for Production' | 'Needs Attention' | 'Not Ready for Deployment';
10
10
  findings: {
11
+ id: string;
11
12
  title: string;
12
13
  file: string;
13
14
  line: number;
@@ -37,88 +38,152 @@ export interface RepoAnalysisResult {
37
38
  /**
38
39
  * 3-Tier Business Audit Translation
39
40
  */
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') {
41
+ /**
42
+ * 3-Tier Business Audit Translation with Standard IDs
43
+ */
44
+ export function getAuditDetails(id: string, type: string, issue: string): { id: string; title: string; category: RepoAnalysisResult['findings'][0]['category']; evidence: string; standard: string; impact: string; action: string; reference: string; whenItMatters: string } {
45
+ const commonFields = { id };
46
+
47
+ if (id === 'RR-SEC-001' || type === 'SecurityRisk') {
42
48
  return {
49
+ ...commonFields,
43
50
  title: "보안 취약점 위험 (Security Vulnerability)",
44
51
  category: 'Security',
45
52
  evidence: issue,
46
53
  standard: "OWASP Top 10 A03:2021 – Injection",
47
- impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 통로가 됩니다. 보안 사고 발생 시 법적 책임 및 서비스 중단이 불가피합니다.",
48
- action: "해당 코드를 즉시 격리하고 subprocess.run(shell=False) 또는 환경 변수(.env)를 활용하여 민감 정보를 분리하세요.",
54
+ impact: "외부 공격자가 시스템 권한을 탈취하거나 민감 정보를 유출할 수 있는 조건이 형성됩니다.",
55
+ action: `
56
+ # Action: 격리 및 환경변수 사용
57
+ subprocess.run(..., shell=False) # 권장
58
+ # 또는 .env 파일 사용
59
+ import os
60
+ SECRET = os.getenv('MY_SECRET')
61
+ `,
49
62
  reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
50
- whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있는 0순위 리스크입니다."
63
+ whenItMatters: "배포 즉시 자동화된 스캐너나 공격자에 의해 탐지될 수 있습니다."
51
64
  };
52
65
  }
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
- }
66
+
67
+ if (id === 'RR-TEST-001') {
90
68
  return {
91
- title: "운영 준비도 미흡 (Production Readiness Gap)",
69
+ ...commonFields,
70
+ title: "자동화 테스트 부재 (Missing Automated Tests)",
92
71
  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: "장애 발생 시 원인 파악을 수분 내에 완료하지 못할 경우 다운타임이 기하급수적으로 늘어납니다."
72
+ evidence: "tests/ 디렉토리 또는 pytest/unittest 관련 설정을 찾을 수 없습니다.",
73
+ standard: "pytest Framework Documentation",
74
+ impact: "코드 변경 기존 기능이 파괴되었는지 확인할 방법이 없어, 배포 장애 발생 확률이 높아집니다.",
75
+ action: `
76
+ # Action: Create tests/test_smoke.py
77
+ def test_health_check():
78
+ assert True # Basic sanity check
79
+ `,
80
+ reference: "https://docs.pytest.org/",
81
+ whenItMatters: "팀원이 2명 이상으로 늘어나거나 배포 주기가 빨라질 때."
99
82
  };
100
83
  }
101
- if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
84
+
85
+ if (id === 'RR-CI-001') {
102
86
  return {
87
+ ...commonFields,
88
+ title: "배포 자동화 파이프라인 부재 (Missing CI Pipeline)",
89
+ category: 'Service Interruption',
90
+ evidence: "GitHub Actions (.github/workflows/*.yml) 또는 CI 설정 파일이 없습니다.",
91
+ standard: "GitHub Actions Documentation",
92
+ impact: "사람의 수동 배포 과정에서 실수가 발생할 수 있으며, 일관된 배포 상태를 보장할 수 없습니다.",
93
+ action: `
94
+ # Action: Create .github/workflows/ci.yml
95
+ name: CI
96
+ on: [push]
97
+ jobs:
98
+ test:
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v3
102
+ - run: npm test
103
+ `,
104
+ reference: "https://docs.github.com/en/actions",
105
+ whenItMatters: "배포 빈도가 주 2회 이상으로 증가할 때."
106
+ };
107
+ }
108
+
109
+ if (id === 'RR-OPS-001') {
110
+ return {
111
+ ...commonFields,
112
+ title: "운영 기본 위생 체크 실패 (Project Hygiene)",
113
+ category: 'Service Interruption',
114
+ evidence: issue, // Consolidated list will be passed here
115
+ standard: "12-Factor App / Docker Documentation",
116
+ impact: "개발 환경과 운영 환경의 불일치로 인해 '내 컴퓨터에서는 되는데 서버에서는 안 되는' 문제가 발생합니다.",
117
+ action: `
118
+ # Checklist to Fix:
119
+ 1. Create 'Dockerfile'
120
+ 2. Create '.gitignore' (use gitignore.io)
121
+ 3. Create 'requirements.txt' or 'package.json'
122
+ 4. Create '.env.example'
123
+ `,
124
+ reference: "https://12factor.net/",
125
+ whenItMatters: "신규 입사자 온보딩 또는 서버 이관 시."
126
+ };
127
+ }
128
+
129
+ if (id === 'RR-LOG-001') {
130
+ return {
131
+ ...commonFields,
132
+ title: "로깅 설정 미흡 (Insufficient Logging)",
133
+ category: 'Maintenance',
134
+ evidence: "코드 내에서 로깅 설정(logging, loguru 등)이 발견되지 않았습니다.",
135
+ standard: "Python Logging Cookbook",
136
+ impact: "장애 발생 시 원인을 추적할 수 있는 데이터가 없어 해결 시간이 길어집니다.",
137
+ action: `
138
+ # Action: Python Logging Setup
139
+ import logging
140
+ logging.basicConfig(level=logging.INFO)
141
+ logger = logging.getLogger(__name__)
142
+ logger.info("Server started")
143
+ `,
144
+ reference: "https://docs.python.org/3/howto/logging-cookbook.html",
145
+ whenItMatters: "운영 중 알 수 없는 500 에러가 발생했을 때."
146
+ };
147
+ }
148
+
149
+ if (id === 'RR-DEP-001' || type === 'CircularDependency') {
150
+ return {
151
+ ...commonFields,
103
152
  title: "구조적 의존성 결함 (Structural Dependency Issue)",
104
153
  category: 'Scalability',
105
- evidence: type === 'CircularDependency' ? "모듈 간의 상호 참조 구조가 감지되었습니다." : issue,
154
+ evidence: issue,
106
155
  standard: "Clean Architecture: Dependency Rule",
107
- impact: "모듈 간 결합도가 높아져 성능 저하 무한 루프 위험이 있으며, 코드 한 곳을 수정할 때 전혀 다른 곳에서 장애가 발생하는 '기술 부채'의 원인이 됩니다.",
108
- action: "의존성 방향을 방향으로 정리하세요.",
109
- reference: "https://peps.python.org/pep-0008/#imports",
110
- whenItMatters: "프로젝트 코드 베이스가 5,000줄 이상으로 늘어날 때 유지보수가 불가능한 시크릿 코드로 변질됩니다."
156
+ impact: "모듈 간 결합도가 높아져 유지보수가 어려워지고, 사이드 이펙트가 발생하기 쉽습니다.",
157
+ action: "상호 참조하는 모듈을 분리하거나 공통 모듈로 추출하세요.",
158
+ reference: "https://refactoring.guru/design-patterns",
159
+ whenItMatters: "프로젝트 규모가 커질수록 리팩토링 비용이 기하급수적으로 증가합니다."
160
+ };
161
+ }
162
+
163
+ if (id === 'RR-LINT-001' || type === 'GodModule') {
164
+ return {
165
+ ...commonFields,
166
+ title: "거대 모듈 감지 (God Module)",
167
+ category: 'Maintenance',
168
+ evidence: issue,
169
+ standard: "Clean Code: Functions",
170
+ impact: "단일 파일의 책임이 과도하여 변경 시 영향 범위를 예측하기 어렵습니다.",
171
+ action: "책임에 따라 파일을 분리하세요 (Separation of Concerns).",
172
+ reference: "https://pypi.org/project/flake8/",
173
+ whenItMatters: "기능 추가 시마다 버그가 발생할 때."
111
174
  };
112
175
  }
176
+
113
177
  return {
178
+ ...commonFields,
114
179
  title: "기타 잠재적 리스크 (Other Potential Risks)",
115
180
  category: 'Maintenance',
116
181
  evidence: issue,
117
182
  standard: "General Coding Best Practices",
118
- impact: "비즈니스 안정성에 위협이 수 있습니다.",
119
- action: "지속적인 모니터링이 필요합니다.",
183
+ impact: "잠재적인 버그나 유지보수 어려움이 있을 수 있습니다.",
184
+ action: "해당 코드를 리뷰하고 리팩토링을 고려하세요.",
120
185
  reference: "#",
121
- whenItMatters: "지속적인 관찰 없이는 언젠가 운영 환경에서 비용적 손실로 이어집니다."
186
+ whenItMatters: "지속적인 코드 품질 저하가 우려될 때."
122
187
  };
123
188
  }
124
189
 
@@ -147,43 +212,66 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
147
212
  let operationalGapCount = 0;
148
213
 
149
214
  // 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
- }
215
+ // 1. Operational Audit & Consolidation
216
+ const hygieneMissing: string[] = [];
217
+
218
+ // Check Tests
219
+ const hasTests = fs.readdirSync(repoPath, { withFileTypes: true })
220
+ .some(d => d.isDirectory() && /tests?|spec/i.test(d.name));
221
+ if (!hasTests) {
222
+ const details = getAuditDetails('RR-TEST-001', 'ProductionRisk', '');
223
+ findings.push({
224
+ file: 'Repository Root',
225
+ line: 0,
226
+ type: 'ProductionRisk',
227
+ ...details
228
+ });
229
+ operationalGapCount++;
180
230
  }
181
231
 
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 });
232
+ // Check CI
233
+ const hasCI = fs.readdirSync(repoPath, { withFileTypes: true })
234
+ .some(d => d.isDirectory() && /(\.github|\.circleci|jenkins|gitlab)/i.test(d.name));
235
+ if (!hasCI) {
236
+ const details = getAuditDetails('RR-CI-001', 'ProductionRisk', '');
237
+ findings.push({
238
+ file: 'Repository Root',
239
+ line: 0,
240
+ type: 'ProductionRisk',
241
+ ...details
242
+ });
243
+ operationalGapCount++;
244
+ }
245
+
246
+ // Check Hygiene (Docker, Pinning, Env, GitIgnore)
247
+ const allFiles = fs.readdirSync(repoPath);
248
+
249
+ // Docker
250
+ if (!allFiles.some(f => /Dockerfile|docker-compose/i.test(f))) {
251
+ hygieneMissing.push("✘ Dockerfile or docker-compose.yml missing");
252
+ }
253
+ // Pinning
254
+ if (!allFiles.some(f => /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/i.test(f))) {
255
+ hygieneMissing.push("✘ Dependency Lockfile (requirements.txt, package-lock.json, etc) missing");
256
+ }
257
+ // Env
258
+ if (!allFiles.some(f => /\.env\.example|\.env\.sample/i.test(f))) {
259
+ hygieneMissing.push("✘ .env.example (Safe environment template) missing");
260
+ }
261
+ // GitIgnore
262
+ if (!fs.existsSync(path.join(repoPath, '.gitignore'))) {
263
+ hygieneMissing.push("✘ .gitignore missing");
264
+ }
265
+
266
+ if (hygieneMissing.length > 0) {
267
+ const evidenceBlock = "\n" + hygieneMissing.join("\n");
268
+ const details = getAuditDetails('RR-OPS-001', 'ProductionRisk', evidenceBlock);
269
+ findings.push({
270
+ file: 'Repository Root',
271
+ line: 0,
272
+ type: 'ProductionRisk',
273
+ ...details
274
+ });
187
275
  operationalGapCount++;
188
276
  }
189
277
 
@@ -203,7 +291,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
203
291
 
204
292
  // God Module Check (>500 lines)
205
293
  if (lines.length > 500) {
206
- const details = getAuditDetails('ProductionRisk', `[유지보수 리스크] 거대 모듈(${lines.length}줄)이 감지되었습니다. 단일 파일의 책임이 과도하여 변경 시 장애 파급력이 큽니다.`);
294
+ const details = getAuditDetails('RR-LINT-001', 'GodModule', `File length: ${lines.length} lines`);
207
295
  findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
208
296
  operationalGapCount++;
209
297
  }
@@ -211,7 +299,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
211
299
  const result = await analyzePythonCode(code, relativePath);
212
300
 
213
301
  if (result.hasError) {
214
- const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
302
+ const details = getAuditDetails('RR-SEC-001', result.type || 'Error', result.error || 'Unknown Issue');
215
303
  findings.push({
216
304
  file: relativePath,
217
305
  line: result.line || 0,
@@ -227,7 +315,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
227
315
  }
228
316
 
229
317
  if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
230
- const details = getAuditDetails('ProductionRisk', '[운영 리스크] 로그 시스템 사용이 감지되지 않습니다. 장애 발생 시 원인 파악이 불가능합니다.');
318
+ const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
231
319
  findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
232
320
  operationalGapCount++;
233
321
  }
@@ -235,7 +323,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
235
323
  // 3. Dependency Scan (Scalability -15)
236
324
  const { adj, cycles } = depsScan(repoPath);
237
325
  for (const cycle of cycles) {
238
- const details = getAuditDetails('CircularDependency', '');
326
+ const details = getAuditDetails('RR-DEP-001', 'CircularDependency', `Cycle: ${cycle.join(' -> ')}`);
239
327
  findings.push({
240
328
  file: cycle[0],
241
329
  line: 0,