@blum84/smart-commit 0.2.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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/chunk-ZS27WQDW.js +162 -0
- package/dist/chunk-ZS27WQDW.js.map +1 -0
- package/dist/classifier-TTZQUM7N.js +14 -0
- package/dist/classifier-TTZQUM7N.js.map +1 -0
- package/dist/index.js +825 -0
- package/dist/index.js.map +1 -0
- package/dist/install-XOLZLFAI.js +84 -0
- package/dist/install-XOLZLFAI.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ChoHeeSung
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# smart-commit
|
|
2
|
+
|
|
3
|
+
AI 기반 지능형 Git 자동 커밋 & 푸시 CLI 도구
|
|
4
|
+
|
|
5
|
+
현재 디렉토리 하위의 모든 Git 저장소를 스캔하여, AI(Gemini/Claude)로 커밋 메시지를 자동 생성하고, 안전하게 커밋/푸시합니다.
|
|
6
|
+
|
|
7
|
+
## 설치 & 실행
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 설치 없이 바로 실행
|
|
11
|
+
npx smart-commit
|
|
12
|
+
|
|
13
|
+
# 글로벌 설치
|
|
14
|
+
npm install -g smart-commit
|
|
15
|
+
smart-commit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 주요 기능
|
|
19
|
+
|
|
20
|
+
- **AI 커밋 메시지 생성** — Gemini/Claude CLI로 diff 분석 후 한국어 커밋 메시지 자동 생성
|
|
21
|
+
- **안전 필터** — `.env`, `.pem`, 대용량 파일 자동 차단, 위험 파일 경고
|
|
22
|
+
- **Git 상태 감지** — Detached HEAD, Rebase/Merge 진행 중, Lock 파일 등 비정상 상태 안전 스킵
|
|
23
|
+
- **AI Fallback** — 주 AI 도구 실패 시 대체 도구로 자동 전환
|
|
24
|
+
- **Dry-run 모드** — 실제 커밋/푸시 없이 미리보기
|
|
25
|
+
- **TUI** — terminal-kit 기반 프로그레스바, 테이블, 메뉴 선택
|
|
26
|
+
|
|
27
|
+
## 사전 요구사항
|
|
28
|
+
|
|
29
|
+
다음 중 하나 이상의 AI CLI 도구가 설치되어 있어야 합니다:
|
|
30
|
+
|
|
31
|
+
- [Gemini CLI](https://github.com/google-gemini/gemini-cli) — `npm install -g @google/gemini-cli`
|
|
32
|
+
- [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) — Anthropic 공식 CLI
|
|
33
|
+
|
|
34
|
+
## 사용법
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 기본 실행 (하위 모든 Git 저장소 스캔)
|
|
38
|
+
smart-commit
|
|
39
|
+
|
|
40
|
+
# Dry-run (미리보기만)
|
|
41
|
+
smart-commit --dry-run
|
|
42
|
+
|
|
43
|
+
# AI 도구 지정
|
|
44
|
+
smart-commit --ai claude
|
|
45
|
+
|
|
46
|
+
# 그룹핑 전략 지정
|
|
47
|
+
smart-commit --group single # 모든 변경을 하나의 커밋으로
|
|
48
|
+
smart-commit --group smart # AI가 의미 단위로 그룹핑 (기본값)
|
|
49
|
+
|
|
50
|
+
# 비대화형 모드
|
|
51
|
+
smart-commit --no-interactive
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 설정
|
|
55
|
+
|
|
56
|
+
프로젝트 루트 또는 홈 디렉토리에 `.smart-commitrc.yaml` 파일을 생성하세요:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
ai:
|
|
60
|
+
primary: gemini
|
|
61
|
+
fallback: claude
|
|
62
|
+
timeout: 30
|
|
63
|
+
|
|
64
|
+
safety:
|
|
65
|
+
maxFileSize: 10MB
|
|
66
|
+
blockedPatterns:
|
|
67
|
+
- "*.env"
|
|
68
|
+
- "*.pem"
|
|
69
|
+
- "*.key"
|
|
70
|
+
- "credentials*"
|
|
71
|
+
warnPatterns:
|
|
72
|
+
- "*.log"
|
|
73
|
+
- "package-lock.json"
|
|
74
|
+
|
|
75
|
+
commit:
|
|
76
|
+
style: conventional
|
|
77
|
+
language: ko
|
|
78
|
+
maxDiffSize: 10000
|
|
79
|
+
|
|
80
|
+
grouping:
|
|
81
|
+
strategy: smart
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 안전 필터
|
|
85
|
+
|
|
86
|
+
| 분류 | 동작 | 패턴 예시 |
|
|
87
|
+
|------|------|----------|
|
|
88
|
+
| **차단** | 커밋에서 자동 제외 | `.env`, `.pem`, `.key`, `credentials*`, 10MB 초과 |
|
|
89
|
+
| **경고** | 사용자 확인 후 포함 | `.log`, `.csv`, `package-lock.json` |
|
|
90
|
+
| **안전** | 정상 커밋 | 그 외 모든 파일 |
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/classifier.ts
|
|
4
|
+
import { minimatch } from "minimatch";
|
|
5
|
+
import { dirname, extname } from "path";
|
|
6
|
+
var SIZE_UNITS = {
|
|
7
|
+
B: 1,
|
|
8
|
+
KB: 1024,
|
|
9
|
+
MB: 1024 * 1024,
|
|
10
|
+
GB: 1024 * 1024 * 1024
|
|
11
|
+
};
|
|
12
|
+
function classifyFiles(files, config) {
|
|
13
|
+
const maxBytes = parseSize(config.safety.maxFileSize);
|
|
14
|
+
const blocked = [];
|
|
15
|
+
const warned = [];
|
|
16
|
+
const safe = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
if (isBlocked(file, config.safety.blockedPatterns, maxBytes)) {
|
|
19
|
+
blocked.push(file);
|
|
20
|
+
} else if (isWarned(file, config.safety.warnPatterns)) {
|
|
21
|
+
warned.push(file);
|
|
22
|
+
} else {
|
|
23
|
+
safe.push(file);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { blocked, warned, safe };
|
|
27
|
+
}
|
|
28
|
+
async function groupFiles(files, strategy, callAiForGrouping, logger) {
|
|
29
|
+
if (files.length === 0) return [];
|
|
30
|
+
if (strategy === "single") {
|
|
31
|
+
return [{ label: "all", files, reason: "single strategy" }];
|
|
32
|
+
}
|
|
33
|
+
if (strategy === "smart" && callAiForGrouping) {
|
|
34
|
+
const aiGroups = await tryAiGrouping(files, callAiForGrouping, logger);
|
|
35
|
+
if (aiGroups) return aiGroups;
|
|
36
|
+
logger.warn("AI grouping failed, falling back to rule-based grouping");
|
|
37
|
+
}
|
|
38
|
+
return ruleBasedGrouping(files);
|
|
39
|
+
}
|
|
40
|
+
async function tryAiGrouping(files, callAi, logger) {
|
|
41
|
+
const fileList = files.map((f) => `${f.status.charAt(0).toUpperCase()} ${f.path}`).join("\n");
|
|
42
|
+
try {
|
|
43
|
+
const response = await callAi(fileList);
|
|
44
|
+
if (!response) return null;
|
|
45
|
+
const parsed = parseAiGroupingResponse(response, files);
|
|
46
|
+
if (parsed.length === 0) return null;
|
|
47
|
+
logger.info({ groupCount: parsed.length }, "AI grouping succeeded");
|
|
48
|
+
return parsed;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
logger.error({ err }, "AI grouping parse error");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseAiGroupingResponse(response, allFiles) {
|
|
55
|
+
try {
|
|
56
|
+
const cleaned = response.replace(/```json?\s*/g, "").replace(/```\s*/g, "").trim();
|
|
57
|
+
const parsed = JSON.parse(cleaned);
|
|
58
|
+
if (!parsed.groups || !Array.isArray(parsed.groups)) return [];
|
|
59
|
+
const fileMap = new Map(allFiles.map((f) => [f.path, f]));
|
|
60
|
+
const usedFiles = /* @__PURE__ */ new Set();
|
|
61
|
+
const groups = [];
|
|
62
|
+
for (const g of parsed.groups) {
|
|
63
|
+
const matchedFiles = g.files.map((path) => fileMap.get(path)).filter((f) => f !== void 0 && !usedFiles.has(f.path));
|
|
64
|
+
for (const f of matchedFiles) usedFiles.add(f.path);
|
|
65
|
+
if (matchedFiles.length > 0) {
|
|
66
|
+
groups.push({
|
|
67
|
+
label: g.label,
|
|
68
|
+
files: matchedFiles,
|
|
69
|
+
reason: g.reason
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const remaining = allFiles.filter((f) => !usedFiles.has(f.path));
|
|
74
|
+
if (remaining.length > 0) {
|
|
75
|
+
groups.push({
|
|
76
|
+
label: "other",
|
|
77
|
+
files: remaining,
|
|
78
|
+
reason: "AI\uAC00 \uBD84\uB958\uD558\uC9C0 \uC54A\uC740 \uB098\uBA38\uC9C0 \uD30C\uC77C"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return groups;
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function ruleBasedGrouping(files) {
|
|
87
|
+
const dirMap = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const dir = dirname(file.path);
|
|
90
|
+
const ext = extname(file.path);
|
|
91
|
+
const topDir = dir.split("/")[0] || ".";
|
|
92
|
+
const category = getCategory(ext);
|
|
93
|
+
const key = `${topDir}/${category}`;
|
|
94
|
+
if (!dirMap.has(key)) dirMap.set(key, []);
|
|
95
|
+
dirMap.get(key).push(file);
|
|
96
|
+
}
|
|
97
|
+
const groups = [];
|
|
98
|
+
for (const [key, groupFiles2] of dirMap) {
|
|
99
|
+
groups.push({
|
|
100
|
+
label: key,
|
|
101
|
+
files: groupFiles2,
|
|
102
|
+
reason: `\uB514\uB809\uD1A0\uB9AC/\uC720\uD615 \uAE30\uBC18 \uADF8\uB8F9\uD551: ${key}`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return groups;
|
|
106
|
+
}
|
|
107
|
+
function getCategory(ext) {
|
|
108
|
+
const categories = {
|
|
109
|
+
source: [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".kt", ".swift", ".c", ".cpp", ".h"],
|
|
110
|
+
style: [".css", ".scss", ".sass", ".less", ".styl"],
|
|
111
|
+
markup: [".html", ".xml", ".svg", ".vue", ".svelte"],
|
|
112
|
+
config: [".json", ".yaml", ".yml", ".toml", ".ini", ".env", ".conf"],
|
|
113
|
+
docs: [".md", ".txt", ".rst", ".adoc"],
|
|
114
|
+
test: [".test.ts", ".test.js", ".spec.ts", ".spec.js", ".test.py"]
|
|
115
|
+
};
|
|
116
|
+
for (const [category, exts] of Object.entries(categories)) {
|
|
117
|
+
if (exts.includes(ext)) return category;
|
|
118
|
+
}
|
|
119
|
+
return "other";
|
|
120
|
+
}
|
|
121
|
+
function buildGroupingPrompt(fileList) {
|
|
122
|
+
return `\uC544\uB798 Git \uBCC0\uACBD \uD30C\uC77C \uBAA9\uB85D\uC744 \uBD84\uC11D\uD558\uC5EC \uC758\uBBF8 \uC788\uB294 \uCEE4\uBC0B \uB2E8\uC704\uB85C \uADF8\uB8F9\uD551\uD574\uC8FC\uC138\uC694.
|
|
123
|
+
|
|
124
|
+
[\uADDC\uCE59]
|
|
125
|
+
1. \uAD00\uB828\uB41C \uD30C\uC77C\uB07C\uB9AC \uBB36\uC5B4 \uD558\uB098\uC758 \uCEE4\uBC0B \uADF8\uB8F9\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694.
|
|
126
|
+
2. \uAC01 \uADF8\uB8F9\uC5D0 \uC9E7\uC740 \uB77C\uBCA8(\uD55C\uAD6D\uC5B4)\uACFC \uC774\uC720\uB97C \uBD99\uC5EC\uC8FC\uC138\uC694.
|
|
127
|
+
3. \uBC18\uB4DC\uC2DC \uC544\uB798 JSON \uD615\uC2DD\uC73C\uB85C\uB9CC \uCD9C\uB825\uD558\uC138\uC694. \uB2E4\uB978 \uD14D\uC2A4\uD2B8 \uC5C6\uC774 JSON\uB9CC \uCD9C\uB825\uD558\uC138\uC694.
|
|
128
|
+
|
|
129
|
+
[\uCD9C\uB825 \uD615\uC2DD]
|
|
130
|
+
{"groups":[{"label":"\uADF8\uB8F9\uBA85","files":["\uD30C\uC77C\uACBD\uB85C1","\uD30C\uC77C\uACBD\uB85C2"],"reason":"\uADF8\uB8F9\uD551 \uC774\uC720"}]}
|
|
131
|
+
|
|
132
|
+
[\uBCC0\uACBD \uD30C\uC77C \uBAA9\uB85D]
|
|
133
|
+
${fileList}`;
|
|
134
|
+
}
|
|
135
|
+
function isBlocked(file, patterns, maxBytes) {
|
|
136
|
+
if (file.size > maxBytes) return true;
|
|
137
|
+
if (file.isBinary) return true;
|
|
138
|
+
return matchesAny(file.path, patterns);
|
|
139
|
+
}
|
|
140
|
+
function isWarned(file, patterns) {
|
|
141
|
+
return matchesAny(file.path, patterns);
|
|
142
|
+
}
|
|
143
|
+
function matchesAny(filePath, patterns) {
|
|
144
|
+
return patterns.some(
|
|
145
|
+
(pattern) => minimatch(filePath, pattern, { dot: true, matchBase: true })
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function parseSize(sizeStr) {
|
|
149
|
+
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
|
|
150
|
+
if (!match) return 10 * 1024 * 1024;
|
|
151
|
+
const value = parseFloat(match[1]);
|
|
152
|
+
const unit = match[2].toUpperCase();
|
|
153
|
+
return value * (SIZE_UNITS[unit] ?? 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
classifyFiles,
|
|
158
|
+
groupFiles,
|
|
159
|
+
ruleBasedGrouping,
|
|
160
|
+
buildGroupingPrompt
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=chunk-ZS27WQDW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/classifier.ts"],"sourcesContent":["import { minimatch } from \"minimatch\";\nimport { dirname, extname } from \"node:path\";\nimport type {\n FileChange,\n SmartCommitConfig,\n SafetyResult,\n CommitGroup,\n AiGroupingResult,\n} from \"./types.js\";\nimport type { Logger } from \"pino\";\n\nconst SIZE_UNITS: Record<string, number> = {\n B: 1,\n KB: 1024,\n MB: 1024 * 1024,\n GB: 1024 * 1024 * 1024,\n};\n\n// ─── Safety classification ───\n\nexport function classifyFiles(\n files: FileChange[],\n config: SmartCommitConfig,\n): SafetyResult {\n const maxBytes = parseSize(config.safety.maxFileSize);\n const blocked: FileChange[] = [];\n const warned: FileChange[] = [];\n const safe: FileChange[] = [];\n\n for (const file of files) {\n if (isBlocked(file, config.safety.blockedPatterns, maxBytes)) {\n blocked.push(file);\n } else if (isWarned(file, config.safety.warnPatterns)) {\n warned.push(file);\n } else {\n safe.push(file);\n }\n }\n\n return { blocked, warned, safe };\n}\n\n// ─── Grouping ───\n\nexport async function groupFiles(\n files: FileChange[],\n strategy: SmartCommitConfig[\"grouping\"][\"strategy\"],\n callAiForGrouping: ((fileList: string) => Promise<string | null>) | null,\n logger: Logger,\n): Promise<CommitGroup[]> {\n if (files.length === 0) return [];\n\n if (strategy === \"single\") {\n return [{ label: \"all\", files, reason: \"single strategy\" }];\n }\n\n if (strategy === \"smart\" && callAiForGrouping) {\n const aiGroups = await tryAiGrouping(files, callAiForGrouping, logger);\n if (aiGroups) return aiGroups;\n logger.warn(\"AI grouping failed, falling back to rule-based grouping\");\n }\n\n return ruleBasedGrouping(files);\n}\n\nasync function tryAiGrouping(\n files: FileChange[],\n callAi: (fileList: string) => Promise<string | null>,\n logger: Logger,\n): Promise<CommitGroup[] | null> {\n const fileList = files\n .map((f) => `${f.status.charAt(0).toUpperCase()} ${f.path}`)\n .join(\"\\n\");\n\n try {\n const response = await callAi(fileList);\n if (!response) return null;\n\n const parsed = parseAiGroupingResponse(response, files);\n if (parsed.length === 0) return null;\n\n logger.info({ groupCount: parsed.length }, \"AI grouping succeeded\");\n return parsed;\n } catch (err) {\n logger.error({ err }, \"AI grouping parse error\");\n return null;\n }\n}\n\nfunction parseAiGroupingResponse(\n response: string,\n allFiles: FileChange[],\n): CommitGroup[] {\n // Try JSON parse first\n try {\n const cleaned = response.replace(/```json?\\s*/g, \"\").replace(/```\\s*/g, \"\").trim();\n const parsed: AiGroupingResult = JSON.parse(cleaned);\n\n if (!parsed.groups || !Array.isArray(parsed.groups)) return [];\n\n const fileMap = new Map(allFiles.map((f) => [f.path, f]));\n const usedFiles = new Set<string>();\n const groups: CommitGroup[] = [];\n\n for (const g of parsed.groups) {\n const matchedFiles = g.files\n .map((path) => fileMap.get(path))\n .filter((f): f is FileChange => f !== undefined && !usedFiles.has(f.path));\n\n for (const f of matchedFiles) usedFiles.add(f.path);\n\n if (matchedFiles.length > 0) {\n groups.push({\n label: g.label,\n files: matchedFiles,\n reason: g.reason,\n });\n }\n }\n\n // remaining files that AI didn't assign\n const remaining = allFiles.filter((f) => !usedFiles.has(f.path));\n if (remaining.length > 0) {\n groups.push({\n label: \"other\",\n files: remaining,\n reason: \"AI가 분류하지 않은 나머지 파일\",\n });\n }\n\n return groups;\n } catch {\n return [];\n }\n}\n\n// ─── Rule-based fallback grouping ───\n\nexport function ruleBasedGrouping(files: FileChange[]): CommitGroup[] {\n const dirMap = new Map<string, FileChange[]>();\n\n for (const file of files) {\n const dir = dirname(file.path);\n const ext = extname(file.path);\n\n // Group by top-level directory + extension category\n const topDir = dir.split(\"/\")[0] || \".\";\n const category = getCategory(ext);\n const key = `${topDir}/${category}`;\n\n if (!dirMap.has(key)) dirMap.set(key, []);\n dirMap.get(key)!.push(file);\n }\n\n const groups: CommitGroup[] = [];\n for (const [key, groupFiles] of dirMap) {\n groups.push({\n label: key,\n files: groupFiles,\n reason: `디렉토리/유형 기반 그룹핑: ${key}`,\n });\n }\n\n return groups;\n}\n\nfunction getCategory(ext: string): string {\n const categories: Record<string, string[]> = {\n source: [\".ts\", \".tsx\", \".js\", \".jsx\", \".py\", \".go\", \".rs\", \".java\", \".kt\", \".swift\", \".c\", \".cpp\", \".h\"],\n style: [\".css\", \".scss\", \".sass\", \".less\", \".styl\"],\n markup: [\".html\", \".xml\", \".svg\", \".vue\", \".svelte\"],\n config: [\".json\", \".yaml\", \".yml\", \".toml\", \".ini\", \".env\", \".conf\"],\n docs: [\".md\", \".txt\", \".rst\", \".adoc\"],\n test: [\".test.ts\", \".test.js\", \".spec.ts\", \".spec.js\", \".test.py\"],\n };\n\n for (const [category, exts] of Object.entries(categories)) {\n if (exts.includes(ext)) return category;\n }\n return \"other\";\n}\n\n// ─── AI grouping prompt builder ───\n\nexport function buildGroupingPrompt(fileList: string): string {\n return `아래 Git 변경 파일 목록을 분석하여 의미 있는 커밋 단위로 그룹핑해주세요.\n\n[규칙]\n1. 관련된 파일끼리 묶어 하나의 커밋 그룹으로 만들어주세요.\n2. 각 그룹에 짧은 라벨(한국어)과 이유를 붙여주세요.\n3. 반드시 아래 JSON 형식으로만 출력하세요. 다른 텍스트 없이 JSON만 출력하세요.\n\n[출력 형식]\n{\"groups\":[{\"label\":\"그룹명\",\"files\":[\"파일경로1\",\"파일경로2\"],\"reason\":\"그룹핑 이유\"}]}\n\n[변경 파일 목록]\n${fileList}`;\n}\n\n// ─── Utilities ───\n\nfunction isBlocked(\n file: FileChange,\n patterns: string[],\n maxBytes: number,\n): boolean {\n if (file.size > maxBytes) return true;\n if (file.isBinary) return true;\n return matchesAny(file.path, patterns);\n}\n\nfunction isWarned(file: FileChange, patterns: string[]): boolean {\n return matchesAny(file.path, patterns);\n}\n\nfunction matchesAny(filePath: string, patterns: string[]): boolean {\n return patterns.some((pattern) =>\n minimatch(filePath, pattern, { dot: true, matchBase: true }),\n );\n}\n\nfunction parseSize(sizeStr: string): number {\n const match = sizeStr.match(/^(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB)$/i);\n if (!match) return 10 * 1024 * 1024; // default 10MB\n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n return value * (SIZE_UNITS[unit] ?? 1);\n}\n"],"mappings":"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","names":["groupFiles"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildGroupingPrompt,
|
|
4
|
+
classifyFiles,
|
|
5
|
+
groupFiles,
|
|
6
|
+
ruleBasedGrouping
|
|
7
|
+
} from "./chunk-ZS27WQDW.js";
|
|
8
|
+
export {
|
|
9
|
+
buildGroupingPrompt,
|
|
10
|
+
classifyFiles,
|
|
11
|
+
groupFiles,
|
|
12
|
+
ruleBasedGrouping
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=classifier-TTZQUM7N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|