rltgjqm
Version:
Google Gemini API를 사용하여 자연어로 Git 명령어를 생성하는 CLI 도구 - 인터랙티브 메뉴와 자동 API 키 설정 지원
292 lines (247 loc) • 10 kB
JavaScript
/**
* 프롬프트 템플릿 관련 함수들
*/
class PromptTemplate {
constructor() {
this.baseTemplate = `
당신은 Git 명령어 전문가입니다. 사용자의 요청을 받아 적절한 Git 명령어를 생성해주세요.
규칙:
1. 명령어는 정확하고 안전해야 합니다.
2. 위험한 명령어의 경우 경고를 포함해주세요.
3. 명령어에 대한 간단한 설명을 포함해주세요.
4. 한 번에 하나의 명령어만 제안해주세요.
5. 응답은 다음 형식으로 해주세요:
\`\`\`bash
[git 명령어]
\`\`\`
**설명:** [명령어에 대한 설명]
**주의사항:** [있다면 주의사항]
사용자 요청: {prompt}
`;
}
/**
* 메인 프롬프트 템플릿 생성 함수 (사용자 요구사항)
* @param {string} userInput - 사용자의 자연어 입력
* @param {string} mode - 실행 모드 ('dry', 'auto', 'interactive')
* @param {Object} gitStatus - 현재 Git 상태 정보
* @returns {string} 완성된 프롬프트
*/
buildPrompt(userInput, mode = 'dry', gitStatus = {}) {
// 기본 템플릿 사용
const base = `
너는 Git 전문가야.
사용자의 목적은: ${userInput}
이에 따라 사용자가 터미널에서 직접 실행할 수 있도록 Git 명령어만 정확하게 출력해줘.
- 설명 없이 명령어만 한 줄씩 출력해줘
- 각 명령어는 새로운 줄에 작성해줘
- 명령어 앞에 번호나 기호를 붙이지 마
- 필요한 경우 gh 명령어(GitHub CLI)도 사용해도 좋아`;
const modeInstructions = {
'dry': '중간 설명 없이 바로 실행 가능한 상태로 정리해줘.',
'auto': '자동 실행용으로 안전하고 순서대로 실행 가능한 명령어들을 작성해줘.',
'interactive': '각 단계가 명확히 나뉘도록 순서대로 작성해줘. 사용자가 각 단계를 확인할 수 있게 해줘.'
};
const suffix = modeInstructions[mode] || modeInstructions['dry'];
// Git 상태 정보 추가
let contextInfo = '\n현재 환경 정보:';
if (gitStatus.isGitRepository) {
contextInfo += `\n✅ Git 레포지토리: ${gitStatus.repositoryName}`;
contextInfo += `\n📁 레포지토리 경로: ${gitStatus.repoRoot}`;
contextInfo += `\n📍 현재 작업 디렉토리: ${gitStatus.currentDir}`;
if (gitStatus.remoteUrl) {
contextInfo += `\n🔗 원격 저장소: ${gitStatus.remoteUrl}`;
} else {
contextInfo += `\n🔗 원격 저장소: 로컬 전용`;
}
if (gitStatus.currentBranch) {
contextInfo += `\n🌿 현재 브랜치: ${gitStatus.currentBranch}`;
}
if (gitStatus.totalCommits > 0) {
contextInfo += `\n📊 총 커밋 수: ${gitStatus.totalCommits}개`;
}
if (!gitStatus.isInRepoRoot) {
contextInfo += `\n⚠️ 현재 위치가 레포지토리 루트가 아님`;
}
if (gitStatus.hasUncommittedChanges) {
contextInfo += `\n📝 커밋되지 않은 변경사항이 있음`;
if (gitStatus.workingTree) {
const fileCount = gitStatus.workingTree.split('\n').length;
contextInfo += `\n📄 변경된 파일: ${fileCount}개`;
}
} else {
contextInfo += `\n✨ 작업 트리가 클린 상태`;
}
if (gitStatus.hasUnpushedCommits) {
contextInfo += `\n📤 푸시되지 않은 커밋이 있음`;
}
} else {
contextInfo += `\n❌ Git 레포지토리가 아님`;
contextInfo += `\n📁 현재 디렉토리: ${gitStatus.currentDir}`;
contextInfo += `\n💡 Git 관련 명령어를 위해서는 레포지토리 초기화가 필요할 수 있음`;
}
return `${base}${contextInfo}\n\n${suffix}`;
}
/**
* Gemini API 응답에서 명령어 추출
* @param {string} response - Gemini API 응답 텍스트
* @returns {Array<string>} 추출된 명령어 배열
*/
parseCommands(response) {
const commands = [];
// 코드 블록에서 명령어 추출
const codeBlocks = response.match(/```(?:bash|shell|sh)?\n([\s\S]*?)```/g);
if (codeBlocks) {
codeBlocks.forEach(block => {
const content = block.replace(/```(?:bash|shell|sh)?\n?/g, '').replace(/```/g, '');
const lines = content.split('\n').filter(line => line.trim() && !line.startsWith('#'));
commands.push(...lines.map(line => line.trim()));
});
}
// 코드 블록이 없는 경우 git으로 시작하는 라인 추출
if (commands.length === 0) {
const lines = response.split('\n');
lines.forEach(line => {
const trimmed = line.trim();
if (trimmed.startsWith('git ') || trimmed.startsWith('gh ')) {
commands.push(trimmed);
}
});
}
// 여전히 명령어가 없는 경우 전체 텍스트에서 git 명령어 패턴 찾기
if (commands.length === 0) {
const gitCommands = response.match(/git\s+[\w\s\-\.\/]+/g);
if (gitCommands) {
commands.push(...gitCommands.map(cmd => cmd.trim()));
}
}
// 중복 제거 및 정리
return [...new Set(commands)].filter(cmd => cmd.length > 0);
}
/**
* 프롬프트 템플릿 생성 (기존 함수 - 호환성 유지)
* @param {string} userPrompt - 사용자 입력 프롬프트
* @returns {string} 완성된 프롬프트
*/
generatePrompt(userPrompt) {
return this.baseTemplate.replace('{prompt}', userPrompt);
}
/**
* 특정 작업에 대한 프롬프트 템플릿 생성
* @param {string} taskType - 작업 유형 (commit, branch, merge, etc.)
* @param {string} userPrompt - 사용자 입력 프롬프트
* @returns {string} 맞춤형 프롬프트
*/
generateTaskSpecificPrompt(taskType, userPrompt) {
const taskTemplates = {
commit: `
커밋과 관련된 Git 명령어를 생성해주세요.
- 커밋 메시지는 명확하고 간결해야 합니다.
- 컨벤션을 따르는 커밋 메시지를 작성해주세요.
- 필요한 경우 파일 추가(add) 명령어도 포함해주세요.
사용자 요청: {prompt}
`,
branch: `
브랜치와 관련된 Git 명령어를 생성해주세요.
- 브랜치 이름은 명확하고 의미있어야 합니다.
- 현재 브랜치 상태를 고려해주세요.
- 필요한 경우 브랜치 전환 명령어도 포함해주세요.
사용자 요청: {prompt}
`,
merge: `
병합과 관련된 Git 명령어를 생성해주세요.
- 병합 전 상태 확인을 권장해주세요.
- 충돌 가능성을 고려해주세요.
- 안전한 병합 절차를 제안해주세요.
사용자 요청: {prompt}
`,
revert: `
되돌리기와 관련된 Git 명령어를 생성해주세요.
- 되돌리기 전 백업을 권장해주세요.
- 다양한 되돌리기 옵션을 고려해주세요.
- 안전한 되돌리기 절차를 제안해주세요.
사용자 요청: {prompt}
`
};
const template = taskTemplates[taskType] || this.baseTemplate;
return template.replace('{prompt}', userPrompt);
}
/**
* 사용자 입력에서 작업 유형 감지
* @param {string} userPrompt - 사용자 입력 프롬프트
* @returns {string} 감지된 작업 유형
*/
detectTaskType(userPrompt) {
const prompt = userPrompt.toLowerCase();
if (prompt.includes('commit') || prompt.includes('커밋')) {
return 'commit';
}
if (prompt.includes('branch') || prompt.includes('브랜치')) {
return 'branch';
}
if (prompt.includes('merge') || prompt.includes('병합')) {
return 'merge';
}
if (prompt.includes('revert') || prompt.includes('되돌리') || prompt.includes('reset')) {
return 'revert';
}
return 'general';
}
/**
* 컨텍스트를 포함한 프롬프트 생성
* @param {string} userPrompt - 사용자 입력 프롬프트
* @param {Object} context - 현재 Git 상태 정보
* @returns {string} 컨텍스트가 포함된 프롬프트
*/
generateContextualPrompt(userPrompt, context = {}) {
const contextInfo = [];
if (context.currentBranch) {
contextInfo.push(`현재 브랜치: ${context.currentBranch}`);
}
if (context.hasUncommittedChanges) {
contextInfo.push('커밋되지 않은 변경사항이 있습니다.');
}
if (context.hasUnpushedCommits) {
contextInfo.push('푸시되지 않은 커밋이 있습니다.');
}
let prompt = this.generatePrompt(userPrompt);
if (contextInfo.length > 0) {
prompt += `\n\n현재 상태:\n${contextInfo.join('\n')}`;
}
return prompt;
}
/**
* 명령어 검증 및 안전성 확인
* @param {Array<string>} commands - 검증할 명령어 배열
* @returns {Object} 검증 결과
*/
validateCommands(commands) {
const dangerousPatterns = [
/git\s+reset\s+--hard/,
/git\s+clean\s+-f/,
/git\s+push\s+--force/,
/rm\s+-rf/,
/git\s+filter-branch/
];
const issues = [];
const warnings = [];
commands.forEach((command, index) => {
// 위험한 명령어 검사
dangerousPatterns.forEach(pattern => {
if (pattern.test(command)) {
warnings.push(`명령어 ${index + 1}: "${command}" - 위험할 수 있는 명령어입니다.`);
}
});
// 기본 구문 검사
if (!command.startsWith('git ') && !command.startsWith('gh ')) {
issues.push(`명령어 ${index + 1}: "${command}" - Git 명령어가 아닙니다.`);
}
});
return {
isValid: issues.length === 0,
issues,
warnings,
commandCount: commands.length
};
}
}
module.exports = new PromptTemplate();