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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/scanner.ts","../src/ai-client.ts","../src/committer.ts","../src/ui.ts","../src/logger.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { loadConfig } from \"./config.js\";\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(\"0.1.0\")\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);\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 = 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 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 { 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 const styleGuide =\n style === \"conventional\"\n ? `Conventional Commits 형식을 반드시 따르세요.\n접두사는 다음 중 선택: ${CONVENTIONAL_PREFIXES.join(\", \")}\n형식: <접두사>(<범위>): <설명> (범위는 선택사항)`\n : \"\";\n\n return `아래의 [Git Diff] 내용을 분석하여 Git Commit Message를 작성해줘.\n\n[CRITICAL INSTRUCTION]\n**결과는 무조건 '${langLabel}'로 작성되어야 합니다.**\n${styleGuide}\n\n[작성 예시]\nfeat(auth): 사용자 로그인 API 구현\n\n- 로그인 요청 처리를 위한 컨트롤러 메서드 추가\n- JWT 토큰 발급 로직 구현\n\n[필수 규칙]\n1. 언어: **100% ${langLabel}**로 작성할 것.\n2. 형식:\n - 첫 줄: 변경 사항을 50자 이내로 요약 (제목)\n - 두 번째 줄: 빈 줄\n - 세 번째 줄부터: 변경된 상세 내용을 불릿 포인트(-)로 정리\n3. 출력: 마크다운 코드 블록이나 부가 설명 없이, 오직 커밋 메시지 텍스트만 출력할 것.\n4. 제한: 어떠한 도구(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- 첫 줄은 반드시 \"${CONVENTIONAL_PREFIXES.join(\"|\")}(<범위>): <설명>\" 형식이어야 합니다.\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 { 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): 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) {\n term.clear();\n term.bold.cyan(\"\\n Smart Commit v0.1.0\\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\n const tableData = [\n [\" #\", \"Repository\", \"Branch\", \"Changes\", \"Status\"],\n ];\n\n repos.forEach((repo, i) => {\n const shortPath = repo.path.split(\"/\").slice(-2).join(\"/\");\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 tableData.push([` ${i + 1}`, shortPath, repo.branch, changes, status]);\n });\n\n term.table(tableData, {\n hasBorder: false,\n contentHasMarkup: true,\n width: 80,\n fit: true,\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 ];\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\"];\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","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":["simpleGit","join","simpleGit"]}
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/install.ts
4
+ import { writeFile, readFile, chmod } from "fs/promises";
5
+ import { join } from "path";
6
+ import { simpleGit } from "simple-git";
7
+ var HOOK_MARKER = "# smart-commit-hook";
8
+ var PREPARE_COMMIT_MSG_HOOK = `#!/bin/sh
9
+ ${HOOK_MARKER}
10
+ # Auto-generate commit message with smart-commit AI
11
+ # This hook is installed by smart-commit
12
+
13
+ COMMIT_MSG_FILE=$1
14
+ COMMIT_SOURCE=$2
15
+
16
+ # Only run for new commits (not merge, squash, etc.)
17
+ if [ -z "$COMMIT_SOURCE" ]; then
18
+ # Check if smart-commit is available
19
+ if command -v smart-commit >/dev/null 2>&1; then
20
+ DIFF=$(git diff --cached)
21
+ if [ -n "$DIFF" ]; then
22
+ MSG=$(smart-commit --no-interactive --dry-run 2>/dev/null | head -1)
23
+ if [ -n "$MSG" ]; then
24
+ echo "$MSG" > "$COMMIT_MSG_FILE"
25
+ fi
26
+ fi
27
+ fi
28
+ fi
29
+ `;
30
+ var POST_COMMIT_HOOK = `#!/bin/sh
31
+ ${HOOK_MARKER}
32
+ # Log commit with smart-commit
33
+ # This hook is installed by smart-commit
34
+
35
+ COMMIT_MSG=$(git log -1 --pretty=%B)
36
+ COMMIT_HASH=$(git rev-parse --short HEAD)
37
+ echo "[smart-commit] Committed: $COMMIT_HASH - $COMMIT_MSG"
38
+ `;
39
+ async function installHooks(repoPath) {
40
+ const git = simpleGit(repoPath);
41
+ const hookDir = join(repoPath, ".git", "hooks");
42
+ const installed = [];
43
+ const skipped = [];
44
+ for (const [name, content] of [
45
+ ["prepare-commit-msg", PREPARE_COMMIT_MSG_HOOK],
46
+ ["post-commit", POST_COMMIT_HOOK]
47
+ ]) {
48
+ const hookPath = join(hookDir, name);
49
+ const existing = await safeRead(hookPath);
50
+ if (existing && !existing.includes(HOOK_MARKER)) {
51
+ skipped.push(name);
52
+ continue;
53
+ }
54
+ await writeFile(hookPath, content, "utf-8");
55
+ await chmod(hookPath, 493);
56
+ installed.push(name);
57
+ }
58
+ return { installed, skipped };
59
+ }
60
+ async function uninstallHooks(repoPath) {
61
+ const hookDir = join(repoPath, ".git", "hooks");
62
+ const removed = [];
63
+ for (const name of ["prepare-commit-msg", "post-commit"]) {
64
+ const hookPath = join(hookDir, name);
65
+ const content = await safeRead(hookPath);
66
+ if (content && content.includes(HOOK_MARKER)) {
67
+ await writeFile(hookPath, "", "utf-8");
68
+ removed.push(name);
69
+ }
70
+ }
71
+ return removed;
72
+ }
73
+ async function safeRead(path) {
74
+ try {
75
+ return await readFile(path, "utf-8");
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ export {
81
+ installHooks,
82
+ uninstallHooks
83
+ };
84
+ //# sourceMappingURL=install-XOLZLFAI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/install.ts"],"sourcesContent":["import { writeFile, readFile, chmod } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { simpleGit } from \"simple-git\";\n\nconst HOOK_MARKER = \"# smart-commit-hook\";\n\nconst PREPARE_COMMIT_MSG_HOOK = `#!/bin/sh\n${HOOK_MARKER}\n# Auto-generate commit message with smart-commit AI\n# This hook is installed by smart-commit\n\nCOMMIT_MSG_FILE=$1\nCOMMIT_SOURCE=$2\n\n# Only run for new commits (not merge, squash, etc.)\nif [ -z \"$COMMIT_SOURCE\" ]; then\n # Check if smart-commit is available\n if command -v smart-commit >/dev/null 2>&1; then\n DIFF=$(git diff --cached)\n if [ -n \"$DIFF\" ]; then\n MSG=$(smart-commit --no-interactive --dry-run 2>/dev/null | head -1)\n if [ -n \"$MSG\" ]; then\n echo \"$MSG\" > \"$COMMIT_MSG_FILE\"\n fi\n fi\n fi\nfi\n`;\n\nconst POST_COMMIT_HOOK = `#!/bin/sh\n${HOOK_MARKER}\n# Log commit with smart-commit\n# This hook is installed by smart-commit\n\nCOMMIT_MSG=$(git log -1 --pretty=%B)\nCOMMIT_HASH=$(git rev-parse --short HEAD)\necho \"[smart-commit] Committed: $COMMIT_HASH - $COMMIT_MSG\"\n`;\n\nexport async function installHooks(repoPath: string): Promise<{ installed: string[]; skipped: string[] }> {\n const git = simpleGit(repoPath);\n const hookDir = join(repoPath, \".git\", \"hooks\");\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n for (const [name, content] of [\n [\"prepare-commit-msg\", PREPARE_COMMIT_MSG_HOOK],\n [\"post-commit\", POST_COMMIT_HOOK],\n ] as const) {\n const hookPath = join(hookDir, name);\n const existing = await safeRead(hookPath);\n\n if (existing && !existing.includes(HOOK_MARKER)) {\n // Existing hook not from us — don't overwrite\n skipped.push(name);\n continue;\n }\n\n await writeFile(hookPath, content, \"utf-8\");\n await chmod(hookPath, 0o755);\n installed.push(name);\n }\n\n return { installed, skipped };\n}\n\nexport async function uninstallHooks(repoPath: string): Promise<string[]> {\n const hookDir = join(repoPath, \".git\", \"hooks\");\n const removed: string[] = [];\n\n for (const name of [\"prepare-commit-msg\", \"post-commit\"]) {\n const hookPath = join(hookDir, name);\n const content = await safeRead(hookPath);\n\n if (content && content.includes(HOOK_MARKER)) {\n await writeFile(hookPath, \"\", \"utf-8\");\n removed.push(name);\n }\n }\n\n return removed;\n}\n\nasync function safeRead(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAAA,SAAS,WAAW,UAAU,aAAa;AAC3C,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,IAAM,cAAc;AAEpB,IAAM,0BAA0B;AAAA,EAC9B,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBb,IAAM,mBAAmB;AAAA,EACvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASb,eAAsB,aAAa,UAAuE;AACxG,QAAM,MAAM,UAAU,QAAQ;AAC9B,QAAM,UAAU,KAAK,UAAU,QAAQ,OAAO;AAE9C,QAAM,YAAsB,CAAC;AAC7B,QAAM,UAAoB,CAAC;AAE3B,aAAW,CAAC,MAAM,OAAO,KAAK;AAAA,IAC5B,CAAC,sBAAsB,uBAAuB;AAAA,IAC9C,CAAC,eAAe,gBAAgB;AAAA,EAClC,GAAY;AACV,UAAM,WAAW,KAAK,SAAS,IAAI;AACnC,UAAM,WAAW,MAAM,SAAS,QAAQ;AAExC,QAAI,YAAY,CAAC,SAAS,SAAS,WAAW,GAAG;AAE/C,cAAQ,KAAK,IAAI;AACjB;AAAA,IACF;AAEA,UAAM,UAAU,UAAU,SAAS,OAAO;AAC1C,UAAM,MAAM,UAAU,GAAK;AAC3B,cAAU,KAAK,IAAI;AAAA,EACrB;AAEA,SAAO,EAAE,WAAW,QAAQ;AAC9B;AAEA,eAAsB,eAAe,UAAqC;AACxE,QAAM,UAAU,KAAK,UAAU,QAAQ,OAAO;AAC9C,QAAM,UAAoB,CAAC;AAE3B,aAAW,QAAQ,CAAC,sBAAsB,aAAa,GAAG;AACxD,UAAM,WAAW,KAAK,SAAS,IAAI;AACnC,UAAM,UAAU,MAAM,SAAS,QAAQ;AAEvC,QAAI,WAAW,QAAQ,SAAS,WAAW,GAAG;AAC5C,YAAM,UAAU,UAAU,IAAI,OAAO;AACrC,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,SAAS,MAAsC;AAC5D,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,OAAO;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "pmatrix-smart-commit",
3
+ "version": "0.1.0",
4
+ "description": "AI-powered intelligent Git auto-commit & push CLI tool",
5
+ "type": "module",
6
+ "bin": {
7
+ "smart-commit": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "test": "vitest",
16
+ "test:run": "vitest run",
17
+ "lint": "tsc --noEmit",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "git",
22
+ "commit",
23
+ "ai",
24
+ "automation",
25
+ "cli",
26
+ "gemini",
27
+ "claude",
28
+ "conventional-commits"
29
+ ],
30
+ "author": "ChoHeeSung",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/ChoHeeSung/smart-commit.git"
35
+ },
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "dependencies": {
40
+ "commander": "^13.1.0",
41
+ "cosmiconfig": "^9.0.0",
42
+ "execa": "^9.5.2",
43
+ "minimatch": "^10.0.1",
44
+ "pino": "^9.6.0",
45
+ "simple-git": "^3.27.0",
46
+ "terminal-kit": "^3.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.15.3",
50
+ "@types/terminal-kit": "^2.5.7",
51
+ "tsup": "^8.4.0",
52
+ "typescript": "^5.8.3",
53
+ "vitest": "^3.1.1"
54
+ }
55
+ }