pmatrix-smart-commit 0.1.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 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":[]}