147 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as plugins from '../plugins.js';
 | 
						|
import { AiDoc } from '../classes.aidoc.js';
 | 
						|
import { ProjectContext } from './projectcontext.js';
 | 
						|
 | 
						|
export interface INextCommitObject {
 | 
						|
  recommendedNextVersionLevel: 'fix' | 'feat' | 'BREAKING CHANGE'; // the recommended next version level of the project
 | 
						|
  recommendedNextVersionScope: string; // the recommended scope name of the next version, like "core" or "cli", or specific class names.
 | 
						|
  recommendedNextVersionMessage: string; // the commit message. Don't put fix() feat() or BREAKING CHANGE in the message. Please just the message itself.
 | 
						|
  recommendedNextVersionDetails: string[]; // detailed bullet points for the changelog
 | 
						|
  recommendedNextVersion: string; // the recommended next version of the project, x.x.x
 | 
						|
  changelog?: string; // the changelog for the next version
 | 
						|
}
 | 
						|
 | 
						|
export class Commit {
 | 
						|
  private aiDocsRef: AiDoc;
 | 
						|
  private projectDir: string;
 | 
						|
 | 
						|
  constructor(aiDocsRef: AiDoc, projectDirArg: string) {
 | 
						|
    this.aiDocsRef = aiDocsRef;
 | 
						|
    this.projectDir = projectDirArg;
 | 
						|
  }
 | 
						|
 | 
						|
  public async buildNextCommitObject(): Promise<INextCommitObject> {
 | 
						|
    const smartgitInstance = new plugins.smartgit.Smartgit();
 | 
						|
    await smartgitInstance.init();
 | 
						|
    const gitRepo = await plugins.smartgit.GitRepo.fromOpeningRepoDir(
 | 
						|
      smartgitInstance,
 | 
						|
      this.projectDir
 | 
						|
    );
 | 
						|
    const diffStringArray = await gitRepo.getUncommittedDiff([
 | 
						|
      'pnpm-lock.yaml',
 | 
						|
      'package-lock.json',
 | 
						|
    ]);
 | 
						|
    // Use the new TaskContextFactory for optimized context
 | 
						|
    const taskContextFactory = new (await import('../context/index.js')).TaskContextFactory(this.projectDir);
 | 
						|
    await taskContextFactory.initialize();
 | 
						|
    
 | 
						|
    // Generate context specifically for commit task
 | 
						|
    const contextResult = await taskContextFactory.createContextForCommit(
 | 
						|
      diffStringArray[0] ? diffStringArray.join('\n\n') : 'No changes.'
 | 
						|
    );
 | 
						|
    
 | 
						|
    // Get the optimized context string
 | 
						|
    let contextString = contextResult.context;
 | 
						|
    
 | 
						|
    // Log token usage statistics
 | 
						|
    console.log(`Token usage - Context: ${contextResult.tokenCount}, Files: ${contextResult.includedFiles.length + contextResult.trimmedFiles.length}, Savings: ${contextResult.tokenSavings}`);
 | 
						|
    
 | 
						|
    // Check for token overflow against model limits
 | 
						|
    const MODEL_TOKEN_LIMIT = 200000; // o4-mini
 | 
						|
    if (contextResult.tokenCount > MODEL_TOKEN_LIMIT * 0.9) {
 | 
						|
      console.log(`⚠️ Warning: Context size (${contextResult.tokenCount} tokens) is close to or exceeds model limit (${MODEL_TOKEN_LIMIT} tokens).`);
 | 
						|
      console.log(`The model may not be able to process all information effectively.`);
 | 
						|
    }
 | 
						|
 | 
						|
    let result = await this.aiDocsRef.openaiInstance.chat({
 | 
						|
      systemMessage: `
 | 
						|
You create a commit message for a git commit.
 | 
						|
The commit message should be based on the files in the project.
 | 
						|
You should not include any licensing information.
 | 
						|
You should not include any personal information.
 | 
						|
 | 
						|
Important: Answer only in valid JSON.
 | 
						|
 | 
						|
Your answer should be parseable with JSON.parse() without modifying anything.
 | 
						|
 | 
						|
Here is the structure of the JSON you should return:
 | 
						|
 | 
						|
interface {
 | 
						|
  recommendedNextVersionLevel: 'fix' | 'feat' | 'BREAKING CHANGE'; // the recommended next version level of the project
 | 
						|
  recommendedNextVersionScope: string; // the recommended scope name of the next version, like "core" or "cli", or specific class names.
 | 
						|
  recommendedNextVersionMessage: string; // the commit message. Don't put fix() feat() or BREAKING CHANGE in the message. Please just the message itself.
 | 
						|
  recommendedNextVersionDetails: string[]; // detailed bullet points for the changelog
 | 
						|
  recommendedNextVersion: string; // the recommended next version of the project, x.x.x
 | 
						|
}
 | 
						|
 | 
						|
For the recommendedNextVersionDetails, please only add a detail entries to the array if it has an obvious value to the reader.
 | 
						|
 | 
						|
You are being given the files of the project. You should use them to create the commit message.
 | 
						|
Also you are given a diff.
 | 
						|
Never mention CLAUDE code, or codex.
 | 
						|
`,
 | 
						|
      messageHistory: [],
 | 
						|
      userMessage: contextString,
 | 
						|
    });
 | 
						|
 | 
						|
    // console.log(result.message);
 | 
						|
    const resultObject: INextCommitObject = JSON.parse(
 | 
						|
      result.message.replace('```json', '').replace('```', '')
 | 
						|
    );
 | 
						|
 | 
						|
    const previousChangelogPath = plugins.path.join(this.projectDir, 'changelog.md');
 | 
						|
    let previousChangelog: plugins.smartfile.SmartFile;
 | 
						|
    if (await plugins.smartfile.fs.fileExists(previousChangelogPath)) {
 | 
						|
      previousChangelog = await plugins.smartfile.SmartFile.fromFilePath(previousChangelogPath);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!previousChangelog) {
 | 
						|
      // lets build the changelog based on that
 | 
						|
      const commitMessages = await gitRepo.getAllCommitMessages();
 | 
						|
      console.log(JSON.stringify(commitMessages, null, 2));
 | 
						|
      let result2 = await this.aiDocsRef.openaiInstance.chat({
 | 
						|
        messageHistory: [],
 | 
						|
        systemMessage: `
 | 
						|
You are building a changelog.md file for the project.
 | 
						|
Omit commits and versions that lack relevant changes, but make sure to mention them as a range with a summarizing message instead.
 | 
						|
 | 
						|
A changelog entry should look like this:
 | 
						|
 | 
						|
    ## yyyy-mm-dd - x.x.x - scope here
 | 
						|
    main descriptiom here
 | 
						|
 | 
						|
    - detailed bullet points follow
 | 
						|
 | 
						|
You are given:
 | 
						|
* the commit messages of the project
 | 
						|
 | 
						|
Only return the changelog file, so it can be written directly to changelog.md`,
 | 
						|
        userMessage: `
 | 
						|
Here are the commit messages:
 | 
						|
 | 
						|
${JSON.stringify(commitMessages, null, 2)}
 | 
						|
  `,
 | 
						|
      });
 | 
						|
 | 
						|
      previousChangelog = await plugins.smartfile.SmartFile.fromString(
 | 
						|
        previousChangelogPath,
 | 
						|
        result2.message.replaceAll('```markdown', '').replaceAll('```', ''),
 | 
						|
        'utf8'
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    let oldChangelog = previousChangelog.contents.toString().replace('# Changelog\n\n', '');
 | 
						|
    if (oldChangelog.startsWith('\n')) {
 | 
						|
      oldChangelog = oldChangelog.replace('\n', '');
 | 
						|
    }
 | 
						|
    let newDateString = new plugins.smarttime.ExtendedDate().exportToHyphedSortableDate();
 | 
						|
    let newChangelog = `# Changelog\n\n${`## ${newDateString} - {{nextVersion}} - {{nextVersionScope}}
 | 
						|
{{nextVersionMessage}}
 | 
						|
 | 
						|
{{nextVersionDetails}}`}\n\n${oldChangelog}`;
 | 
						|
    resultObject.changelog = newChangelog;
 | 
						|
 | 
						|
    return resultObject;
 | 
						|
  }
 | 
						|
}
 |