archrisk-engine 1.0.0 β†’ 1.0.2

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/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from './aiDiagnosis.js';
3
3
  export * from './archScanner.js';
4
4
  export * from './repoAnalyzer.js';
5
5
  export * from './deepAnalysis.js';
6
+ export * from './jsAnalyzer.js';
package/dist/index.js CHANGED
@@ -19,3 +19,4 @@ __exportStar(require("./aiDiagnosis.js"), exports);
19
19
  __exportStar(require("./archScanner.js"), exports);
20
20
  __exportStar(require("./repoAnalyzer.js"), exports);
21
21
  __exportStar(require("./deepAnalysis.js"), exports);
22
+ __exportStar(require("./jsAnalyzer.js"), exports);
@@ -0,0 +1,8 @@
1
+ import { AnalysisResult } from './analyzer.js';
2
+ /**
3
+ * [The Eye] JS/TS Analysis Engine
4
+ *
5
+ * RegEx-based static analysis for JavaScript/TypeScript files.
6
+ * MVP: Focused on 8 specific credibility rules.
7
+ */
8
+ export declare function analyzeJsTsCode(code: string, fileName: string): Promise<AnalysisResult>;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeJsTsCode = analyzeJsTsCode;
4
+ /**
5
+ * [The Eye] JS/TS Analysis Engine
6
+ *
7
+ * RegEx-based static analysis for JavaScript/TypeScript files.
8
+ * MVP: Focused on 8 specific credibility rules.
9
+ */
10
+ async function analyzeJsTsCode(code, fileName) {
11
+ // 1. Architecture Health Scan (God Module / Large File)
12
+ const lines = code.split('\n');
13
+ if (lines.length > 800) {
14
+ return {
15
+ hasError: true,
16
+ error: `Large File Risk: ${lines.length} lines. Maintanability risk.`,
17
+ line: 1,
18
+ type: 'ProductionRisk',
19
+ file: fileName
20
+ };
21
+ }
22
+ // 2. Risk Scan (Security & Production Readiness)
23
+ const riskResult = scanForJsRisks(code, fileName, lines);
24
+ if (riskResult.hasError) {
25
+ return riskResult;
26
+ }
27
+ return { hasError: false };
28
+ }
29
+ function scanForJsRisks(code, fileName, lines) {
30
+ const risks = [
31
+ // Critical (πŸ”΄) - Security
32
+ {
33
+ pattern: /eval\s*\(|new\s+Function\s*\(/,
34
+ type: 'SecurityRisk',
35
+ message: '[Security] Dynamic code execution detected (eval/new Function). This is a severe security risk.'
36
+ },
37
+ {
38
+ pattern: /child_process\.(exec|execSync)\s*\(/,
39
+ type: 'SecurityRisk',
40
+ message: '[Security] Shell command execution detected. Ensure inputs are sanitized or use spawn without shell.'
41
+ },
42
+ {
43
+ pattern: /spawn\s*\(.*,\s*\{.*shell:\s*true/s, // Multi-line match attempt with DOTALL flag simulated or just simple check
44
+ type: 'SecurityRisk',
45
+ message: '[Security] spawn with { shell: true } detected. This enables shell command injection.'
46
+ },
47
+ {
48
+ pattern: /(?:api[._-]?key|password|secret|token)\s*[:=]\s*['"][a-zA-Z0-9_-]{10,}['"]/i,
49
+ type: 'SecurityRisk',
50
+ message: '[Security] Hardcoded secret detected. Use environment variables.'
51
+ },
52
+ // Warning (🟑) - Production Readiness
53
+ {
54
+ condition: (l) => /(axios(\.[a-z]+)?|fetch|http\.(get|request))\s*\(/.test(l) && !/timeout/.test(l),
55
+ type: 'ProductionRisk',
56
+ message: '[Reliability] HTTP call missing explicit timeout. This can cause cascading failures.'
57
+ },
58
+ // Heuristic: App listen but no global error handler (simplified: check for generic app.use((err... pattern)
59
+ {
60
+ condition: (c) => c.includes('app.listen') && !/app\.use\s*\(\s*\(\s*err/.test(c),
61
+ type: 'ProductionRisk',
62
+ message: '[Reliability] Express app detected but Global Error Handler missing. Unhandled errors may crash the server.'
63
+ },
64
+ {
65
+ pattern: /console\.log\s*\(/,
66
+ type: 'ProductionRisk',
67
+ message: '[Observability] console.log used in production code. Use a structured logger (winston/pino).'
68
+ }
69
+ ];
70
+ // Line-based checks
71
+ for (let i = 0; i < lines.length; i++) {
72
+ const line = lines[i];
73
+ for (const risk of risks) {
74
+ // Pattern check
75
+ if (risk.pattern && risk.pattern.test(line)) {
76
+ // Skip comments for simple cases
77
+ if (line.trim().startsWith('//') || line.trim().startsWith('*'))
78
+ continue;
79
+ return {
80
+ hasError: true,
81
+ error: risk.message,
82
+ line: i + 1,
83
+ type: risk.type,
84
+ file: fileName
85
+ };
86
+ }
87
+ // Line-based Condition check (special case for timeout)
88
+ // We distinguish global vs line condition by context?
89
+ // The 'app.listen' check is clearly global (checks whole code).
90
+ // The 'timeout' check is clearly line based (checks 'l').
91
+ // Let's rely on the message content to know if it is line-based for MVP simplicity
92
+ if (risk.condition && risk.message.includes('HTTP missing timeout') && risk.condition(line)) {
93
+ if (line.trim().startsWith('//') || line.trim().startsWith('*'))
94
+ continue;
95
+ return {
96
+ hasError: true,
97
+ error: risk.message,
98
+ line: i + 1,
99
+ type: risk.type,
100
+ file: fileName
101
+ };
102
+ }
103
+ }
104
+ }
105
+ // File-based checks (Global patterns)
106
+ for (const risk of risks) {
107
+ // Global Condition check
108
+ if (risk.condition && !risk.message.includes('HTTP missing timeout') && risk.condition(code)) {
109
+ return {
110
+ hasError: true,
111
+ error: risk.message,
112
+ line: 0,
113
+ type: risk.type,
114
+ file: fileName
115
+ };
116
+ }
117
+ // Multi-line regex checks
118
+ if (risk.pattern && risk.pattern.flags.includes('s') && risk.pattern.test(code)) {
119
+ return {
120
+ hasError: true,
121
+ error: risk.message,
122
+ line: 0,
123
+ type: risk.type,
124
+ file: fileName
125
+ };
126
+ }
127
+ }
128
+ return { hasError: false };
129
+ }
@@ -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;
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const child_process_1 = require("child_process");
41
41
  const analyzer_js_1 = require("./analyzer.js");
42
+ const jsAnalyzer_js_1 = require("./jsAnalyzer.js");
42
43
  const archScanner_js_1 = require("./archScanner.js");
43
44
  /**
44
45
  * CEO-ready "Business Translation" for technical risks
@@ -46,88 +47,144 @@ const archScanner_js_1 = require("./archScanner.js");
46
47
  /**
47
48
  * 3-Tier Business Audit Translation
48
49
  */
49
- function getAuditDetails(type, issue) {
50
- if (type === 'SecurityRisk') {
50
+ /**
51
+ * 3-Tier Business Audit Translation with Standard IDs
52
+ */
53
+ function getAuditDetails(id, type, issue) {
54
+ const commonFields = { id };
55
+ if (id === 'RR-SEC-001' || type === 'SecurityRisk') {
51
56
  return {
57
+ ...commonFields,
52
58
  title: "λ³΄μ•ˆ 취약점 μœ„ν—˜ (Security Vulnerability)",
53
59
  category: 'Security',
54
60
  evidence: issue,
55
61
  standard: "OWASP Top 10 A03:2021 – Injection",
56
- impact: "μ™ΈλΆ€ κ³΅κ²©μžκ°€ μ‹œμŠ€ν…œ κΆŒν•œμ„ νƒˆμ·¨ν•˜κ±°λ‚˜ 민감 정보λ₯Ό μœ μΆœν•  수 μžˆλŠ” ν†΅λ‘œκ°€ λ©λ‹ˆλ‹€. λ³΄μ•ˆ 사고 λ°œμƒ μ‹œ 법적 μ±…μž„ 및 μ„œλΉ„μŠ€ 쀑단이 λΆˆκ°€ν”Όν•©λ‹ˆλ‹€.",
57
- action: "ν•΄λ‹Ή μ½”λ“œλ₯Ό μ¦‰μ‹œ κ²©λ¦¬ν•˜κ³  subprocess.run(shell=False) λ˜λŠ” ν™˜κ²½ λ³€μˆ˜(.env)λ₯Ό ν™œμš©ν•˜μ—¬ 민감 정보λ₯Ό λΆ„λ¦¬ν•˜μ„Έμš”.",
62
+ impact: "μ™ΈλΆ€ κ³΅κ²©μžκ°€ μ‹œμŠ€ν…œ κΆŒν•œμ„ νƒˆμ·¨ν•˜κ±°λ‚˜ 민감 정보λ₯Ό μœ μΆœν•  수 μžˆλŠ” 쑰건이 ν˜•μ„±λ©λ‹ˆλ‹€.",
63
+ action: `
64
+ # Action: 격리 및 ν™˜κ²½λ³€μˆ˜ μ‚¬μš©
65
+ subprocess.run(..., shell=False) # ꢌμž₯
66
+ # λ˜λŠ” .env 파일 μ‚¬μš©
67
+ import os
68
+ SECRET = os.getenv('MY_SECRET')
69
+ `,
58
70
  reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
59
- whenItMatters: "배포 μ¦‰μ‹œ μžλ™ν™”λœ μŠ€μΊλ„ˆλ‚˜ κ³΅κ²©μžμ— μ˜ν•΄ 탐지될 수 μžˆλŠ” 0μˆœμœ„ λ¦¬μŠ€ν¬μž…λ‹ˆλ‹€."
71
+ whenItMatters: "배포 μ¦‰μ‹œ μžλ™ν™”λœ μŠ€μΊλ„ˆλ‚˜ κ³΅κ²©μžμ— μ˜ν•΄ 탐지될 수 μžˆμŠ΅λ‹ˆλ‹€."
60
72
  };
61
73
  }
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
- }
74
+ if (id === 'RR-TEST-001') {
99
75
  return {
100
- title: "운영 쀀비도 미흑 (Production Readiness Gap)",
76
+ ...commonFields,
77
+ title: "μžλ™ν™” ν…ŒμŠ€νŠΈ λΆ€μž¬ (Missing Automated Tests)",
101
78
  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: "μž₯μ•  λ°œμƒ μ‹œ 원인 νŒŒμ•…μ„ μˆ˜λΆ„ 내에 μ™„λ£Œν•˜μ§€ λͺ»ν•  경우 λ‹€μš΄νƒ€μž„μ΄ κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ λŠ˜μ–΄λ‚©λ‹ˆλ‹€."
79
+ evidence: "tests/ 디렉토리 λ˜λŠ” pytest/unittest κ΄€λ ¨ 섀정을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.",
80
+ standard: "pytest Framework Documentation",
81
+ impact: "μ½”λ“œ λ³€κ²½ μ‹œ κΈ°μ‘΄ κΈ°λŠ₯이 νŒŒκ΄΄λ˜μ—ˆλŠ”μ§€ 확인할 방법이 μ—†μ–΄, 배포 ν›„ μž₯μ•  λ°œμƒ ν™•λ₯ μ΄ λ†’μ•„μ§‘λ‹ˆλ‹€.",
82
+ action: `
83
+ # Action: Create tests/test_smoke.py
84
+ def test_health_check():
85
+ assert True # Basic sanity check
86
+ `,
87
+ reference: "https://docs.pytest.org/",
88
+ whenItMatters: "νŒ€μ›μ΄ 2λͺ… μ΄μƒμœΌλ‘œ λŠ˜μ–΄λ‚˜κ±°λ‚˜ 배포 μ£ΌκΈ°κ°€ 빨라질 λ•Œ."
89
+ };
90
+ }
91
+ if (id === 'RR-CI-001') {
92
+ return {
93
+ ...commonFields,
94
+ title: "배포 μžλ™ν™” νŒŒμ΄ν”„λΌμΈ λΆ€μž¬ (Missing CI Pipeline)",
95
+ category: 'Service Interruption',
96
+ evidence: "GitHub Actions (.github/workflows/*.yml) λ˜λŠ” CI μ„€μ • 파일이 μ—†μŠ΅λ‹ˆλ‹€.",
97
+ standard: "GitHub Actions Documentation",
98
+ impact: "μ‚¬λžŒμ˜ μˆ˜λ™ 배포 κ³Όμ •μ—μ„œ μ‹€μˆ˜κ°€ λ°œμƒν•  수 있으며, μΌκ΄€λœ 배포 μƒνƒœλ₯Ό 보μž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€.",
99
+ action: `
100
+ # Action: Create .github/workflows/ci.yml
101
+ name: CI
102
+ on: [push]
103
+ jobs:
104
+ test:
105
+ runs-on: ubuntu-latest
106
+ steps:
107
+ - uses: actions/checkout@v3
108
+ - run: npm test
109
+ `,
110
+ reference: "https://docs.github.com/en/actions",
111
+ whenItMatters: "배포 λΉˆλ„κ°€ μ£Ό 2회 μ΄μƒμœΌλ‘œ 증가할 λ•Œ."
112
+ };
113
+ }
114
+ if (id === 'RR-OPS-001') {
115
+ return {
116
+ ...commonFields,
117
+ title: "운영 κΈ°λ³Έ μœ„μƒ 체크 μ‹€νŒ¨ (Project Hygiene)",
118
+ category: 'Service Interruption',
119
+ evidence: issue, // Consolidated list will be passed here
120
+ standard: "12-Factor App / Docker Documentation",
121
+ impact: "개발 ν™˜κ²½κ³Ό 운영 ν™˜κ²½μ˜ 뢈일치둜 인해 'λ‚΄ μ»΄ν“¨ν„°μ—μ„œλŠ” λ˜λŠ”λ° μ„œλ²„μ—μ„œλŠ” μ•ˆ λ˜λŠ”' λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.",
122
+ action: `
123
+ # Checklist to Fix:
124
+ 1. Create 'Dockerfile'
125
+ 2. Create '.gitignore' (use gitignore.io)
126
+ 3. Create 'requirements.txt' or 'package.json'
127
+ 4. Create '.env.example'
128
+ `,
129
+ reference: "https://12factor.net/",
130
+ whenItMatters: "μ‹ κ·œ μž…μ‚¬μž μ˜¨λ³΄λ”© λ˜λŠ” μ„œλ²„ 이관 μ‹œ."
131
+ };
132
+ }
133
+ if (id === 'RR-LOG-001') {
134
+ return {
135
+ ...commonFields,
136
+ title: "λ‘œκΉ… μ„€μ • 미흑 (Insufficient Logging)",
137
+ category: 'Maintenance',
138
+ evidence: "μ½”λ“œ λ‚΄μ—μ„œ λ‘œκΉ… μ„€μ •(logging, loguru λ“±)이 λ°œκ²¬λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.",
139
+ standard: "Python Logging Cookbook",
140
+ impact: "μž₯μ•  λ°œμƒ μ‹œ 원인을 좔적할 수 μžˆλŠ” 데이터가 μ—†μ–΄ ν•΄κ²° μ‹œκ°„μ΄ κΈΈμ–΄μ§‘λ‹ˆλ‹€.",
141
+ action: `
142
+ # Action: Python Logging Setup
143
+ import logging
144
+ logging.basicConfig(level=logging.INFO)
145
+ logger = logging.getLogger(__name__)
146
+ logger.info("Server started")
147
+ `,
148
+ reference: "https://docs.python.org/3/howto/logging-cookbook.html",
149
+ whenItMatters: "운영 쀑 μ•Œ 수 μ—†λŠ” 500 μ—λŸ¬κ°€ λ°œμƒν–ˆμ„ λ•Œ."
108
150
  };
109
151
  }
110
- if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
152
+ if (id === 'RR-DEP-001' || type === 'CircularDependency') {
111
153
  return {
154
+ ...commonFields,
112
155
  title: "ꡬ쑰적 μ˜μ‘΄μ„± 결함 (Structural Dependency Issue)",
113
156
  category: 'Scalability',
114
- evidence: type === 'CircularDependency' ? "λͺ¨λ“ˆ κ°„μ˜ μƒν˜Έ μ°Έμ‘° ꡬ쑰가 κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€." : issue,
157
+ evidence: issue,
115
158
  standard: "Clean Architecture: Dependency Rule",
116
- impact: "λͺ¨λ“ˆ κ°„ 결합도가 λ†’μ•„μ Έ μ„±λŠ₯ μ €ν•˜ 및 λ¬΄ν•œ 루프 μœ„ν—˜μ΄ 있으며, μ½”λ“œ ν•œ 곳을 μˆ˜μ •ν•  λ•Œ μ „ν˜€ λ‹€λ₯Έ κ³³μ—μ„œ μž₯μ• κ°€ λ°œμƒν•˜λŠ” '기술 뢀채'의 원인이 λ©λ‹ˆλ‹€.",
117
- action: "μ˜μ‘΄μ„± λ°©ν–₯을 ν•œ λ°©ν–₯으둜 μ •λ¦¬ν•˜μ„Έμš”.",
118
- reference: "https://peps.python.org/pep-0008/#imports",
119
- whenItMatters: "ν”„λ‘œμ νŠΈ μ½”λ“œ λ² μ΄μŠ€κ°€ 5,000쀄 μ΄μƒμœΌλ‘œ λŠ˜μ–΄λ‚  λ•Œ μœ μ§€λ³΄μˆ˜κ°€ λΆˆκ°€λŠ₯ν•œ μ‹œν¬λ¦Ώ μ½”λ“œλ‘œ λ³€μ§ˆλ©λ‹ˆλ‹€."
159
+ impact: "λͺ¨λ“ˆ κ°„ 결합도가 λ†’μ•„μ Έ μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μ›Œμ§€κ³ , μ‚¬μ΄λ“œ μ΄νŽ™νŠΈκ°€ λ°œμƒν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€.",
160
+ action: "μƒν˜Έ μ°Έμ‘°ν•˜λŠ” λͺ¨λ“ˆμ„ λΆ„λ¦¬ν•˜κ±°λ‚˜ 곡톡 λͺ¨λ“ˆλ‘œ μΆ”μΆœν•˜μ„Έμš”.",
161
+ reference: "https://refactoring.guru/design-patterns",
162
+ whenItMatters: "ν”„λ‘œμ νŠΈ 규λͺ¨κ°€ 컀질수둝 λ¦¬νŒ©ν† λ§ λΉ„μš©μ΄ κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ μ¦κ°€ν•©λ‹ˆλ‹€."
163
+ };
164
+ }
165
+ if (id === 'RR-LINT-001' || type === 'GodModule') {
166
+ return {
167
+ ...commonFields,
168
+ title: "κ±°λŒ€ λͺ¨λ“ˆ 감지 (God Module)",
169
+ category: 'Maintenance',
170
+ evidence: issue,
171
+ standard: "Clean Code: Functions",
172
+ impact: "단일 파일의 μ±…μž„μ΄ κ³Όλ„ν•˜μ—¬ λ³€κ²½ μ‹œ 영ν–₯ λ²”μœ„λ₯Ό μ˜ˆμΈ‘ν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.",
173
+ action: "μ±…μž„μ— 따라 νŒŒμΌμ„ λΆ„λ¦¬ν•˜μ„Έμš” (Separation of Concerns).",
174
+ reference: "https://pypi.org/project/flake8/",
175
+ whenItMatters: "κΈ°λŠ₯ μΆ”κ°€ μ‹œλ§ˆλ‹€ 버그가 λ°œμƒν•  λ•Œ."
120
176
  };
121
177
  }
122
178
  return {
179
+ ...commonFields,
123
180
  title: "기타 잠재적 리슀크 (Other Potential Risks)",
124
181
  category: 'Maintenance',
125
182
  evidence: issue,
126
183
  standard: "General Coding Best Practices",
127
- impact: "λΉ„μ¦ˆλ‹ˆμŠ€ μ•ˆμ •μ„±μ— μœ„ν˜‘μ΄ 될 수 μžˆμŠ΅λ‹ˆλ‹€.",
128
- action: "지속적인 λͺ¨λ‹ˆν„°λ§μ΄ ν•„μš”ν•©λ‹ˆλ‹€.",
184
+ impact: "잠재적인 λ²„κ·Έλ‚˜ μœ μ§€λ³΄μˆ˜ 어렀움이 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.",
185
+ action: "ν•΄λ‹Ή μ½”λ“œλ₯Ό λ¦¬λ·°ν•˜κ³  λ¦¬νŒ©ν† λ§μ„ κ³ λ €ν•˜μ„Έμš”.",
129
186
  reference: "#",
130
- whenItMatters: "지속적인 κ΄€μ°° μ—†μ΄λŠ” μ–Έμ  κ°€ 운영 ν™˜κ²½μ—μ„œ λΉ„μš©μ  μ†μ‹€λ‘œ μ΄μ–΄μ§‘λ‹ˆλ‹€."
187
+ whenItMatters: "지속적인 μ½”λ“œ ν’ˆμ§ˆ μ €ν•˜κ°€ 우렀될 λ•Œ."
131
188
  };
132
189
  }
133
190
  async function analyzeRepository(repoPath, resultsDir) {
@@ -158,44 +215,61 @@ async function analyzeRepository(repoPath, resultsDir) {
158
215
  let criticalCount = 0;
159
216
  let operationalGapCount = 0;
160
217
  // 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
- }
218
+ // 1. Operational Audit & Consolidation
219
+ const hygieneMissing = [];
220
+ // Check Tests
221
+ const hasTests = fs.readdirSync(repoPath, { withFileTypes: true })
222
+ .some(d => d.isDirectory() && /tests?|spec/i.test(d.name));
223
+ if (!hasTests) {
224
+ const details = getAuditDetails('RR-TEST-001', 'ProductionRisk', '');
225
+ findings.push({
226
+ file: 'Repository Root',
227
+ line: 0,
228
+ type: 'ProductionRisk',
229
+ ...details
230
+ });
231
+ operationalGapCount++;
193
232
  }
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 });
233
+ // Check CI
234
+ const hasCI = fs.readdirSync(repoPath, { withFileTypes: true })
235
+ .some(d => d.isDirectory() && /(\.github|\.circleci|jenkins|gitlab)/i.test(d.name));
236
+ if (!hasCI) {
237
+ const details = getAuditDetails('RR-CI-001', 'ProductionRisk', '');
238
+ findings.push({
239
+ file: 'Repository Root',
240
+ line: 0,
241
+ type: 'ProductionRisk',
242
+ ...details
243
+ });
244
+ operationalGapCount++;
245
+ }
246
+ // Check Hygiene (Docker, Pinning, Env, GitIgnore)
247
+ const allFiles = fs.readdirSync(repoPath);
248
+ // Docker
249
+ if (!allFiles.some(f => /Dockerfile|docker-compose/i.test(f))) {
250
+ hygieneMissing.push("✘ Dockerfile or docker-compose.yml missing");
251
+ }
252
+ // Pinning
253
+ if (!allFiles.some(f => /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/i.test(f))) {
254
+ hygieneMissing.push("✘ Dependency Lockfile (requirements.txt, package-lock.json, etc) missing");
255
+ }
256
+ // Env
257
+ if (!allFiles.some(f => /\.env\.example|\.env\.sample/i.test(f))) {
258
+ hygieneMissing.push("✘ .env.example (Safe environment template) missing");
259
+ }
260
+ // GitIgnore
261
+ if (!fs.existsSync(path.join(repoPath, '.gitignore'))) {
262
+ hygieneMissing.push("✘ .gitignore missing");
263
+ }
264
+ if (hygieneMissing.length > 0) {
265
+ const evidenceBlock = "\n" + hygieneMissing.join("\n");
266
+ const details = getAuditDetails('RR-OPS-001', 'ProductionRisk', evidenceBlock);
267
+ findings.push({
268
+ file: 'Repository Root',
269
+ line: 0,
270
+ type: 'ProductionRisk',
271
+ ...details
272
+ });
199
273
  operationalGapCount++;
200
274
  }
201
275
  let hasLogging = false;
@@ -211,13 +285,37 @@ async function analyzeRepository(repoPath, resultsDir) {
211
285
  }
212
286
  // God Module Check (>500 lines)
213
287
  if (lines.length > 500) {
214
- const details = getAuditDetails('ProductionRisk', `[μœ μ§€λ³΄μˆ˜ 리슀크] κ±°λŒ€ λͺ¨λ“ˆ(${lines.length}쀄)이 κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 단일 파일의 μ±…μž„μ΄ κ³Όλ„ν•˜μ—¬ λ³€κ²½ μ‹œ μž₯μ•  νŒŒκΈ‰λ ₯이 ν½λ‹ˆλ‹€.`);
288
+ const details = getAuditDetails('RR-LINT-001', 'GodModule', `File length: ${lines.length} lines`);
215
289
  findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
216
290
  operationalGapCount++;
217
291
  }
218
292
  const result = await (0, analyzer_js_1.analyzePythonCode)(code, relativePath);
219
293
  if (result.hasError) {
220
- const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
294
+ const details = getAuditDetails('RR-SEC-001', result.type || 'Error', result.error || 'Unknown Issue');
295
+ findings.push({
296
+ file: relativePath,
297
+ line: result.line || 0,
298
+ type: result.type || 'Error',
299
+ ...details
300
+ });
301
+ if (result.type === 'SecurityRisk')
302
+ criticalCount++;
303
+ }
304
+ }
305
+ catch (e) {
306
+ console.error(`Error analyzing ${f}:`, e);
307
+ }
308
+ }
309
+ // 2.1 Code Level Analysis (JS/TS logic)
310
+ for (const f of files.filter(f => /\.(js|ts|jsx|tsx)$/.test(f))) {
311
+ try {
312
+ const code = fs.readFileSync(f, 'utf8');
313
+ const relativePath = path.relative(repoPath, f);
314
+ // Logging Check (Simple console.log check is in rules, but here specific frameworks?)
315
+ // We rely on rules for now.
316
+ const result = await (0, jsAnalyzer_js_1.analyzeJsTsCode)(code, relativePath);
317
+ if (result.hasError) {
318
+ const details = getAuditDetails('RR-SEC-002', result.type || 'Error', result.error || 'Unknown Issue'); // Use SEC-002 for JS? Or reuse.
221
319
  findings.push({
222
320
  file: relativePath,
223
321
  line: result.line || 0,
@@ -233,14 +331,14 @@ async function analyzeRepository(repoPath, resultsDir) {
233
331
  }
234
332
  }
235
333
  if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
236
- const details = getAuditDetails('ProductionRisk', '[운영 리슀크] 둜그 μ‹œμŠ€ν…œ μ‚¬μš©μ΄ κ°μ§€λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μž₯μ•  λ°œμƒ μ‹œ 원인 νŒŒμ•…μ΄ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.');
334
+ const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
237
335
  findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
238
336
  operationalGapCount++;
239
337
  }
240
338
  // 3. Dependency Scan (Scalability -15)
241
339
  const { adj, cycles } = (0, archScanner_js_1.depsScan)(repoPath);
242
340
  for (const cycle of cycles) {
243
- const details = getAuditDetails('CircularDependency', '');
341
+ const details = getAuditDetails('RR-DEP-001', 'CircularDependency', `Cycle: ${cycle.join(' -> ')}`);
244
342
  findings.push({
245
343
  file: cycle[0],
246
344
  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.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './aiDiagnosis.js';
3
3
  export * from './archScanner.js';
4
4
  export * from './repoAnalyzer.js';
5
5
  export * from './deepAnalysis.js';
6
+ export * from './jsAnalyzer.js';
@@ -0,0 +1,143 @@
1
+ import { AnalysisResult } from './analyzer.js';
2
+
3
+ /**
4
+ * [The Eye] JS/TS Analysis Engine
5
+ *
6
+ * RegEx-based static analysis for JavaScript/TypeScript files.
7
+ * MVP: Focused on 8 specific credibility rules.
8
+ */
9
+
10
+ export async function analyzeJsTsCode(code: string, fileName: string): Promise<AnalysisResult> {
11
+ // 1. Architecture Health Scan (God Module / Large File)
12
+ const lines = code.split('\n');
13
+ if (lines.length > 800) {
14
+ return {
15
+ hasError: true,
16
+ error: `Large File Risk: ${lines.length} lines. Maintanability risk.`,
17
+ line: 1,
18
+ type: 'ProductionRisk',
19
+ file: fileName
20
+ };
21
+ }
22
+
23
+ // 2. Risk Scan (Security & Production Readiness)
24
+ const riskResult = scanForJsRisks(code, fileName, lines);
25
+ if (riskResult.hasError) {
26
+ return riskResult;
27
+ }
28
+
29
+ return { hasError: false };
30
+ }
31
+
32
+ interface RiskRule {
33
+ pattern?: RegExp;
34
+ condition?: (codeOrLine: string) => boolean;
35
+ type: string;
36
+ message: string;
37
+ }
38
+
39
+ function scanForJsRisks(code: string, fileName: string, lines: string[]): AnalysisResult {
40
+ const risks: RiskRule[] = [
41
+ // Critical (πŸ”΄) - Security
42
+ {
43
+ pattern: /eval\s*\(|new\s+Function\s*\(/,
44
+ type: 'SecurityRisk',
45
+ message: '[Security] Dynamic code execution detected (eval/new Function). This is a severe security risk.'
46
+ },
47
+ {
48
+ pattern: /child_process\.(exec|execSync)\s*\(/,
49
+ type: 'SecurityRisk',
50
+ message: '[Security] Shell command execution detected. Ensure inputs are sanitized or use spawn without shell.'
51
+ },
52
+ {
53
+ pattern: /spawn\s*\(.*,\s*\{.*shell:\s*true/s, // Multi-line match attempt with DOTALL flag simulated or just simple check
54
+ type: 'SecurityRisk',
55
+ message: '[Security] spawn with { shell: true } detected. This enables shell command injection.'
56
+ },
57
+ {
58
+ pattern: /(?:api[._-]?key|password|secret|token)\s*[:=]\s*['"][a-zA-Z0-9_-]{10,}['"]/i,
59
+ type: 'SecurityRisk',
60
+ message: '[Security] Hardcoded secret detected. Use environment variables.'
61
+ },
62
+
63
+ // Warning (🟑) - Production Readiness
64
+ {
65
+ condition: (l) => /(axios(\.[a-z]+)?|fetch|http\.(get|request))\s*\(/.test(l) && !/timeout/.test(l),
66
+ type: 'ProductionRisk',
67
+ message: '[Reliability] HTTP call missing explicit timeout. This can cause cascading failures.'
68
+ },
69
+ // Heuristic: App listen but no global error handler (simplified: check for generic app.use((err... pattern)
70
+ {
71
+ condition: (c) => c.includes('app.listen') && !/app\.use\s*\(\s*\(\s*err/.test(c),
72
+ type: 'ProductionRisk',
73
+ message: '[Reliability] Express app detected but Global Error Handler missing. Unhandled errors may crash the server.'
74
+ },
75
+ {
76
+ pattern: /console\.log\s*\(/,
77
+ type: 'ProductionRisk',
78
+ message: '[Observability] console.log used in production code. Use a structured logger (winston/pino).'
79
+ }
80
+ ];
81
+
82
+ // Line-based checks
83
+ for (let i = 0; i < lines.length; i++) {
84
+ const line = lines[i];
85
+ for (const risk of risks) {
86
+ // Pattern check
87
+ if (risk.pattern && risk.pattern.test(line)) {
88
+ // Skip comments for simple cases
89
+ if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
90
+
91
+ return {
92
+ hasError: true,
93
+ error: risk.message,
94
+ line: i + 1,
95
+ type: risk.type,
96
+ file: fileName
97
+ };
98
+ }
99
+
100
+ // Line-based Condition check (special case for timeout)
101
+ // We distinguish global vs line condition by context?
102
+ // The 'app.listen' check is clearly global (checks whole code).
103
+ // The 'timeout' check is clearly line based (checks 'l').
104
+ // Let's rely on the message content to know if it is line-based for MVP simplicity
105
+ if (risk.condition && risk.message.includes('HTTP missing timeout') && risk.condition(line)) {
106
+ if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
107
+ return {
108
+ hasError: true,
109
+ error: risk.message,
110
+ line: i + 1,
111
+ type: risk.type,
112
+ file: fileName
113
+ };
114
+ }
115
+ }
116
+ }
117
+
118
+ // File-based checks (Global patterns)
119
+ for (const risk of risks) {
120
+ // Global Condition check
121
+ if (risk.condition && !risk.message.includes('HTTP missing timeout') && risk.condition(code)) {
122
+ return {
123
+ hasError: true,
124
+ error: risk.message,
125
+ line: 0,
126
+ type: risk.type,
127
+ file: fileName
128
+ };
129
+ }
130
+ // Multi-line regex checks
131
+ if (risk.pattern && risk.pattern.flags.includes('s') && risk.pattern.test(code)) {
132
+ return {
133
+ hasError: true,
134
+ error: risk.message,
135
+ line: 0,
136
+ type: risk.type,
137
+ file: fileName
138
+ };
139
+ }
140
+ }
141
+
142
+ return { hasError: false };
143
+ }
@@ -2,12 +2,14 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { execSync } from 'child_process';
4
4
  import { analyzePythonCode, AnalysisResult } from './analyzer.js';
5
+ import { analyzeJsTsCode } from './jsAnalyzer.js';
5
6
  import { depsScan } from './archScanner.js';
6
7
 
7
8
  export interface RepoAnalysisResult {
8
9
  score: number; // Release Readiness Score (RRS)
9
10
  status: 'Ready for Production' | 'Needs Attention' | 'Not Ready for Deployment';
10
11
  findings: {
12
+ id: string;
11
13
  title: string;
12
14
  file: string;
13
15
  line: number;
@@ -37,88 +39,152 @@ export interface RepoAnalysisResult {
37
39
  /**
38
40
  * 3-Tier Business Audit Translation
39
41
  */
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
+ /**
43
+ * 3-Tier Business Audit Translation with Standard IDs
44
+ */
45
+ 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 } {
46
+ const commonFields = { id };
47
+
48
+ if (id === 'RR-SEC-001' || type === 'SecurityRisk') {
42
49
  return {
50
+ ...commonFields,
43
51
  title: "λ³΄μ•ˆ 취약점 μœ„ν—˜ (Security Vulnerability)",
44
52
  category: 'Security',
45
53
  evidence: issue,
46
54
  standard: "OWASP Top 10 A03:2021 – Injection",
47
- impact: "μ™ΈλΆ€ κ³΅κ²©μžκ°€ μ‹œμŠ€ν…œ κΆŒν•œμ„ νƒˆμ·¨ν•˜κ±°λ‚˜ 민감 정보λ₯Ό μœ μΆœν•  수 μžˆλŠ” ν†΅λ‘œκ°€ λ©λ‹ˆλ‹€. λ³΄μ•ˆ 사고 λ°œμƒ μ‹œ 법적 μ±…μž„ 및 μ„œλΉ„μŠ€ 쀑단이 λΆˆκ°€ν”Όν•©λ‹ˆλ‹€.",
48
- action: "ν•΄λ‹Ή μ½”λ“œλ₯Ό μ¦‰μ‹œ κ²©λ¦¬ν•˜κ³  subprocess.run(shell=False) λ˜λŠ” ν™˜κ²½ λ³€μˆ˜(.env)λ₯Ό ν™œμš©ν•˜μ—¬ 민감 정보λ₯Ό λΆ„λ¦¬ν•˜μ„Έμš”.",
55
+ impact: "μ™ΈλΆ€ κ³΅κ²©μžκ°€ μ‹œμŠ€ν…œ κΆŒν•œμ„ νƒˆμ·¨ν•˜κ±°λ‚˜ 민감 정보λ₯Ό μœ μΆœν•  수 μžˆλŠ” 쑰건이 ν˜•μ„±λ©λ‹ˆλ‹€.",
56
+ action: `
57
+ # Action: 격리 및 ν™˜κ²½λ³€μˆ˜ μ‚¬μš©
58
+ subprocess.run(..., shell=False) # ꢌμž₯
59
+ # λ˜λŠ” .env 파일 μ‚¬μš©
60
+ import os
61
+ SECRET = os.getenv('MY_SECRET')
62
+ `,
49
63
  reference: "https://docs.python.org/3/library/subprocess.html#security-considerations",
50
- whenItMatters: "배포 μ¦‰μ‹œ μžλ™ν™”λœ μŠ€μΊλ„ˆλ‚˜ κ³΅κ²©μžμ— μ˜ν•΄ 탐지될 수 μžˆλŠ” 0μˆœμœ„ λ¦¬μŠ€ν¬μž…λ‹ˆλ‹€."
64
+ whenItMatters: "배포 μ¦‰μ‹œ μžλ™ν™”λœ μŠ€μΊλ„ˆλ‚˜ κ³΅κ²©μžμ— μ˜ν•΄ 탐지될 수 μžˆμŠ΅λ‹ˆλ‹€."
51
65
  };
52
66
  }
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
- }
67
+
68
+ if (id === 'RR-TEST-001') {
90
69
  return {
91
- title: "운영 쀀비도 미흑 (Production Readiness Gap)",
70
+ ...commonFields,
71
+ title: "μžλ™ν™” ν…ŒμŠ€νŠΈ λΆ€μž¬ (Missing Automated Tests)",
92
72
  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: "μž₯μ•  λ°œμƒ μ‹œ 원인 νŒŒμ•…μ„ μˆ˜λΆ„ 내에 μ™„λ£Œν•˜μ§€ λͺ»ν•  경우 λ‹€μš΄νƒ€μž„μ΄ κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ λŠ˜μ–΄λ‚©λ‹ˆλ‹€."
73
+ evidence: "tests/ 디렉토리 λ˜λŠ” pytest/unittest κ΄€λ ¨ 섀정을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.",
74
+ standard: "pytest Framework Documentation",
75
+ impact: "μ½”λ“œ λ³€κ²½ μ‹œ κΈ°μ‘΄ κΈ°λŠ₯이 νŒŒκ΄΄λ˜μ—ˆλŠ”μ§€ 확인할 방법이 μ—†μ–΄, 배포 ν›„ μž₯μ•  λ°œμƒ ν™•λ₯ μ΄ λ†’μ•„μ§‘λ‹ˆλ‹€.",
76
+ action: `
77
+ # Action: Create tests/test_smoke.py
78
+ def test_health_check():
79
+ assert True # Basic sanity check
80
+ `,
81
+ reference: "https://docs.pytest.org/",
82
+ whenItMatters: "νŒ€μ›μ΄ 2λͺ… μ΄μƒμœΌλ‘œ λŠ˜μ–΄λ‚˜κ±°λ‚˜ 배포 μ£ΌκΈ°κ°€ 빨라질 λ•Œ."
83
+ };
84
+ }
85
+
86
+ if (id === 'RR-CI-001') {
87
+ return {
88
+ ...commonFields,
89
+ title: "배포 μžλ™ν™” νŒŒμ΄ν”„λΌμΈ λΆ€μž¬ (Missing CI Pipeline)",
90
+ category: 'Service Interruption',
91
+ evidence: "GitHub Actions (.github/workflows/*.yml) λ˜λŠ” CI μ„€μ • 파일이 μ—†μŠ΅λ‹ˆλ‹€.",
92
+ standard: "GitHub Actions Documentation",
93
+ impact: "μ‚¬λžŒμ˜ μˆ˜λ™ 배포 κ³Όμ •μ—μ„œ μ‹€μˆ˜κ°€ λ°œμƒν•  수 있으며, μΌκ΄€λœ 배포 μƒνƒœλ₯Ό 보μž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€.",
94
+ action: `
95
+ # Action: Create .github/workflows/ci.yml
96
+ name: CI
97
+ on: [push]
98
+ jobs:
99
+ test:
100
+ runs-on: ubuntu-latest
101
+ steps:
102
+ - uses: actions/checkout@v3
103
+ - run: npm test
104
+ `,
105
+ reference: "https://docs.github.com/en/actions",
106
+ whenItMatters: "배포 λΉˆλ„κ°€ μ£Ό 2회 μ΄μƒμœΌλ‘œ 증가할 λ•Œ."
107
+ };
108
+ }
109
+
110
+ if (id === 'RR-OPS-001') {
111
+ return {
112
+ ...commonFields,
113
+ title: "운영 κΈ°λ³Έ μœ„μƒ 체크 μ‹€νŒ¨ (Project Hygiene)",
114
+ category: 'Service Interruption',
115
+ evidence: issue, // Consolidated list will be passed here
116
+ standard: "12-Factor App / Docker Documentation",
117
+ impact: "개발 ν™˜κ²½κ³Ό 운영 ν™˜κ²½μ˜ 뢈일치둜 인해 'λ‚΄ μ»΄ν“¨ν„°μ—μ„œλŠ” λ˜λŠ”λ° μ„œλ²„μ—μ„œλŠ” μ•ˆ λ˜λŠ”' λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.",
118
+ action: `
119
+ # Checklist to Fix:
120
+ 1. Create 'Dockerfile'
121
+ 2. Create '.gitignore' (use gitignore.io)
122
+ 3. Create 'requirements.txt' or 'package.json'
123
+ 4. Create '.env.example'
124
+ `,
125
+ reference: "https://12factor.net/",
126
+ whenItMatters: "μ‹ κ·œ μž…μ‚¬μž μ˜¨λ³΄λ”© λ˜λŠ” μ„œλ²„ 이관 μ‹œ."
99
127
  };
100
128
  }
101
- if (type === 'ArchitectureRisk' || type === 'CircularDependency') {
129
+
130
+ if (id === 'RR-LOG-001') {
102
131
  return {
132
+ ...commonFields,
133
+ title: "λ‘œκΉ… μ„€μ • 미흑 (Insufficient Logging)",
134
+ category: 'Maintenance',
135
+ evidence: "μ½”λ“œ λ‚΄μ—μ„œ λ‘œκΉ… μ„€μ •(logging, loguru λ“±)이 λ°œκ²¬λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.",
136
+ standard: "Python Logging Cookbook",
137
+ impact: "μž₯μ•  λ°œμƒ μ‹œ 원인을 좔적할 수 μžˆλŠ” 데이터가 μ—†μ–΄ ν•΄κ²° μ‹œκ°„μ΄ κΈΈμ–΄μ§‘λ‹ˆλ‹€.",
138
+ action: `
139
+ # Action: Python Logging Setup
140
+ import logging
141
+ logging.basicConfig(level=logging.INFO)
142
+ logger = logging.getLogger(__name__)
143
+ logger.info("Server started")
144
+ `,
145
+ reference: "https://docs.python.org/3/howto/logging-cookbook.html",
146
+ whenItMatters: "운영 쀑 μ•Œ 수 μ—†λŠ” 500 μ—λŸ¬κ°€ λ°œμƒν–ˆμ„ λ•Œ."
147
+ };
148
+ }
149
+
150
+ if (id === 'RR-DEP-001' || type === 'CircularDependency') {
151
+ return {
152
+ ...commonFields,
103
153
  title: "ꡬ쑰적 μ˜μ‘΄μ„± 결함 (Structural Dependency Issue)",
104
154
  category: 'Scalability',
105
- evidence: type === 'CircularDependency' ? "λͺ¨λ“ˆ κ°„μ˜ μƒν˜Έ μ°Έμ‘° ꡬ쑰가 κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€." : issue,
155
+ evidence: issue,
106
156
  standard: "Clean Architecture: Dependency Rule",
107
- impact: "λͺ¨λ“ˆ κ°„ 결합도가 λ†’μ•„μ Έ μ„±λŠ₯ μ €ν•˜ 및 λ¬΄ν•œ 루프 μœ„ν—˜μ΄ 있으며, μ½”λ“œ ν•œ 곳을 μˆ˜μ •ν•  λ•Œ μ „ν˜€ λ‹€λ₯Έ κ³³μ—μ„œ μž₯μ• κ°€ λ°œμƒν•˜λŠ” '기술 뢀채'의 원인이 λ©λ‹ˆλ‹€.",
108
- action: "μ˜μ‘΄μ„± λ°©ν–₯을 ν•œ λ°©ν–₯으둜 μ •λ¦¬ν•˜μ„Έμš”.",
109
- reference: "https://peps.python.org/pep-0008/#imports",
110
- whenItMatters: "ν”„λ‘œμ νŠΈ μ½”λ“œ λ² μ΄μŠ€κ°€ 5,000쀄 μ΄μƒμœΌλ‘œ λŠ˜μ–΄λ‚  λ•Œ μœ μ§€λ³΄μˆ˜κ°€ λΆˆκ°€λŠ₯ν•œ μ‹œν¬λ¦Ώ μ½”λ“œλ‘œ λ³€μ§ˆλ©λ‹ˆλ‹€."
157
+ impact: "λͺ¨λ“ˆ κ°„ 결합도가 λ†’μ•„μ Έ μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μ›Œμ§€κ³ , μ‚¬μ΄λ“œ μ΄νŽ™νŠΈκ°€ λ°œμƒν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€.",
158
+ action: "μƒν˜Έ μ°Έμ‘°ν•˜λŠ” λͺ¨λ“ˆμ„ λΆ„λ¦¬ν•˜κ±°λ‚˜ 곡톡 λͺ¨λ“ˆλ‘œ μΆ”μΆœν•˜μ„Έμš”.",
159
+ reference: "https://refactoring.guru/design-patterns",
160
+ whenItMatters: "ν”„λ‘œμ νŠΈ 규λͺ¨κ°€ 컀질수둝 λ¦¬νŒ©ν† λ§ λΉ„μš©μ΄ κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ μ¦κ°€ν•©λ‹ˆλ‹€."
161
+ };
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: "κΈ°λŠ₯ μΆ”κ°€ μ‹œλ§ˆλ‹€ 버그가 λ°œμƒν•  λ•Œ."
111
175
  };
112
176
  }
177
+
113
178
  return {
179
+ ...commonFields,
114
180
  title: "기타 잠재적 리슀크 (Other Potential Risks)",
115
181
  category: 'Maintenance',
116
182
  evidence: issue,
117
183
  standard: "General Coding Best Practices",
118
- impact: "λΉ„μ¦ˆλ‹ˆμŠ€ μ•ˆμ •μ„±μ— μœ„ν˜‘μ΄ 될 수 μžˆμŠ΅λ‹ˆλ‹€.",
119
- action: "지속적인 λͺ¨λ‹ˆν„°λ§μ΄ ν•„μš”ν•©λ‹ˆλ‹€.",
184
+ impact: "잠재적인 λ²„κ·Έλ‚˜ μœ μ§€λ³΄μˆ˜ 어렀움이 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.",
185
+ action: "ν•΄λ‹Ή μ½”λ“œλ₯Ό λ¦¬λ·°ν•˜κ³  λ¦¬νŒ©ν† λ§μ„ κ³ λ €ν•˜μ„Έμš”.",
120
186
  reference: "#",
121
- whenItMatters: "지속적인 κ΄€μ°° μ—†μ΄λŠ” μ–Έμ  κ°€ 운영 ν™˜κ²½μ—μ„œ λΉ„μš©μ  μ†μ‹€λ‘œ μ΄μ–΄μ§‘λ‹ˆλ‹€."
187
+ whenItMatters: "지속적인 μ½”λ“œ ν’ˆμ§ˆ μ €ν•˜κ°€ 우렀될 λ•Œ."
122
188
  };
123
189
  }
124
190
 
@@ -147,43 +213,66 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
147
213
  let operationalGapCount = 0;
148
214
 
149
215
  // 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
- }
216
+ // 1. Operational Audit & Consolidation
217
+ const hygieneMissing: string[] = [];
218
+
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++;
180
231
  }
181
232
 
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 });
233
+ // Check CI
234
+ const hasCI = fs.readdirSync(repoPath, { withFileTypes: true })
235
+ .some(d => d.isDirectory() && /(\.github|\.circleci|jenkins|gitlab)/i.test(d.name));
236
+ if (!hasCI) {
237
+ const details = getAuditDetails('RR-CI-001', 'ProductionRisk', '');
238
+ findings.push({
239
+ file: 'Repository Root',
240
+ line: 0,
241
+ type: 'ProductionRisk',
242
+ ...details
243
+ });
244
+ operationalGapCount++;
245
+ }
246
+
247
+ // Check Hygiene (Docker, Pinning, Env, GitIgnore)
248
+ const allFiles = fs.readdirSync(repoPath);
249
+
250
+ // Docker
251
+ if (!allFiles.some(f => /Dockerfile|docker-compose/i.test(f))) {
252
+ hygieneMissing.push("✘ Dockerfile or docker-compose.yml missing");
253
+ }
254
+ // Pinning
255
+ if (!allFiles.some(f => /requirements\.txt|poetry\.lock|pyproject\.toml|package-lock\.json|pnpm-lock\.yaml/i.test(f))) {
256
+ hygieneMissing.push("✘ Dependency Lockfile (requirements.txt, package-lock.json, etc) missing");
257
+ }
258
+ // Env
259
+ if (!allFiles.some(f => /\.env\.example|\.env\.sample/i.test(f))) {
260
+ hygieneMissing.push("✘ .env.example (Safe environment template) missing");
261
+ }
262
+ // GitIgnore
263
+ if (!fs.existsSync(path.join(repoPath, '.gitignore'))) {
264
+ hygieneMissing.push("✘ .gitignore missing");
265
+ }
266
+
267
+ if (hygieneMissing.length > 0) {
268
+ const evidenceBlock = "\n" + hygieneMissing.join("\n");
269
+ const details = getAuditDetails('RR-OPS-001', 'ProductionRisk', evidenceBlock);
270
+ findings.push({
271
+ file: 'Repository Root',
272
+ line: 0,
273
+ type: 'ProductionRisk',
274
+ ...details
275
+ });
187
276
  operationalGapCount++;
188
277
  }
189
278
 
@@ -203,7 +292,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
203
292
 
204
293
  // God Module Check (>500 lines)
205
294
  if (lines.length > 500) {
206
- const details = getAuditDetails('ProductionRisk', `[μœ μ§€λ³΄μˆ˜ 리슀크] κ±°λŒ€ λͺ¨λ“ˆ(${lines.length}쀄)이 κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 단일 파일의 μ±…μž„μ΄ κ³Όλ„ν•˜μ—¬ λ³€κ²½ μ‹œ μž₯μ•  νŒŒκΈ‰λ ₯이 ν½λ‹ˆλ‹€.`);
295
+ const details = getAuditDetails('RR-LINT-001', 'GodModule', `File length: ${lines.length} lines`);
207
296
  findings.push({ file: relativePath, line: 0, type: 'ProductionRisk', ...details });
208
297
  operationalGapCount++;
209
298
  }
@@ -211,7 +300,34 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
211
300
  const result = await analyzePythonCode(code, relativePath);
212
301
 
213
302
  if (result.hasError) {
214
- const details = getAuditDetails(result.type || 'Error', result.error || 'Unknown Issue');
303
+ const details = getAuditDetails('RR-SEC-001', result.type || 'Error', result.error || 'Unknown Issue');
304
+ findings.push({
305
+ file: relativePath,
306
+ line: result.line || 0,
307
+ type: result.type || 'Error',
308
+ ...details
309
+ });
310
+
311
+ if (result.type === 'SecurityRisk') criticalCount++;
312
+ }
313
+ } catch (e) {
314
+ console.error(`Error analyzing ${f}:`, e);
315
+ }
316
+ }
317
+
318
+ // 2.1 Code Level Analysis (JS/TS logic)
319
+ for (const f of files.filter(f => /\.(js|ts|jsx|tsx)$/.test(f))) {
320
+ try {
321
+ const code = fs.readFileSync(f, 'utf8');
322
+ const relativePath = path.relative(repoPath, f);
323
+
324
+ // Logging Check (Simple console.log check is in rules, but here specific frameworks?)
325
+ // We rely on rules for now.
326
+
327
+ const result = await analyzeJsTsCode(code, relativePath);
328
+
329
+ if (result.hasError) {
330
+ const details = getAuditDetails('RR-SEC-002', result.type || 'Error', result.error || 'Unknown Issue'); // Use SEC-002 for JS? Or reuse.
215
331
  findings.push({
216
332
  file: relativePath,
217
333
  line: result.line || 0,
@@ -227,7 +343,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
227
343
  }
228
344
 
229
345
  if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
230
- const details = getAuditDetails('ProductionRisk', '[운영 리슀크] 둜그 μ‹œμŠ€ν…œ μ‚¬μš©μ΄ κ°μ§€λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μž₯μ•  λ°œμƒ μ‹œ 원인 νŒŒμ•…μ΄ λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.');
346
+ const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
231
347
  findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
232
348
  operationalGapCount++;
233
349
  }
@@ -235,7 +351,7 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
235
351
  // 3. Dependency Scan (Scalability -15)
236
352
  const { adj, cycles } = depsScan(repoPath);
237
353
  for (const cycle of cycles) {
238
- const details = getAuditDetails('CircularDependency', '');
354
+ const details = getAuditDetails('RR-DEP-001', 'CircularDependency', `Cycle: ${cycle.join(' -> ')}`);
239
355
  findings.push({
240
356
  file: cycle[0],
241
357
  line: 0,