leerness 1.12.1 → 1.14.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.
package/lib/analyzers.js CHANGED
@@ -1,90 +1,90 @@
1
- // lib/analyzers.js — 순수 분석/검증 함수 (부작용 0, 입력→출력).
2
- // 1.9.304 (UR-0025): bin/harness.js 에서 비파괴 분리. selftest(evidenceQuality/parseEvidenceStats/shellGuardAnalyze/claimFileInGit)가 동작 검증.
3
- 'use strict';
4
-
5
- function _shellGuardAnalyze(cmd, ctx) {
6
- const c = String(cmd || '');
7
- const shell = (ctx && ctx.shell) || 'unknown';
8
- const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
9
- const issues = [];
10
- const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
11
- // 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
12
- if (isWinPowerShell && /\s&&\s|\s\|\|\s/.test(c)) {
13
- issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
14
- }
15
- // 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
16
- if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
17
- issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
18
- }
19
- // 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
20
- if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
21
- issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
22
- }
23
- // 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
24
- if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
25
- issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
26
- }
27
- // 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
28
- if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
29
- issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
30
- }
31
- // 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
32
- if (shell === 'powershell' && psVer == null && /\s&&\s/.test(c)) {
33
- issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
34
- }
35
- return { shell, psVersion: psVer, issues };
36
- }
37
- function _evidenceQuality(evidence) {
38
- const e = String(evidence || '');
39
- const hasFile = /(?:[A-Za-z][\w-]*[\/\\])?[A-Za-z][\w./\\-]*\.(?:js|ts|tsx|jsx|mjs|cjs|py|go|rs|rb|kt|cs|gd|java|php|swift|c|cpp|h|html|css|scss|vue|svelte|json|yaml|yml|toml|md|sql|sh)\b/i.test(e);
40
- const hasTest = /(\d+)\s*(?:\/\s*\d+\s*)?(?:통과|passed|passing|개\s*테스트)|\btests?\b\s*[:=]?\s*\d|Tests?:\s*\d|\b\d+\s*tests?\b/i.test(e);
41
- const hasLog = /Exit\s*[:=]|exit\s*code|Command\s*[:=]|npm\s+(?:test|run)|pytest|cargo\s+test|go\s+test/i.test(e);
42
- const missing = [];
43
- if (!hasFile) missing.push('수정 파일 경로');
44
- if (!hasTest) missing.push('테스트명/개수');
45
- if (!hasLog) missing.push('실행 로그(Command/Exit)');
46
- return { hasFile, hasTest, hasLog, ok: hasFile && hasTest, missing };
47
- }
48
- function _claimFileInGit(claimed, gitSet) {
49
- if (!gitSet) return null;
50
- const c = String(claimed).replace(/\\/g, '/').replace(/^\.\//, '');
51
- for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || c.endsWith('/' + g)) return true; }
52
- return false;
53
- }
54
- function _parseEvidenceStats(text) {
55
- const t = String(text || '');
56
- const blocks = t.split(/\n(?=## )/).filter(b => /Command:|Exit:|verify|test/i.test(b));
57
- let pass = 0, fail = 0;
58
- for (const b of blocks) {
59
- const exitM = b.match(/Exit:\s*(-?\d+)/i);
60
- if (exitM) { (parseInt(exitM[1], 10) === 0 ? pass++ : fail++); continue; }
61
- if (/\bPASS\b|통과|성공|✓/i.test(b)) pass++;
62
- else if (/\bFAIL\b|실패|오류|error|✗/i.test(b)) fail++;
63
- }
64
- const entries = blocks.length;
65
- return { entries, pass, fail, rate: (pass + fail) ? Math.round(pass / (pass + fail) * 100) : null };
66
- }
67
-
68
- // 1.9.305 (사용자 명시): AI 인식론적 정직성 점검 — 모르는 걸 아는 척 / 정보 미수집 / 미검증 섣부른 판단 휴리스틱 탐지.
69
- // 순수 함수(텍스트→findings). 휴리스틱 advisory — 단정/추정/외부참조 표현 vs 근거·수집 흔적 대조. opt-in 점검용.
70
- function _epistemicHonestyCheck(text) {
71
- const t = String(text || '');
72
- const findings = [];
73
- // 공통: 근거/출처 흔적 (파일경로·URL·테스트결과·Exit·문서·api-skill·인용·조회 흔적)
74
- const hasSource = /(?:[\w./-]+\.(?:js|ts|tsx|jsx|py|go|rs|rb|md|json|ya?ml|toml|sql|sh)\b)|https?:\/\/|\bExit\s*[:=]|\d+\s*\/\s*\d+\s*(?:통과|passed)|\b(?:passed|passing)\b|근거[::]|출처[::]|api-skill|공식\s*문서|문서\s*(?:확인|참조|에\s*따르면)|읽었|조회(?:함|했|함\b)|확인(?:함|했|됨)|grep|로그[::]/i.test(t);
75
- // 차원1: 모르는 걸 아는 척 — 단정 표현인데 근거 없음
76
- const definitive = /(반드시|항상|언제나|무조건|확실(?:히|함|하게)|당연히|틀림없|100\s*%|always|never|guaranteed|definitely|obviously|certainly)/i.test(t);
77
- if (definitive && !hasSource) findings.push({ dim: 'pretend-knowledge', severity: 'high', label: '근거 없는 단정', detail: '단정적 표현이 있으나 근거/출처(파일·문서·테스트·로그)가 없음 — 모르는 정보를 아는 척할 위험.' });
78
- // 차원2: 미검증 섣부른 판단 — 추정 표현 + 완료/성공 결론인데 근거 없음
79
- const assumption = /(아마|추정|것\s*같|듯\s*(?:하|싶)|probably|likely|maybe|perhaps|i\s*(?:think|assume|guess|believe|suppose)|should\s*(?:work|be|pass|fix)|생각(?:됩니다|된다|함|돼)|일\s*것|예상(?:됩니다|된다|됨)|짐작)/i.test(t);
80
- const conclusion = /(완료|done|성공|통과|해결(?:됨|했|함|되었)|fixed|resolved|works?\b|작동(?:함|한다|됨)|구현(?:됨|했|완료))/i.test(t);
81
- if (assumption && conclusion && !hasSource) findings.push({ dim: 'premature-judgment', severity: 'high', label: '검증 없는 섣부른 판단', detail: '가정·추정 표현과 완료·성공 결론이 함께 있으나 검증 근거가 없음 — 검증 없이 섣부르게 판단할 위험.' });
82
- // 차원3: 정보 미수집 — 외부 API/라이브러리/버전/스펙 언급인데 수집·근거 흔적 없음
83
- // \bAPI\b(?!\.[a-z]) 로 파일경로(api.js/api.ts) 오탐 제외. 강한 근거(hasSource)나 수집 흔적(gathered) 있으면 통과.
84
- const externalRef = /(\bAPI\b(?!\.[a-z])|\bSDK\b|라이브러리|\blibrary\b|\bpackage\b|엔드포인트|\bendpoint\b|버전\s*\d|v\d+\.\d+|\bspec\b|rate\s*limit|레이트\s*리밋|문서에\s*따르면)/i.test(t);
85
- const gathered = /(https?:\/\/|api-skill|공식\s*문서|\bdocs?\b|문서\s*(?:확인|참조|읽)|읽었|조회(?:함|했)|확인(?:함|했|됨)|fetch|검색(?:함|했)|레퍼런스|reference)/i.test(t);
86
- if (externalRef && !gathered && !hasSource) findings.push({ dim: 'no-info-gathering', severity: 'medium', label: '외부 정보 미수집', detail: '외부 API/라이브러리/버전/스펙 언급이 있으나 정보 수집(공식문서·api-skill·조회) 흔적이 없음 — 정확한 정보를 먼저 수집 권장.' });
87
- return { ok: findings.length === 0, findings, dimensions: ['pretend-knowledge', 'premature-judgment', 'no-info-gathering'] };
88
- }
89
-
90
- module.exports = { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck };
1
+ // lib/analyzers.js — 순수 분석/검증 함수 (부작용 0, 입력→출력).
2
+ // 1.9.304 (UR-0025): bin/harness.js 에서 비파괴 분리. selftest(evidenceQuality/parseEvidenceStats/shellGuardAnalyze/claimFileInGit)가 동작 검증.
3
+ 'use strict';
4
+
5
+ function _shellGuardAnalyze(cmd, ctx) {
6
+ const c = String(cmd || '');
7
+ const shell = (ctx && ctx.shell) || 'unknown';
8
+ const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
9
+ const issues = [];
10
+ const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
11
+ // 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
12
+ if (isWinPowerShell && /&&|\|\|/.test(c)) { // 1.12.5 (15th 버그헌트 P2, UR-0018): 공백 무관 — PS5.1 은 a&&b 도 거부(이전 /\s&&\s/ 는 양쪽 공백 요구해 npm 체인 a&&b 미탐).
13
+ issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
14
+ }
15
+ // 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
16
+ if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
17
+ issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
18
+ }
19
+ // 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
20
+ if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
21
+ issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
22
+ }
23
+ // 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
24
+ if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
25
+ issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
26
+ }
27
+ // 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
28
+ if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
29
+ issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
30
+ }
31
+ // 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
32
+ if (shell === 'powershell' && psVer == null && /&&|\|\|/.test(c)) { // 1.12.5 (UR-0018): 공백 무관 매칭
33
+ issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
34
+ }
35
+ return { shell, psVersion: psVer, issues };
36
+ }
37
+ function _evidenceQuality(evidence) {
38
+ const e = String(evidence || '');
39
+ const hasFile = /(?:[A-Za-z][\w-]*[\/\\])?[A-Za-z][\w./\\-]*\.(?:js|ts|tsx|jsx|mjs|cjs|py|go|rs|rb|kt|cs|gd|java|php|swift|c|cpp|h|html|css|scss|vue|svelte|json|yaml|yml|toml|md|sql|sh)\b/i.test(e);
40
+ const hasTest = /(\d+)\s*(?:\/\s*\d+\s*)?(?:통과|passed|passing|개\s*테스트)|\btests?\b\s*[:=]?\s*\d|Tests?:\s*\d|\b\d+\s*tests?\b/i.test(e);
41
+ const hasLog = /Exit\s*[:=]|exit\s*code|Command\s*[:=]|npm\s+(?:test|run)|pytest|cargo\s+test|go\s+test/i.test(e);
42
+ const missing = [];
43
+ if (!hasFile) missing.push('수정 파일 경로');
44
+ if (!hasTest) missing.push('테스트명/개수');
45
+ if (!hasLog) missing.push('실행 로그(Command/Exit)');
46
+ return { hasFile, hasTest, hasLog, ok: hasFile && hasTest, missing };
47
+ }
48
+ function _claimFileInGit(claimed, gitSet) {
49
+ if (!gitSet) return null;
50
+ const c = String(claimed).replace(/\\/g, '/').replace(/^\.\//, '');
51
+ for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || c.endsWith('/' + g)) return true; }
52
+ return false;
53
+ }
54
+ function _parseEvidenceStats(text) {
55
+ const t = String(text || '');
56
+ const blocks = t.split(/\n(?=## )/).filter(b => /Command:|Exit:|verify|test/i.test(b));
57
+ let pass = 0, fail = 0;
58
+ for (const b of blocks) {
59
+ const exitM = b.match(/Exit:\s*(-?\d+)/i);
60
+ if (exitM) { (parseInt(exitM[1], 10) === 0 ? pass++ : fail++); continue; }
61
+ if (/\bPASS\b|통과|성공|✓/i.test(b)) pass++;
62
+ else if (/\bFAIL\b|실패|오류|error|✗/i.test(b)) fail++;
63
+ }
64
+ const entries = blocks.length;
65
+ return { entries, pass, fail, rate: (pass + fail) ? Math.round(pass / (pass + fail) * 100) : null };
66
+ }
67
+
68
+ // 1.9.305 (사용자 명시): AI 인식론적 정직성 점검 — 모르는 걸 아는 척 / 정보 미수집 / 미검증 섣부른 판단 휴리스틱 탐지.
69
+ // 순수 함수(텍스트→findings). 휴리스틱 advisory — 단정/추정/외부참조 표현 vs 근거·수집 흔적 대조. opt-in 점검용.
70
+ function _epistemicHonestyCheck(text) {
71
+ const t = String(text || '');
72
+ const findings = [];
73
+ // 공통: 근거/출처 흔적 (파일경로·URL·테스트결과·Exit·문서·api-skill·인용·조회 흔적)
74
+ const hasSource = /(?:[\w./-]+\.(?:js|ts|tsx|jsx|py|go|rs|rb|md|json|ya?ml|toml|sql|sh)\b)|https?:\/\/|\bExit\s*[:=]|\d+\s*\/\s*\d+\s*(?:통과|passed)|\b(?:passed|passing)\b|근거[::]|출처[::]|api-skill|공식\s*문서|문서\s*(?:확인|참조|에\s*따르면)|읽었|조회(?:함|했|함\b)|확인(?:함|했|됨)|grep|로그[::]/i.test(t);
75
+ // 차원1: 모르는 걸 아는 척 — 단정 표현인데 근거 없음
76
+ const definitive = /(반드시|항상|언제나|무조건|확실(?:히|함|하게)|당연히|틀림없|100\s*%|always|never|guaranteed|definitely|obviously|certainly)/i.test(t);
77
+ if (definitive && !hasSource) findings.push({ dim: 'pretend-knowledge', severity: 'high', label: '근거 없는 단정', detail: '단정적 표현이 있으나 근거/출처(파일·문서·테스트·로그)가 없음 — 모르는 정보를 아는 척할 위험.' });
78
+ // 차원2: 미검증 섣부른 판단 — 추정 표현 + 완료/성공 결론인데 근거 없음
79
+ const assumption = /(아마|추정|것\s*같|듯\s*(?:하|싶)|probably|likely|maybe|perhaps|i\s*(?:think|assume|guess|believe|suppose)|should\s*(?:work|be|pass|fix)|생각(?:됩니다|된다|함|돼)|일\s*것|예상(?:됩니다|된다|됨)|짐작)/i.test(t);
80
+ const conclusion = /(완료|done|성공|통과|해결(?:됨|했|함|되었)|fixed|resolved|works?\b|작동(?:함|한다|됨)|구현(?:됨|했|완료))/i.test(t);
81
+ if (assumption && conclusion && !hasSource) findings.push({ dim: 'premature-judgment', severity: 'high', label: '검증 없는 섣부른 판단', detail: '가정·추정 표현과 완료·성공 결론이 함께 있으나 검증 근거가 없음 — 검증 없이 섣부르게 판단할 위험.' });
82
+ // 차원3: 정보 미수집 — 외부 API/라이브러리/버전/스펙 언급인데 수집·근거 흔적 없음
83
+ // \bAPI\b(?!\.[a-z]) 로 파일경로(api.js/api.ts) 오탐 제외. 강한 근거(hasSource)나 수집 흔적(gathered) 있으면 통과.
84
+ const externalRef = /(\bAPI\b(?!\.[a-z])|\bSDK\b|라이브러리|\blibrary\b|\bpackage\b|엔드포인트|\bendpoint\b|버전\s*\d|v\d+\.\d+|\bspec\b|rate\s*limit|레이트\s*리밋|문서에\s*따르면)/i.test(t);
85
+ const gathered = /(https?:\/\/|api-skill|공식\s*문서|\bdocs?\b|문서\s*(?:확인|참조|읽)|읽었|조회(?:함|했)|확인(?:함|했|됨)|fetch|검색(?:함|했)|레퍼런스|reference)/i.test(t);
86
+ if (externalRef && !gathered && !hasSource) findings.push({ dim: 'no-info-gathering', severity: 'medium', label: '외부 정보 미수집', detail: '외부 API/라이브러리/버전/스펙 언급이 있으나 정보 수집(공식문서·api-skill·조회) 흔적이 없음 — 정확한 정보를 먼저 수집 권장.' });
87
+ return { ok: findings.length === 0, findings, dimensions: ['pretend-knowledge', 'premature-judgment', 'no-info-gathering'] };
88
+ }
89
+
90
+ module.exports = { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck };
package/lib/catalogs.js CHANGED
@@ -232,37 +232,38 @@ const _LSP_LANG_PATTERNS = {
232
232
  // evidence에 "DB 저장" / "insert N건" / "DB에" → db.*/pg.*/mysql.*/mongoose.*/prisma.* 없으면 의심
233
233
  // evidence에 "이메일 발송" / "메일 전송" → sendMail/nodemailer/smtp 없으면 의심
234
234
  // 1.9.27: 패턴 카탈로그 확장 (5 → 10) + URL/메서드 단위 매핑 추가
235
+ // 1.12.4 (15th 버그헌트 P1, UR-0014): codeRe 다언어 확장 — _scanCodeForPatterns 는 13개 언어를 읽는데 패턴이 JS전용이라 Python/Ruby/Go/C#/Java/PHP/Rust 정상 구현을 '호출 흔적 없음' 으로 오판(verify-claim 기본 게이트 false-fail, 1.12.0 핵심가치 회귀). 각 codeRe 에 교차언어 idiom 추가. (검출 관대화 → 정직한 비JS 작업 오차단 제거; 과탐보다 안전한 방향.)
235
236
  const OPTIMISM_PATTERNS = [
236
237
  { kind: 'API', evidenceRe: /(API\s*호출|HTTP\s*\d{3}|POST\s*\/|GET\s*\/|PUT\s*\/|DELETE\s*\/|fetch|REST 응답|응답 확인|endpoint|엔드포인트)/i,
237
- codeRe: /\b(fetch\s*\(|http\.request|https\.request|axios\.|got\.|undici|node-fetch)/i,
238
+ codeRe: /\b(fetch\s*\(|http\.request|https\.request|axios\.|got\.|undici|node-fetch|requests\.|httpx|urllib|http\.client|net\/http|Net::HTTP|HTTParty|Faraday|HttpClient|http\.Get|http\.Post|http\.NewRequest|reqwest|curl_|HttpURLConnection|RestTemplate|URLSession)/i,
238
239
  label: 'API/HTTP 호출' },
239
240
  { kind: 'DB', evidenceRe: /(DB에?\s*저장|insert\s+\d+|데이터베이스|SQL\s*(INSERT|UPDATE|DELETE)|migration|마이그레이션 적용)/i,
240
- codeRe: /\b(db\.|pg\.|pool\.|mysql\.|mongoose\.|prisma\.|sequelize|knex|sqlite3|MongoClient|createConnection)/i,
241
+ codeRe: /\b(db\.|pg\.|pool\.|mysql\.|mongoose\.|prisma\.|sequelize|knex|sqlite3|MongoClient|createConnection|psycopg|sqlalchemy|cursor\.execute|django\.db|sqlx|gorm|database\/sql|ActiveRecord|jdbc|EntityManager|DbContext|mysqli)/i,
241
242
  label: 'DB 호출' },
242
243
  { kind: 'Email', evidenceRe: /(이메일[^.\n]{0,30}(발송|전송|보냈|보냄|완료)|메일[^.\n]{0,30}(발송|전송|보냈|보냄)|sendMail|smtp\s*(전송|발송))/i,
243
- codeRe: /\b(sendMail|nodemailer|smtp|@sendgrid|mailgun|aws-sdk\/ses|resend\.)/i,
244
+ codeRe: /\b(sendMail|nodemailer|smtp|@sendgrid|mailgun|aws-sdk\/ses|resend\.|smtplib|django\.core\.mail|ActionMailer|net\/smtp|Net::SMTP|SmtpClient|javax\.mail|JavaMailSender)/i,
244
245
  label: '이메일 전송' },
245
246
  { kind: 'Webhook', evidenceRe: /(웹훅\s*(호출|전송|발송)|webhook\s+(sent|posted|triggered))/i,
246
- codeRe: /\b(fetch\s*\(|http\.request|axios\.)/i,
247
+ codeRe: /\b(fetch\s*\(|http\.request|axios\.|requests\.|urllib|net\/http|Net::HTTP|http\.Post|reqwest|curl_|HttpClient)/i,
247
248
  label: '웹훅' },
248
249
  { kind: 'Payment', evidenceRe: /(결제\s*(완료|성공|승인|취소)|payment\s+(processed|charged)|stripe 결제|toss\s*결제|카카오페이|네이버페이|kakaopay|nicepay|iamport 결제|페이팔|paypal)/i,
249
- codeRe: /\b(stripe|toss|@stripe|tosspayments|iamport|kakao|nicepay|naverpay|paypal-rest-sdk|@paypal)/i,
250
+ codeRe: /\b(stripe|toss|@stripe|tosspayments|iamport|kakao|nicepay|naverpay|paypal-rest-sdk|@paypal|razorpay|braintree)/i,
250
251
  label: '결제' },
251
252
  // 1.9.27 신규 카테고리
252
253
  { kind: 'FileIO', evidenceRe: /(파일[^.\n]{0,20}(생성|저장|작성|기록)|\d+개[^.\n]{0,20}파일|디스크[^.\n]{0,20}저장|로그 파일 작성)/i,
253
- codeRe: /\b(fs\.write|fs\.appendFile|writeFileSync|appendFileSync|fs\/promises|fs\.createWriteStream)/i,
254
+ codeRe: /\b(fs\.write|fs\.appendFile|writeFileSync|appendFileSync|fs\/promises|fs\.createWriteStream|open\s*\([^)]*['"][wa]|File\.Write|ioutil\.WriteFile|os\.WriteFile|fopen|File\.open|Files\.write)/i,
254
255
  label: '파일 I/O 쓰기' },
255
256
  { kind: 'Queue', evidenceRe: /(메시지\s*큐|발행\s*완료|publish\s*(완료|성공)|RabbitMQ|Kafka|SQS|Redis Pub|이벤트 발행)/i,
256
- codeRe: /\b(amqp|kafkajs|rabbit|redis\.(publish|xadd)|@aws-sdk\/client-sqs|bull|bullmq)/i,
257
+ codeRe: /\b(amqp|kafkajs|rabbit|redis\.(publish|xadd)|@aws-sdk\/client-sqs|bull|bullmq|pika|kombu|confluent_kafka|sidekiq|celery|boto3)/i,
257
258
  label: '메시지 큐 발행' },
258
259
  { kind: 'Cache', evidenceRe: /(Redis[^.\n]{0,20}(저장|set|get)|캐시[^.\n]{0,20}(저장|기록|적중)|memcache)/i,
259
- codeRe: /\b(redis\.|ioredis|memcached|node-cache|@upstash\/redis|connect-redis)/i,
260
+ codeRe: /\b(redis\.|ioredis|memcached|node-cache|@upstash\/redis|connect-redis|Redis\.|StackExchange\.Redis|go-redis|jedis)/i,
260
261
  label: '캐시 저장' },
261
262
  { kind: 'Notify', evidenceRe: /(슬랙\s*(알림|발송|전송)|Slack\s+(notification|sent|posted)|Discord\s+(알림|발송|webhook)|푸시 알림 전송)/i,
262
- codeRe: /\b(@slack\/web-api|slack-webhook|discord\.js|discord-webhook|@discordjs|firebase\/messaging|expo-notifications)/i,
263
+ codeRe: /\b(@slack\/web-api|slack-webhook|discord\.js|discord-webhook|@discordjs|firebase\/messaging|expo-notifications|slack_sdk|slack-sdk|discord\.py|discordgo)/i,
263
264
  label: '슬랙/Discord 알림' },
264
265
  { kind: 'Storage', evidenceRe: /(S3\s*(업로드|저장)|GCS\s*업로드|Azure Blob|클라우드 스토리지 업로드|object storage 저장)/i,
265
- codeRe: /\b(@aws-sdk\/client-s3|aws-sdk[^a-z]|@google-cloud\/storage|@azure\/storage-blob|aws-s3)/i,
266
+ codeRe: /\b(@aws-sdk\/client-s3|aws-sdk[^a-z]|@google-cloud\/storage|@azure\/storage-blob|aws-s3|boto3|google\.cloud\.storage|azure\.storage|minio)/i,
266
267
  label: '클라우드 스토리지' }
267
268
  ];
268
269