sumgit
Version:
AI-powered CLI to instantly summarize Git commits, diffs, and generate intelligent commit messages for cleaner repos and better changelogs.
204 lines (162 loc) • 5.23 kB
JavaScript
import { execSync } from 'child_process'
import { summarizeWithAI } from './api.js'
import ora from 'ora'
const MAX_PROMPT_LENGTH = 100000
const IGNORED_FILES = [
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'bun.lockb',
'composer.lock',
'Gemfile.lock',
'Pipfile.lock',
'poetry.lock',
'Cargo.lock',
'mix.lock',
'go.sum',
'npm-shrinkwrap.json',
'gradle.lockfile',
'Podfile.lock',
'pubspec.lock',
]
const IGNORED_EXTENSIONS = [
'.lock',
'.log',
'.class',
'.jar',
'.war',
'.zip',
'.tar',
'.gz',
'.7z',
'.iml',
'.xml',
'.gradle',
'.idea',
'.swp',
'.pyc'
]
function shouldIgnoreFile(file) {
if (IGNORED_FILES.includes(file)) return true
return IGNORED_EXTENSIONS.some(ext => file.endsWith(ext))
}
export async function summarizeCommits(limit) {
const gitLog = execSync(`git log -n ${limit} --pretty=format:"%s"`, { stdio: 'pipe' }).toString()
const commits = gitLog.trim().split('\n').filter(Boolean)
if (commits.length === 0) throw new Error('No commits found')
const prompt = `
You are an AI release note writer.
Given the following Git commit messages, write a concise paragraph describing the key changes made.
Requirements:
- Summarize into exactly 4-6 full sentences.
- Focus on major features added, removed, or improved.
- Mention general themes if appropriate.
- Do NOT copy or repeat the commit messages directly.
- Do NOT list bullet points.
- Output only a paragraph, no titles or headers.
Here are the commit messages:
${commits.join('\n')}
Summary:
`.slice(0, MAX_PROMPT_LENGTH)
const spinner = ora('Summarizing commit messages...').start()
try {
const summary = await summarizeWithAI(prompt)
spinner.succeed('Commit messages summarized.')
return summary
} catch (error) {
spinner.fail('Failed to summarize commit messages.')
throw error
}
}
export async function summarizeDiffs(limit) {
const statOutput = execSync('git diff --numstat', { stdio: 'pipe' }).toString()
const files = statOutput
.trim()
.split('\n')
.map(line => {
const [added, deleted, file] = line.trim().split(/\s+/)
return { file, added: parseInt(added, 10), deleted: parseInt(deleted, 10) }
})
.filter(f =>
f.file &&
!shouldIgnoreFile(f.file) &&
f.added + f.deleted > 2 &&
f.added + f.deleted < 1000
)
let promptBody = ''
for (const { file } of files) {
const diff = execSync(`git diff -- ${file}`, { stdio: 'pipe' }).toString()
if (!diff.trim()) continue
let chunk = `# File: ${file}\n${diff}\n`
if (chunk.length > 10000) chunk = chunk.slice(0, 10000) + '\n[...truncated]\n'
if ((promptBody.length + chunk.length) < MAX_PROMPT_LENGTH) {
promptBody += chunk
} else {
break
}
}
const prompt = `
Summarize the following code diffs into a clear description of what changed.
Focus on meaningful functionality and structural changes. Ignore trivial formatting or version bumps.
${promptBody}
Summary:
`
const spinner = ora('Summarizing code diffs...').start()
try {
const summary = await summarizeWithAI(prompt)
spinner.succeed('Code diffs summarized.')
return summary
} catch (error) {
spinner.fail('Failed to summarize code diffs.')
throw error
}
}
export async function generateCommitMessageFromDiff() {
const gitDiff = execSync(`git diff`, { stdio: 'pipe' }).toString()
if (!gitDiff.trim()) throw new Error('No unstaged changes found')
const prompt = `
You are an AI Git commit assistant.
Given this git diff, generate:
Title: a one-line commit title (max 60 chars)
Description: a 2-3 sentence description
Format:
Title: <title>
Description: <description>
Diff:
${gitDiff}
`.slice(0, MAX_PROMPT_LENGTH)
const spinner = ora('Generating commit message from diff...').start()
try {
const raw = await summarizeWithAI(prompt)
const titleMatch = raw.match(/title:\s*(.+)/i)
const descMatch = raw.match(/description:\s*([\s\S]*)/i)
const title = titleMatch?.[1]?.trim()
const description = descMatch?.[1]?.trim() || ''
console.log(raw)
if (!title) throw new Error('AI did not return title')
spinner.succeed('Commit message generated.')
return { title, description }
} catch (error) {
spinner.fail('Failed to generate commit message.')
throw error
}
}
export async function summarizeFileDiff(path) {
const gitDiff = execSync(`git diff -- ${path}`, { stdio: 'pipe' }).toString()
if (!gitDiff.trim()) throw new Error(`No unstaged changes found in "${path}"`)
const prompt = `
Summarize the following git diff for file: ${path}
Explain what changed clearly and concisely.
${gitDiff}
Summary:
`.slice(0, MAX_PROMPT_LENGTH)
const spinner = ora(`Summarizing changes in ${path}...`).start()
try {
const summary = await summarizeWithAI(prompt)
spinner.succeed(`Summary for ${path} ready.`)
return summary
} catch (error) {
spinner.fail(`Failed to summarize ${path}.`)
throw error
}
}