@blum84/smart-commit 0.2.0 → 0.2.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.js CHANGED
@@ -1,443 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createAiClient,
4
+ createLogger,
5
+ getOfflineTemplates,
6
+ isAiAvailable,
7
+ loadConfig,
8
+ scanRepositories
9
+ } from "./chunk-LYR3U55E.js";
2
10
  import {
3
11
  classifyFiles,
4
12
  groupFiles
5
- } from "./chunk-ZS27WQDW.js";
13
+ } from "./chunk-MYMEBX2Q.js";
6
14
 
7
15
  // src/index.ts
8
16
  import { Command } from "commander";
9
-
10
- // src/config.ts
11
- import { cosmiconfig } from "cosmiconfig";
12
- var DEFAULT_CONFIG = {
13
- ai: {
14
- primary: "gemini",
15
- fallback: "claude",
16
- timeout: 30
17
- },
18
- safety: {
19
- maxFileSize: "10MB",
20
- blockedPatterns: [
21
- "*.env",
22
- ".env.*",
23
- "*.pem",
24
- "*.key",
25
- "credentials*",
26
- "*.sqlite",
27
- "*.sqlite3"
28
- ],
29
- warnPatterns: [
30
- "*.log",
31
- "*.csv",
32
- "package-lock.json",
33
- "yarn.lock",
34
- "pnpm-lock.yaml"
35
- ]
36
- },
37
- commit: {
38
- style: "conventional",
39
- language: "ko",
40
- maxDiffSize: 1e4
41
- },
42
- grouping: {
43
- strategy: "smart"
44
- }
45
- };
46
- async function loadConfig(cliOptions = {}) {
47
- const explorer = cosmiconfig("smart-commit", {
48
- searchPlaces: [
49
- ".smart-commitrc",
50
- ".smart-commitrc.yaml",
51
- ".smart-commitrc.yml",
52
- ".smart-commitrc.json",
53
- "smart-commit.config.js",
54
- "package.json"
55
- ]
56
- });
57
- const result = await explorer.search();
58
- const fileConfig = result?.config ?? {};
59
- const config = deepMerge(
60
- DEFAULT_CONFIG,
61
- fileConfig
62
- );
63
- if (cliOptions.ai && typeof cliOptions.ai === "string") {
64
- config.ai.primary = cliOptions.ai;
65
- }
66
- if (cliOptions.group && typeof cliOptions.group === "string") {
67
- config.grouping.strategy = cliOptions.group;
68
- }
69
- return config;
70
- }
71
- function deepMerge(target, source) {
72
- const result = { ...target };
73
- for (const key of Object.keys(source)) {
74
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
75
- result[key] = deepMerge(
76
- target[key],
77
- source[key]
78
- );
79
- } else {
80
- result[key] = source[key];
81
- }
82
- }
83
- return result;
84
- }
85
-
86
- // src/scanner.ts
87
- import { simpleGit } from "simple-git";
88
- import { readdir, stat, access } from "fs/promises";
89
- import { join } from "path";
90
- async function scanRepositories(baseDir, ui, logger) {
91
- const gitDirs = await findGitDirs(baseDir);
92
- const repos = [];
93
- ui.showProgress("Scanning repositories...", 0, gitDirs.length);
94
- for (let i = 0; i < gitDirs.length; i++) {
95
- const dir = gitDirs[i];
96
- ui.showProgress(`Scanning: ${dir}`, i + 1, gitDirs.length);
97
- try {
98
- const repo = await inspectRepo(dir, logger);
99
- repos.push(repo);
100
- } catch (err) {
101
- logger.warn({ dir, err }, "Failed to inspect repository");
102
- }
103
- }
104
- return repos;
105
- }
106
- async function findGitDirs(baseDir) {
107
- const dirs = [];
108
- const entries = await readdir(baseDir, { withFileTypes: true });
109
- for (const entry of entries) {
110
- if (!entry.isDirectory()) continue;
111
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
112
- const fullPath = join(baseDir, entry.name);
113
- const gitPath = join(fullPath, ".git");
114
- try {
115
- await access(gitPath);
116
- dirs.push(fullPath);
117
- } catch {
118
- const subDirs = await findGitDirs(fullPath);
119
- dirs.push(...subDirs);
120
- }
121
- }
122
- try {
123
- const selfGit = join(baseDir, ".git");
124
- await access(selfGit);
125
- if (!dirs.some((d) => d === baseDir)) {
126
- dirs.unshift(baseDir);
127
- }
128
- } catch {
129
- }
130
- return dirs;
131
- }
132
- async function inspectRepo(dir, logger) {
133
- const git = simpleGit(dir);
134
- const gitStatus = await detectGitStatus(dir, git);
135
- if (gitStatus === "locked") {
136
- logger.warn({ dir }, "Git index locked \u2014 skipping");
137
- return { path: dir, branch: "", status: "locked", files: [], unpushedCommits: 0 };
138
- }
139
- if (gitStatus === "detached") {
140
- logger.warn({ dir }, "Detached HEAD \u2014 skipping");
141
- return { path: dir, branch: "HEAD (detached)", status: "detached", files: [], unpushedCommits: 0 };
142
- }
143
- if (gitStatus === "rebasing") {
144
- logger.warn({ dir }, "Rebase in progress \u2014 skipping");
145
- return { path: dir, branch: "", status: "rebasing", files: [], unpushedCommits: 0 };
146
- }
147
- const statusResult = await git.status();
148
- const branch = statusResult.current ?? "unknown";
149
- const files = [];
150
- for (const f of statusResult.files) {
151
- const filePath = join(dir, f.path);
152
- let size = 0;
153
- try {
154
- const s = await stat(filePath);
155
- size = s.size;
156
- } catch {
157
- }
158
- files.push({
159
- path: f.path,
160
- status: mapGitStatus(f.working_dir, f.index),
161
- size,
162
- isBinary: false
163
- // will be checked by classifier
164
- });
165
- }
166
- let unpushedCommits = 0;
167
- try {
168
- const log = await git.log(["@{u}..HEAD"]);
169
- unpushedCommits = log.total;
170
- } catch {
171
- }
172
- const repoStatus = gitStatus === "merging" ? "merging" : files.length > 0 ? "dirty" : "clean";
173
- return { path: dir, branch, status: repoStatus, files, unpushedCommits };
174
- }
175
- async function detectGitStatus(dir, git) {
176
- try {
177
- await access(join(dir, ".git", "index.lock"));
178
- return "locked";
179
- } catch {
180
- }
181
- try {
182
- await access(join(dir, ".git", "rebase-merge"));
183
- return "rebasing";
184
- } catch {
185
- }
186
- try {
187
- await access(join(dir, ".git", "rebase-apply"));
188
- return "rebasing";
189
- } catch {
190
- }
191
- try {
192
- await access(join(dir, ".git", "MERGE_HEAD"));
193
- return "merging";
194
- } catch {
195
- }
196
- try {
197
- await git.raw(["symbolic-ref", "HEAD"]);
198
- } catch {
199
- return "detached";
200
- }
201
- return "clean";
202
- }
203
- function mapGitStatus(workingDir, index) {
204
- if (index === "?" || workingDir === "?") return "untracked";
205
- if (index === "A" || workingDir === "A") return "added";
206
- if (index === "D" || workingDir === "D") return "deleted";
207
- if (index === "R" || workingDir === "R") return "renamed";
208
- return "modified";
209
- }
210
-
211
- // src/ai-client.ts
212
- import { execa } from "execa";
213
- var CONVENTIONAL_PREFIXES = [
214
- "feat",
215
- "fix",
216
- "refactor",
217
- "docs",
218
- "style",
219
- "test",
220
- "chore",
221
- "perf",
222
- "ci",
223
- "build",
224
- "revert"
225
- ];
226
- var CONVENTIONAL_RE = new RegExp(`^(${CONVENTIONAL_PREFIXES.join("|")})(\\(.+\\))?!?:\\s.+`);
227
- var OFFLINE_TEMPLATES = CONVENTIONAL_PREFIXES.map((prefix) => `${prefix}: `);
228
- function getOfflineTemplates() {
229
- return OFFLINE_TEMPLATES;
230
- }
231
- async function isAiAvailable(tool) {
232
- try {
233
- const cmd = tool === "gpt" ? "openai" : tool;
234
- await execa("which", [cmd], { timeout: 3e3 });
235
- return true;
236
- } catch {
237
- return false;
238
- }
239
- }
240
- function createAiClient(config, logger) {
241
- async function callWithFallback(prompt) {
242
- let result = await callAi(config.ai.primary, prompt, config.ai.timeout, logger, config);
243
- if (!result && config.ai.fallback !== config.ai.primary) {
244
- logger.warn({ fallback: config.ai.fallback }, "Primary AI failed, trying fallback");
245
- result = await callAi(config.ai.fallback, prompt, config.ai.timeout, logger, config);
246
- }
247
- return result;
248
- }
249
- return {
250
- async generateCommitMessage(diff, language) {
251
- const summarized = await this.summarizeDiff(diff);
252
- const prompt = buildCommitPrompt(summarized, language, config.commit.style);
253
- logger.info({ tool: config.ai.primary, diffLength: summarized.length }, "Requesting commit message");
254
- let result = await callWithFallback(prompt);
255
- if (result) {
256
- if (config.commit.style === "conventional" && !validateConventionalCommit(result)) {
257
- logger.warn({ message: result.split("\n")[0] }, "Invalid conventional commit, retrying");
258
- const retryPrompt = buildRetryPrompt(result, language);
259
- const retried = await callWithFallback(retryPrompt);
260
- if (retried && validateConventionalCommit(retried)) {
261
- result = retried;
262
- }
263
- }
264
- result = stripCodeBlocks(result);
265
- logger.info({ messageLength: result.length }, "Commit message generated");
266
- }
267
- return result;
268
- },
269
- async resolveConflict(localContent, remoteContent) {
270
- const prompt = buildConflictPrompt(localContent, remoteContent);
271
- return callWithFallback(prompt);
272
- },
273
- async groupFiles(fileList) {
274
- const { buildGroupingPrompt } = await import("./classifier-TTZQUM7N.js");
275
- const prompt = buildGroupingPrompt(fileList);
276
- return callWithFallback(prompt);
277
- },
278
- async summarizeDiff(diff) {
279
- if (diff.length <= config.commit.maxDiffSize) {
280
- return diff;
281
- }
282
- const statSection = extractDiffStat(diff);
283
- const hunks = extractKeyHunks(diff, config.commit.maxDiffSize - statSection.length - 200);
284
- const truncated = `${statSection}
285
-
286
- [\uC8FC\uC694 \uBCC0\uACBD \uB0B4\uC6A9 (\uC804\uCCB4 ${diff.length}\uC790 \uC911 \uD575\uC2EC\uBD80\uB9CC \uCD94\uCD9C)]
287
- ${hunks}`;
288
- if (truncated.length > config.commit.maxDiffSize * 1.5) {
289
- logger.info("Diff too large, requesting AI summary");
290
- const summaryPrompt = buildDiffSummaryPrompt(truncated.slice(0, config.commit.maxDiffSize));
291
- const summary = await callWithFallback(summaryPrompt);
292
- return summary ?? truncated.slice(0, config.commit.maxDiffSize);
293
- }
294
- return truncated;
295
- }
296
- };
297
- }
298
- function validateConventionalCommit(message) {
299
- const firstLine = message.split("\n")[0].trim();
300
- return CONVENTIONAL_RE.test(firstLine);
301
- }
302
- function stripCodeBlocks(text) {
303
- return text.replace(/^```[\w]*\n?/gm, "").replace(/^```\s*$/gm, "").trim();
304
- }
305
- function extractDiffStat(diff) {
306
- const lines = diff.split("\n");
307
- const statLines = [];
308
- for (const line of lines) {
309
- if (line.startsWith("diff --git")) {
310
- statLines.push(line);
311
- } else if (line.startsWith("--- ") || line.startsWith("+++ ")) {
312
- statLines.push(line);
313
- }
314
- }
315
- return statLines.join("\n");
316
- }
317
- function extractKeyHunks(diff, maxLength) {
318
- const hunks = [];
319
- let currentHunk = "";
320
- let totalLength = 0;
321
- for (const line of diff.split("\n")) {
322
- if (line.startsWith("@@")) {
323
- if (currentHunk && totalLength + currentHunk.length <= maxLength) {
324
- hunks.push(currentHunk);
325
- totalLength += currentHunk.length;
326
- }
327
- currentHunk = line + "\n";
328
- } else if (line.startsWith("+") || line.startsWith("-")) {
329
- currentHunk += line + "\n";
330
- }
331
- }
332
- if (currentHunk && totalLength + currentHunk.length <= maxLength) {
333
- hunks.push(currentHunk);
334
- }
335
- return hunks.join("\n");
336
- }
337
- async function callAi(tool, prompt, timeout, logger, config) {
338
- try {
339
- const { command, args } = buildAiCommand(tool, prompt, config);
340
- const { stdout } = await execa(command, args, {
341
- timeout: timeout * 1e3,
342
- stdin: "ignore"
343
- });
344
- const trimmed = stdout.trim();
345
- return trimmed || null;
346
- } catch (err) {
347
- logger.error({ tool, err }, "AI call failed");
348
- return null;
349
- }
350
- }
351
- function buildAiCommand(tool, prompt, config) {
352
- switch (tool) {
353
- case "gemini":
354
- return { command: "gemini", args: [prompt] };
355
- case "claude":
356
- return { command: "claude", args: ["-p", prompt] };
357
- case "gpt":
358
- return { command: "openai", args: ["api", "chat.completions.create", "-m", "gpt-4o", "-g", "user", prompt] };
359
- case "ollama": {
360
- const model = config?.ai?.ollama?.model ?? "llama3";
361
- return { command: "ollama", args: ["run", model, prompt] };
362
- }
363
- default:
364
- return { command: tool, args: [prompt] };
365
- }
366
- }
367
- function buildCommitPrompt(diff, language, style) {
368
- const langLabel = language === "ko" ? "\uD55C\uAD6D\uC5B4" : "English";
369
- const styleGuide = style === "conventional" ? `Conventional Commits \uD615\uC2DD\uC744 \uBC18\uB4DC\uC2DC \uB530\uB974\uC138\uC694.
370
- \uC811\uB450\uC0AC\uB294 \uB2E4\uC74C \uC911 \uC120\uD0DD: ${CONVENTIONAL_PREFIXES.join(", ")}
371
- \uD615\uC2DD: <\uC811\uB450\uC0AC>(<\uBC94\uC704>): <\uC124\uBA85> (\uBC94\uC704\uB294 \uC120\uD0DD\uC0AC\uD56D)` : "";
372
- return `\uC544\uB798\uC758 [Git Diff] \uB0B4\uC6A9\uC744 \uBD84\uC11D\uD558\uC5EC Git Commit Message\uB97C \uC791\uC131\uD574\uC918.
373
-
374
- [CRITICAL INSTRUCTION]
375
- **\uACB0\uACFC\uB294 \uBB34\uC870\uAC74 '${langLabel}'\uB85C \uC791\uC131\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4.**
376
- ${styleGuide}
377
-
378
- [\uC791\uC131 \uC608\uC2DC]
379
- feat(auth): \uC0AC\uC6A9\uC790 \uB85C\uADF8\uC778 API \uAD6C\uD604
380
-
381
- - \uB85C\uADF8\uC778 \uC694\uCCAD \uCC98\uB9AC\uB97C \uC704\uD55C \uCEE8\uD2B8\uB864\uB7EC \uBA54\uC11C\uB4DC \uCD94\uAC00
382
- - JWT \uD1A0\uD070 \uBC1C\uAE09 \uB85C\uC9C1 \uAD6C\uD604
383
-
384
- [\uD544\uC218 \uADDC\uCE59]
385
- 1. \uC5B8\uC5B4: **100% ${langLabel}**\uB85C \uC791\uC131\uD560 \uAC83.
386
- 2. \uD615\uC2DD:
387
- - \uCCAB \uC904: \uBCC0\uACBD \uC0AC\uD56D\uC744 50\uC790 \uC774\uB0B4\uB85C \uC694\uC57D (\uC81C\uBAA9)
388
- - \uB450 \uBC88\uC9F8 \uC904: \uBE48 \uC904
389
- - \uC138 \uBC88\uC9F8 \uC904\uBD80\uD130: \uBCC0\uACBD\uB41C \uC0C1\uC138 \uB0B4\uC6A9\uC744 \uBD88\uB9BF \uD3EC\uC778\uD2B8(-)\uB85C \uC815\uB9AC
390
- 3. \uCD9C\uB825: \uB9C8\uD06C\uB2E4\uC6B4 \uCF54\uB4DC \uBE14\uB85D\uC774\uB098 \uBD80\uAC00 \uC124\uBA85 \uC5C6\uC774, \uC624\uC9C1 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD14D\uC2A4\uD2B8\uB9CC \uCD9C\uB825\uD560 \uAC83.
391
- 4. \uC81C\uD55C: \uC5B4\uB5A0\uD55C \uB3C4\uAD6C(Functions/Tools)\uB3C4 \uC0AC\uC6A9\uD558\uC9C0 \uB9D0 \uAC83. \uC624\uC9C1 \uD14D\uC2A4\uD2B8\uB9CC \uC0DD\uC131\uD558\uB77C.
392
-
393
- [Git Diff]
394
- ${diff}`;
395
- }
396
- function buildRetryPrompt(invalidMessage, language) {
397
- const langLabel = language === "ko" ? "\uD55C\uAD6D\uC5B4" : "English";
398
- return `\uC544\uB798 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uAC00 Conventional Commits \uD615\uC2DD\uC5D0 \uB9DE\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC218\uC815\uD574\uC8FC\uC138\uC694.
399
-
400
- [\uD604\uC7AC \uBA54\uC2DC\uC9C0]
401
- ${invalidMessage}
402
-
403
- [\uADDC\uCE59]
404
- - \uCCAB \uC904\uC740 \uBC18\uB4DC\uC2DC "${CONVENTIONAL_PREFIXES.join("|")}(<\uBC94\uC704>): <\uC124\uBA85>" \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.
405
- - ${langLabel}\uB85C \uC791\uC131\uD558\uC138\uC694.
406
- - \uC218\uC815\uB41C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB9CC \uCD9C\uB825\uD558\uC138\uC694.`;
407
- }
408
- function buildConflictPrompt(localContent, remoteContent) {
409
- return `\uC544\uB798\uC5D0 Git \uCDA9\uB3CC\uC774 \uBC1C\uC0DD\uD55C \uD30C\uC77C\uC758 [\uB85C\uCEEC \uBC84\uC804]\uACFC [\uC6D0\uACA9 \uBC84\uC804]\uC774 \uC788\uC2B5\uB2C8\uB2E4.
410
- \uB450 \uBC84\uC804\uC744 \uBD84\uC11D\uD558\uC5EC **\uC62C\uBC14\uB974\uAC8C \uBCD1\uD569\uB41C \uCD5C\uC885 \uD30C\uC77C \uB0B4\uC6A9**\uC744 \uC0DD\uC131\uD574\uC8FC\uC138\uC694.
411
-
412
- [\uD544\uC218 \uADDC\uCE59]
413
- 1. \uB450 \uBC84\uC804\uC758 \uBCC0\uACBD \uC0AC\uD56D\uC744 \uBAA8\uB450 \uD3EC\uD568\uD558\uC5EC \uBCD1\uD569\uD560 \uAC83
414
- 2. \uCDA9\uB3CC \uB9C8\uCEE4(<<<<<<, ======, >>>>>>)\uB294 \uC808\uB300 \uD3EC\uD568\uD558\uC9C0 \uB9D0 \uAC83
415
- 3. \uCF54\uB4DC\uC758 \uB17C\uB9AC\uC801 \uC77C\uAD00\uC131\uC744 \uC720\uC9C0\uD560 \uAC83
416
- 4. \uCD9C\uB825\uC740 **\uC624\uC9C1 \uBCD1\uD569\uB41C \uD30C\uC77C \uB0B4\uC6A9\uB9CC** \uCD9C\uB825\uD560 \uAC83
417
-
418
- [\uB85C\uCEEC \uBC84\uC804]
419
- ${localContent}
420
-
421
- [\uC6D0\uACA9 \uBC84\uC804]
422
- ${remoteContent}`;
423
- }
424
- function buildDiffSummaryPrompt(diff) {
425
- return `\uC544\uB798 Git Diff\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4. \uD575\uC2EC \uBCC0\uACBD \uC0AC\uD56D\uB9CC \uC694\uC57D\uD574\uC8FC\uC138\uC694.
426
-
427
- [\uADDC\uCE59]
428
- 1. \uC5B4\uB5A4 \uD30C\uC77C\uC5D0\uC11C \uBB34\uC5C7\uC774 \uBCC0\uACBD\uB418\uC5C8\uB294\uC9C0 \uC694\uC57D
429
- 2. \uCD94\uAC00/\uC218\uC815/\uC0AD\uC81C\uB41C \uC8FC\uC694 \uD568\uC218/\uD074\uB798\uC2A4/\uBCC0\uC218 \uB098\uC5F4
430
- 3. diff \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825 (+ / - \uC811\uB450\uC0AC \uC0AC\uC6A9)
431
- 4. 500\uC790 \uC774\uB0B4\uB85C \uC694\uC57D
432
-
433
- [Diff]
434
- ${diff}`;
435
- }
17
+ import { createRequire } from "module";
436
18
 
437
19
  // src/committer.ts
438
- import { simpleGit as simpleGit2 } from "simple-git";
20
+ import { simpleGit } from "simple-git";
439
21
  async function commitAndPush(repo, files, message, action, ui, logger) {
440
- const git = simpleGit2(repo.path);
22
+ const git = simpleGit(repo.path);
441
23
  if (action === "cancel") {
442
24
  ui.showMessage(`${repo.path}: \uAC74\uB108\uB701\uB2C8\uB2E4.`, "info");
443
25
  return;
@@ -493,9 +75,11 @@ var term = termkit.terminal;
493
75
  function createUI() {
494
76
  let progressBar = null;
495
77
  return {
496
- showHeader(config) {
78
+ showHeader(config, version) {
497
79
  term.clear();
498
- term.bold.cyan("\n Smart Commit v0.1.0\n");
80
+ term.bold.cyan(`
81
+ Smart Commit v${version ?? "unknown"}
82
+ `);
499
83
  term.gray(` AI: ${config.ai.primary} (fallback: ${config.ai.fallback})
500
84
  `);
501
85
  term.gray(` Style: ${config.commit.style} | Language: ${config.commit.language}
@@ -591,12 +175,14 @@ function createUI() {
591
175
  const items = [
592
176
  "Push (\uD478\uC2DC \uC2E4\uD589)",
593
177
  "Skip (\uB85C\uCEEC \uCEE4\uBC0B \uC720\uC9C0)",
594
- "Cancel (\uCEE4\uBC0B \uCDE8\uC18C)"
178
+ "Cancel (\uCEE4\uBC0B \uCDE8\uC18C)",
179
+ "Skip repo (\uC774 \uC800\uC7A5\uC18C \uAC74\uB108\uB6F0\uAE30)",
180
+ "Exit (\uC885\uB8CC)"
595
181
  ];
596
182
  term(" \u25B6 Select action:\n");
597
183
  const response = await term.singleColumnMenu(items).promise;
598
184
  term("\n");
599
- const map = ["push", "skip", "cancel"];
185
+ const map = ["push", "skip", "cancel", "skip-repo", "exit"];
600
186
  return map[response.selectedIndex] ?? "skip";
601
187
  },
602
188
  async promptOfflineTemplate(templates) {
@@ -669,36 +255,18 @@ function padEnd(str, len) {
669
255
  return str + " ".repeat(diff);
670
256
  }
671
257
 
672
- // src/logger.ts
673
- import pino from "pino";
674
- import { join as join2 } from "path";
675
- import { mkdirSync } from "fs";
676
- import { homedir } from "os";
677
- function createLogger() {
678
- const logDir = join2(homedir(), ".smart-commit", "logs");
679
- try {
680
- mkdirSync(logDir, { recursive: true });
681
- } catch {
682
- return pino({ level: "info" });
683
- }
684
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
685
- const logFile = join2(logDir, `${today}.log`);
686
- return pino(
687
- { level: "info" },
688
- pino.destination({ dest: logFile, append: true, sync: false })
689
- );
690
- }
691
-
692
258
  // src/index.ts
259
+ var require2 = createRequire(import.meta.url);
260
+ var { version: PKG_VERSION } = require2("../package.json");
693
261
  var program = new Command();
694
- program.name("smart-commit").description("AI-powered intelligent Git auto-commit & push CLI tool").version("0.1.0").option("-d, --dry-run", "Preview without committing or pushing").option("-g, --group <strategy>", "Grouping strategy: smart | single | manual").option("-a, --ai <tool>", "AI tool: gemini | claude | gpt | ollama").option("--no-interactive", "Headless mode (no prompts)").option("--offline", "Offline mode (use templates instead of AI)").action(async (options) => {
262
+ program.name("smart-commit").description("AI-powered intelligent Git auto-commit & push CLI tool").version(PKG_VERSION).option("-d, --dry-run", "Preview without committing or pushing").option("-g, --group <strategy>", "Grouping strategy: smart | single | manual").option("-a, --ai <tool>", "AI tool: gemini | claude | gpt | ollama").option("--no-interactive", "Headless mode (no prompts)").option("--offline", "Offline mode (use templates instead of AI)").action(async (options) => {
695
263
  const config = await loadConfig(options);
696
264
  const logger = createLogger();
697
265
  const ui = createUI();
698
266
  const ai = createAiClient(config, logger);
699
267
  const isHeadless = options.interactive === false;
700
268
  logger.info({ options }, "smart-commit started");
701
- ui.showHeader(config);
269
+ ui.showHeader(config, PKG_VERSION);
702
270
  let offlineMode = options.offline ?? false;
703
271
  if (!offlineMode) {
704
272
  const primaryAvail = await isAiAvailable(config.ai.primary);
@@ -731,7 +299,7 @@ program.name("smart-commit").description("AI-powered intelligent Git auto-commit
731
299
  }
732
300
  continue;
733
301
  }
734
- const safety = classifyFiles(repo.files, config);
302
+ const safety = await classifyFiles(repo.files, config);
735
303
  if (safety.blocked.length > 0) {
736
304
  ui.showBlocked(repo, safety.blocked);
737
305
  }
@@ -787,6 +355,15 @@ program.name("smart-commit").description("AI-powered intelligent Git auto-commit
787
355
  continue;
788
356
  }
789
357
  const action = isHeadless ? "push" : await ui.promptAction();
358
+ if (action === "exit") {
359
+ ui.showMessage("\uC885\uB8CC\uD569\uB2C8\uB2E4.", "info");
360
+ ui.cleanup();
361
+ return;
362
+ }
363
+ if (action === "skip-repo") {
364
+ ui.showMessage(`${repo.path}: \uC800\uC7A5\uC18C \uAC74\uB108\uB6F0\uAE30`, "info");
365
+ break;
366
+ }
790
367
  await commitAndPush(repo, group.files, commitMsg, action, ui, logger);
791
368
  }
792
369
  }
@@ -815,8 +392,8 @@ program.command("hook").description("Install or uninstall Git hooks").option("--
815
392
  ui.cleanup();
816
393
  });
817
394
  async function getDiff(repo, filePaths) {
818
- const { simpleGit: simpleGit3 } = await import("simple-git");
819
- const git = simpleGit3(repo.path);
395
+ const { simpleGit: simpleGit2 } = await import("simple-git");
396
+ const git = simpleGit2(repo.path);
820
397
  await git.add(filePaths);
821
398
  const diff = await git.diff(["--cached", "--", ...filePaths]);
822
399
  return diff;