2023-06-28 07:41:49 -04:00
const {
minimumSummaryChars ,
maximumSummaryChars ,
maximumBodyLineChars ,
allowedTypes ,
} = require ( "./mrCommitsConstants.js" ) ;
2023-06-27 04:04:04 -04:00
const { gptStandardModelTokens } = require ( "./mrCommitsConstants.js" ) ;
const { ChatPromptTemplate } = require ( "langchain/prompts" ) ;
const { SystemMessagePromptTemplate } = require ( "langchain/prompts" ) ;
2023-03-30 05:16:39 -04:00
const { LLMChain } = require ( "langchain/chains" ) ;
const { ChatOpenAI } = require ( "langchain/chat_models/openai" ) ;
const openAiTokenCount = require ( "openai-gpt-token-counter" ) ;
module . exports = async function ( ) {
let outputDangerMessage = ` \n \n Perhaps you could use an AI-generated suggestion for your commit message. Here is one ` ;
2023-06-27 04:04:04 -04:00
let mrDiff = await getMrGitDiff ( danger . git . modified _files ) ;
const mrCommitMessages = getCommitMessages ( danger . gitlab . commits ) ;
const inputPrompt = getInputPrompt ( ) ;
const inputLlmTokens = getInputLlmTokens (
inputPrompt ,
mrDiff ,
mrCommitMessages
) ;
console . log ( ` Input tokens for LLM: ${ inputLlmTokens } ` ) ;
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
if ( inputLlmTokens >= gptStandardModelTokens ) {
mrDiff = "" ; // If the input mrDiff is larger than 16k model, don't use mrDiff, use only current commit messages
outputDangerMessage += ` (based only on your current commit messages, git-diff of this MR is too big ( ${ inputLlmTokens } tokens) for the AI models): \n \n ` ;
} else {
outputDangerMessage += ` (based on your MR git-diff and your current commit messages): \n \n ` ;
}
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
// Generate AI commit message
let generatedCommitMessage = "" ;
try {
const rawCommitMessage = await createAiGitMessage (
inputPrompt ,
mrDiff ,
mrCommitMessages
) ;
generatedCommitMessage = postProcessCommitMessage ( rawCommitMessage ) ;
} catch ( error ) {
console . error ( "Error in generating AI commit message: " , error ) ;
outputDangerMessage +=
"\nCould not generate commit message due to an error.\n" ;
}
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
// Append closing statements ("Closes https://github.com/espressif/esp-idf/issues/XXX") to the generated commit message
let closingStatements = extractClosingStatements ( mrCommitMessages ) ;
if ( closingStatements . length > 0 ) {
generatedCommitMessage += "\n\n" + closingStatements ;
}
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
// Add the generated git message, format to the markdown code block
outputDangerMessage += ` \n \` \` \` \n ${ generatedCommitMessage } \n \` \` \` \n ` ;
outputDangerMessage +=
"\n**NOTE: AI-generated suggestions may not always be correct, please review the suggestion before using it.**" ; // Add disclaimer
return outputDangerMessage ;
} ;
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
async function getMrGitDiff ( mrModifiedFiles ) {
const fileDiffs = await Promise . all (
mrModifiedFiles . map ( ( file ) => danger . git . diffForFile ( file ) )
) ;
return fileDiffs . map ( ( fileDiff ) => fileDiff . diff . trim ( ) ) . join ( " " ) ;
}
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
function getCommitMessages ( mrCommits ) {
return mrCommits . map ( ( commit ) => commit . message ) ;
}
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
function getInputPrompt ( ) {
return ` You are a helpful assistant that creates suggestions for single git commit message, that user can use to describe all the changes in their merge request.
Use git diff : { mrDiff } and users current commit messages : { mrCommitMessages } to get the changes made in the commit .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
Output should be git commit message following the conventional commit format .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
Output only git commit message in desired format , without comments and other text .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
Do not include the closing statements ( "Closes https://...." ) in the output .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
Here are the strict rules you must follow :
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
- Avoid mentioning any JIRA tickets ( e . g . , "Closes JIRA-123" ) .
- Be specific . Don ' t use vague terms ( e . g . , "some checks" , "add new ones" , "few changes" ) .
- The commit message structure should be : < type > < ( scope / component ) > : < summary >
- Types allowed : $ { allowedTypes . join ( ", " ) }
- If 'scope/component' is used , it must start with a lowercase letter .
- The 'summary' must NOT end with a period .
- The 'summary' must be between $ { minimumSummaryChars } and $ { maximumSummaryChars } characters long .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
If a 'body' of commit message is used :
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
- Each line must be no longer than $ { maximumBodyLineChars } characters .
- It must be separated from the 'summary' by a blank line .
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
Examples of correct commit messages :
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
- With scope and body :
fix ( freertos ) : Fix startup timeout issue
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
This is a text of commit message body ...
- adds support for wifi6
- adds validations for logging script
2023-03-30 05:16:39 -04:00
2023-06-27 04:04:04 -04:00
- Without scope and body :
ci : added target test job for ESP32 - Wifi6 ` ;
2023-03-30 05:16:39 -04:00
}
2023-06-27 04:04:04 -04:00
function getInputLlmTokens ( inputPrompt , mrDiff , mrCommitMessages ) {
const mrCommitMessagesTokens = openAiTokenCount ( mrCommitMessages . join ( " " ) ) ;
const gitDiffTokens = openAiTokenCount ( mrDiff ) ;
const promptTokens = openAiTokenCount ( inputPrompt ) ;
return mrCommitMessagesTokens + gitDiffTokens + promptTokens ;
2023-03-30 05:16:39 -04:00
}
async function createAiGitMessage ( inputPrompt , mrDiff , mrCommitMessages ) {
2023-06-27 04:04:04 -04:00
const chat = new ChatOpenAI ( { engine : "gpt-3.5-turbo" , temperature : 0 } ) ;
2023-03-30 05:16:39 -04:00
const chatPrompt = ChatPromptTemplate . fromPromptMessages ( [
SystemMessagePromptTemplate . fromTemplate ( inputPrompt ) ,
] ) ;
2023-06-27 04:04:04 -04:00
const chain = new LLMChain ( { prompt : chatPrompt , llm : chat } ) ;
2023-03-30 05:16:39 -04:00
const response = await chain . call ( {
mrDiff : mrDiff ,
mrCommitMessages : mrCommitMessages ,
} ) ;
return response . text ;
}
2023-06-27 04:04:04 -04:00
function postProcessCommitMessage ( rawCommitMessage ) {
// Split the result into lines
let lines = rawCommitMessage . split ( "\n" ) ;
// Format each line
for ( let i = 0 ; i < lines . length ; i ++ ) {
let line = lines [ i ] . trim ( ) ;
// If the line is longer than maximumBodyLineChars, split it into multiple lines
if ( line . length > maximumBodyLineChars ) {
let newLines = [ ] ;
while ( line . length > maximumBodyLineChars ) {
let lastSpaceIndex = line . lastIndexOf (
" " ,
maximumBodyLineChars
) ;
newLines . push ( line . substring ( 0 , lastSpaceIndex ) ) ;
line = line . substring ( lastSpaceIndex + 1 ) ;
}
newLines . push ( line ) ;
lines [ i ] = newLines . join ( "\n" ) ;
}
}
// Join the lines back into a single string with a newline between each one
return lines . join ( "\n" ) ;
}
function extractClosingStatements ( mrCommitMessages ) {
let closingStatements = [ ] ;
mrCommitMessages . forEach ( ( message ) => {
const lines = message . split ( "\n" ) ;
lines . forEach ( ( line ) => {
if ( line . startsWith ( "Closes" ) ) {
closingStatements . push ( line ) ;
}
} ) ;
} ) ;
return closingStatements . join ( "\n" ) ;
}