@blum84/smart-commit 0.2.3 → 0.3.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.
|
@@ -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
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":";;;AAAA,SAAS,mBAAmB;AAG5B,IAAM,iBAAoC;AAAA,EACxC,IAAI;AAAA,IACF,SAAS;AAAA,IACT,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,iBAAiB;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,aAAa;AAAA,EACf;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAEA,eAAsB,WACpB,aAAsC,CAAC,GACX;AAC5B,QAAM,WAAW,YAAY,gBAAgB;AAAA,IAC3C,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,SAAS,MAAM,SAAS,OAAO;AACrC,QAAM,aAAa,QAAQ,UAAU,CAAC;AAEtC,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,EACF;AAEA,MAAI,WAAW,MAAM,OAAO,WAAW,OAAO,UAAU;AACtD,WAAO,GAAG,UAAU,WAAW;AAAA,EACjC;AACA,MAAI,WAAW,SAAS,OAAO,WAAW,UAAU,UAAU;AAC5D,WAAO,SAAS,WAAW,WAAW;AAAA,EACxC;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,QAAiC,QAA0D;AAC5G,QAAM,SAAS,EAAE,GAAG,OAAO;AAC3B,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,QACE,OAAO,GAAG,KACV,OAAO,OAAO,GAAG,MAAM,YACvB,CAAC,MAAM,QAAQ,OAAO,GAAG,CAAC,KAC1B,OAAO,GAAG,KACV,OAAO,OAAO,GAAG,MAAM,YACvB,CAAC,MAAM,QAAQ,OAAO,GAAG,CAAC,GAC1B;AACA,aAAO,GAAG,IAAI;AAAA,QACZ,OAAO,GAAG;AAAA,QACV,OAAO,GAAG;AAAA,MACZ;AAAA,IACF,OAAO;AACL,aAAO,GAAG,IAAI,OAAO,GAAG;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;;;AC1FA,SAAS,iBAAiC;AAC1C,SAAS,SAAS,MAAM,cAAc;AACtC,SAAS,YAAqB;AAK9B,eAAsB,iBACpB,SACA,IACA,QACsB;AACtB,QAAM,UAAU,MAAM,YAAY,OAAO;AACzC,QAAM,QAAqB,CAAC;AAE5B,KAAG,aAAa,4BAA4B,GAAG,QAAQ,MAAM;AAE7D,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,MAAM,QAAQ,CAAC;AACrB,OAAG,aAAa,aAAa,GAAG,IAAI,IAAI,GAAG,QAAQ,MAAM;AAEzD,QAAI;AACF,YAAM,OAAO,MAAM,YAAY,KAAK,MAAM;AAC1C,YAAM,KAAK,IAAI;AAAA,IACjB,SAAS,KAAK;AACZ,aAAO,KAAK,EAAE,KAAK,IAAI,GAAG,8BAA8B;AAAA,IAC1D;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,YAAY,SAAoC;AAC7D,QAAM,OAAiB,CAAC;AACxB,QAAM,UAAU,MAAM,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AAE9D,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,QAAI,MAAM,SAAS,kBAAkB,MAAM,KAAK,WAAW,GAAG,EAAG;AAEjE,UAAM,WAAW,KAAK,SAAS,MAAM,IAAI;AACzC,UAAM,UAAU,KAAK,UAAU,MAAM;AAErC,QAAI;AACF,YAAM,OAAO,OAAO;AACpB,WAAK,KAAK,QAAQ;AAAA,IACpB,QAAQ;AAEN,YAAM,UAAU,MAAM,YAAY,QAAQ;AAC1C,WAAK,KAAK,GAAG,OAAO;AAAA,IACtB;AAAA,EACF;AAGA,MAAI;AACF,UAAM,UAAU,KAAK,SAAS,MAAM;AACpC,UAAM,OAAO,OAAO;AACpB,QAAI,CAAC,KAAK,KAAK,CAAC,MAAM,MAAM,OAAO,GAAG;AACpC,WAAK,QAAQ,OAAO;AAAA,IACtB;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAEA,eAAe,YAAY,KAAa,QAAoC;AAC1E,QAAM,MAAiB,UAAU,GAAG;AAEpC,QAAM,YAAY,MAAM,gBAAgB,KAAK,GAAG;AAEhD,MAAI,cAAc,UAAU;AAC1B,WAAO,KAAK,EAAE,IAAI,GAAG,kCAA6B;AAClD,WAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,QAAQ,UAAU,OAAO,CAAC,GAAG,iBAAiB,EAAE;AAAA,EAClF;AACA,MAAI,cAAc,YAAY;AAC5B,WAAO,KAAK,EAAE,IAAI,GAAG,+BAA0B;AAC/C,WAAO,EAAE,MAAM,KAAK,QAAQ,mBAAmB,QAAQ,YAAY,OAAO,CAAC,GAAG,iBAAiB,EAAE;AAAA,EACnG;AACA,MAAI,cAAc,YAAY;AAC5B,WAAO,KAAK,EAAE,IAAI,GAAG,oCAA+B;AACpD,WAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,QAAQ,YAAY,OAAO,CAAC,GAAG,iBAAiB,EAAE;AAAA,EACpF;AAEA,QAAM,eAAe,MAAM,IAAI,OAAO;AACtC,QAAM,SAAS,aAAa,WAAW;AAEvC,QAAM,QAAsB,CAAC;AAC7B,aAAW,KAAK,aAAa,OAAO;AAClC,UAAM,WAAW,KAAK,KAAK,EAAE,IAAI;AACjC,QAAI,OAAO;AACX,QAAI;AACF,YAAM,IAAI,MAAM,KAAK,QAAQ;AAC7B,aAAO,EAAE;AAAA,IACX,QAAQ;AAAA,IAER;AAEA,UAAM,KAAK;AAAA,MACT,MAAM,EAAE;AAAA,MACR,QAAQ,aAAa,EAAE,aAAa,EAAE,KAAK;AAAA,MAC3C;AAAA,MACA,UAAU;AAAA;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,MAAI,kBAAkB;AACtB,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC;AACxC,sBAAkB,IAAI;AAAA,EACxB,QAAQ;AAAA,EAER;AAEA,QAAM,aACJ,cAAc,YACV,YACA,MAAM,SAAS,IACb,UACA;AAER,SAAO,EAAE,MAAM,KAAK,QAAQ,QAAQ,YAAY,OAAO,gBAAgB;AACzE;AAEA,eAAe,gBAAgB,KAAa,KAAwC;AAElF,MAAI;AACF,UAAM,OAAO,KAAK,KAAK,QAAQ,YAAY,CAAC;AAC5C,WAAO;AAAA,EACT,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,UAAM,OAAO,KAAK,KAAK,QAAQ,cAAc,CAAC;AAC9C,WAAO;AAAA,EACT,QAAQ;AAAA,EAER;AACA,MAAI;AACF,UAAM,OAAO,KAAK,KAAK,QAAQ,cAAc,CAAC;AAC9C,WAAO;AAAA,EACT,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,UAAM,OAAO,KAAK,KAAK,QAAQ,YAAY,CAAC;AAC5C,WAAO;AAAA,EACT,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,UAAM,IAAI,IAAI,CAAC,gBAAgB,MAAM,CAAC;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,YAAoB,OAAqC;AAC7E,MAAI,UAAU,OAAO,eAAe,IAAK,QAAO;AAChD,MAAI,UAAU,OAAO,eAAe,IAAK,QAAO;AAChD,MAAI,UAAU,OAAO,eAAe,IAAK,QAAO;AAChD,MAAI,UAAU,OAAO,eAAe,IAAK,QAAO;AAChD,SAAO;AACT;;;AC5KA,SAAS,aAAa;AAItB,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAY;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAM;AAAA,EAAS;AACtF;AACA,IAAM,kBAAkB,IAAI,OAAO,KAAK,sBAAsB,KAAK,GAAG,CAAC,sBAAsB;AAI7F,IAAM,oBAAoB,sBAAsB,IAAI,CAAC,WAAW,GAAG,MAAM,IAAI;AAEtE,SAAS,sBAAgC;AAC9C,SAAO;AACT;AAEA,eAAsB,cAAc,MAAgC;AAClE,MAAI;AACF,UAAM,MAAM,SAAS,QAAQ,WAAW;AACxC,UAAM,MAAM,SAAS,CAAC,GAAG,GAAG,EAAE,SAAS,IAAK,CAAC;AAC7C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,eAAe,QAA2B,QAA0B;AAClF,iBAAe,iBAAiB,QAAwC;AACtE,QAAI,SAAS,MAAM,OAAO,OAAO,GAAG,SAAS,QAAQ,OAAO,GAAG,SAAS,QAAQ,MAAM;AACtF,QAAI,CAAC,UAAU,OAAO,GAAG,aAAa,OAAO,GAAG,SAAS;AACvD,aAAO,KAAK,EAAE,UAAU,OAAO,GAAG,SAAS,GAAG,oCAAoC;AAClF,eAAS,MAAM,OAAO,OAAO,GAAG,UAAU,QAAQ,OAAO,GAAG,SAAS,QAAQ,MAAM;AAAA,IACrF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,sBAAsB,MAAM,UAAU;AAC1C,YAAM,aAAa,MAAM,KAAK,cAAc,IAAI;AAChD,YAAM,SAAS,kBAAkB,YAAY,UAAU,OAAO,OAAO,KAAK;AAE1E,aAAO,KAAK,EAAE,MAAM,OAAO,GAAG,SAAS,YAAY,WAAW,OAAO,GAAG,2BAA2B;AAEnG,UAAI,SAAS,MAAM,iBAAiB,MAAM;AAE1C,UAAI,QAAQ;AAEV,YAAI,OAAO,OAAO,UAAU,kBAAkB,CAAC,2BAA2B,MAAM,GAAG;AACjF,iBAAO,KAAK,EAAE,SAAS,OAAO,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,uCAAuC;AACvF,gBAAM,cAAc,iBAAiB,QAAQ,QAAQ;AACrD,gBAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,cAAI,WAAW,2BAA2B,OAAO,GAAG;AAClD,qBAAS;AAAA,UACX;AAAA,QAEF;AAGA,iBAAS,gBAAgB,MAAM;AAE/B,eAAO,KAAK,EAAE,eAAe,OAAO,OAAO,GAAG,0BAA0B;AAAA,MAC1E;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,gBAAgB,cAAc,eAAe;AACjD,YAAM,SAAS,oBAAoB,cAAc,aAAa;AAC9D,aAAO,iBAAiB,MAAM;AAAA,IAChC;AAAA,IAEA,MAAM,WAAW,UAAU;AACzB,YAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,0BAAiB;AAC9D,YAAM,SAAS,oBAAoB,QAAQ;AAC3C,aAAO,iBAAiB,MAAM;AAAA,IAChC;AAAA,IAEA,MAAM,cAAc,MAAM;AACxB,UAAI,KAAK,UAAU,OAAO,OAAO,aAAa;AAC5C,eAAO;AAAA,MACT;AAGA,YAAM,cAAc,gBAAgB,IAAI;AACxC,YAAM,QAAQ,gBAAgB,MAAM,OAAO,OAAO,cAAc,YAAY,SAAS,GAAG;AAExF,YAAM,YAAY,GAAG,WAAW;AAAA;AAAA,wDAAqB,KAAK,MAAM;AAAA,EAAkB,KAAK;AAGvF,UAAI,UAAU,SAAS,OAAO,OAAO,cAAc,KAAK;AACtD,eAAO,KAAK,uCAAuC;AACnD,cAAM,gBAAgB,uBAAuB,UAAU,MAAM,GAAG,OAAO,OAAO,WAAW,CAAC;AAC1F,cAAM,UAAU,MAAM,iBAAiB,aAAa;AACpD,eAAO,WAAW,UAAU,MAAM,GAAG,OAAO,OAAO,WAAW;AAAA,MAChE;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAIO,SAAS,2BAA2B,SAA0B;AACnE,QAAM,YAAY,QAAQ,MAAM,IAAI,EAAE,CAAC,EAAE,KAAK;AAC9C,SAAO,gBAAgB,KAAK,SAAS;AACvC;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,KACJ,QAAQ,kBAAkB,EAAE,EAC5B,QAAQ,cAAc,EAAE,EACxB,KAAK;AACV;AAIA,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,YAAsB,CAAC;AAE7B,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,YAAY,GAAG;AACjC,gBAAU,KAAK,IAAI;AAAA,IACrB,WAAW,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,MAAM,GAAG;AAC7D,gBAAU,KAAK,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,UAAU,KAAK,IAAI;AAC5B;AAEA,SAAS,gBAAgB,MAAc,WAA2B;AAChE,QAAM,QAAkB,CAAC;AACzB,MAAI,cAAc;AAClB,MAAI,cAAc;AAElB,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,UAAI,eAAe,cAAc,YAAY,UAAU,WAAW;AAChE,cAAM,KAAK,WAAW;AACtB,uBAAe,YAAY;AAAA,MAC7B;AACA,oBAAc,OAAO;AAAA,IACvB,WAAW,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AAEvD,qBAAe,OAAO;AAAA,IACxB;AAAA,EACF;AAGA,MAAI,eAAe,cAAc,YAAY,UAAU,WAAW;AAChE,UAAM,KAAK,WAAW;AAAA,EACxB;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIA,eAAe,OACb,MACA,QACA,SACA,QACA,QACwB;AACxB,MAAI;AACF,UAAM,EAAE,SAAS,KAAK,IAAI,eAAe,MAAM,QAAQ,MAAM;AAE7D,UAAM,EAAE,OAAO,IAAI,MAAM,MAAM,SAAS,MAAM;AAAA,MAC5C,SAAS,UAAU;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAED,UAAM,UAAU,OAAO,KAAK;AAC5B,WAAO,WAAW;AAAA,EACpB,SAAS,KAAK;AACZ,WAAO,MAAM,EAAE,MAAM,IAAI,GAAG,gBAAgB;AAC5C,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eACP,MACA,QACA,QACqC;AACrC,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,EAAE,SAAS,UAAU,MAAM,CAAC,MAAM,EAAE;AAAA,IAC7C,KAAK;AACH,aAAO,EAAE,SAAS,UAAU,MAAM,CAAC,MAAM,MAAM,EAAE;AAAA,IACnD,KAAK;AAEH,aAAO,EAAE,SAAS,UAAU,MAAM,CAAC,OAAO,2BAA2B,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE;AAAA,IAC7G,KAAK,UAAU;AACb,YAAM,QAAQ,QAAQ,IAAI,QAAQ,SAAS;AAC3C,aAAO,EAAE,SAAS,UAAU,MAAM,CAAC,OAAO,OAAO,MAAM,EAAE;AAAA,IAC3D;AAAA,IACA;AAEE,aAAO,EAAE,SAAS,MAAM,MAAM,CAAC,MAAM,EAAE;AAAA,EAC3C;AACF;AAIA,SAAS,kBAAkB,MAAc,UAAkB,OAAuB;AAChF,QAAM,YAAY,aAAa,OAAO,uBAAQ;AAE9C,QAAM,oBAAoB,UAAU,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iEAS9B,sBAAsB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0DAgBlC;AAErB,SAAO;AAAA;AAAA;AAAA,2CAGI,SAAS;AAAA,EACpB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBjB,IAAI;AACN;AAEA,SAAS,iBAAiB,gBAAwB,UAA0B;AAC1E,QAAM,YAAY,aAAa,OAAO,uBAAQ;AAC9C,SAAO;AAAA;AAAA;AAAA,EAGP,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oEAQO,sBAAsB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,IAInD,SAAS;AAAA;AAEb;AAEA,SAAS,oBAAoB,cAAsB,eAA+B;AAChF,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUP,YAAY;AAAA;AAAA;AAAA,EAGZ,aAAa;AACf;AAEA,SAAS,uBAAuB,MAAsB;AACpD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASP,IAAI;AACN;;;ACvUA,OAAO,UAAU;AACjB,SAAS,QAAAA,aAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAEjB,SAAS,eAA4B;AAC1C,QAAM,SAASA,MAAK,QAAQ,GAAG,iBAAiB,MAAM;AAEtD,MAAI;AACF,cAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC,QAAQ;AAEN,WAAO,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,EAC/B;AAEA,QAAM,SAAQ,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAClD,QAAM,UAAUA,MAAK,QAAQ,GAAG,KAAK,MAAM;AAE3C,SAAO;AAAA,IACL,EAAE,OAAO,OAAO;AAAA,IAChB,KAAK,YAAY,EAAE,MAAM,SAAS,QAAQ,MAAM,MAAM,MAAM,CAAC;AAAA,EAC/D;AACF;","names":["join"]}
|