genai-commit 1.0.1 → 1.0.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.
@@ -751,13 +751,13 @@ Staging files for commit ${i + 1}/${commits.length}...`));
751
751
  }
752
752
 
753
753
  // src/jira/extractor.ts
754
- var JIRA_KEY_PATTERN = /[A-Z]+-\d+/g;
754
+ var JIRA_KEY_PATTERN = /[A-Za-z0-9]+-\d+/g;
755
755
  function extractJiraKeys(input) {
756
756
  const matches = input.match(JIRA_KEY_PATTERN);
757
757
  if (!matches) {
758
758
  return [];
759
759
  }
760
- return [...new Set(matches)];
760
+ return [matches[matches.length - 1]];
761
761
  }
762
762
  function formatJiraKeys(keys) {
763
763
  return keys.join(", ");
@@ -828,4 +828,4 @@ export {
828
828
  validateFilesExist,
829
829
  isValidConventionalCommit
830
830
  };
831
- //# sourceMappingURL=chunk-3MNZUGYE.js.map
831
+ //# sourceMappingURL=chunk-UKE57VMH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/exec.ts","../src/prompts/templates.ts","../src/parser/json.ts","../src/config/defaults.ts","../src/providers/claude.ts","../src/parser/delimiter.ts","../src/providers/cursor.ts","../src/providers/index.ts","../src/git/status.ts","../src/git/tree.ts","../src/git/diff.ts","../src/utils/logger.ts","../src/git/executor.ts","../src/jira/extractor.ts","../src/utils/validation.ts"],"sourcesContent":["/**\n * Shell command execution utilities\n */\n\nimport { spawn } from 'child_process';\n\nexport interface ExecOptions {\n input?: string;\n timeout?: number;\n cwd?: string;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command with optional input and timeout\n */\nexport async function execCommand(\n command: string,\n args: string[],\n options: ExecOptions = {}\n): Promise<ExecResult> {\n const { input, timeout = 120000, cwd } = options;\n\n return new Promise((resolve, reject) => {\n const proc = spawn(command, args, {\n cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n const timer = setTimeout(() => {\n killed = true;\n proc.kill('SIGTERM');\n reject(new Error(`Command timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n proc.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (!killed) {\n resolve({\n stdout,\n stderr,\n exitCode: code ?? 0,\n });\n }\n });\n\n proc.on('error', (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n if (input) {\n proc.stdin.write(input);\n proc.stdin.end();\n }\n });\n}\n\n/**\n * Execute a command and return stdout only\n */\nexport async function execSimple(\n command: string,\n args: string[],\n options: ExecOptions = {}\n): Promise<string> {\n const result = await execCommand(command, args, options);\n if (result.exitCode !== 0) {\n throw new Error(`Command failed with exit code ${result.exitCode}: ${result.stderr}`);\n }\n return result.stdout;\n}\n","/**\n * Embedded prompt templates for AI providers\n */\n\nexport type ProviderPromptType = 'claude' | 'cursor';\nexport type PromptCategory = 'commit' | 'regroup';\n\n// Claude Code prompts (JSON output)\nconst CLAUDE_COMMIT_PROMPT = `You are a commit message generator.\n\nAnalyze the git diff and generate commit messages.\n\nRules:\n- Follow Conventional Commits: type(scope): description\n- Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build\n- Title under 72 characters\n- Split into multiple commits when changes are logically separate\n- Group related file changes into single commit when appropriate\n- NEVER include Jira ticket numbers (like AS-123, PROJ-456) in titles or messages\n- Jira tickets are assigned separately via the [t] option\n\nLanguage settings (check the input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: Language for commit title (after the type(scope): prefix)\n- MESSAGE_LANG: Language for detailed message\n- Default: title in English, message in Korean\n\nExamples:\n- Title (en): \"feat(auth): add OAuth login support\"\n- Title (ko): \"feat(auth): OAuth 로그인 지원 추가\"\n- Message (en): \"Implemented OAuth 2.0 flow with Google provider\"\n- Message (ko): \"Google OAuth 2.0 인증 흐름 구현\"\n\nOutput ONLY valid JSON matching the required schema. No other text.`;\n\nconst CLAUDE_REGROUP_PROMPT = `You are a commit message regrouper.\n\nYour task is to merge commits that share the same Jira ticket URL into a single commit.\n\nRules:\n1. Commits with the SAME Jira URL must be merged into ONE commit\n2. Combine all files from the merged commits\n3. Create a new summarized title that covers all merged changes\n4. Create a new summarized message that describes all combined changes\n5. Keep the Jira URL in the merged commit (jira_url field)\n6. Commits WITHOUT a Jira URL should remain unchanged\n7. Follow Conventional Commits: type(scope): description\n8. Title under 72 characters\n\nLanguage settings (check the input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: Language for commit title\n- MESSAGE_LANG: Language for detailed message\n\nOutput ONLY valid JSON matching the required schema. No other text.`;\n\n// Cursor CLI prompts (delimiter format)\nconst CURSOR_COMMIT_PROMPT = `You are a commit message generator. Analyze git changes and generate commit messages.\n\nIMPORTANT: You MUST reply ONLY in the EXACT format below. No markdown, no explanation, no other text.\n\n===COMMIT===\nFILES: file1.ts, file2.ts\nTITLE: type(scope): description\nMESSAGE: detailed message here\n\nRULES:\n1. Each commit block MUST start with ===COMMIT=== on its own line\n2. FILES: comma-separated file paths (use ONLY files from the input, NEVER invent files)\n3. TITLE: follow Conventional Commits format, under 72 characters\n4. MESSAGE: detailed description in specified language\n5. You may output multiple ===COMMIT=== blocks for separate logical changes\n6. Group related files into the same commit\n7. NEVER include Jira ticket numbers in titles or messages\n\nConventional Commit Types:\n- feat: new feature\n- fix: bug fix\n- docs: documentation\n- style: formatting (no code change)\n- refactor: code restructuring\n- test: adding tests\n- chore: maintenance\n- perf: performance improvement\n- ci: CI/CD changes\n- build: build system changes\n\nLanguage Settings (check input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: en = English title, ko = Korean title\n- MESSAGE_LANG: en = English message, ko = Korean message\n\nExample Output:\n===COMMIT===\nFILES: src/auth/login.ts, src/auth/logout.ts\nTITLE: feat(auth): add OAuth login support\nMESSAGE: OAuth 2.0 인증 흐름을 구현하고 로그아웃 처리를 추가했습니다.\n===COMMIT===\nFILES: src/utils/helper.ts\nTITLE: chore(utils): add helper functions\nMESSAGE: 공통 유틸리티 함수를 추가했습니다.`;\n\nconst CURSOR_REGROUP_PROMPT = `You are a commit message regrouper. Merge commits with the same Jira ticket into a single commit.\n\nIMPORTANT: You MUST reply ONLY in the EXACT format below. No markdown, no explanation, no other text.\n\n===COMMIT===\nFILES: file1.ts, file2.ts\nTITLE: type(scope): description (JIRA-123)\nMESSAGE: detailed message here\n\nRULES:\n1. Merge all commits with the SAME Jira key into ONE commit\n2. Combine all files from merged commits (no duplicates)\n3. Create a summarized title covering all merged changes\n4. Add the Jira key at the end of the title: \"description (AS-123)\"\n5. Create a summarized message describing all combined changes\n6. Follow Conventional Commits format\n7. Title under 72 characters\n\nLanguage Settings (check input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: en = English title, ko = Korean title\n- MESSAGE_LANG: en = English message, ko = Korean message\n\nExample Output:\n===COMMIT===\nFILES: src/components/Button.tsx, src/components/Input.tsx\nTITLE: feat(ui): add Button and Input components (AS-123)\nMESSAGE: Button과 Input 컴포넌트를 추가했습니다.`;\n\n// JSON Schema for Claude output\nconst COMMIT_SCHEMA = {\n type: 'object',\n properties: {\n commits: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n files: {\n type: 'array',\n items: { type: 'string' },\n },\n title: { type: 'string' },\n message: { type: 'string' },\n jira_key: { type: 'string' },\n },\n required: ['files', 'title', 'message'],\n },\n },\n },\n required: ['commits'],\n};\n\n/**\n * Get prompt template for a provider and category\n */\nexport function getPromptTemplate(\n provider: ProviderPromptType,\n category: PromptCategory\n): string {\n if (provider === 'claude') {\n return category === 'commit' ? CLAUDE_COMMIT_PROMPT : CLAUDE_REGROUP_PROMPT;\n } else {\n return category === 'commit' ? CURSOR_COMMIT_PROMPT : CURSOR_REGROUP_PROMPT;\n }\n}\n\n/**\n * Get JSON schema for structured output\n */\nexport function getJsonSchema(): object {\n return COMMIT_SCHEMA;\n}\n","/**\n * JSON response parser for Claude Code CLI\n */\n\nimport type { Commit, CommitResult } from '../types/commit.js';\n\n/**\n * Parse JSON response from Claude CLI\n */\nexport function parseJsonResponse(raw: string): CommitResult {\n try {\n const parsed = JSON.parse(raw);\n\n if (!parsed.commits || !Array.isArray(parsed.commits)) {\n throw new Error('Response does not contain commits array');\n }\n\n const commits: Commit[] = parsed.commits.map((c: Record<string, unknown>) => ({\n files: Array.isArray(c.files) ? c.files : [],\n title: String(c.title || ''),\n message: String(c.message || ''),\n jiraKey: c.jira_key ? String(c.jira_key) : undefined,\n }));\n\n // Filter out invalid commits\n const validCommits = commits.filter(\n (c) => c.files.length > 0 && c.title.length > 0\n );\n\n if (validCommits.length === 0) {\n throw new Error('No valid commits found in response');\n }\n\n return { commits: validCommits };\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`Invalid JSON response: ${raw.substring(0, 200)}...`);\n }\n throw error;\n }\n}\n","/**\n * Default configuration values\n */\n\nimport type { GencoConfig } from './types.js';\n\nexport const DEFAULT_CONFIG: GencoConfig = {\n maxInputSize: 30000,\n maxDiffSize: 15000,\n timeout: 120000, // 120 seconds\n treeDepth: 3,\n maxRetries: 2,\n titleLang: 'en',\n messageLang: 'ko',\n};\n\nexport const CURSOR_DEFAULT_MODEL = 'gemini-3-flash';\nexport const CLAUDE_DEFAULT_MODEL = 'haiku';\n","/**\n * Claude Code CLI provider implementation\n */\n\nimport type { AIProvider, ProviderResponse, ProviderStatus, ProviderOptions, PromptType } from './types.js';\nimport type { CommitResult } from '../types/commit.js';\nimport { execCommand, execSimple } from '../utils/exec.js';\nimport { getPromptTemplate, getJsonSchema } from '../prompts/templates.js';\nimport { parseJsonResponse } from '../parser/json.js';\nimport { CLAUDE_DEFAULT_MODEL } from '../config/defaults.js';\n\nexport class ClaudeCodeProvider implements AIProvider {\n readonly name = 'claude-code' as const;\n private sessionId?: string;\n private timeout: number;\n private model: string;\n\n constructor(options?: ProviderOptions) {\n this.timeout = options?.timeout ?? 120000;\n this.model = options?.model ?? CLAUDE_DEFAULT_MODEL;\n }\n\n async generate(input: string, promptType: PromptType): Promise<ProviderResponse> {\n const prompt = getPromptTemplate('claude', promptType);\n const schema = getJsonSchema();\n\n const args = [\n '-p',\n '--model', this.model,\n '--output-format', 'json',\n '--json-schema', JSON.stringify(schema),\n '--append-system-prompt', prompt,\n ];\n\n if (this.sessionId) {\n args.push('--resume', this.sessionId);\n }\n\n const result = await execCommand('claude', args, {\n input,\n timeout: this.timeout,\n });\n\n if (result.exitCode !== 0) {\n throw new Error(`Claude CLI failed: ${result.stderr}`);\n }\n\n try {\n const parsed = JSON.parse(result.stdout);\n this.sessionId = parsed.session_id;\n\n return {\n raw: JSON.stringify(parsed.structured_output),\n sessionId: this.sessionId,\n };\n } catch (error) {\n throw new Error(`Failed to parse Claude response: ${result.stdout}`);\n }\n }\n\n parseResponse(response: ProviderResponse): CommitResult {\n return parseJsonResponse(response.raw);\n }\n\n async login(): Promise<void> {\n console.log('Setting up Claude Code authentication token...');\n console.log('This requires a Claude subscription.');\n console.log('');\n await execCommand('claude', ['setup-token'], { timeout: 120000 });\n }\n\n async status(): Promise<ProviderStatus> {\n try {\n const version = await execSimple('claude', ['--version'], { timeout: 10000 });\n return {\n available: true,\n version: version.trim(),\n details: 'Claude Code CLI is available',\n };\n } catch {\n return {\n available: false,\n details: 'Claude Code CLI not found. Install it first.',\n };\n }\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n clearSession(): void {\n this.sessionId = undefined;\n }\n}\n","/**\n * Delimiter-based response parser for Cursor CLI\n * Parses ===COMMIT=== delimited format\n */\n\nimport type { Commit, CommitResult } from '../types/commit.js';\n\nconst COMMIT_DELIMITER = '===COMMIT===';\n\n/**\n * Parse a single commit block\n */\nfunction parseCommitBlock(block: string): Commit | null {\n const lines = block.split('\\n');\n let files = '';\n let title = '';\n let message = '';\n\n for (const line of lines) {\n const trimmedLine = line.trim();\n\n if (trimmedLine.startsWith('FILES:')) {\n files = trimmedLine.substring(6).trim();\n } else if (trimmedLine.startsWith('TITLE:')) {\n title = trimmedLine.substring(6).trim();\n } else if (trimmedLine.startsWith('MESSAGE:')) {\n message = trimmedLine.substring(8).trim();\n }\n }\n\n // Validate required fields\n if (!files || !title) {\n return null;\n }\n\n // Parse files as comma-separated list\n const fileList = files\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n\n if (fileList.length === 0) {\n return null;\n }\n\n return {\n files: fileList,\n title,\n message: message || '',\n };\n}\n\n/**\n * Parse delimiter-based response from Cursor CLI\n */\nexport function parseDelimiterResponse(raw: string): CommitResult {\n const commits: Commit[] = [];\n\n // Split by delimiter and skip content before first delimiter\n const blocks = raw.split(COMMIT_DELIMITER).slice(1);\n\n for (const block of blocks) {\n const trimmedBlock = block.trim();\n if (trimmedBlock) {\n const commit = parseCommitBlock(trimmedBlock);\n if (commit) {\n commits.push(commit);\n }\n }\n }\n\n if (commits.length === 0) {\n throw new Error(\n `No valid commits found in response. Raw response:\\n${raw.substring(0, 500)}...`\n );\n }\n\n return { commits };\n}\n\n/**\n * Convert commits to delimiter format (for debugging/testing)\n */\nexport function toDelimiterFormat(commits: Commit[]): string {\n return commits\n .map(\n (c) =>\n `${COMMIT_DELIMITER}\\nFILES: ${c.files.join(', ')}\\nTITLE: ${c.title}\\nMESSAGE: ${c.message}`\n )\n .join('\\n');\n}\n","/**\n * Cursor CLI provider implementation\n */\n\nimport type { AIProvider, ProviderResponse, ProviderStatus, ProviderOptions, PromptType } from './types.js';\nimport type { CommitResult } from '../types/commit.js';\nimport { execCommand } from '../utils/exec.js';\nimport { getPromptTemplate } from '../prompts/templates.js';\nimport { parseDelimiterResponse } from '../parser/delimiter.js';\nimport { CURSOR_DEFAULT_MODEL } from '../config/defaults.js';\n\nexport class CursorCLIProvider implements AIProvider {\n readonly name = 'cursor-cli' as const;\n private timeout: number;\n private model: string;\n\n constructor(options?: ProviderOptions) {\n this.timeout = options?.timeout ?? 120000;\n this.model = options?.model ?? CURSOR_DEFAULT_MODEL;\n }\n\n async generate(input: string, promptType: PromptType): Promise<ProviderResponse> {\n const prompt = getPromptTemplate('cursor', promptType);\n const fullInput = `${prompt}\\n\\n---\\n\\n${input}`;\n\n const result = await execCommand(\n 'cursor',\n ['agent', '-p', '--model', this.model, '--output-format', 'text'],\n {\n input: fullInput,\n timeout: this.timeout,\n }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(`Cursor CLI failed: ${result.stderr}`);\n }\n\n return { raw: result.stdout };\n }\n\n parseResponse(response: ProviderResponse): CommitResult {\n return parseDelimiterResponse(response.raw);\n }\n\n async login(): Promise<void> {\n console.log('Logging in to Cursor...');\n await execCommand('cursor', ['agent', 'login'], { timeout: 60000 });\n }\n\n async status(): Promise<ProviderStatus> {\n try {\n const result = await execCommand('cursor', ['agent', 'status'], { timeout: 10000 });\n return {\n available: true,\n details: result.stdout.trim() || 'Cursor CLI is available',\n };\n } catch {\n return {\n available: false,\n details: 'Cursor CLI not available. Install it first.',\n };\n }\n }\n\n getSessionId(): string | undefined {\n return undefined; // Cursor doesn't support session resume\n }\n\n clearSession(): void {\n // No-op for Cursor\n }\n}\n","/**\n * Provider module exports and factory\n */\n\nimport { ClaudeCodeProvider } from './claude.js';\nimport { CursorCLIProvider } from './cursor.js';\nimport type { AIProvider, ProviderType, ProviderOptions } from './types.js';\n\nexport * from './types.js';\nexport { ClaudeCodeProvider } from './claude.js';\nexport { CursorCLIProvider } from './cursor.js';\n\n/**\n * Create a provider instance by type\n */\nexport function createProvider(\n type: ProviderType,\n options?: ProviderOptions\n): AIProvider {\n switch (type) {\n case 'claude-code':\n return new ClaudeCodeProvider(options);\n case 'cursor-cli':\n return new CursorCLIProvider(options);\n default:\n throw new Error(`Unknown provider: ${type}`);\n }\n}\n\n/**\n * Validate provider type string\n */\nexport function isValidProviderType(type: string): type is ProviderType {\n return type === 'claude-code' || type === 'cursor-cli';\n}\n","/**\n * Git status collection\n */\n\nimport simpleGit, { SimpleGit, StatusResult } from 'simple-git';\nimport type { GitChange, GitStats } from '../types/git.js';\n\nlet gitInstance: SimpleGit | null = null;\n\n/**\n * Get or create simple-git instance\n */\nexport function getGit(cwd?: string): SimpleGit {\n if (!gitInstance || cwd) {\n gitInstance = simpleGit(cwd);\n }\n return gitInstance;\n}\n\n/**\n * Check if current directory is a git repository\n */\nexport async function isGitRepository(cwd?: string): Promise<boolean> {\n try {\n const git = getGit(cwd);\n await git.revparse(['--is-inside-work-tree']);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get current branch name\n */\nexport async function getCurrentBranch(cwd?: string): Promise<string> {\n const git = getGit(cwd);\n const branch = await git.revparse(['--abbrev-ref', 'HEAD']);\n return branch.trim();\n}\n\n/**\n * Get git status and convert to our format\n */\nexport async function getGitStatus(cwd?: string): Promise<{\n changes: GitChange[];\n stats: GitStats;\n}> {\n const git = getGit(cwd);\n const status: StatusResult = await git.status();\n\n const changes: GitChange[] = [];\n const stats: GitStats = {\n added: 0,\n modified: 0,\n deleted: 0,\n renamed: 0,\n untracked: 0,\n total: 0,\n };\n\n // Staged files\n for (const file of status.created) {\n changes.push({ file, status: 'A' });\n stats.added++;\n }\n\n for (const file of status.modified) {\n changes.push({ file, status: 'M' });\n stats.modified++;\n }\n\n for (const file of status.deleted) {\n changes.push({ file, status: 'D' });\n stats.deleted++;\n }\n\n for (const file of status.renamed) {\n changes.push({ file: file.to, status: 'R' });\n stats.renamed++;\n }\n\n // Unstaged modified files\n for (const file of status.not_added) {\n if (!changes.some((c) => c.file === file)) {\n changes.push({ file, status: 'M' });\n stats.modified++;\n }\n }\n\n // Untracked files\n for (const file of status.files) {\n if (file.index === '?' && file.working_dir === '?') {\n changes.push({ file: file.path, status: '?' });\n stats.untracked++;\n }\n }\n\n stats.total = stats.added + stats.modified + stats.deleted + stats.renamed + stats.untracked;\n\n return { changes, stats };\n}\n\n/**\n * Get all valid changed files (for validation)\n */\nexport async function getAllChangedFiles(cwd?: string): Promise<Set<string>> {\n const { changes } = await getGitStatus(cwd);\n return new Set(changes.map((c) => c.file));\n}\n\n/**\n * Check if there are any changes to commit\n */\nexport async function hasChanges(cwd?: string): Promise<boolean> {\n const { stats } = await getGitStatus(cwd);\n return stats.total > 0;\n}\n","/**\n * Tree summary compression (ported from bash awk logic)\n */\n\nimport type { GitChange, ChangeStatus } from '../types/git.js';\n\nexport interface TreeSummaryOptions {\n treeDepth: number;\n compressionThreshold: number;\n}\n\nconst DEFAULT_OPTIONS: TreeSummaryOptions = {\n treeDepth: 3,\n compressionThreshold: 10,\n};\n\n/**\n * Get file extension from path\n */\nfunction getExtension(file: string): string {\n const match = file.match(/\\.([^./]+)$/);\n return match ? match[1] : '';\n}\n\n/**\n * Generate compressed tree summary for a list of files\n * Ported from bash awk logic in generate-commit-msg-claude.sh\n */\nexport function generateTreeSummary(\n files: string[],\n changeType: ChangeStatus,\n options: Partial<TreeSummaryOptions> = {}\n): string {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n if (files.length === 0) {\n return '';\n }\n\n // If files are few, just list them\n if (files.length <= opts.compressionThreshold) {\n return files.map((f) => `${changeType} ${f}`).join('\\n');\n }\n\n // Group files by directory at treeDepth level\n const dirGroups = new Map<string, { count: number; extensions: Map<string, number> }>();\n const directFiles: string[] = [];\n\n for (const file of files) {\n const parts = file.split('/');\n\n if (parts.length <= opts.treeDepth) {\n directFiles.push(`${changeType} ${file}`);\n } else {\n const dir = parts.slice(0, opts.treeDepth).join('/');\n const ext = getExtension(file);\n\n if (!dirGroups.has(dir)) {\n dirGroups.set(dir, { count: 0, extensions: new Map() });\n }\n\n const group = dirGroups.get(dir)!;\n group.count++;\n\n if (ext) {\n group.extensions.set(ext, (group.extensions.get(ext) ?? 0) + 1);\n }\n }\n }\n\n // Format compressed output: \"M src/components/ [15 files: 8 *.tsx, 7 *.css]\"\n const compressed = [...dirGroups.entries()].map(([dir, group]) => {\n const extSummary = [...group.extensions.entries()]\n .map(([ext, count]) => `${count} *.${ext}`)\n .join(', ');\n\n if (extSummary) {\n return `${changeType} ${dir}/ [${group.count} files: ${extSummary}]`;\n } else {\n return `${changeType} ${dir}/ [${group.count} files]`;\n }\n });\n\n return [...directFiles, ...compressed].join('\\n');\n}\n\n/**\n * Generate full tree summary from git changes\n */\nexport function generateFullTreeSummary(\n branch: string,\n changes: GitChange[],\n options: Partial<TreeSummaryOptions> = {}\n): string {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n // Group changes by status\n const added = changes.filter((c) => c.status === 'A').map((c) => c.file);\n const modified = changes.filter((c) => c.status === 'M').map((c) => c.file);\n const deleted = changes.filter((c) => c.status === 'D').map((c) => c.file);\n const renamed = changes.filter((c) => c.status === 'R').map((c) => c.file);\n const untracked = changes.filter((c) => c.status === '?').map((c) => c.file);\n\n const total = added.length + modified.length + deleted.length + renamed.length + untracked.length;\n\n let output = `=== CHANGE SUMMARY ===\nBranch: ${branch}\nTotal: ${total} files\n - Added (A): ${added.length}\n - Modified (M): ${modified.length}\n - Deleted (D): ${deleted.length}\n - Renamed (R): ${renamed.length}\n - Untracked (?): ${untracked.length}\n\n=== FILE TREE ===\n`;\n\n if (modified.length > 0) {\n output += `\\n--- Modified (${modified.length}) ---\\n`;\n output += generateTreeSummary(modified, 'M', opts);\n output += '\\n';\n }\n\n if (added.length > 0) {\n output += `\\n--- Added (${added.length}) ---\\n`;\n output += generateTreeSummary(added, 'A', opts);\n output += '\\n';\n }\n\n if (deleted.length > 0) {\n output += `\\n--- Deleted (${deleted.length}) ---\\n`;\n output += generateTreeSummary(deleted, 'D', opts);\n output += '\\n';\n }\n\n if (renamed.length > 0) {\n output += `\\n--- Renamed (${renamed.length}) ---\\n`;\n output += generateTreeSummary(renamed, 'R', opts);\n output += '\\n';\n }\n\n if (untracked.length > 0) {\n output += `\\n--- Untracked (${untracked.length}) ---\\n`;\n output += generateTreeSummary(untracked, '?', opts);\n output += '\\n';\n }\n\n return output;\n}\n","/**\n * Git diff extraction with size limits\n */\n\nimport { getGit } from './status.js';\nimport type { DiffOptions } from '../types/git.js';\n\nconst DEFAULT_OPTIONS: DiffOptions = {\n maxInputSize: 30000,\n maxDiffSize: 15000,\n treeDepth: 3,\n};\n\n/**\n * Get diff for modified files with size limit\n */\nexport async function getModifiedDiffs(\n maxSize: number,\n cwd?: string\n): Promise<string> {\n const git = getGit(cwd);\n\n // Get list of modified files\n const diffSummary = await git.diffSummary(['HEAD']);\n const modifiedFiles = diffSummary.files\n .filter((f) => !f.binary && 'changes' in f && (f as { changes: number }).changes > 0)\n .map((f) => f.file);\n\n if (modifiedFiles.length === 0) {\n return '';\n }\n\n let output = `\\n=== MODIFIED FILE DIFFS (${modifiedFiles.length} files) ===`;\n let currentSize = output.length;\n\n for (const file of modifiedFiles) {\n try {\n const fileDiff = await git.diff(['HEAD', '--', file]);\n const diffSize = fileDiff.length;\n\n if (currentSize + diffSize + 100 > maxSize) {\n const remaining = modifiedFiles.length - modifiedFiles.indexOf(file);\n output += `\\n\\n[... ${remaining} more files truncated due to size limit]`;\n break;\n }\n\n if (fileDiff) {\n output += `\\n\\n--- ${file} ---\\n${fileDiff}`;\n currentSize += diffSize + file.length + 10;\n }\n } catch {\n // Skip files that can't be diffed\n }\n }\n\n return output;\n}\n\n/**\n * Get complete diff content with tree summary\n */\nexport async function getDiffContent(\n treeSummary: string,\n options: Partial<DiffOptions> = {},\n cwd?: string\n): Promise<string> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n const treeSize = treeSummary.length;\n const remainingSize = opts.maxInputSize - treeSize - 500;\n\n let diffContent = '';\n\n if (remainingSize > 1000) {\n const maxDiff = Math.min(remainingSize, opts.maxDiffSize);\n diffContent = await getModifiedDiffs(maxDiff, cwd);\n }\n\n return diffContent;\n}\n\n/**\n * Get git shortstat summary\n */\nexport async function getShortStat(cwd?: string): Promise<string> {\n const git = getGit(cwd);\n try {\n const result = await git.diff(['--shortstat', 'HEAD']);\n return result.trim();\n } catch {\n return '';\n }\n}\n","/**\n * Colored console output utilities\n */\n\nimport chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => console.log(chalk.cyan(message)),\n success: (message: string) => console.log(chalk.green(message)),\n warning: (message: string) => console.log(chalk.yellow(message)),\n error: (message: string) => console.error(chalk.red(message)),\n highlight: (message: string) => console.log(chalk.magenta(message)),\n dim: (message: string) => console.log(chalk.dim(message)),\n};\n\nexport const colors = {\n red: chalk.red,\n green: chalk.green,\n yellow: chalk.yellow,\n cyan: chalk.cyan,\n blue: chalk.blue,\n magenta: chalk.magenta,\n dim: chalk.dim,\n bold: chalk.bold,\n};\n","/**\n * Git commit execution\n */\n\nimport { getGit } from './status.js';\nimport type { Commit } from '../types/commit.js';\nimport { logger, colors } from '../utils/logger.js';\nimport fs from 'fs';\n\n/**\n * Stage files for commit\n */\nexport async function stageFiles(files: string[], cwd?: string): Promise<void> {\n const git = getGit(cwd);\n\n for (const file of files) {\n try {\n // Check if file exists\n if (fs.existsSync(file)) {\n await git.add(file);\n } else {\n // File might be deleted, try to stage the deletion\n try {\n await git.rm(file);\n } catch {\n // If rm fails, try add with update flag\n await git.add(['-A', file]);\n }\n }\n } catch (error) {\n logger.warning(`Failed to stage file: ${file}`);\n }\n }\n}\n\n/**\n * Execute a single commit\n */\nexport async function executeCommit(\n commit: Commit,\n cwd?: string\n): Promise<boolean> {\n const git = getGit(cwd);\n\n try {\n // Prepare title with Jira key if present\n let title = commit.title;\n if (commit.jiraKey && !title.includes(`(${commit.jiraKey})`)) {\n title = `${title} (${commit.jiraKey})`;\n }\n\n // Stage files\n logger.info('Staging files...');\n await stageFiles(commit.files, cwd);\n\n // Execute commit\n logger.success(`Committing: ${title}`);\n await git.commit([title, commit.message]);\n\n return true;\n } catch (error) {\n logger.error(`Commit failed: ${error}`);\n return false;\n }\n}\n\n/**\n * Execute all commits in order\n */\nexport async function executeCommits(\n commits: Commit[],\n cwd?: string\n): Promise<boolean> {\n for (let i = 0; i < commits.length; i++) {\n const commit = commits[i];\n console.log(colors.yellow(`\\nStaging files for commit ${i + 1}/${commits.length}...`));\n\n const success = await executeCommit(commit, cwd);\n if (!success) {\n logger.error('Commit failed. Aborting.');\n return false;\n }\n\n console.log('');\n }\n\n logger.success('All commits completed successfully!');\n return true;\n}\n","/**\n * Jira key extraction utilities\n */\n\n// Pattern to match Jira issue keys (e.g., AS-123, PROJ-456)\nconst JIRA_KEY_PATTERN = /[A-Z]+-\\d+/g;\n\n/**\n * Extract Jira keys from a URL or text\n * @param input URL or text containing Jira keys\n * @returns Array of unique Jira keys\n */\nexport function extractJiraKeys(input: string): string[] {\n const matches = input.match(JIRA_KEY_PATTERN);\n if (!matches) {\n return [];\n }\n // Remove duplicates\n return [...new Set(matches)];\n}\n\n/**\n * Format multiple Jira keys as comma-separated string\n */\nexport function formatJiraKeys(keys: string[]): string {\n return keys.join(', ');\n}\n\n/**\n * Check if a string contains valid Jira keys\n */\nexport function hasJiraKeys(input: string): boolean {\n return JIRA_KEY_PATTERN.test(input);\n}\n\n/**\n * Validate if a string is a valid Jira key\n */\nexport function isValidJiraKey(key: string): boolean {\n const pattern = /^[A-Z]+-\\d+$/;\n return pattern.test(key);\n}\n","/**\n * Validation utilities\n */\n\nimport type { Commit } from '../types/commit.js';\nimport { logger } from './logger.js';\n\nconst MAX_TITLE_LENGTH = 72;\n\n/**\n * Validate commit title length\n */\nexport function validateTitleLength(commits: Commit[]): void {\n commits.forEach((commit, i) => {\n if (commit.title.length > MAX_TITLE_LENGTH) {\n logger.warning(\n `Commit ${i + 1} title exceeds ${MAX_TITLE_LENGTH} chars (${commit.title.length} chars)`\n );\n }\n });\n}\n\n/**\n * Validate that files in commits exist in the valid files list\n */\nexport function validateFilesExist(\n commits: Commit[],\n validFiles: Set<string>\n): void {\n commits.forEach((commit) => {\n commit.files.forEach((file) => {\n if (!validFiles.has(file)) {\n logger.warning(\n `File '${file}' not in change list (may be AI hallucination or deleted file)`\n );\n }\n });\n });\n}\n\n/**\n * Check if a string is a valid Conventional Commit title\n */\nexport function isValidConventionalCommit(title: string): boolean {\n const pattern = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\\([^)]+\\))?:\\s.+/;\n return pattern.test(title);\n}\n"],"mappings":"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","names":["DEFAULT_OPTIONS"]}
1
+ {"version":3,"sources":["../src/utils/exec.ts","../src/prompts/templates.ts","../src/parser/json.ts","../src/config/defaults.ts","../src/providers/claude.ts","../src/parser/delimiter.ts","../src/providers/cursor.ts","../src/providers/index.ts","../src/git/status.ts","../src/git/tree.ts","../src/git/diff.ts","../src/utils/logger.ts","../src/git/executor.ts","../src/jira/extractor.ts","../src/utils/validation.ts"],"sourcesContent":["/**\n * Shell command execution utilities\n */\n\nimport { spawn } from 'child_process';\n\nexport interface ExecOptions {\n input?: string;\n timeout?: number;\n cwd?: string;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command with optional input and timeout\n */\nexport async function execCommand(\n command: string,\n args: string[],\n options: ExecOptions = {}\n): Promise<ExecResult> {\n const { input, timeout = 120000, cwd } = options;\n\n return new Promise((resolve, reject) => {\n const proc = spawn(command, args, {\n cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n const timer = setTimeout(() => {\n killed = true;\n proc.kill('SIGTERM');\n reject(new Error(`Command timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on('data', (data) => {\n stdout += data.toString();\n });\n\n proc.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (!killed) {\n resolve({\n stdout,\n stderr,\n exitCode: code ?? 0,\n });\n }\n });\n\n proc.on('error', (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n if (input) {\n proc.stdin.write(input);\n proc.stdin.end();\n }\n });\n}\n\n/**\n * Execute a command and return stdout only\n */\nexport async function execSimple(\n command: string,\n args: string[],\n options: ExecOptions = {}\n): Promise<string> {\n const result = await execCommand(command, args, options);\n if (result.exitCode !== 0) {\n throw new Error(`Command failed with exit code ${result.exitCode}: ${result.stderr}`);\n }\n return result.stdout;\n}\n","/**\n * Embedded prompt templates for AI providers\n */\n\nexport type ProviderPromptType = 'claude' | 'cursor';\nexport type PromptCategory = 'commit' | 'regroup';\n\n// Claude Code prompts (JSON output)\nconst CLAUDE_COMMIT_PROMPT = `You are a commit message generator.\n\nAnalyze the git diff and generate commit messages.\n\nRules:\n- Follow Conventional Commits: type(scope): description\n- Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build\n- Title under 72 characters\n- Split into multiple commits when changes are logically separate\n- Group related file changes into single commit when appropriate\n- NEVER include Jira ticket numbers (like AS-123, PROJ-456) in titles or messages\n- Jira tickets are assigned separately via the [t] option\n\nLanguage settings (check the input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: Language for commit title (after the type(scope): prefix)\n- MESSAGE_LANG: Language for detailed message\n- Default: title in English, message in Korean\n\nExamples:\n- Title (en): \"feat(auth): add OAuth login support\"\n- Title (ko): \"feat(auth): OAuth 로그인 지원 추가\"\n- Message (en): \"Implemented OAuth 2.0 flow with Google provider\"\n- Message (ko): \"Google OAuth 2.0 인증 흐름 구현\"\n\nOutput ONLY valid JSON matching the required schema. No other text.`;\n\nconst CLAUDE_REGROUP_PROMPT = `You are a commit message regrouper.\n\nYour task is to merge commits that share the same Jira ticket URL into a single commit.\n\nRules:\n1. Commits with the SAME Jira URL must be merged into ONE commit\n2. Combine all files from the merged commits\n3. Create a new summarized title that covers all merged changes\n4. Create a new summarized message that describes all combined changes\n5. Keep the Jira URL in the merged commit (jira_url field)\n6. Commits WITHOUT a Jira URL should remain unchanged\n7. Follow Conventional Commits: type(scope): description\n8. Title under 72 characters\n\nLanguage settings (check the input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: Language for commit title\n- MESSAGE_LANG: Language for detailed message\n\nOutput ONLY valid JSON matching the required schema. No other text.`;\n\n// Cursor CLI prompts (delimiter format)\nconst CURSOR_COMMIT_PROMPT = `You are a commit message generator. Analyze git changes and generate commit messages.\n\nIMPORTANT: You MUST reply ONLY in the EXACT format below. No markdown, no explanation, no other text.\n\n===COMMIT===\nFILES: file1.ts, file2.ts\nTITLE: type(scope): description\nMESSAGE: detailed message here\n\nRULES:\n1. Each commit block MUST start with ===COMMIT=== on its own line\n2. FILES: comma-separated file paths (use ONLY files from the input, NEVER invent files)\n3. TITLE: follow Conventional Commits format, under 72 characters\n4. MESSAGE: detailed description in specified language\n5. You may output multiple ===COMMIT=== blocks for separate logical changes\n6. Group related files into the same commit\n7. NEVER include Jira ticket numbers in titles or messages\n\nConventional Commit Types:\n- feat: new feature\n- fix: bug fix\n- docs: documentation\n- style: formatting (no code change)\n- refactor: code restructuring\n- test: adding tests\n- chore: maintenance\n- perf: performance improvement\n- ci: CI/CD changes\n- build: build system changes\n\nLanguage Settings (check input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: en = English title, ko = Korean title\n- MESSAGE_LANG: en = English message, ko = Korean message\n\nExample Output:\n===COMMIT===\nFILES: src/auth/login.ts, src/auth/logout.ts\nTITLE: feat(auth): add OAuth login support\nMESSAGE: OAuth 2.0 인증 흐름을 구현하고 로그아웃 처리를 추가했습니다.\n===COMMIT===\nFILES: src/utils/helper.ts\nTITLE: chore(utils): add helper functions\nMESSAGE: 공통 유틸리티 함수를 추가했습니다.`;\n\nconst CURSOR_REGROUP_PROMPT = `You are a commit message regrouper. Merge commits with the same Jira ticket into a single commit.\n\nIMPORTANT: You MUST reply ONLY in the EXACT format below. No markdown, no explanation, no other text.\n\n===COMMIT===\nFILES: file1.ts, file2.ts\nTITLE: type(scope): description (JIRA-123)\nMESSAGE: detailed message here\n\nRULES:\n1. Merge all commits with the SAME Jira key into ONE commit\n2. Combine all files from merged commits (no duplicates)\n3. Create a summarized title covering all merged changes\n4. Add the Jira key at the end of the title: \"description (AS-123)\"\n5. Create a summarized message describing all combined changes\n6. Follow Conventional Commits format\n7. Title under 72 characters\n\nLanguage Settings (check input for TITLE_LANG and MESSAGE_LANG):\n- TITLE_LANG: en = English title, ko = Korean title\n- MESSAGE_LANG: en = English message, ko = Korean message\n\nExample Output:\n===COMMIT===\nFILES: src/components/Button.tsx, src/components/Input.tsx\nTITLE: feat(ui): add Button and Input components (AS-123)\nMESSAGE: Button과 Input 컴포넌트를 추가했습니다.`;\n\n// JSON Schema for Claude output\nconst COMMIT_SCHEMA = {\n type: 'object',\n properties: {\n commits: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n files: {\n type: 'array',\n items: { type: 'string' },\n },\n title: { type: 'string' },\n message: { type: 'string' },\n jira_key: { type: 'string' },\n },\n required: ['files', 'title', 'message'],\n },\n },\n },\n required: ['commits'],\n};\n\n/**\n * Get prompt template for a provider and category\n */\nexport function getPromptTemplate(\n provider: ProviderPromptType,\n category: PromptCategory\n): string {\n if (provider === 'claude') {\n return category === 'commit' ? CLAUDE_COMMIT_PROMPT : CLAUDE_REGROUP_PROMPT;\n } else {\n return category === 'commit' ? CURSOR_COMMIT_PROMPT : CURSOR_REGROUP_PROMPT;\n }\n}\n\n/**\n * Get JSON schema for structured output\n */\nexport function getJsonSchema(): object {\n return COMMIT_SCHEMA;\n}\n","/**\n * JSON response parser for Claude Code CLI\n */\n\nimport type { Commit, CommitResult } from '../types/commit.js';\n\n/**\n * Parse JSON response from Claude CLI\n */\nexport function parseJsonResponse(raw: string): CommitResult {\n try {\n const parsed = JSON.parse(raw);\n\n if (!parsed.commits || !Array.isArray(parsed.commits)) {\n throw new Error('Response does not contain commits array');\n }\n\n const commits: Commit[] = parsed.commits.map((c: Record<string, unknown>) => ({\n files: Array.isArray(c.files) ? c.files : [],\n title: String(c.title || ''),\n message: String(c.message || ''),\n jiraKey: c.jira_key ? String(c.jira_key) : undefined,\n }));\n\n // Filter out invalid commits\n const validCommits = commits.filter(\n (c) => c.files.length > 0 && c.title.length > 0\n );\n\n if (validCommits.length === 0) {\n throw new Error('No valid commits found in response');\n }\n\n return { commits: validCommits };\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`Invalid JSON response: ${raw.substring(0, 200)}...`);\n }\n throw error;\n }\n}\n","/**\n * Default configuration values\n */\n\nimport type { GencoConfig } from './types.js';\n\nexport const DEFAULT_CONFIG: GencoConfig = {\n maxInputSize: 30000,\n maxDiffSize: 15000,\n timeout: 120000, // 120 seconds\n treeDepth: 3,\n maxRetries: 2,\n titleLang: 'en',\n messageLang: 'ko',\n};\n\nexport const CURSOR_DEFAULT_MODEL = 'gemini-3-flash';\nexport const CLAUDE_DEFAULT_MODEL = 'haiku';\n","/**\n * Claude Code CLI provider implementation\n */\n\nimport type { AIProvider, ProviderResponse, ProviderStatus, ProviderOptions, PromptType } from './types.js';\nimport type { CommitResult } from '../types/commit.js';\nimport { execCommand, execSimple } from '../utils/exec.js';\nimport { getPromptTemplate, getJsonSchema } from '../prompts/templates.js';\nimport { parseJsonResponse } from '../parser/json.js';\nimport { CLAUDE_DEFAULT_MODEL } from '../config/defaults.js';\n\nexport class ClaudeCodeProvider implements AIProvider {\n readonly name = 'claude-code' as const;\n private sessionId?: string;\n private timeout: number;\n private model: string;\n\n constructor(options?: ProviderOptions) {\n this.timeout = options?.timeout ?? 120000;\n this.model = options?.model ?? CLAUDE_DEFAULT_MODEL;\n }\n\n async generate(input: string, promptType: PromptType): Promise<ProviderResponse> {\n const prompt = getPromptTemplate('claude', promptType);\n const schema = getJsonSchema();\n\n const args = [\n '-p',\n '--model', this.model,\n '--output-format', 'json',\n '--json-schema', JSON.stringify(schema),\n '--append-system-prompt', prompt,\n ];\n\n if (this.sessionId) {\n args.push('--resume', this.sessionId);\n }\n\n const result = await execCommand('claude', args, {\n input,\n timeout: this.timeout,\n });\n\n if (result.exitCode !== 0) {\n throw new Error(`Claude CLI failed: ${result.stderr}`);\n }\n\n try {\n const parsed = JSON.parse(result.stdout);\n this.sessionId = parsed.session_id;\n\n return {\n raw: JSON.stringify(parsed.structured_output),\n sessionId: this.sessionId,\n };\n } catch (error) {\n throw new Error(`Failed to parse Claude response: ${result.stdout}`);\n }\n }\n\n parseResponse(response: ProviderResponse): CommitResult {\n return parseJsonResponse(response.raw);\n }\n\n async login(): Promise<void> {\n console.log('Setting up Claude Code authentication token...');\n console.log('This requires a Claude subscription.');\n console.log('');\n await execCommand('claude', ['setup-token'], { timeout: 120000 });\n }\n\n async status(): Promise<ProviderStatus> {\n try {\n const version = await execSimple('claude', ['--version'], { timeout: 10000 });\n return {\n available: true,\n version: version.trim(),\n details: 'Claude Code CLI is available',\n };\n } catch {\n return {\n available: false,\n details: 'Claude Code CLI not found. Install it first.',\n };\n }\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n clearSession(): void {\n this.sessionId = undefined;\n }\n}\n","/**\n * Delimiter-based response parser for Cursor CLI\n * Parses ===COMMIT=== delimited format\n */\n\nimport type { Commit, CommitResult } from '../types/commit.js';\n\nconst COMMIT_DELIMITER = '===COMMIT===';\n\n/**\n * Parse a single commit block\n */\nfunction parseCommitBlock(block: string): Commit | null {\n const lines = block.split('\\n');\n let files = '';\n let title = '';\n let message = '';\n\n for (const line of lines) {\n const trimmedLine = line.trim();\n\n if (trimmedLine.startsWith('FILES:')) {\n files = trimmedLine.substring(6).trim();\n } else if (trimmedLine.startsWith('TITLE:')) {\n title = trimmedLine.substring(6).trim();\n } else if (trimmedLine.startsWith('MESSAGE:')) {\n message = trimmedLine.substring(8).trim();\n }\n }\n\n // Validate required fields\n if (!files || !title) {\n return null;\n }\n\n // Parse files as comma-separated list\n const fileList = files\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n\n if (fileList.length === 0) {\n return null;\n }\n\n return {\n files: fileList,\n title,\n message: message || '',\n };\n}\n\n/**\n * Parse delimiter-based response from Cursor CLI\n */\nexport function parseDelimiterResponse(raw: string): CommitResult {\n const commits: Commit[] = [];\n\n // Split by delimiter and skip content before first delimiter\n const blocks = raw.split(COMMIT_DELIMITER).slice(1);\n\n for (const block of blocks) {\n const trimmedBlock = block.trim();\n if (trimmedBlock) {\n const commit = parseCommitBlock(trimmedBlock);\n if (commit) {\n commits.push(commit);\n }\n }\n }\n\n if (commits.length === 0) {\n throw new Error(\n `No valid commits found in response. Raw response:\\n${raw.substring(0, 500)}...`\n );\n }\n\n return { commits };\n}\n\n/**\n * Convert commits to delimiter format (for debugging/testing)\n */\nexport function toDelimiterFormat(commits: Commit[]): string {\n return commits\n .map(\n (c) =>\n `${COMMIT_DELIMITER}\\nFILES: ${c.files.join(', ')}\\nTITLE: ${c.title}\\nMESSAGE: ${c.message}`\n )\n .join('\\n');\n}\n","/**\n * Cursor CLI provider implementation\n */\n\nimport type { AIProvider, ProviderResponse, ProviderStatus, ProviderOptions, PromptType } from './types.js';\nimport type { CommitResult } from '../types/commit.js';\nimport { execCommand } from '../utils/exec.js';\nimport { getPromptTemplate } from '../prompts/templates.js';\nimport { parseDelimiterResponse } from '../parser/delimiter.js';\nimport { CURSOR_DEFAULT_MODEL } from '../config/defaults.js';\n\nexport class CursorCLIProvider implements AIProvider {\n readonly name = 'cursor-cli' as const;\n private timeout: number;\n private model: string;\n\n constructor(options?: ProviderOptions) {\n this.timeout = options?.timeout ?? 120000;\n this.model = options?.model ?? CURSOR_DEFAULT_MODEL;\n }\n\n async generate(input: string, promptType: PromptType): Promise<ProviderResponse> {\n const prompt = getPromptTemplate('cursor', promptType);\n const fullInput = `${prompt}\\n\\n---\\n\\n${input}`;\n\n const result = await execCommand(\n 'cursor',\n ['agent', '-p', '--model', this.model, '--output-format', 'text'],\n {\n input: fullInput,\n timeout: this.timeout,\n }\n );\n\n if (result.exitCode !== 0) {\n throw new Error(`Cursor CLI failed: ${result.stderr}`);\n }\n\n return { raw: result.stdout };\n }\n\n parseResponse(response: ProviderResponse): CommitResult {\n return parseDelimiterResponse(response.raw);\n }\n\n async login(): Promise<void> {\n console.log('Logging in to Cursor...');\n await execCommand('cursor', ['agent', 'login'], { timeout: 60000 });\n }\n\n async status(): Promise<ProviderStatus> {\n try {\n const result = await execCommand('cursor', ['agent', 'status'], { timeout: 10000 });\n return {\n available: true,\n details: result.stdout.trim() || 'Cursor CLI is available',\n };\n } catch {\n return {\n available: false,\n details: 'Cursor CLI not available. Install it first.',\n };\n }\n }\n\n getSessionId(): string | undefined {\n return undefined; // Cursor doesn't support session resume\n }\n\n clearSession(): void {\n // No-op for Cursor\n }\n}\n","/**\n * Provider module exports and factory\n */\n\nimport { ClaudeCodeProvider } from './claude.js';\nimport { CursorCLIProvider } from './cursor.js';\nimport type { AIProvider, ProviderType, ProviderOptions } from './types.js';\n\nexport * from './types.js';\nexport { ClaudeCodeProvider } from './claude.js';\nexport { CursorCLIProvider } from './cursor.js';\n\n/**\n * Create a provider instance by type\n */\nexport function createProvider(\n type: ProviderType,\n options?: ProviderOptions\n): AIProvider {\n switch (type) {\n case 'claude-code':\n return new ClaudeCodeProvider(options);\n case 'cursor-cli':\n return new CursorCLIProvider(options);\n default:\n throw new Error(`Unknown provider: ${type}`);\n }\n}\n\n/**\n * Validate provider type string\n */\nexport function isValidProviderType(type: string): type is ProviderType {\n return type === 'claude-code' || type === 'cursor-cli';\n}\n","/**\n * Git status collection\n */\n\nimport simpleGit, { SimpleGit, StatusResult } from 'simple-git';\nimport type { GitChange, GitStats } from '../types/git.js';\n\nlet gitInstance: SimpleGit | null = null;\n\n/**\n * Get or create simple-git instance\n */\nexport function getGit(cwd?: string): SimpleGit {\n if (!gitInstance || cwd) {\n gitInstance = simpleGit(cwd);\n }\n return gitInstance;\n}\n\n/**\n * Check if current directory is a git repository\n */\nexport async function isGitRepository(cwd?: string): Promise<boolean> {\n try {\n const git = getGit(cwd);\n await git.revparse(['--is-inside-work-tree']);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get current branch name\n */\nexport async function getCurrentBranch(cwd?: string): Promise<string> {\n const git = getGit(cwd);\n const branch = await git.revparse(['--abbrev-ref', 'HEAD']);\n return branch.trim();\n}\n\n/**\n * Get git status and convert to our format\n */\nexport async function getGitStatus(cwd?: string): Promise<{\n changes: GitChange[];\n stats: GitStats;\n}> {\n const git = getGit(cwd);\n const status: StatusResult = await git.status();\n\n const changes: GitChange[] = [];\n const stats: GitStats = {\n added: 0,\n modified: 0,\n deleted: 0,\n renamed: 0,\n untracked: 0,\n total: 0,\n };\n\n // Staged files\n for (const file of status.created) {\n changes.push({ file, status: 'A' });\n stats.added++;\n }\n\n for (const file of status.modified) {\n changes.push({ file, status: 'M' });\n stats.modified++;\n }\n\n for (const file of status.deleted) {\n changes.push({ file, status: 'D' });\n stats.deleted++;\n }\n\n for (const file of status.renamed) {\n changes.push({ file: file.to, status: 'R' });\n stats.renamed++;\n }\n\n // Unstaged modified files\n for (const file of status.not_added) {\n if (!changes.some((c) => c.file === file)) {\n changes.push({ file, status: 'M' });\n stats.modified++;\n }\n }\n\n // Untracked files\n for (const file of status.files) {\n if (file.index === '?' && file.working_dir === '?') {\n changes.push({ file: file.path, status: '?' });\n stats.untracked++;\n }\n }\n\n stats.total = stats.added + stats.modified + stats.deleted + stats.renamed + stats.untracked;\n\n return { changes, stats };\n}\n\n/**\n * Get all valid changed files (for validation)\n */\nexport async function getAllChangedFiles(cwd?: string): Promise<Set<string>> {\n const { changes } = await getGitStatus(cwd);\n return new Set(changes.map((c) => c.file));\n}\n\n/**\n * Check if there are any changes to commit\n */\nexport async function hasChanges(cwd?: string): Promise<boolean> {\n const { stats } = await getGitStatus(cwd);\n return stats.total > 0;\n}\n","/**\n * Tree summary compression (ported from bash awk logic)\n */\n\nimport type { GitChange, ChangeStatus } from '../types/git.js';\n\nexport interface TreeSummaryOptions {\n treeDepth: number;\n compressionThreshold: number;\n}\n\nconst DEFAULT_OPTIONS: TreeSummaryOptions = {\n treeDepth: 3,\n compressionThreshold: 10,\n};\n\n/**\n * Get file extension from path\n */\nfunction getExtension(file: string): string {\n const match = file.match(/\\.([^./]+)$/);\n return match ? match[1] : '';\n}\n\n/**\n * Generate compressed tree summary for a list of files\n * Ported from bash awk logic in generate-commit-msg-claude.sh\n */\nexport function generateTreeSummary(\n files: string[],\n changeType: ChangeStatus,\n options: Partial<TreeSummaryOptions> = {}\n): string {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n if (files.length === 0) {\n return '';\n }\n\n // If files are few, just list them\n if (files.length <= opts.compressionThreshold) {\n return files.map((f) => `${changeType} ${f}`).join('\\n');\n }\n\n // Group files by directory at treeDepth level\n const dirGroups = new Map<string, { count: number; extensions: Map<string, number> }>();\n const directFiles: string[] = [];\n\n for (const file of files) {\n const parts = file.split('/');\n\n if (parts.length <= opts.treeDepth) {\n directFiles.push(`${changeType} ${file}`);\n } else {\n const dir = parts.slice(0, opts.treeDepth).join('/');\n const ext = getExtension(file);\n\n if (!dirGroups.has(dir)) {\n dirGroups.set(dir, { count: 0, extensions: new Map() });\n }\n\n const group = dirGroups.get(dir)!;\n group.count++;\n\n if (ext) {\n group.extensions.set(ext, (group.extensions.get(ext) ?? 0) + 1);\n }\n }\n }\n\n // Format compressed output: \"M src/components/ [15 files: 8 *.tsx, 7 *.css]\"\n const compressed = [...dirGroups.entries()].map(([dir, group]) => {\n const extSummary = [...group.extensions.entries()]\n .map(([ext, count]) => `${count} *.${ext}`)\n .join(', ');\n\n if (extSummary) {\n return `${changeType} ${dir}/ [${group.count} files: ${extSummary}]`;\n } else {\n return `${changeType} ${dir}/ [${group.count} files]`;\n }\n });\n\n return [...directFiles, ...compressed].join('\\n');\n}\n\n/**\n * Generate full tree summary from git changes\n */\nexport function generateFullTreeSummary(\n branch: string,\n changes: GitChange[],\n options: Partial<TreeSummaryOptions> = {}\n): string {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n // Group changes by status\n const added = changes.filter((c) => c.status === 'A').map((c) => c.file);\n const modified = changes.filter((c) => c.status === 'M').map((c) => c.file);\n const deleted = changes.filter((c) => c.status === 'D').map((c) => c.file);\n const renamed = changes.filter((c) => c.status === 'R').map((c) => c.file);\n const untracked = changes.filter((c) => c.status === '?').map((c) => c.file);\n\n const total = added.length + modified.length + deleted.length + renamed.length + untracked.length;\n\n let output = `=== CHANGE SUMMARY ===\nBranch: ${branch}\nTotal: ${total} files\n - Added (A): ${added.length}\n - Modified (M): ${modified.length}\n - Deleted (D): ${deleted.length}\n - Renamed (R): ${renamed.length}\n - Untracked (?): ${untracked.length}\n\n=== FILE TREE ===\n`;\n\n if (modified.length > 0) {\n output += `\\n--- Modified (${modified.length}) ---\\n`;\n output += generateTreeSummary(modified, 'M', opts);\n output += '\\n';\n }\n\n if (added.length > 0) {\n output += `\\n--- Added (${added.length}) ---\\n`;\n output += generateTreeSummary(added, 'A', opts);\n output += '\\n';\n }\n\n if (deleted.length > 0) {\n output += `\\n--- Deleted (${deleted.length}) ---\\n`;\n output += generateTreeSummary(deleted, 'D', opts);\n output += '\\n';\n }\n\n if (renamed.length > 0) {\n output += `\\n--- Renamed (${renamed.length}) ---\\n`;\n output += generateTreeSummary(renamed, 'R', opts);\n output += '\\n';\n }\n\n if (untracked.length > 0) {\n output += `\\n--- Untracked (${untracked.length}) ---\\n`;\n output += generateTreeSummary(untracked, '?', opts);\n output += '\\n';\n }\n\n return output;\n}\n","/**\n * Git diff extraction with size limits\n */\n\nimport { getGit } from './status.js';\nimport type { DiffOptions } from '../types/git.js';\n\nconst DEFAULT_OPTIONS: DiffOptions = {\n maxInputSize: 30000,\n maxDiffSize: 15000,\n treeDepth: 3,\n};\n\n/**\n * Get diff for modified files with size limit\n */\nexport async function getModifiedDiffs(\n maxSize: number,\n cwd?: string\n): Promise<string> {\n const git = getGit(cwd);\n\n // Get list of modified files\n const diffSummary = await git.diffSummary(['HEAD']);\n const modifiedFiles = diffSummary.files\n .filter((f) => !f.binary && 'changes' in f && (f as { changes: number }).changes > 0)\n .map((f) => f.file);\n\n if (modifiedFiles.length === 0) {\n return '';\n }\n\n let output = `\\n=== MODIFIED FILE DIFFS (${modifiedFiles.length} files) ===`;\n let currentSize = output.length;\n\n for (const file of modifiedFiles) {\n try {\n const fileDiff = await git.diff(['HEAD', '--', file]);\n const diffSize = fileDiff.length;\n\n if (currentSize + diffSize + 100 > maxSize) {\n const remaining = modifiedFiles.length - modifiedFiles.indexOf(file);\n output += `\\n\\n[... ${remaining} more files truncated due to size limit]`;\n break;\n }\n\n if (fileDiff) {\n output += `\\n\\n--- ${file} ---\\n${fileDiff}`;\n currentSize += diffSize + file.length + 10;\n }\n } catch {\n // Skip files that can't be diffed\n }\n }\n\n return output;\n}\n\n/**\n * Get complete diff content with tree summary\n */\nexport async function getDiffContent(\n treeSummary: string,\n options: Partial<DiffOptions> = {},\n cwd?: string\n): Promise<string> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n const treeSize = treeSummary.length;\n const remainingSize = opts.maxInputSize - treeSize - 500;\n\n let diffContent = '';\n\n if (remainingSize > 1000) {\n const maxDiff = Math.min(remainingSize, opts.maxDiffSize);\n diffContent = await getModifiedDiffs(maxDiff, cwd);\n }\n\n return diffContent;\n}\n\n/**\n * Get git shortstat summary\n */\nexport async function getShortStat(cwd?: string): Promise<string> {\n const git = getGit(cwd);\n try {\n const result = await git.diff(['--shortstat', 'HEAD']);\n return result.trim();\n } catch {\n return '';\n }\n}\n","/**\n * Colored console output utilities\n */\n\nimport chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => console.log(chalk.cyan(message)),\n success: (message: string) => console.log(chalk.green(message)),\n warning: (message: string) => console.log(chalk.yellow(message)),\n error: (message: string) => console.error(chalk.red(message)),\n highlight: (message: string) => console.log(chalk.magenta(message)),\n dim: (message: string) => console.log(chalk.dim(message)),\n};\n\nexport const colors = {\n red: chalk.red,\n green: chalk.green,\n yellow: chalk.yellow,\n cyan: chalk.cyan,\n blue: chalk.blue,\n magenta: chalk.magenta,\n dim: chalk.dim,\n bold: chalk.bold,\n};\n","/**\n * Git commit execution\n */\n\nimport { getGit } from './status.js';\nimport type { Commit } from '../types/commit.js';\nimport { logger, colors } from '../utils/logger.js';\nimport fs from 'fs';\n\n/**\n * Stage files for commit\n */\nexport async function stageFiles(files: string[], cwd?: string): Promise<void> {\n const git = getGit(cwd);\n\n for (const file of files) {\n try {\n // Check if file exists\n if (fs.existsSync(file)) {\n await git.add(file);\n } else {\n // File might be deleted, try to stage the deletion\n try {\n await git.rm(file);\n } catch {\n // If rm fails, try add with update flag\n await git.add(['-A', file]);\n }\n }\n } catch (error) {\n logger.warning(`Failed to stage file: ${file}`);\n }\n }\n}\n\n/**\n * Execute a single commit\n */\nexport async function executeCommit(\n commit: Commit,\n cwd?: string\n): Promise<boolean> {\n const git = getGit(cwd);\n\n try {\n // Prepare title with Jira key if present\n let title = commit.title;\n if (commit.jiraKey && !title.includes(`(${commit.jiraKey})`)) {\n title = `${title} (${commit.jiraKey})`;\n }\n\n // Stage files\n logger.info('Staging files...');\n await stageFiles(commit.files, cwd);\n\n // Execute commit\n logger.success(`Committing: ${title}`);\n await git.commit([title, commit.message]);\n\n return true;\n } catch (error) {\n logger.error(`Commit failed: ${error}`);\n return false;\n }\n}\n\n/**\n * Execute all commits in order\n */\nexport async function executeCommits(\n commits: Commit[],\n cwd?: string\n): Promise<boolean> {\n for (let i = 0; i < commits.length; i++) {\n const commit = commits[i];\n console.log(colors.yellow(`\\nStaging files for commit ${i + 1}/${commits.length}...`));\n\n const success = await executeCommit(commit, cwd);\n if (!success) {\n logger.error('Commit failed. Aborting.');\n return false;\n }\n\n console.log('');\n }\n\n logger.success('All commits completed successfully!');\n return true;\n}\n","/**\n * Jira key extraction utilities\n */\n\n// Pattern to match Jira issue keys (e.g., AS-123, Proj-456, abc123-789)\nconst JIRA_KEY_PATTERN = /[A-Za-z0-9]+-\\d+/g;\n\n/**\n * Extract the last Jira key from a URL path or text\n * @param input URL or text containing Jira keys\n * @returns Array with the last Jira key found (for path-based extraction)\n */\nexport function extractJiraKeys(input: string): string[] {\n const matches = input.match(JIRA_KEY_PATTERN);\n if (!matches) {\n return [];\n }\n // Return only the last match (path-based: last segment is the ticket)\n return [matches[matches.length - 1]];\n}\n\n/**\n * Format multiple Jira keys as comma-separated string\n */\nexport function formatJiraKeys(keys: string[]): string {\n return keys.join(', ');\n}\n\n/**\n * Check if a string contains valid Jira keys\n */\nexport function hasJiraKeys(input: string): boolean {\n return JIRA_KEY_PATTERN.test(input);\n}\n\n/**\n * Validate if a string is a valid Jira key\n */\nexport function isValidJiraKey(key: string): boolean {\n const pattern = /^[A-Za-z0-9]+-\\d+$/;\n return pattern.test(key);\n}\n","/**\n * Validation utilities\n */\n\nimport type { Commit } from '../types/commit.js';\nimport { logger } from './logger.js';\n\nconst MAX_TITLE_LENGTH = 72;\n\n/**\n * Validate commit title length\n */\nexport function validateTitleLength(commits: Commit[]): void {\n commits.forEach((commit, i) => {\n if (commit.title.length > MAX_TITLE_LENGTH) {\n logger.warning(\n `Commit ${i + 1} title exceeds ${MAX_TITLE_LENGTH} chars (${commit.title.length} chars)`\n );\n }\n });\n}\n\n/**\n * Validate that files in commits exist in the valid files list\n */\nexport function validateFilesExist(\n commits: Commit[],\n validFiles: Set<string>\n): void {\n commits.forEach((commit) => {\n commit.files.forEach((file) => {\n if (!validFiles.has(file)) {\n logger.warning(\n `File '${file}' not in change list (may be AI hallucination or deleted file)`\n );\n }\n });\n });\n}\n\n/**\n * Check if a string is a valid Conventional Commit title\n */\nexport function isValidConventionalCommit(title: string): boolean {\n const pattern = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\\([^)]+\\))?:\\s.+/;\n return pattern.test(title);\n}\n"],"mappings":"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","names":["DEFAULT_OPTIONS"]}
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  logger,
17
17
  validateFilesExist,
18
18
  validateTitleLength
19
- } from "./chunk-3MNZUGYE.js";
19
+ } from "./chunk-UKE57VMH.js";
20
20
 
21
21
  // src/cli.ts
22
22
  import { Command } from "commander";
package/dist/index.d.ts CHANGED
@@ -267,9 +267,9 @@ declare function toDelimiterFormat(commits: Commit[]): string;
267
267
  * Jira key extraction utilities
268
268
  */
269
269
  /**
270
- * Extract Jira keys from a URL or text
270
+ * Extract the last Jira key from a URL path or text
271
271
  * @param input URL or text containing Jira keys
272
- * @returns Array of unique Jira keys
272
+ * @returns Array with the last Jira key found (for path-based extraction)
273
273
  */
274
274
  declare function extractJiraKeys(input: string): string[];
275
275
  /**
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  toDelimiterFormat,
33
33
  validateFilesExist,
34
34
  validateTitleLength
35
- } from "./chunk-3MNZUGYE.js";
35
+ } from "./chunk-UKE57VMH.js";
36
36
  export {
37
37
  CLAUDE_DEFAULT_MODEL,
38
38
  CURSOR_DEFAULT_MODEL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genai-commit",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "AI-powered commit message generator using Claude Code or Cursor CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",