@blum84/smart-commit 0.2.3 → 0.3.1
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.
|
@@ -139,8 +139,17 @@ async function inspectRepo(dir, logger) {
|
|
|
139
139
|
}
|
|
140
140
|
const statusResult = await git.status();
|
|
141
141
|
const branch = statusResult.current ?? "unknown";
|
|
142
|
+
const ignoredPaths = /* @__PURE__ */ new Set();
|
|
143
|
+
try {
|
|
144
|
+
const checkIgnore = await git.raw(["check-ignore", ...statusResult.files.map((f) => f.path)]);
|
|
145
|
+
for (const line of checkIgnore.split("\n")) {
|
|
146
|
+
if (line.trim()) ignoredPaths.add(line.trim());
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
142
150
|
const files = [];
|
|
143
151
|
for (const f of statusResult.files) {
|
|
152
|
+
if (ignoredPaths.has(f.path)) continue;
|
|
144
153
|
const filePath = join(dir, f.path);
|
|
145
154
|
let size = 0;
|
|
146
155
|
try {
|
|
@@ -493,4 +502,4 @@ export {
|
|
|
493
502
|
createAiClient,
|
|
494
503
|
createLogger
|
|
495
504
|
};
|
|
496
|
-
//# sourceMappingURL=chunk-
|
|
505
|
+
//# sourceMappingURL=chunk-4PNDTOIB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/scanner.ts","../src/ai-client.ts","../src/logger.ts"],"sourcesContent":["import { cosmiconfig } from \"cosmiconfig\";\nimport type { SmartCommitConfig } from \"./types.js\";\n\nconst DEFAULT_CONFIG: SmartCommitConfig = {\n ai: {\n primary: \"gemini\",\n fallback: \"claude\",\n timeout: 30,\n },\n safety: {\n maxFileSize: \"10MB\",\n blockedPatterns: [\n \"*.env\",\n \".env.*\",\n \"*.pem\",\n \"*.key\",\n \"credentials*\",\n \"*.sqlite\",\n \"*.sqlite3\",\n ],\n warnPatterns: [\n \"*.log\",\n \"*.csv\",\n \"package-lock.json\",\n \"yarn.lock\",\n \"pnpm-lock.yaml\",\n ],\n },\n commit: {\n style: \"conventional\",\n language: \"ko\",\n maxDiffSize: 10000,\n },\n grouping: {\n strategy: \"smart\",\n },\n};\n\nexport async function loadConfig(\n cliOptions: Record<string, unknown> = {},\n): Promise<SmartCommitConfig> {\n const explorer = cosmiconfig(\"smart-commit\", {\n searchPlaces: [\n \".smart-commitrc\",\n \".smart-commitrc.yaml\",\n \".smart-commitrc.yml\",\n \".smart-commitrc.json\",\n \"smart-commit.config.js\",\n \"package.json\",\n ],\n });\n\n const result = await explorer.search();\n const fileConfig = result?.config ?? {};\n\n const config = deepMerge(\n DEFAULT_CONFIG as unknown as Record<string, unknown>,\n fileConfig as Record<string, unknown>,\n ) as unknown as SmartCommitConfig;\n\n if (cliOptions.ai && typeof cliOptions.ai === \"string\") {\n config.ai.primary = cliOptions.ai as string;\n }\n if (cliOptions.group && typeof cliOptions.group === \"string\") {\n config.grouping.strategy = cliOptions.group as \"smart\" | \"single\" | \"manual\";\n }\n\n return config;\n}\n\nfunction deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === \"object\" &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === \"object\" &&\n !Array.isArray(target[key])\n ) {\n result[key] = deepMerge(\n target[key] as Record<string, unknown>,\n source[key] as Record<string, unknown>,\n );\n } else {\n result[key] = source[key];\n }\n }\n return result;\n}\n","import { simpleGit, type SimpleGit } from \"simple-git\";\nimport { readdir, stat, access } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { RepoState, RepoGitStatus, FileChange } from \"./types.js\";\nimport type { UI } from \"./ui.js\";\nimport type { Logger } from \"pino\";\n\nexport async function scanRepositories(\n baseDir: string,\n ui: UI,\n logger: Logger,\n): Promise<RepoState[]> {\n const gitDirs = await findGitDirs(baseDir);\n const repos: RepoState[] = [];\n\n ui.showProgress(\"Scanning repositories...\", 0, gitDirs.length);\n\n for (let i = 0; i < gitDirs.length; i++) {\n const dir = gitDirs[i];\n ui.showProgress(`Scanning: ${dir}`, i + 1, gitDirs.length);\n\n try {\n const repo = await inspectRepo(dir, logger);\n repos.push(repo);\n } catch (err) {\n logger.warn({ dir, err }, \"Failed to inspect repository\");\n }\n }\n\n return repos;\n}\n\nasync function findGitDirs(baseDir: string): Promise<string[]> {\n const dirs: string[] = [];\n const entries = await readdir(baseDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n\n const fullPath = join(baseDir, entry.name);\n const gitPath = join(fullPath, \".git\");\n\n try {\n await access(gitPath);\n dirs.push(fullPath);\n } catch {\n // not a git repo, check subdirectories\n const subDirs = await findGitDirs(fullPath);\n dirs.push(...subDirs);\n }\n }\n\n // also check if baseDir itself is a git repo\n try {\n const selfGit = join(baseDir, \".git\");\n await access(selfGit);\n if (!dirs.some((d) => d === baseDir)) {\n dirs.unshift(baseDir);\n }\n } catch {\n // baseDir is not a git repo\n }\n\n return dirs;\n}\n\nasync function inspectRepo(dir: string, logger: Logger): Promise<RepoState> {\n const git: SimpleGit = simpleGit(dir);\n\n const gitStatus = await detectGitStatus(dir, git);\n\n if (gitStatus === \"locked\") {\n logger.warn({ dir }, \"Git index locked — skipping\");\n return { path: dir, branch: \"\", status: \"locked\", files: [], unpushedCommits: 0 };\n }\n if (gitStatus === \"detached\") {\n logger.warn({ dir }, \"Detached HEAD — skipping\");\n return { path: dir, branch: \"HEAD (detached)\", status: \"detached\", files: [], unpushedCommits: 0 };\n }\n if (gitStatus === \"rebasing\") {\n logger.warn({ dir }, \"Rebase in progress — skipping\");\n return { path: dir, branch: \"\", status: \"rebasing\", files: [], unpushedCommits: 0 };\n }\n\n const statusResult = await git.status();\n const branch = statusResult.current ?? \"unknown\";\n\n // Get list of ignored paths to filter them out\n const ignoredPaths = new Set<string>();\n try {\n const checkIgnore = await git.raw([\"check-ignore\", ...statusResult.files.map((f) => f.path)]);\n for (const line of checkIgnore.split(\"\\n\")) {\n if (line.trim()) ignoredPaths.add(line.trim());\n }\n } catch {\n // check-ignore returns non-zero if no files are ignored — that's fine\n }\n\n const files: FileChange[] = [];\n for (const f of statusResult.files) {\n // Skip files that are ignored by .gitignore\n if (ignoredPaths.has(f.path)) continue;\n\n const filePath = join(dir, f.path);\n let size = 0;\n try {\n const s = await stat(filePath);\n size = s.size;\n } catch {\n // file might have been deleted\n }\n\n files.push({\n path: f.path,\n status: mapGitStatus(f.working_dir, f.index),\n size,\n isBinary: false, // will be checked by classifier\n });\n }\n\n let unpushedCommits = 0;\n try {\n const log = await git.log([\"@{u}..HEAD\"]);\n unpushedCommits = log.total;\n } catch {\n // no upstream set\n }\n\n const repoStatus: RepoGitStatus =\n gitStatus === \"merging\"\n ? \"merging\"\n : files.length > 0\n ? \"dirty\"\n : \"clean\";\n\n return { path: dir, branch, status: repoStatus, files, unpushedCommits };\n}\n\nasync function detectGitStatus(dir: string, git: SimpleGit): Promise<RepoGitStatus> {\n // Check lock file\n try {\n await access(join(dir, \".git\", \"index.lock\"));\n return \"locked\";\n } catch {\n // no lock\n }\n\n // Check rebase\n try {\n await access(join(dir, \".git\", \"rebase-merge\"));\n return \"rebasing\";\n } catch {\n // not rebasing\n }\n try {\n await access(join(dir, \".git\", \"rebase-apply\"));\n return \"rebasing\";\n } catch {\n // not rebasing\n }\n\n // Check merge\n try {\n await access(join(dir, \".git\", \"MERGE_HEAD\"));\n return \"merging\";\n } catch {\n // not merging\n }\n\n // Check detached HEAD\n try {\n await git.raw([\"symbolic-ref\", \"HEAD\"]);\n } catch {\n return \"detached\";\n }\n\n return \"clean\";\n}\n\nfunction mapGitStatus(workingDir: string, index: string): FileChange[\"status\"] {\n if (index === \"?\" || workingDir === \"?\") return \"untracked\";\n if (index === \"A\" || workingDir === \"A\") return \"added\";\n if (index === \"D\" || workingDir === \"D\") return \"deleted\";\n if (index === \"R\" || workingDir === \"R\") return \"renamed\";\n return \"modified\";\n}\n","import { execa } from \"execa\";\nimport type { SmartCommitConfig, AiTool } from \"./types.js\";\nimport type { Logger } from \"pino\";\n\nconst CONVENTIONAL_PREFIXES = [\n \"feat\", \"fix\", \"refactor\", \"docs\", \"style\", \"test\", \"chore\", \"perf\", \"ci\", \"build\", \"revert\",\n];\nconst CONVENTIONAL_RE = new RegExp(`^(${CONVENTIONAL_PREFIXES.join(\"|\")})(\\\\(.+\\\\))?!?:\\\\s.+`);\n\n// ─── Offline templates ───\n\nconst OFFLINE_TEMPLATES = CONVENTIONAL_PREFIXES.map((prefix) => `${prefix}: `);\n\nexport function getOfflineTemplates(): string[] {\n return OFFLINE_TEMPLATES;\n}\n\nexport async function isAiAvailable(tool: AiTool): Promise<boolean> {\n try {\n const cmd = tool === \"gpt\" ? \"openai\" : tool;\n await execa(\"which\", [cmd], { timeout: 3000 });\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface AiClient {\n generateCommitMessage(diff: string, language: string): Promise<string | null>;\n resolveConflict(localContent: string, remoteContent: string): Promise<string | null>;\n groupFiles(fileList: string): Promise<string | null>;\n summarizeDiff(diff: string): Promise<string>;\n}\n\nexport function createAiClient(config: SmartCommitConfig, logger: Logger): AiClient {\n async function callWithFallback(prompt: string): Promise<string | null> {\n let result = await callAi(config.ai.primary, prompt, config.ai.timeout, logger, config);\n if (!result && config.ai.fallback !== config.ai.primary) {\n logger.warn({ fallback: config.ai.fallback }, \"Primary AI failed, trying fallback\");\n result = await callAi(config.ai.fallback, prompt, config.ai.timeout, logger, config);\n }\n return result;\n }\n\n return {\n async generateCommitMessage(diff, language) {\n const summarized = await this.summarizeDiff(diff);\n const prompt = buildCommitPrompt(summarized, language, config.commit.style);\n\n logger.info({ tool: config.ai.primary, diffLength: summarized.length }, \"Requesting commit message\");\n\n let result = await callWithFallback(prompt);\n\n if (result) {\n // Conventional commit validation + retry\n if (config.commit.style === \"conventional\" && !validateConventionalCommit(result)) {\n logger.warn({ message: result.split(\"\\n\")[0] }, \"Invalid conventional commit, retrying\");\n const retryPrompt = buildRetryPrompt(result, language);\n const retried = await callWithFallback(retryPrompt);\n if (retried && validateConventionalCommit(retried)) {\n result = retried;\n }\n // use original if retry also fails — better than nothing\n }\n\n // Strip markdown code blocks if AI wrapped it\n result = stripCodeBlocks(result);\n\n logger.info({ messageLength: result.length }, \"Commit message generated\");\n }\n\n return result;\n },\n\n async resolveConflict(localContent, remoteContent) {\n const prompt = buildConflictPrompt(localContent, remoteContent);\n return callWithFallback(prompt);\n },\n\n async groupFiles(fileList) {\n const { buildGroupingPrompt } = await import(\"./classifier.js\");\n const prompt = buildGroupingPrompt(fileList);\n return callWithFallback(prompt);\n },\n\n async summarizeDiff(diff) {\n if (diff.length <= config.commit.maxDiffSize) {\n return diff;\n }\n\n // Smart truncation: stat header + most important hunks\n const statSection = extractDiffStat(diff);\n const hunks = extractKeyHunks(diff, config.commit.maxDiffSize - statSection.length - 200);\n\n const truncated = `${statSection}\\n\\n[주요 변경 내용 (전체 ${diff.length}자 중 핵심부만 추출)]\\n${hunks}`;\n\n // If still too large, ask AI to summarize\n if (truncated.length > config.commit.maxDiffSize * 1.5) {\n logger.info(\"Diff too large, requesting AI summary\");\n const summaryPrompt = buildDiffSummaryPrompt(truncated.slice(0, config.commit.maxDiffSize));\n const summary = await callWithFallback(summaryPrompt);\n return summary ?? truncated.slice(0, config.commit.maxDiffSize);\n }\n\n return truncated;\n },\n };\n}\n\n// ─── Conventional commit validation ───\n\nexport function validateConventionalCommit(message: string): boolean {\n const firstLine = message.split(\"\\n\")[0].trim();\n return CONVENTIONAL_RE.test(firstLine);\n}\n\nfunction stripCodeBlocks(text: string): string {\n return text\n .replace(/^```[\\w]*\\n?/gm, \"\")\n .replace(/^```\\s*$/gm, \"\")\n .trim();\n}\n\n// ─── Diff summarization ───\n\nfunction extractDiffStat(diff: string): string {\n const lines = diff.split(\"\\n\");\n const statLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith(\"diff --git\")) {\n statLines.push(line);\n } else if (line.startsWith(\"--- \") || line.startsWith(\"+++ \")) {\n statLines.push(line);\n }\n }\n\n return statLines.join(\"\\n\");\n}\n\nfunction extractKeyHunks(diff: string, maxLength: number): string {\n const hunks: string[] = [];\n let currentHunk = \"\";\n let totalLength = 0;\n\n for (const line of diff.split(\"\\n\")) {\n if (line.startsWith(\"@@\")) {\n if (currentHunk && totalLength + currentHunk.length <= maxLength) {\n hunks.push(currentHunk);\n totalLength += currentHunk.length;\n }\n currentHunk = line + \"\\n\";\n } else if (line.startsWith(\"+\") || line.startsWith(\"-\")) {\n // Prioritize actual changes over context\n currentHunk += line + \"\\n\";\n }\n }\n\n // Don't forget the last hunk\n if (currentHunk && totalLength + currentHunk.length <= maxLength) {\n hunks.push(currentHunk);\n }\n\n return hunks.join(\"\\n\");\n}\n\n// ─── AI call ───\n\nasync function callAi(\n tool: AiTool,\n prompt: string,\n timeout: number,\n logger: Logger,\n config?: SmartCommitConfig,\n): Promise<string | null> {\n try {\n const { command, args } = buildAiCommand(tool, prompt, config);\n\n const { stdout } = await execa(command, args, {\n timeout: timeout * 1000,\n stdin: \"ignore\",\n });\n\n const trimmed = stdout.trim();\n return trimmed || null;\n } catch (err) {\n logger.error({ tool, err }, \"AI call failed\");\n return null;\n }\n}\n\nfunction buildAiCommand(\n tool: AiTool,\n prompt: string,\n config?: SmartCommitConfig,\n): { command: string; args: string[] } {\n switch (tool) {\n case \"gemini\":\n return { command: \"gemini\", args: [prompt] };\n case \"claude\":\n return { command: \"claude\", args: [\"-p\", prompt] };\n case \"gpt\":\n // OpenAI CLI: https://platform.openai.com/docs/guides/command-line\n return { command: \"openai\", args: [\"api\", \"chat.completions.create\", \"-m\", \"gpt-4o\", \"-g\", \"user\", prompt] };\n case \"ollama\": {\n const model = config?.ai?.ollama?.model ?? \"llama3\";\n return { command: \"ollama\", args: [\"run\", model, prompt] };\n }\n default:\n // Generic: treat tool name as command, pass prompt as first arg\n return { command: tool, args: [prompt] };\n }\n}\n\n// ─── Prompt builders ───\n\nfunction buildCommitPrompt(diff: string, language: string, style: string): string {\n const langLabel = language === \"ko\" ? \"한국어\" : \"English\";\n\n const conventionalGuide = style === \"conventional\" ? `\n[Conventional Commits 규칙]\n반드시 아래 구조를 따르세요:\n\n<type>(<scope>): <subject>\n\n<body>\n\n구조 설명:\n- type (필수): 다음 중 하나 — ${CONVENTIONAL_PREFIXES.join(\", \")}\n- scope (선택): 영향 범위를 괄호 안에 표기 (예: auth, api, ui)\n- subject (필수): 50자 이내, 명령조, 핵심 요약\n- body (선택): 72자/줄 제한, \"왜\" 변경했는지 bullet(-) 목록으로 설명\n\n[Type 선택 기준]\n- feat: 새로운 기능 추가\n- fix: 버그 수정\n- refactor: 기능 변경 없는 코드 개선\n- docs: 문서 변경\n- style: 포맷팅, 세미콜론 등 (로직 변경 없음)\n- test: 테스트 추가/수정\n- chore: 빌드, 설정, 의존성 변경\n- perf: 성능 개선\n- ci: CI/CD 설정\n- build: 빌드 시스템 변경\n- revert: 이전 커밋 되돌림` : \"\";\n\n return `아래의 [Git Diff]를 분석하여 Git Commit Message를 작성하라.\n\n[CRITICAL INSTRUCTION]\n**결과는 무조건 '${langLabel}'로 작성되어야 한다.**\n${conventionalGuide}\n\n[작성 원칙]\n1. subject는 \"무엇을\" 했는지 — 명령조 사용 (\"추가\", \"수정\", \"제거\")\n2. body는 \"왜\" 변경했는지 — 구체적 변경 내용을 bullet(-)로 나열\n3. 하나의 메시지는 하나의 논리적 변경만 설명\n4. 부수 효과가 있으면 body에 명시\n\n[좋은 예시]\nfeat(auth): JWT 기반 인증 미들웨어 구현\n\n- Access/Refresh 토큰 발급 로직 추가\n- 토큰 만료 시 자동 갱신 처리\n- 인증 실패 시 401 응답 통일\n\n[나쁜 예시 — 절대 이렇게 작성하지 말 것]\n- \"수정함\" (type 없음, 무엇을 수정했는지 불명)\n- \"fix: 버그 수정\" (어떤 버그인지 불명)\n- \"여러가지 수정 및 기능 추가\" (하나의 커밋에 여러 변경 혼합)\n\n[출력 규칙]\n- 마크다운 코드 블록(\\`\\`\\`)이나 부가 설명 없이, 오직 커밋 메시지 텍스트만 출력\n- 어떠한 도구(Functions/Tools)도 사용하지 말 것\n\n[Git Diff]\n${diff}`;\n}\n\nfunction buildRetryPrompt(invalidMessage: string, language: string): string {\n const langLabel = language === \"ko\" ? \"한국어\" : \"English\";\n return `아래 커밋 메시지가 Conventional Commits 형식에 맞지 않습니다. 수정해주세요.\n\n[현재 메시지]\n${invalidMessage}\n\n[필수 구조]\n<type>(<scope>): <subject>\n\n<body>\n\n[규칙]\n- type은 반드시 다음 중 하나: ${CONVENTIONAL_PREFIXES.join(\", \")}\n- scope는 선택사항 (괄호 안에 영향 범위)\n- subject는 50자 이내, 명령조 사용\n- body는 72자/줄 제한, bullet(-) 목록\n- ${langLabel}로 작성\n- 수정된 커밋 메시지만 출력 (다른 설명 없이)`;\n}\n\nfunction buildConflictPrompt(localContent: string, remoteContent: string): string {\n return `아래에 Git 충돌이 발생한 파일의 [로컬 버전]과 [원격 버전]이 있습니다.\n두 버전을 분석하여 **올바르게 병합된 최종 파일 내용**을 생성해주세요.\n\n[필수 규칙]\n1. 두 버전의 변경 사항을 모두 포함하여 병합할 것\n2. 충돌 마커(<<<<<<, ======, >>>>>>)는 절대 포함하지 말 것\n3. 코드의 논리적 일관성을 유지할 것\n4. 출력은 **오직 병합된 파일 내용만** 출력할 것\n\n[로컬 버전]\n${localContent}\n\n[원격 버전]\n${remoteContent}`;\n}\n\nfunction buildDiffSummaryPrompt(diff: string): string {\n return `아래 Git Diff가 너무 큽니다. 핵심 변경 사항만 요약해주세요.\n\n[규칙]\n1. 어떤 파일에서 무엇이 변경되었는지 요약\n2. 추가/수정/삭제된 주요 함수/클래스/변수 나열\n3. diff 형식으로 출력 (+ / - 접두사 사용)\n4. 500자 이내로 요약\n\n[Diff]\n${diff}`;\n}\n","import pino from \"pino\";\nimport { join } from \"node:path\";\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\n\nexport function createLogger(): pino.Logger {\n const logDir = join(homedir(), \".smart-commit\", \"logs\");\n\n try {\n mkdirSync(logDir, { recursive: true });\n } catch {\n // fallback: log to stderr only\n return pino({ level: \"info\" });\n }\n\n const today = new Date().toISOString().slice(0, 10);\n const logFile = join(logDir, `${today}.log`);\n\n return pino(\n { level: \"info\" },\n pino.destination({ dest: logFile, append: true, sync: false }),\n );\n}\n"],"mappings":"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","names":["join"]}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
isAiAvailable,
|
|
7
7
|
loadConfig,
|
|
8
8
|
scanRepositories
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4PNDTOIB.js";
|
|
10
10
|
import {
|
|
11
11
|
classifyFiles,
|
|
12
12
|
groupFiles
|
|
@@ -29,8 +29,16 @@ async function commitAndPush(repo, files, message, action, ui, logger) {
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
const filePaths = files.map((f) => f.path);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const staged = [];
|
|
33
|
+
for (const fp of filePaths) {
|
|
34
|
+
try {
|
|
35
|
+
await git.add(fp);
|
|
36
|
+
staged.push(fp);
|
|
37
|
+
} catch {
|
|
38
|
+
logger.warn({ repo: repo.path, file: fp }, "Skipped (gitignored or inaccessible)");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
logger.info({ repo: repo.path, files: staged }, "Files staged");
|
|
34
42
|
try {
|
|
35
43
|
await git.commit(message);
|
|
36
44
|
ui.showMessage(`${repo.path}: \uCEE4\uBC0B \uC644\uB8CC`, "success");
|
|
@@ -394,7 +402,12 @@ program.command("hook").description("Install or uninstall Git hooks").option("--
|
|
|
394
402
|
async function getDiff(repo, filePaths) {
|
|
395
403
|
const { simpleGit: simpleGit2 } = await import("simple-git");
|
|
396
404
|
const git = simpleGit2(repo.path);
|
|
397
|
-
|
|
405
|
+
for (const fp of filePaths) {
|
|
406
|
+
try {
|
|
407
|
+
await git.add(fp);
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
398
411
|
const diff = await git.diff(["--cached", "--", ...filePaths]);
|
|
399
412
|
return diff;
|
|
400
413
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/committer.ts","../src/ui.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { createRequire } from \"node:module\";\nimport { loadConfig } from \"./config.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version: PKG_VERSION } = require(\"../package.json\");\nimport { scanRepositories } from \"./scanner.js\";\nimport { classifyFiles, groupFiles } from \"./classifier.js\";\nimport { createAiClient, isAiAvailable, getOfflineTemplates } from \"./ai-client.js\";\nimport { commitAndPush } from \"./committer.js\";\nimport { createUI } from \"./ui.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { RepoState, UserAction } from \"./types.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"smart-commit\")\n .description(\"AI-powered intelligent Git auto-commit & push CLI tool\")\n .version(PKG_VERSION)\n .option(\"-d, --dry-run\", \"Preview without committing or pushing\")\n .option(\"-g, --group <strategy>\", \"Grouping strategy: smart | single | manual\")\n .option(\"-a, --ai <tool>\", \"AI tool: gemini | claude | gpt | ollama\")\n .option(\"--no-interactive\", \"Headless mode (no prompts)\")\n .option(\"--offline\", \"Offline mode (use templates instead of AI)\")\n .action(async (options) => {\n const config = await loadConfig(options);\n const logger = createLogger();\n const ui = createUI();\n const ai = createAiClient(config, logger);\n const isHeadless = options.interactive === false;\n\n logger.info({ options }, \"smart-commit started\");\n\n ui.showHeader(config, PKG_VERSION);\n\n // Check AI availability (skip in offline mode)\n let offlineMode = options.offline ?? false;\n if (!offlineMode) {\n const primaryAvail = await isAiAvailable(config.ai.primary);\n const fallbackAvail = await isAiAvailable(config.ai.fallback);\n if (!primaryAvail && !fallbackAvail) {\n ui.showMessage(\"AI 도구를 찾을 수 없습니다. 오프라인 모드로 전환합니다.\", \"warn\");\n offlineMode = true;\n logger.warn(\"No AI tools available, switching to offline mode\");\n } else if (!primaryAvail) {\n ui.showMessage(`${config.ai.primary}를 찾을 수 없습니다. ${config.ai.fallback}를 사용합니다.`, \"warn\");\n }\n }\n\n const repos = await scanRepositories(process.cwd(), ui, logger);\n\n if (repos.length === 0) {\n ui.showMessage(\"변경 사항이 있는 저장소가 없습니다.\", \"info\");\n ui.cleanup();\n return;\n }\n\n ui.showRepoTable(repos);\n\n for (const repo of repos) {\n if (repo.status !== \"dirty\") {\n // Handle unpushed commits\n if (repo.status === \"clean\" && repo.unpushedCommits > 0) {\n ui.showMessage(`${repo.path}: 푸시되지 않은 커밋 ${repo.unpushedCommits}개`, \"info\");\n if (!isHeadless) {\n const action = await ui.promptAction();\n if (action === \"push\") {\n await commitAndPush(repo, [], \"\", \"push\", ui, logger);\n }\n }\n }\n continue;\n }\n\n const safety = await classifyFiles(repo.files, config);\n\n if (safety.blocked.length > 0) {\n ui.showBlocked(repo, safety.blocked);\n }\n\n if (safety.warned.length > 0) {\n if (isHeadless) {\n ui.showMessage(`${repo.path}: 경고 파일 ${safety.warned.length}개 — headless 모드에서 제외`, \"warn\");\n } else {\n const proceed = await ui.confirmWarned(repo, safety.warned);\n if (proceed) {\n safety.safe.push(...safety.warned);\n }\n }\n }\n\n if (safety.safe.length === 0) {\n ui.showMessage(`${repo.path}: 커밋할 안전한 파일이 없습니다.`, \"warn\");\n continue;\n }\n\n // Group files (skip AI grouping in offline mode)\n const groups = await groupFiles(\n safety.safe,\n offlineMode ? \"single\" : config.grouping.strategy,\n !offlineMode && config.grouping.strategy === \"smart\"\n ? (fileList) => ai.groupFiles(fileList)\n : null,\n logger,\n );\n\n for (const group of groups) {\n let commitMsg: string | null = null;\n\n if (offlineMode) {\n // Offline mode: use template\n if (isHeadless) {\n commitMsg = `chore: auto-commit ${group.files.length} files`;\n } else {\n commitMsg = await ui.promptOfflineTemplate(getOfflineTemplates());\n }\n } else {\n // AI mode\n const diff = await getDiff(repo, group.files.map((f) => f.path));\n const summarizedDiff = await ai.summarizeDiff(diff);\n commitMsg = await ai.generateCommitMessage(summarizedDiff, config.commit.language);\n\n if (!commitMsg) {\n ui.showMessage(`${repo.path} [${group.label}]: AI 메시지 생성 실패`, \"warn\");\n if (!isHeadless) {\n ui.showMessage(\"오프라인 템플릿으로 전환합니다.\", \"info\");\n commitMsg = await ui.promptOfflineTemplate(getOfflineTemplates());\n } else {\n commitMsg = `chore: auto-commit ${group.files.length} files`;\n }\n }\n }\n\n if (!commitMsg) continue;\n\n ui.showCommitPreview(repo, commitMsg, group.files);\n\n if (group.reason) {\n ui.showMessage(` 그룹핑 이유: ${group.reason}`, \"info\");\n }\n\n if (options.dryRun) {\n ui.showMessage(\"(dry-run) 실제 커밋/푸시를 수행하지 않습니다.\", \"info\");\n continue;\n }\n\n const action: UserAction = isHeadless ? \"push\" : await ui.promptAction();\n\n if (action === \"exit\") {\n ui.showMessage(\"종료합니다.\", \"info\");\n ui.cleanup();\n return;\n }\n\n if (action === \"skip-repo\") {\n ui.showMessage(`${repo.path}: 저장소 건너뛰기`, \"info\");\n break; // break out of groups loop, continue to next repo\n }\n\n await commitAndPush(repo, group.files, commitMsg, action, ui, logger);\n }\n }\n\n ui.showComplete();\n ui.cleanup();\n });\n\n// ─── Hook subcommand ───\n\nprogram\n .command(\"hook\")\n .description(\"Install or uninstall Git hooks\")\n .option(\"--uninstall\", \"Remove smart-commit hooks\")\n .action(async (options) => {\n const { installHooks, uninstallHooks } = await import(\"./hooks/install.js\");\n const ui = createUI();\n\n if (options.uninstall) {\n const removed = await uninstallHooks(process.cwd());\n if (removed.length > 0) {\n ui.showMessage(`훅 제거 완료: ${removed.join(\", \")}`, \"success\");\n } else {\n ui.showMessage(\"제거할 smart-commit 훅이 없습니다.\", \"info\");\n }\n } else {\n const { installed, skipped } = await installHooks(process.cwd());\n if (installed.length > 0) {\n ui.showMessage(`훅 설치 완료: ${installed.join(\", \")}`, \"success\");\n }\n if (skipped.length > 0) {\n ui.showMessage(`기존 훅이 있어 건너뜀: ${skipped.join(\", \")}`, \"warn\");\n }\n }\n\n ui.cleanup();\n });\n\nasync function getDiff(repo: RepoState, filePaths: string[]): Promise<string> {\n const { simpleGit } = await import(\"simple-git\");\n const git = simpleGit(repo.path);\n await git.add(filePaths);\n const diff = await git.diff([\"--cached\", \"--\", ...filePaths]);\n return diff;\n}\n\nprogram.parse();\n","import { simpleGit } from \"simple-git\";\nimport type { RepoState, FileChange, UserAction } from \"./types.js\";\nimport type { UI } from \"./ui.js\";\nimport type { Logger } from \"pino\";\n\nexport async function commitAndPush(\n repo: RepoState,\n files: FileChange[],\n message: string,\n action: UserAction,\n ui: UI,\n logger: Logger,\n): Promise<void> {\n const git = simpleGit(repo.path);\n\n if (action === \"cancel\") {\n ui.showMessage(`${repo.path}: 건너뜁니다.`, \"info\");\n return;\n }\n\n if (action === \"edit\") {\n // in future: allow user to edit message\n ui.showMessage(\"메시지 편집은 Phase 2에서 지원됩니다.\", \"info\");\n return;\n }\n\n // Stage only safe files\n const filePaths = files.map((f) => f.path);\n await git.add(filePaths);\n logger.info({ repo: repo.path, files: filePaths }, \"Files staged\");\n\n // Commit\n try {\n await git.commit(message);\n ui.showMessage(`${repo.path}: 커밋 완료`, \"success\");\n logger.info({ repo: repo.path, message }, \"Committed\");\n } catch (err) {\n logger.error({ repo: repo.path, err }, \"Commit failed\");\n ui.showMessage(`${repo.path}: 커밋 실패 — ${err}`, \"error\");\n return;\n }\n\n if (action === \"skip\") {\n ui.showMessage(`${repo.path}: 로컬 커밋 유지, 푸시 건너뜀`, \"info\");\n return;\n }\n\n // Push\n if (action === \"push\") {\n await pushWithRetry(repo, git, ui, logger);\n }\n}\n\nasync function pushWithRetry(\n repo: RepoState,\n git: ReturnType<typeof simpleGit>,\n ui: UI,\n logger: Logger,\n): Promise<void> {\n ui.showMessage(`${repo.path}: 푸시 중...`, \"info\");\n\n try {\n await git.push();\n ui.showMessage(`${repo.path}: 푸시 성공!`, \"success\");\n logger.info({ repo: repo.path }, \"Pushed\");\n } catch {\n ui.showMessage(`${repo.path}: 푸시 실패, pull 후 재시도...`, \"warn\");\n logger.warn({ repo: repo.path }, \"Push failed, attempting pull\");\n\n try {\n await git.pull();\n await git.push();\n ui.showMessage(`${repo.path}: pull 후 푸시 성공!`, \"success\");\n logger.info({ repo: repo.path }, \"Push succeeded after pull\");\n } catch (pullErr) {\n ui.showMessage(`${repo.path}: pull/push 실패 — 수동 확인 필요`, \"error\");\n logger.error({ repo: repo.path, err: pullErr }, \"Pull+push failed\");\n }\n }\n}\n","import termkit from \"terminal-kit\";\nimport type { RepoState, FileChange, SmartCommitConfig, UserAction } from \"./types.js\";\n\nconst term = termkit.terminal;\n\nexport interface UI {\n showHeader(config: SmartCommitConfig, version?: string): void;\n showProgress(label: string, current: number, total: number): void;\n showRepoTable(repos: RepoState[]): void;\n showBlocked(repo: RepoState, files: FileChange[]): void;\n confirmWarned(repo: RepoState, files: FileChange[]): Promise<boolean>;\n showCommitPreview(repo: RepoState, message: string, files: FileChange[]): void;\n promptAction(): Promise<UserAction>;\n promptOfflineTemplate(templates: string[]): Promise<string>;\n promptInput(label: string): Promise<string>;\n showMessage(msg: string, level: \"info\" | \"success\" | \"warn\" | \"error\"): void;\n showComplete(): void;\n cleanup(): void;\n}\n\nexport function createUI(): UI {\n let progressBar: termkit.Terminal.ProgressBarController | null = null;\n\n return {\n showHeader(config, version) {\n term.clear();\n term.bold.cyan(`\\n Smart Commit v${version ?? \"unknown\"}\\n`);\n term.gray(` AI: ${config.ai.primary} (fallback: ${config.ai.fallback})\\n`);\n term.gray(` Style: ${config.commit.style} | Language: ${config.commit.language}\\n`);\n term(\"\\n\");\n },\n\n showProgress(label, current, total) {\n if (!progressBar) {\n term(\" \");\n progressBar = term.progressBar({\n width: 50,\n title: label,\n percent: true,\n });\n }\n progressBar.update({ progress: current / total, title: label });\n\n if (current >= total) {\n term(\"\\n\");\n progressBar = null;\n }\n },\n\n showRepoTable(repos) {\n term(\"\\n\");\n term.gray(\" # Repository Branch Changes Status\\n\");\n term.gray(\" ── ───────────────────────────── ────────────────── ───────── ──────────\\n\");\n\n repos.forEach((repo, i) => {\n const shortPath = truncate(repo.path.split(\"/\").slice(-2).join(\"/\"), 30);\n const branch = truncate(repo.branch, 18);\n const changes =\n repo.files.length > 0\n ? `${repo.files.length} files`\n : repo.unpushedCommits > 0\n ? `${repo.unpushedCommits} unpushed`\n : \"-\";\n const status = statusIcon(repo.status);\n const num = String(i + 1).padStart(2);\n\n const line = ` ${num} ${padEnd(shortPath, 32)}${padEnd(branch, 20)}${padEnd(changes, 11)}${status}\\n`;\n\n if (repo.status === \"dirty\") {\n term.yellow(line);\n } else {\n term(line);\n }\n });\n\n term(\"\\n\");\n },\n\n showBlocked(repo, files) {\n const shortPath = repo.path.split(\"/\").slice(-1)[0];\n term.red(` ✖ ${shortPath}: 차단된 파일 (커밋 제외)\\n`);\n for (const f of files) {\n term.red(` - ${f.path}\\n`);\n }\n term(\"\\n\");\n },\n\n async confirmWarned(repo, files) {\n const shortPath = repo.path.split(\"/\").slice(-1)[0];\n term.yellow(` ⚠ ${shortPath}: 주의 필요한 파일\\n`);\n for (const f of files) {\n term.yellow(` - ${f.path}\\n`);\n }\n\n term(\"\\n 포함하시겠습니까? \");\n const result = await term.yesOrNo({ yes: [\"y\", \"ENTER\"], no: [\"n\"] })\n .promise;\n term(\"\\n\");\n return result ?? false;\n },\n\n showCommitPreview(repo, message, files) {\n const shortPath = repo.path.split(\"/\").slice(-2).join(\"/\");\n term.bold(`\\n 📂 ${shortPath}\\n`);\n term(\" ──────────────────────���──────────────────\\n\");\n term.green(` ${message.split(\"\\n\")[0]}\\n`);\n\n const body = message.split(\"\\n\").slice(1).join(\"\\n\").trim();\n if (body) {\n term.gray(` ${body.replace(/\\n/g, \"\\n \")}\\n`);\n }\n\n term(\" ─────────────────────────────────────────\\n\");\n term.gray(` Files (${files.length}):\\n`);\n for (const f of files.slice(0, 10)) {\n const icon = f.status === \"added\" ? \"A\" : f.status === \"deleted\" ? \"D\" : \"M\";\n term.gray(` ${icon} ${f.path}\\n`);\n }\n if (files.length > 10) {\n term.gray(` ... and ${files.length - 10} more\\n`);\n }\n term(\"\\n\");\n },\n\n async promptAction() {\n const items = [\n \"Push (푸시 실행)\",\n \"Skip (로컬 커밋 유지)\",\n \"Cancel (커밋 취소)\",\n \"Skip repo (이 저장소 건너뛰기)\",\n \"Exit (종료)\",\n ];\n\n term(\" ▶ Select action:\\n\");\n const response = await term.singleColumnMenu(items).promise;\n term(\"\\n\");\n\n const map: UserAction[] = [\"push\", \"skip\", \"cancel\", \"skip-repo\", \"exit\"];\n return map[response.selectedIndex] ?? \"skip\";\n },\n\n async promptOfflineTemplate(templates) {\n term.yellow(\" ⚠ AI 사용 불가 — 오프라인 템플릿을 선택하세요:\\n\");\n const response = await term.singleColumnMenu(templates).promise;\n term(\"\\n\");\n\n const selected = templates[response.selectedIndex];\n term(\" 커밋 메시지를 입력하세요 (접두사 포함): \");\n const input = await term.inputField({ default: selected }).promise;\n term(\"\\n\");\n return input ?? selected;\n },\n\n async promptInput(label) {\n term(` ${label}: `);\n const input = await term.inputField().promise;\n term(\"\\n\");\n return input ?? \"\";\n },\n\n showMessage(msg, level) {\n const icon = { info: \"ℹ\", success: \"✅\", warn: \"⚠️\", error: \"✖\" };\n const text = ` ${icon[level]} ${msg}\\n`;\n switch (level) {\n case \"info\": term.cyan(text); break;\n case \"success\": term.green(text); break;\n case \"warn\": term.yellow(text); break;\n case \"error\": term.red(text); break;\n }\n },\n\n showComplete() {\n term(\"\\n\");\n term.bold.green(\" 🎉 모든 저장소 작업 완료!\\n\\n\");\n },\n\n cleanup() {\n term.processExit(0);\n },\n };\n}\n\nfunction statusIcon(status: RepoState[\"status\"]): string {\n switch (status) {\n case \"dirty\":\n return \"📝 변경됨\";\n case \"clean\":\n return \"✅ Clean\";\n case \"detached\":\n return \"⚠️ Detached\";\n case \"rebasing\":\n return \"🔄 Rebasing\";\n case \"merging\":\n return \"🔀 Merging\";\n case \"locked\":\n return \"🔒 Locked\";\n }\n}\n\nfunction truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"…\";\n}\n\nfunction padEnd(str: string, len: number): string {\n // Simple padding — works better than term.table for CJK characters\n const diff = len - str.length;\n if (diff <= 0) return str;\n return str + \" \".repeat(diff);\n}\n"],"mappings":"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","names":["require","simpleGit"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/committer.ts","../src/ui.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { createRequire } from \"node:module\";\nimport { loadConfig } from \"./config.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version: PKG_VERSION } = require(\"../package.json\");\nimport { scanRepositories } from \"./scanner.js\";\nimport { classifyFiles, groupFiles } from \"./classifier.js\";\nimport { createAiClient, isAiAvailable, getOfflineTemplates } from \"./ai-client.js\";\nimport { commitAndPush } from \"./committer.js\";\nimport { createUI } from \"./ui.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { RepoState, UserAction } from \"./types.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"smart-commit\")\n .description(\"AI-powered intelligent Git auto-commit & push CLI tool\")\n .version(PKG_VERSION)\n .option(\"-d, --dry-run\", \"Preview without committing or pushing\")\n .option(\"-g, --group <strategy>\", \"Grouping strategy: smart | single | manual\")\n .option(\"-a, --ai <tool>\", \"AI tool: gemini | claude | gpt | ollama\")\n .option(\"--no-interactive\", \"Headless mode (no prompts)\")\n .option(\"--offline\", \"Offline mode (use templates instead of AI)\")\n .action(async (options) => {\n const config = await loadConfig(options);\n const logger = createLogger();\n const ui = createUI();\n const ai = createAiClient(config, logger);\n const isHeadless = options.interactive === false;\n\n logger.info({ options }, \"smart-commit started\");\n\n ui.showHeader(config, PKG_VERSION);\n\n // Check AI availability (skip in offline mode)\n let offlineMode = options.offline ?? false;\n if (!offlineMode) {\n const primaryAvail = await isAiAvailable(config.ai.primary);\n const fallbackAvail = await isAiAvailable(config.ai.fallback);\n if (!primaryAvail && !fallbackAvail) {\n ui.showMessage(\"AI 도구를 찾을 수 없습니다. 오프라인 모드로 전환합니다.\", \"warn\");\n offlineMode = true;\n logger.warn(\"No AI tools available, switching to offline mode\");\n } else if (!primaryAvail) {\n ui.showMessage(`${config.ai.primary}를 찾을 수 없습니다. ${config.ai.fallback}를 사용합니다.`, \"warn\");\n }\n }\n\n const repos = await scanRepositories(process.cwd(), ui, logger);\n\n if (repos.length === 0) {\n ui.showMessage(\"변경 사항이 있는 저장소가 없습니다.\", \"info\");\n ui.cleanup();\n return;\n }\n\n ui.showRepoTable(repos);\n\n for (const repo of repos) {\n if (repo.status !== \"dirty\") {\n // Handle unpushed commits\n if (repo.status === \"clean\" && repo.unpushedCommits > 0) {\n ui.showMessage(`${repo.path}: 푸시되지 않은 커밋 ${repo.unpushedCommits}개`, \"info\");\n if (!isHeadless) {\n const action = await ui.promptAction();\n if (action === \"push\") {\n await commitAndPush(repo, [], \"\", \"push\", ui, logger);\n }\n }\n }\n continue;\n }\n\n const safety = await classifyFiles(repo.files, config);\n\n if (safety.blocked.length > 0) {\n ui.showBlocked(repo, safety.blocked);\n }\n\n if (safety.warned.length > 0) {\n if (isHeadless) {\n ui.showMessage(`${repo.path}: 경고 파일 ${safety.warned.length}개 — headless 모드에서 제외`, \"warn\");\n } else {\n const proceed = await ui.confirmWarned(repo, safety.warned);\n if (proceed) {\n safety.safe.push(...safety.warned);\n }\n }\n }\n\n if (safety.safe.length === 0) {\n ui.showMessage(`${repo.path}: 커밋할 안전한 파일이 없습니다.`, \"warn\");\n continue;\n }\n\n // Group files (skip AI grouping in offline mode)\n const groups = await groupFiles(\n safety.safe,\n offlineMode ? \"single\" : config.grouping.strategy,\n !offlineMode && config.grouping.strategy === \"smart\"\n ? (fileList) => ai.groupFiles(fileList)\n : null,\n logger,\n );\n\n for (const group of groups) {\n let commitMsg: string | null = null;\n\n if (offlineMode) {\n // Offline mode: use template\n if (isHeadless) {\n commitMsg = `chore: auto-commit ${group.files.length} files`;\n } else {\n commitMsg = await ui.promptOfflineTemplate(getOfflineTemplates());\n }\n } else {\n // AI mode\n const diff = await getDiff(repo, group.files.map((f) => f.path));\n const summarizedDiff = await ai.summarizeDiff(diff);\n commitMsg = await ai.generateCommitMessage(summarizedDiff, config.commit.language);\n\n if (!commitMsg) {\n ui.showMessage(`${repo.path} [${group.label}]: AI 메시지 생성 실패`, \"warn\");\n if (!isHeadless) {\n ui.showMessage(\"오프라인 템플릿으로 전환합니다.\", \"info\");\n commitMsg = await ui.promptOfflineTemplate(getOfflineTemplates());\n } else {\n commitMsg = `chore: auto-commit ${group.files.length} files`;\n }\n }\n }\n\n if (!commitMsg) continue;\n\n ui.showCommitPreview(repo, commitMsg, group.files);\n\n if (group.reason) {\n ui.showMessage(` 그룹핑 이유: ${group.reason}`, \"info\");\n }\n\n if (options.dryRun) {\n ui.showMessage(\"(dry-run) 실제 커밋/푸시를 수행하지 않습니다.\", \"info\");\n continue;\n }\n\n const action: UserAction = isHeadless ? \"push\" : await ui.promptAction();\n\n if (action === \"exit\") {\n ui.showMessage(\"종료합니다.\", \"info\");\n ui.cleanup();\n return;\n }\n\n if (action === \"skip-repo\") {\n ui.showMessage(`${repo.path}: 저장소 건너뛰기`, \"info\");\n break; // break out of groups loop, continue to next repo\n }\n\n await commitAndPush(repo, group.files, commitMsg, action, ui, logger);\n }\n }\n\n ui.showComplete();\n ui.cleanup();\n });\n\n// ─── Hook subcommand ───\n\nprogram\n .command(\"hook\")\n .description(\"Install or uninstall Git hooks\")\n .option(\"--uninstall\", \"Remove smart-commit hooks\")\n .action(async (options) => {\n const { installHooks, uninstallHooks } = await import(\"./hooks/install.js\");\n const ui = createUI();\n\n if (options.uninstall) {\n const removed = await uninstallHooks(process.cwd());\n if (removed.length > 0) {\n ui.showMessage(`훅 제거 완료: ${removed.join(\", \")}`, \"success\");\n } else {\n ui.showMessage(\"제거할 smart-commit 훅이 없습니다.\", \"info\");\n }\n } else {\n const { installed, skipped } = await installHooks(process.cwd());\n if (installed.length > 0) {\n ui.showMessage(`훅 설치 완료: ${installed.join(\", \")}`, \"success\");\n }\n if (skipped.length > 0) {\n ui.showMessage(`기존 훅이 있어 건너뜀: ${skipped.join(\", \")}`, \"warn\");\n }\n }\n\n ui.cleanup();\n });\n\nasync function getDiff(repo: RepoState, filePaths: string[]): Promise<string> {\n const { simpleGit } = await import(\"simple-git\");\n const git = simpleGit(repo.path);\n\n // Stage files one by one, skipping any that fail (e.g. gitignored)\n for (const fp of filePaths) {\n try {\n await git.add(fp);\n } catch {\n // Skip files that can't be staged (gitignored, etc.)\n }\n }\n\n const diff = await git.diff([\"--cached\", \"--\", ...filePaths]);\n return diff;\n}\n\nprogram.parse();\n","import { simpleGit } from \"simple-git\";\nimport type { RepoState, FileChange, UserAction } from \"./types.js\";\nimport type { UI } from \"./ui.js\";\nimport type { Logger } from \"pino\";\n\nexport async function commitAndPush(\n repo: RepoState,\n files: FileChange[],\n message: string,\n action: UserAction,\n ui: UI,\n logger: Logger,\n): Promise<void> {\n const git = simpleGit(repo.path);\n\n if (action === \"cancel\") {\n ui.showMessage(`${repo.path}: 건너뜁니다.`, \"info\");\n return;\n }\n\n if (action === \"edit\") {\n // in future: allow user to edit message\n ui.showMessage(\"메시지 편집은 Phase 2에서 지원됩니다.\", \"info\");\n return;\n }\n\n // Stage only safe files (skip gitignored)\n const filePaths = files.map((f) => f.path);\n const staged: string[] = [];\n for (const fp of filePaths) {\n try {\n await git.add(fp);\n staged.push(fp);\n } catch {\n logger.warn({ repo: repo.path, file: fp }, \"Skipped (gitignored or inaccessible)\");\n }\n }\n logger.info({ repo: repo.path, files: staged }, \"Files staged\");\n\n // Commit\n try {\n await git.commit(message);\n ui.showMessage(`${repo.path}: 커밋 완료`, \"success\");\n logger.info({ repo: repo.path, message }, \"Committed\");\n } catch (err) {\n logger.error({ repo: repo.path, err }, \"Commit failed\");\n ui.showMessage(`${repo.path}: 커밋 실패 — ${err}`, \"error\");\n return;\n }\n\n if (action === \"skip\") {\n ui.showMessage(`${repo.path}: 로컬 커밋 유지, 푸시 건너뜀`, \"info\");\n return;\n }\n\n // Push\n if (action === \"push\") {\n await pushWithRetry(repo, git, ui, logger);\n }\n}\n\nasync function pushWithRetry(\n repo: RepoState,\n git: ReturnType<typeof simpleGit>,\n ui: UI,\n logger: Logger,\n): Promise<void> {\n ui.showMessage(`${repo.path}: 푸시 중...`, \"info\");\n\n try {\n await git.push();\n ui.showMessage(`${repo.path}: 푸시 성공!`, \"success\");\n logger.info({ repo: repo.path }, \"Pushed\");\n } catch {\n ui.showMessage(`${repo.path}: 푸시 실패, pull 후 재시도...`, \"warn\");\n logger.warn({ repo: repo.path }, \"Push failed, attempting pull\");\n\n try {\n await git.pull();\n await git.push();\n ui.showMessage(`${repo.path}: pull 후 푸시 성공!`, \"success\");\n logger.info({ repo: repo.path }, \"Push succeeded after pull\");\n } catch (pullErr) {\n ui.showMessage(`${repo.path}: pull/push 실패 — 수동 확인 필요`, \"error\");\n logger.error({ repo: repo.path, err: pullErr }, \"Pull+push failed\");\n }\n }\n}\n","import termkit from \"terminal-kit\";\nimport type { RepoState, FileChange, SmartCommitConfig, UserAction } from \"./types.js\";\n\nconst term = termkit.terminal;\n\nexport interface UI {\n showHeader(config: SmartCommitConfig, version?: string): void;\n showProgress(label: string, current: number, total: number): void;\n showRepoTable(repos: RepoState[]): void;\n showBlocked(repo: RepoState, files: FileChange[]): void;\n confirmWarned(repo: RepoState, files: FileChange[]): Promise<boolean>;\n showCommitPreview(repo: RepoState, message: string, files: FileChange[]): void;\n promptAction(): Promise<UserAction>;\n promptOfflineTemplate(templates: string[]): Promise<string>;\n promptInput(label: string): Promise<string>;\n showMessage(msg: string, level: \"info\" | \"success\" | \"warn\" | \"error\"): void;\n showComplete(): void;\n cleanup(): void;\n}\n\nexport function createUI(): UI {\n let progressBar: termkit.Terminal.ProgressBarController | null = null;\n\n return {\n showHeader(config, version) {\n term.clear();\n term.bold.cyan(`\\n Smart Commit v${version ?? \"unknown\"}\\n`);\n term.gray(` AI: ${config.ai.primary} (fallback: ${config.ai.fallback})\\n`);\n term.gray(` Style: ${config.commit.style} | Language: ${config.commit.language}\\n`);\n term(\"\\n\");\n },\n\n showProgress(label, current, total) {\n if (!progressBar) {\n term(\" \");\n progressBar = term.progressBar({\n width: 50,\n title: label,\n percent: true,\n });\n }\n progressBar.update({ progress: current / total, title: label });\n\n if (current >= total) {\n term(\"\\n\");\n progressBar = null;\n }\n },\n\n showRepoTable(repos) {\n term(\"\\n\");\n term.gray(\" # Repository Branch Changes Status\\n\");\n term.gray(\" ── ───────────────────────────── ────────────────── ───────── ──────────\\n\");\n\n repos.forEach((repo, i) => {\n const shortPath = truncate(repo.path.split(\"/\").slice(-2).join(\"/\"), 30);\n const branch = truncate(repo.branch, 18);\n const changes =\n repo.files.length > 0\n ? `${repo.files.length} files`\n : repo.unpushedCommits > 0\n ? `${repo.unpushedCommits} unpushed`\n : \"-\";\n const status = statusIcon(repo.status);\n const num = String(i + 1).padStart(2);\n\n const line = ` ${num} ${padEnd(shortPath, 32)}${padEnd(branch, 20)}${padEnd(changes, 11)}${status}\\n`;\n\n if (repo.status === \"dirty\") {\n term.yellow(line);\n } else {\n term(line);\n }\n });\n\n term(\"\\n\");\n },\n\n showBlocked(repo, files) {\n const shortPath = repo.path.split(\"/\").slice(-1)[0];\n term.red(` ✖ ${shortPath}: 차단된 파일 (커밋 제외)\\n`);\n for (const f of files) {\n term.red(` - ${f.path}\\n`);\n }\n term(\"\\n\");\n },\n\n async confirmWarned(repo, files) {\n const shortPath = repo.path.split(\"/\").slice(-1)[0];\n term.yellow(` ⚠ ${shortPath}: 주의 필요한 파일\\n`);\n for (const f of files) {\n term.yellow(` - ${f.path}\\n`);\n }\n\n term(\"\\n 포함하시겠습니까? \");\n const result = await term.yesOrNo({ yes: [\"y\", \"ENTER\"], no: [\"n\"] })\n .promise;\n term(\"\\n\");\n return result ?? false;\n },\n\n showCommitPreview(repo, message, files) {\n const shortPath = repo.path.split(\"/\").slice(-2).join(\"/\");\n term.bold(`\\n 📂 ${shortPath}\\n`);\n term(\" ──────────────────────���──────────────────\\n\");\n term.green(` ${message.split(\"\\n\")[0]}\\n`);\n\n const body = message.split(\"\\n\").slice(1).join(\"\\n\").trim();\n if (body) {\n term.gray(` ${body.replace(/\\n/g, \"\\n \")}\\n`);\n }\n\n term(\" ─────────────────────────────────────────\\n\");\n term.gray(` Files (${files.length}):\\n`);\n for (const f of files.slice(0, 10)) {\n const icon = f.status === \"added\" ? \"A\" : f.status === \"deleted\" ? \"D\" : \"M\";\n term.gray(` ${icon} ${f.path}\\n`);\n }\n if (files.length > 10) {\n term.gray(` ... and ${files.length - 10} more\\n`);\n }\n term(\"\\n\");\n },\n\n async promptAction() {\n const items = [\n \"Push (푸시 실행)\",\n \"Skip (로컬 커밋 유지)\",\n \"Cancel (커밋 취소)\",\n \"Skip repo (이 저장소 건너뛰기)\",\n \"Exit (종료)\",\n ];\n\n term(\" ▶ Select action:\\n\");\n const response = await term.singleColumnMenu(items).promise;\n term(\"\\n\");\n\n const map: UserAction[] = [\"push\", \"skip\", \"cancel\", \"skip-repo\", \"exit\"];\n return map[response.selectedIndex] ?? \"skip\";\n },\n\n async promptOfflineTemplate(templates) {\n term.yellow(\" ⚠ AI 사용 불가 — 오프라인 템플릿을 선택하세요:\\n\");\n const response = await term.singleColumnMenu(templates).promise;\n term(\"\\n\");\n\n const selected = templates[response.selectedIndex];\n term(\" 커밋 메시지를 입력하세요 (접두사 포함): \");\n const input = await term.inputField({ default: selected }).promise;\n term(\"\\n\");\n return input ?? selected;\n },\n\n async promptInput(label) {\n term(` ${label}: `);\n const input = await term.inputField().promise;\n term(\"\\n\");\n return input ?? \"\";\n },\n\n showMessage(msg, level) {\n const icon = { info: \"ℹ\", success: \"✅\", warn: \"⚠️\", error: \"✖\" };\n const text = ` ${icon[level]} ${msg}\\n`;\n switch (level) {\n case \"info\": term.cyan(text); break;\n case \"success\": term.green(text); break;\n case \"warn\": term.yellow(text); break;\n case \"error\": term.red(text); break;\n }\n },\n\n showComplete() {\n term(\"\\n\");\n term.bold.green(\" 🎉 모든 저장소 작업 완료!\\n\\n\");\n },\n\n cleanup() {\n term.processExit(0);\n },\n };\n}\n\nfunction statusIcon(status: RepoState[\"status\"]): string {\n switch (status) {\n case \"dirty\":\n return \"📝 변경됨\";\n case \"clean\":\n return \"✅ Clean\";\n case \"detached\":\n return \"⚠️ Detached\";\n case \"rebasing\":\n return \"🔄 Rebasing\";\n case \"merging\":\n return \"🔀 Merging\";\n case \"locked\":\n return \"🔒 Locked\";\n }\n}\n\nfunction truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"…\";\n}\n\nfunction padEnd(str: string, len: number): string {\n // Simple padding — works better than term.table for CJK characters\n const diff = len - str.length;\n if (diff <= 0) return str;\n return str + \" \".repeat(diff);\n}\n"],"mappings":"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","names":["require","simpleGit"]}
|
package/dist/mcp-server.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/scanner.ts","../src/ai-client.ts","../src/logger.ts"],"sourcesContent":["import { cosmiconfig } from \"cosmiconfig\";\nimport type { SmartCommitConfig } from \"./types.js\";\n\nconst DEFAULT_CONFIG: SmartCommitConfig = {\n ai: {\n primary: \"gemini\",\n fallback: \"claude\",\n timeout: 30,\n },\n safety: {\n maxFileSize: \"10MB\",\n blockedPatterns: [\n \"*.env\",\n \".env.*\",\n \"*.pem\",\n \"*.key\",\n \"credentials*\",\n \"*.sqlite\",\n \"*.sqlite3\",\n ],\n warnPatterns: [\n \"*.log\",\n \"*.csv\",\n \"package-lock.json\",\n \"yarn.lock\",\n \"pnpm-lock.yaml\",\n ],\n },\n commit: {\n style: \"conventional\",\n language: \"ko\",\n maxDiffSize: 10000,\n },\n grouping: {\n strategy: \"smart\",\n },\n};\n\nexport async function loadConfig(\n cliOptions: Record<string, unknown> = {},\n): Promise<SmartCommitConfig> {\n const explorer = cosmiconfig(\"smart-commit\", {\n searchPlaces: [\n \".smart-commitrc\",\n \".smart-commitrc.yaml\",\n \".smart-commitrc.yml\",\n \".smart-commitrc.json\",\n \"smart-commit.config.js\",\n \"package.json\",\n ],\n });\n\n const result = await explorer.search();\n const fileConfig = result?.config ?? {};\n\n const config = deepMerge(\n DEFAULT_CONFIG as unknown as Record<string, unknown>,\n fileConfig as Record<string, unknown>,\n ) as unknown as SmartCommitConfig;\n\n if (cliOptions.ai && typeof cliOptions.ai === \"string\") {\n config.ai.primary = cliOptions.ai as string;\n }\n if (cliOptions.group && typeof cliOptions.group === \"string\") {\n config.grouping.strategy = cliOptions.group as \"smart\" | \"single\" | \"manual\";\n }\n\n return config;\n}\n\nfunction deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === \"object\" &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === \"object\" &&\n !Array.isArray(target[key])\n ) {\n result[key] = deepMerge(\n target[key] as Record<string, unknown>,\n source[key] as Record<string, unknown>,\n );\n } else {\n result[key] = source[key];\n }\n }\n return result;\n}\n","import { simpleGit, type SimpleGit } from \"simple-git\";\nimport { readdir, stat, access } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { RepoState, RepoGitStatus, FileChange } from \"./types.js\";\nimport type { UI } from \"./ui.js\";\nimport type { Logger } from \"pino\";\n\nexport async function scanRepositories(\n baseDir: string,\n ui: UI,\n logger: Logger,\n): Promise<RepoState[]> {\n const gitDirs = await findGitDirs(baseDir);\n const repos: RepoState[] = [];\n\n ui.showProgress(\"Scanning repositories...\", 0, gitDirs.length);\n\n for (let i = 0; i < gitDirs.length; i++) {\n const dir = gitDirs[i];\n ui.showProgress(`Scanning: ${dir}`, i + 1, gitDirs.length);\n\n try {\n const repo = await inspectRepo(dir, logger);\n repos.push(repo);\n } catch (err) {\n logger.warn({ dir, err }, \"Failed to inspect repository\");\n }\n }\n\n return repos;\n}\n\nasync function findGitDirs(baseDir: string): Promise<string[]> {\n const dirs: string[] = [];\n const entries = await readdir(baseDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n\n const fullPath = join(baseDir, entry.name);\n const gitPath = join(fullPath, \".git\");\n\n try {\n await access(gitPath);\n dirs.push(fullPath);\n } catch {\n // not a git repo, check subdirectories\n const subDirs = await findGitDirs(fullPath);\n dirs.push(...subDirs);\n }\n }\n\n // also check if baseDir itself is a git repo\n try {\n const selfGit = join(baseDir, \".git\");\n await access(selfGit);\n if (!dirs.some((d) => d === baseDir)) {\n dirs.unshift(baseDir);\n }\n } catch {\n // baseDir is not a git repo\n }\n\n return dirs;\n}\n\nasync function inspectRepo(dir: string, logger: Logger): Promise<RepoState> {\n const git: SimpleGit = simpleGit(dir);\n\n const gitStatus = await detectGitStatus(dir, git);\n\n if (gitStatus === \"locked\") {\n logger.warn({ dir }, \"Git index locked — skipping\");\n return { path: dir, branch: \"\", status: \"locked\", files: [], unpushedCommits: 0 };\n }\n if (gitStatus === \"detached\") {\n logger.warn({ dir }, \"Detached HEAD — skipping\");\n return { path: dir, branch: \"HEAD (detached)\", status: \"detached\", files: [], unpushedCommits: 0 };\n }\n if (gitStatus === \"rebasing\") {\n logger.warn({ dir }, \"Rebase in progress — skipping\");\n return { path: dir, branch: \"\", status: \"rebasing\", files: [], unpushedCommits: 0 };\n }\n\n const statusResult = await git.status();\n const branch = statusResult.current ?? \"unknown\";\n\n const files: FileChange[] = [];\n for (const f of statusResult.files) {\n const filePath = join(dir, f.path);\n let size = 0;\n try {\n const s = await stat(filePath);\n size = s.size;\n } catch {\n // file might have been deleted\n }\n\n files.push({\n path: f.path,\n status: mapGitStatus(f.working_dir, f.index),\n size,\n isBinary: false, // will be checked by classifier\n });\n }\n\n let unpushedCommits = 0;\n try {\n const log = await git.log([\"@{u}..HEAD\"]);\n unpushedCommits = log.total;\n } catch {\n // no upstream set\n }\n\n const repoStatus: RepoGitStatus =\n gitStatus === \"merging\"\n ? \"merging\"\n : files.length > 0\n ? \"dirty\"\n : \"clean\";\n\n return { path: dir, branch, status: repoStatus, files, unpushedCommits };\n}\n\nasync function detectGitStatus(dir: string, git: SimpleGit): Promise<RepoGitStatus> {\n // Check lock file\n try {\n await access(join(dir, \".git\", \"index.lock\"));\n return \"locked\";\n } catch {\n // no lock\n }\n\n // Check rebase\n try {\n await access(join(dir, \".git\", \"rebase-merge\"));\n return \"rebasing\";\n } catch {\n // not rebasing\n }\n try {\n await access(join(dir, \".git\", \"rebase-apply\"));\n return \"rebasing\";\n } catch {\n // not rebasing\n }\n\n // Check merge\n try {\n await access(join(dir, \".git\", \"MERGE_HEAD\"));\n return \"merging\";\n } catch {\n // not merging\n }\n\n // Check detached HEAD\n try {\n await git.raw([\"symbolic-ref\", \"HEAD\"]);\n } catch {\n return \"detached\";\n }\n\n return \"clean\";\n}\n\nfunction mapGitStatus(workingDir: string, index: string): FileChange[\"status\"] {\n if (index === \"?\" || workingDir === \"?\") return \"untracked\";\n if (index === \"A\" || workingDir === \"A\") return \"added\";\n if (index === \"D\" || workingDir === \"D\") return \"deleted\";\n if (index === \"R\" || workingDir === \"R\") return \"renamed\";\n return \"modified\";\n}\n","import { execa } from \"execa\";\nimport type { SmartCommitConfig, AiTool } from \"./types.js\";\nimport type { Logger } from \"pino\";\n\nconst CONVENTIONAL_PREFIXES = [\n \"feat\", \"fix\", \"refactor\", \"docs\", \"style\", \"test\", \"chore\", \"perf\", \"ci\", \"build\", \"revert\",\n];\nconst CONVENTIONAL_RE = new RegExp(`^(${CONVENTIONAL_PREFIXES.join(\"|\")})(\\\\(.+\\\\))?!?:\\\\s.+`);\n\n// ─── Offline templates ───\n\nconst OFFLINE_TEMPLATES = CONVENTIONAL_PREFIXES.map((prefix) => `${prefix}: `);\n\nexport function getOfflineTemplates(): string[] {\n return OFFLINE_TEMPLATES;\n}\n\nexport async function isAiAvailable(tool: AiTool): Promise<boolean> {\n try {\n const cmd = tool === \"gpt\" ? \"openai\" : tool;\n await execa(\"which\", [cmd], { timeout: 3000 });\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface AiClient {\n generateCommitMessage(diff: string, language: string): Promise<string | null>;\n resolveConflict(localContent: string, remoteContent: string): Promise<string | null>;\n groupFiles(fileList: string): Promise<string | null>;\n summarizeDiff(diff: string): Promise<string>;\n}\n\nexport function createAiClient(config: SmartCommitConfig, logger: Logger): AiClient {\n async function callWithFallback(prompt: string): Promise<string | null> {\n let result = await callAi(config.ai.primary, prompt, config.ai.timeout, logger, config);\n if (!result && config.ai.fallback !== config.ai.primary) {\n logger.warn({ fallback: config.ai.fallback }, \"Primary AI failed, trying fallback\");\n result = await callAi(config.ai.fallback, prompt, config.ai.timeout, logger, config);\n }\n return result;\n }\n\n return {\n async generateCommitMessage(diff, language) {\n const summarized = await this.summarizeDiff(diff);\n const prompt = buildCommitPrompt(summarized, language, config.commit.style);\n\n logger.info({ tool: config.ai.primary, diffLength: summarized.length }, \"Requesting commit message\");\n\n let result = await callWithFallback(prompt);\n\n if (result) {\n // Conventional commit validation + retry\n if (config.commit.style === \"conventional\" && !validateConventionalCommit(result)) {\n logger.warn({ message: result.split(\"\\n\")[0] }, \"Invalid conventional commit, retrying\");\n const retryPrompt = buildRetryPrompt(result, language);\n const retried = await callWithFallback(retryPrompt);\n if (retried && validateConventionalCommit(retried)) {\n result = retried;\n }\n // use original if retry also fails — better than nothing\n }\n\n // Strip markdown code blocks if AI wrapped it\n result = stripCodeBlocks(result);\n\n logger.info({ messageLength: result.length }, \"Commit message generated\");\n }\n\n return result;\n },\n\n async resolveConflict(localContent, remoteContent) {\n const prompt = buildConflictPrompt(localContent, remoteContent);\n return callWithFallback(prompt);\n },\n\n async groupFiles(fileList) {\n const { buildGroupingPrompt } = await import(\"./classifier.js\");\n const prompt = buildGroupingPrompt(fileList);\n return callWithFallback(prompt);\n },\n\n async summarizeDiff(diff) {\n if (diff.length <= config.commit.maxDiffSize) {\n return diff;\n }\n\n // Smart truncation: stat header + most important hunks\n const statSection = extractDiffStat(diff);\n const hunks = extractKeyHunks(diff, config.commit.maxDiffSize - statSection.length - 200);\n\n const truncated = `${statSection}\\n\\n[주요 변경 내용 (전체 ${diff.length}자 중 핵심부만 추출)]\\n${hunks}`;\n\n // If still too large, ask AI to summarize\n if (truncated.length > config.commit.maxDiffSize * 1.5) {\n logger.info(\"Diff too large, requesting AI summary\");\n const summaryPrompt = buildDiffSummaryPrompt(truncated.slice(0, config.commit.maxDiffSize));\n const summary = await callWithFallback(summaryPrompt);\n return summary ?? truncated.slice(0, config.commit.maxDiffSize);\n }\n\n return truncated;\n },\n };\n}\n\n// ─── Conventional commit validation ───\n\nexport function validateConventionalCommit(message: string): boolean {\n const firstLine = message.split(\"\\n\")[0].trim();\n return CONVENTIONAL_RE.test(firstLine);\n}\n\nfunction stripCodeBlocks(text: string): string {\n return text\n .replace(/^```[\\w]*\\n?/gm, \"\")\n .replace(/^```\\s*$/gm, \"\")\n .trim();\n}\n\n// ─── Diff summarization ───\n\nfunction extractDiffStat(diff: string): string {\n const lines = diff.split(\"\\n\");\n const statLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith(\"diff --git\")) {\n statLines.push(line);\n } else if (line.startsWith(\"--- \") || line.startsWith(\"+++ \")) {\n statLines.push(line);\n }\n }\n\n return statLines.join(\"\\n\");\n}\n\nfunction extractKeyHunks(diff: string, maxLength: number): string {\n const hunks: string[] = [];\n let currentHunk = \"\";\n let totalLength = 0;\n\n for (const line of diff.split(\"\\n\")) {\n if (line.startsWith(\"@@\")) {\n if (currentHunk && totalLength + currentHunk.length <= maxLength) {\n hunks.push(currentHunk);\n totalLength += currentHunk.length;\n }\n currentHunk = line + \"\\n\";\n } else if (line.startsWith(\"+\") || line.startsWith(\"-\")) {\n // Prioritize actual changes over context\n currentHunk += line + \"\\n\";\n }\n }\n\n // Don't forget the last hunk\n if (currentHunk && totalLength + currentHunk.length <= maxLength) {\n hunks.push(currentHunk);\n }\n\n return hunks.join(\"\\n\");\n}\n\n// ─── AI call ───\n\nasync function callAi(\n tool: AiTool,\n prompt: string,\n timeout: number,\n logger: Logger,\n config?: SmartCommitConfig,\n): Promise<string | null> {\n try {\n const { command, args } = buildAiCommand(tool, prompt, config);\n\n const { stdout } = await execa(command, args, {\n timeout: timeout * 1000,\n stdin: \"ignore\",\n });\n\n const trimmed = stdout.trim();\n return trimmed || null;\n } catch (err) {\n logger.error({ tool, err }, \"AI call failed\");\n return null;\n }\n}\n\nfunction buildAiCommand(\n tool: AiTool,\n prompt: string,\n config?: SmartCommitConfig,\n): { command: string; args: string[] } {\n switch (tool) {\n case \"gemini\":\n return { command: \"gemini\", args: [prompt] };\n case \"claude\":\n return { command: \"claude\", args: [\"-p\", prompt] };\n case \"gpt\":\n // OpenAI CLI: https://platform.openai.com/docs/guides/command-line\n return { command: \"openai\", args: [\"api\", \"chat.completions.create\", \"-m\", \"gpt-4o\", \"-g\", \"user\", prompt] };\n case \"ollama\": {\n const model = config?.ai?.ollama?.model ?? \"llama3\";\n return { command: \"ollama\", args: [\"run\", model, prompt] };\n }\n default:\n // Generic: treat tool name as command, pass prompt as first arg\n return { command: tool, args: [prompt] };\n }\n}\n\n// ─── Prompt builders ───\n\nfunction buildCommitPrompt(diff: string, language: string, style: string): string {\n const langLabel = language === \"ko\" ? \"한국어\" : \"English\";\n\n const conventionalGuide = style === \"conventional\" ? `\n[Conventional Commits 규칙]\n반드시 아래 구조를 따르세요:\n\n<type>(<scope>): <subject>\n\n<body>\n\n구조 설명:\n- type (필수): 다음 중 하나 — ${CONVENTIONAL_PREFIXES.join(\", \")}\n- scope (선택): 영향 범위를 괄호 안에 표기 (예: auth, api, ui)\n- subject (필수): 50자 이내, 명령조, 핵심 요약\n- body (선택): 72자/줄 제한, \"왜\" 변경했는지 bullet(-) 목록으로 설명\n\n[Type 선택 기준]\n- feat: 새로운 기능 추가\n- fix: 버그 수정\n- refactor: 기능 변경 없는 코드 개선\n- docs: 문서 변경\n- style: 포맷팅, 세미콜론 등 (로직 변경 없음)\n- test: 테스트 추가/수정\n- chore: 빌드, 설정, 의존성 변경\n- perf: 성능 개선\n- ci: CI/CD 설정\n- build: 빌드 시스템 변경\n- revert: 이전 커밋 되돌림` : \"\";\n\n return `아래의 [Git Diff]를 분석하여 Git Commit Message를 작성하라.\n\n[CRITICAL INSTRUCTION]\n**결과는 무조건 '${langLabel}'로 작성되어야 한다.**\n${conventionalGuide}\n\n[작성 원칙]\n1. subject는 \"무엇을\" 했는지 — 명령조 사용 (\"추가\", \"수정\", \"제거\")\n2. body는 \"왜\" 변경했는지 — 구체적 변경 내용을 bullet(-)로 나열\n3. 하나의 메시지는 하나의 논리적 변경만 설명\n4. 부수 효과가 있으면 body에 명시\n\n[좋은 예시]\nfeat(auth): JWT 기반 인증 미들웨어 구현\n\n- Access/Refresh 토큰 발급 로직 추가\n- 토큰 만료 시 자동 갱신 처리\n- 인증 실패 시 401 응답 통일\n\n[나쁜 예시 — 절대 이렇게 작성하지 말 것]\n- \"수정함\" (type 없음, 무엇을 수정했는지 불명)\n- \"fix: 버그 수정\" (어떤 버그인지 불명)\n- \"여러가지 수정 및 기능 추가\" (하나의 커밋에 여러 변경 혼합)\n\n[출력 규칙]\n- 마크다운 코드 블록(\\`\\`\\`)이나 부가 설명 없이, 오직 커밋 메시지 텍스트만 출력\n- 어떠한 도구(Functions/Tools)도 사용하지 말 것\n\n[Git Diff]\n${diff}`;\n}\n\nfunction buildRetryPrompt(invalidMessage: string, language: string): string {\n const langLabel = language === \"ko\" ? \"한국어\" : \"English\";\n return `아래 커밋 메시지가 Conventional Commits 형식에 맞지 않습니다. 수정해주세요.\n\n[현재 메시지]\n${invalidMessage}\n\n[필수 구조]\n<type>(<scope>): <subject>\n\n<body>\n\n[규칙]\n- type은 반드시 다음 중 하나: ${CONVENTIONAL_PREFIXES.join(\", \")}\n- scope는 선택사항 (괄호 안에 영향 범위)\n- subject는 50자 이내, 명령조 사용\n- body는 72자/줄 제한, bullet(-) 목록\n- ${langLabel}로 작성\n- 수정된 커밋 메시지만 출력 (다른 설명 없이)`;\n}\n\nfunction buildConflictPrompt(localContent: string, remoteContent: string): string {\n return `아래에 Git 충돌이 발생한 파일의 [로컬 버전]과 [원격 버전]이 있습니다.\n두 버전을 분석하여 **올바르게 병합된 최종 파일 내용**을 생성해주세요.\n\n[필수 규칙]\n1. 두 버전의 변경 사항을 모두 포함하여 병합할 것\n2. 충돌 마커(<<<<<<, ======, >>>>>>)는 절대 포함하지 말 것\n3. 코드의 논리적 일관성을 유지할 것\n4. 출력은 **오직 병합된 파일 내용만** 출력할 것\n\n[로컬 버전]\n${localContent}\n\n[원격 버전]\n${remoteContent}`;\n}\n\nfunction buildDiffSummaryPrompt(diff: string): string {\n return `아래 Git Diff가 너무 큽니다. 핵심 변경 사항만 요약해주세요.\n\n[규칙]\n1. 어떤 파일에서 무엇이 변경되었는지 요약\n2. 추가/수정/삭제된 주요 함수/클래스/변수 나열\n3. diff 형식으로 출력 (+ / - 접두사 사용)\n4. 500자 이내로 요약\n\n[Diff]\n${diff}`;\n}\n","import pino from \"pino\";\nimport { join } from \"node:path\";\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\n\nexport function createLogger(): pino.Logger {\n const logDir = join(homedir(), \".smart-commit\", \"logs\");\n\n try {\n mkdirSync(logDir, { recursive: true });\n } catch {\n // fallback: log to stderr only\n return pino({ level: \"info\" });\n }\n\n const today = new Date().toISOString().slice(0, 10);\n const logFile = join(logDir, `${today}.log`);\n\n return pino(\n { level: \"info\" },\n pino.destination({ dest: logFile, append: true, sync: false }),\n );\n}\n"],"mappings":"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","names":["join"]}
|