@blum84/smart-commit 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -298,8 +298,15 @@ program.name("smart-commit").description("AI-powered intelligent Git auto-commit
|
|
|
298
298
|
if (repo.status !== "dirty") {
|
|
299
299
|
if (repo.status === "clean" && repo.unpushedCommits > 0) {
|
|
300
300
|
ui.showMessage(`${repo.path}: \uD478\uC2DC\uB418\uC9C0 \uC54A\uC740 \uCEE4\uBC0B ${repo.unpushedCommits}\uAC1C`, "info");
|
|
301
|
-
if (
|
|
301
|
+
if (options.dryRun) {
|
|
302
|
+
ui.showMessage("(dry-run) \uD478\uC2DC\uB97C \uC218\uD589\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "info");
|
|
303
|
+
} else if (!isHeadless) {
|
|
302
304
|
const action = await ui.promptAction();
|
|
305
|
+
if (action === "exit") {
|
|
306
|
+
ui.showMessage("\uC885\uB8CC\uD569\uB2C8\uB2E4.", "info");
|
|
307
|
+
ui.cleanup();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
303
310
|
if (action === "push") {
|
|
304
311
|
await commitAndPush(repo, [], "", "push", ui, logger);
|
|
305
312
|
}
|
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\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"]}
|
|
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 (options.dryRun) {\n ui.showMessage(\"(dry-run) 푸시를 수행하지 않습니다.\", \"info\");\n } else if (!isHeadless) {\n const action = await ui.promptAction();\n if (action === \"exit\") {\n ui.showMessage(\"종료합니다.\", \"info\");\n ui.cleanup();\n return;\n }\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"]}
|