Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cloving",
"version": "0.3.20",
"version": "0.3.21",
"packageManager": "yarn@1.22.22",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
6 changes: 5 additions & 1 deletion src/cloving_gpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,11 @@ class ClovingGPT {
`Details: ${colors.yellow(JSON.stringify(error.response?.data || {}, null, 2))}\n`,
)

throw new Error(errorMessage)
// Include the API error message in the thrown error for better error handling
const responseData = error.response?.data as any
const apiErrorMessage = responseData?.error?.message
const detailedMessage = apiErrorMessage ? `${errorMessage}. ${apiErrorMessage}` : errorMessage
throw new Error(detailedMessage)
}
}

Expand Down
83 changes: 81 additions & 2 deletions src/managers/CommitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,68 @@ class CommitManager {
this.gpt = new ClovingGPT(options)
}

private async generateCommitWithSimplifiedDiff(): Promise<void> {
// Get a simplified diff (just the file names and basic stats)
const diffStat = execSync('git diff HEAD --stat').toString().trim()
const diffNameOnly = execSync('git diff HEAD --name-only').toString().trim()

const simplifiedPrompt = `Generate a conventional commit message following the Conventional Commits specification.

Files changed:
${diffNameOnly}

Change summary:
${diffStat}

Generate a concise conventional commit message in the format:
<type>[optional scope]: <description>

Do not add any commentary or context to the message other than the commit message itself.`

// Get the commit message with simplified context
const rawCommitMessage = await this.gpt.generateText({ prompt: simplifiedPrompt })
const commitMessage = this.cleanCommitMessage(extractMarkdown(rawCommitMessage))

if (this.autoAccept) {
try {
execFileSync('git', ['commit', '-a', '-m', commitMessage], {
stdio: 'inherit',
})
} catch (commitError) {
console.log('Commit failed:', (commitError as Error).message)
}
} else {
// Write the commit message to a temporary file
const tempCommitFilePath = path.join('.git', 'SUGGESTED_COMMIT_EDITMSG')
fs.writeFileSync(tempCommitFilePath, commitMessage)

// Commit the changes using the generated commit message
try {
execFileSync('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], {
stdio: 'inherit',
})
} catch (commitError) {
console.log('Commit was canceled or failed.')
}

// Remove the temporary file
fs.unlink(tempCommitFilePath, (err) => {
if (err) throw err
})
}
}

private cleanCommitMessage(message: string): string {
// First check if there's a conventional commit message inside a code block
const conventionalCommitRegex = /```\n([a-z]+(?:\([^)]+\))?: [\s\S]*?)```/m
const conventionalMatch = message.match(conventionalCommitRegex)

if (conventionalMatch) {
return conventionalMatch[1].trim()
}

// Remove markdown code block formatting if present, including any text before and after
const codeBlockRegex = /.*?```.*\n(.*?)\n```.*/m
const codeBlockRegex = /.*?```.*\n([\s\S]*?)\n```.*/m
const match = message.match(codeBlockRegex)
return match ? match[1].trim() : message.trim()
}
Expand Down Expand Up @@ -75,7 +134,27 @@ class CommitManager {
})
}
} catch (err) {
const error = err as AxiosError
const error = err as Error

// Check if error is due to token limit (prompt too long)
if (
error.message.includes('prompt is too long') ||
error.message.includes('too many tokens') ||
error.message.includes('maximum')
) {
console.warn('Prompt too long, retrying with simplified diff...')
try {
await this.generateCommitWithSimplifiedDiff()
return
} catch (retryErr) {
console.error(
'Could not generate commit message even with simplified diff:',
(retryErr as Error).message,
)
return
}
}

console.error('Could not generate commit message:', error.message)
}
}
Expand Down
94 changes: 94 additions & 0 deletions tests/managers/CommitManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Test the cleanCommitMessage logic independently
function cleanCommitMessage(message: string): string {
// First check if there's a conventional commit message inside a code block
const conventionalCommitRegex = /```\n([a-z]+(?:\([^)]+\))?: [\s\S]*?)```/m
const conventionalMatch = message.match(conventionalCommitRegex)

if (conventionalMatch) {
return conventionalMatch[1].trim()
}

// Remove markdown code block formatting if present, including any text before and after
const codeBlockRegex = /.*?```.*\n([\s\S]*?)\n```.*/m
const match = message.match(codeBlockRegex)
return match ? match[1].trim() : message.trim()
}

describe('CommitManager cleanCommitMessage', () => {
describe('cleanCommitMessage', () => {
it('should extract conventional commit message from code block', () => {
const input = `Here's a conventional commit message based on the provided diff:

\`\`\`
feat(workshops): add facilitator features and improve user management
\`\`\`

This commit adds new functionality for workshop facilitators.`

const result = cleanCommitMessage(input)
expect(result).toBe('feat(workshops): add facilitator features and improve user management')
})

it('should handle commit messages with # symbols', () => {
const input = `\`\`\`
fix(auth): resolve issue #123 with authentication flow
\`\`\``

const result = cleanCommitMessage(input)
expect(result).toBe('fix(auth): resolve issue #123 with authentication flow')
})

it('should handle commit messages with multiple # symbols', () => {
const input = `\`\`\`
feat(ui): add support for #hashtags and issue #456 references
\`\`\``

const result = cleanCommitMessage(input)
expect(result).toBe('feat(ui): add support for #hashtags and issue #456 references')
})

it('should handle commit messages with # at the beginning', () => {
const input = `\`\`\`
docs: update #README with installation instructions
\`\`\``

const result = cleanCommitMessage(input)
expect(result).toBe('docs: update #README with installation instructions')
})

it('should handle regular markdown code blocks without conventional commit pattern', () => {
const input = `Here's the solution:

\`\`\`
Some regular commit message here
\`\`\``

const result = cleanCommitMessage(input)
expect(result).toBe('Some regular commit message here')
})

it('should handle plain text without code blocks', () => {
const input = 'feat: add new feature with #123 issue reference'

const result = cleanCommitMessage(input)
expect(result).toBe('feat: add new feature with #123 issue reference')
})

it('should handle multiline commit messages with # symbols', () => {
const input = `\`\`\`
feat(api): add new endpoint for user management

- Implement user creation with #validation
- Add error handling for issue #789
- Update documentation
\`\`\``

const result = cleanCommitMessage(input)
expect(result).toBe(`feat(api): add new endpoint for user management

- Implement user creation with #validation
- Add error handling for issue #789
- Update documentation`)
})
})
})
Loading