git-coco 0.1.0 β 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.esm.mjs +140 -245
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +139 -246
- package/dist/lib/parsers/noResult.d.ts +4 -6
- package/dist/lib/simple-git/createCommit.d.ts +2 -0
- package/dist/lib/simple-git/getChanges.d.ts +12 -0
- package/dist/lib/simple-git/getDiff.d.ts +7 -0
- package/dist/lib/simple-git/getStatus.d.ts +3 -0
- package/dist/lib/simple-git/getSummaryText.d.ts +3 -0
- package/dist/lib/types.d.ts +3 -3
- package/dist/stats.html +1 -1
- package/package.json +4 -6
- package/dist/lib/parsers/default/utils/parseFileDiff.d.ts +0 -4
- package/dist/lib/utils/git/constants.d.ts +0 -1
- package/dist/lib/utils/git/createCommit.d.ts +0 -2
- package/dist/lib/utils/git/getChanges.d.ts +0 -43
- package/dist/lib/utils/git/getStatus.d.ts +0 -3
- package/dist/lib/utils/git/getSummaryText.d.ts +0 -2
- package/dist/lib/utils/git/parsePatches.d.ts +0 -18
- /package/dist/lib/parsers/default/{fileChangeParser.d.ts β index.d.ts} +0 -0
package/README.md
CHANGED
package/dist/index.esm.mjs
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as nodegit from 'nodegit';
|
|
3
|
-
import { Diff, Blob, Tree, Repository } from 'nodegit';
|
|
4
2
|
import { select, editor } from '@inquirer/prompts';
|
|
5
3
|
import * as fs from 'fs';
|
|
6
4
|
import * as os from 'os';
|
|
7
5
|
import * as path from 'path';
|
|
6
|
+
import path__default from 'path';
|
|
8
7
|
import * as ini from 'ini';
|
|
9
8
|
import yargs from 'yargs';
|
|
10
9
|
import { hideBin } from 'yargs/helpers';
|
|
11
10
|
import { PromptTemplate } from 'langchain/prompts';
|
|
12
|
-
import
|
|
11
|
+
import pQueue from 'p-queue';
|
|
13
12
|
import chalk from 'chalk';
|
|
14
13
|
import ora from 'ora';
|
|
15
14
|
import now from 'performance-now';
|
|
16
15
|
import prettyMilliseconds from 'pretty-ms';
|
|
17
16
|
import { Document } from 'langchain/document';
|
|
18
|
-
import { createTwoFilesPatch } from 'diff';
|
|
19
|
-
import * as util from 'util';
|
|
20
17
|
import { loadSummarizationChain, LLMChain } from 'langchain/chains';
|
|
21
18
|
import { OpenAI } from 'langchain/llms/openai';
|
|
22
19
|
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
|
|
20
|
+
import { createTwoFilesPatch } from 'diff';
|
|
23
21
|
import GPT3NodeTokenizer from 'gpt3-tokenizer';
|
|
24
|
-
import {
|
|
22
|
+
import { simpleGit } from 'simple-git';
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
25
|
* Returns a new object with all undefined keys removed
|
|
@@ -419,7 +417,7 @@ const defaultOutputCallback = (group) => {
|
|
|
419
417
|
let output = `
|
|
420
418
|
-------\n* changes in "/${group.path}"\n\n`;
|
|
421
419
|
if (group.summary) {
|
|
422
|
-
output += `${group.diffs.map((diff) => ` β’ ${diff.summary}`).join('\n')}\n\nSummary
|
|
420
|
+
output += `${group.diffs.map((diff) => ` β’ ${diff.summary}`).join('\n')}\n\nSummary:\n\n${group.summary}\n\n`;
|
|
423
421
|
}
|
|
424
422
|
else {
|
|
425
423
|
output += `${group.diffs.map((diff) => ` β’ ${diff.summary}\n\n${diff.diff}`).join('\n\n')}\n\n`;
|
|
@@ -428,7 +426,7 @@ const defaultOutputCallback = (group) => {
|
|
|
428
426
|
};
|
|
429
427
|
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
430
428
|
const logger = new Logger(config);
|
|
431
|
-
const queue = new
|
|
429
|
+
const queue = new pQueue({ concurrency: 8 });
|
|
432
430
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
433
431
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
434
432
|
// Sort by token count descending
|
|
@@ -543,61 +541,6 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
543
541
|
};
|
|
544
542
|
}
|
|
545
543
|
|
|
546
|
-
const readFile = util.promisify(fs.readFile);
|
|
547
|
-
|
|
548
|
-
const parseDefaultFileDiff = async (nodeFile, repo, headTree, index) => {
|
|
549
|
-
let result = '';
|
|
550
|
-
const diff = await Diff.treeToIndex(repo, headTree, index, {
|
|
551
|
-
flags: 33554432 /* Diff.OPTION.SHOW_UNTRACKED_CONTENT */ | 16 /* Diff.OPTION.RECURSE_UNTRACKED_DIRS */,
|
|
552
|
-
pathspec: nodeFile.filepath,
|
|
553
|
-
});
|
|
554
|
-
const patches = await diff.patches();
|
|
555
|
-
for (const patch of patches) {
|
|
556
|
-
const hunks = await patch.hunks();
|
|
557
|
-
for (const hunk of hunks) {
|
|
558
|
-
const lines = await hunk.lines();
|
|
559
|
-
result += lines.map((line) => String.fromCharCode(line.origin()) + line.content()).join('');
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return result;
|
|
563
|
-
};
|
|
564
|
-
const parseRenamedFileDiff = async (nodeFile, repo, headTree, index, logger) => {
|
|
565
|
-
let result = '';
|
|
566
|
-
const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
|
|
567
|
-
try {
|
|
568
|
-
const headEntry = await headTree.entryByPath(oldFilepath); // use old name to look up in latest commit
|
|
569
|
-
const indexEntry = index.getByPath(nodeFile.filepath); // use new name to look up in index
|
|
570
|
-
// Compare the file contents in the latest commit and index
|
|
571
|
-
const headBlob = await Blob.lookup(repo, headEntry.sha());
|
|
572
|
-
const indexBlobContent = await readFile(indexEntry.path); // read file from filesystem
|
|
573
|
-
const headContent = headBlob.content().toString();
|
|
574
|
-
const indexContent = indexBlobContent.toString();
|
|
575
|
-
if (headContent !== indexContent) {
|
|
576
|
-
result = createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent, '', '', { context: 3 });
|
|
577
|
-
// remove the first 4 lines of the patch (they contain the old and new file names)
|
|
578
|
-
result = result.split('\n').slice(4).join('\n');
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
result = 'File contents are unchanged.';
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
catch (err) {
|
|
585
|
-
logger.verbose(`Error comparing file contents for ${nodeFile.filepath}`, { color: 'red' });
|
|
586
|
-
result = 'Error comparing file contents.';
|
|
587
|
-
}
|
|
588
|
-
return result;
|
|
589
|
-
};
|
|
590
|
-
const parseFileDiff = async (nodeFile, repo, headTree, index, logger) => {
|
|
591
|
-
if (nodeFile.status === 'deleted') {
|
|
592
|
-
return 'This file has been deleted.';
|
|
593
|
-
}
|
|
594
|
-
if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
|
|
595
|
-
return parseRenamedFileDiff(nodeFile, repo, headTree, index, logger);
|
|
596
|
-
}
|
|
597
|
-
// If not deleted or renamed, get the diff from the index
|
|
598
|
-
return parseDefaultFileDiff(nodeFile, repo, headTree, index);
|
|
599
|
-
};
|
|
600
|
-
|
|
601
544
|
// TODO: Extend this to support other models! π
|
|
602
545
|
function getModel(fields, configuration) {
|
|
603
546
|
return new OpenAI(fields, configuration);
|
|
@@ -638,12 +581,54 @@ function validatePromptTemplate(text, inputVariables) {
|
|
|
638
581
|
return true;
|
|
639
582
|
}
|
|
640
583
|
|
|
584
|
+
const parseDefaultFileDiff = async (nodeFile, git) => {
|
|
585
|
+
return await git.diff(['--staged', nodeFile.filepath]);
|
|
586
|
+
};
|
|
587
|
+
const parseRenamedFileDiff = async (nodeFile, git, logger) => {
|
|
588
|
+
let result = '';
|
|
589
|
+
const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
|
|
590
|
+
try {
|
|
591
|
+
const [headContent, indexContent] = await Promise.all([
|
|
592
|
+
// git.diff(['HEAD', '-M', '--', oldFilepath]),
|
|
593
|
+
// git.diff(['-z', '-M', '--staged', nodeFile.filepath]),
|
|
594
|
+
git.show([`HEAD:${oldFilepath}`]),
|
|
595
|
+
git.show([`:${nodeFile.filepath}`]),
|
|
596
|
+
// readFile(nodeFile.filepath),
|
|
597
|
+
]);
|
|
598
|
+
if (headContent !== indexContent) {
|
|
599
|
+
result = createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent.toString(), '', '', {
|
|
600
|
+
context: 3,
|
|
601
|
+
});
|
|
602
|
+
// remove the first 4 lines of the patch (they contain the old and new file names)
|
|
603
|
+
result = result.split('\n').slice(4).join('\n');
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
result = 'File contents are unchanged.';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
catch (err) {
|
|
610
|
+
logger.verbose(`Error comparing file contents for ${nodeFile.filepath}`, { color: 'red' });
|
|
611
|
+
console.log(err);
|
|
612
|
+
result = 'Error comparing file contents.';
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
};
|
|
616
|
+
const getDiff = async (nodeFile, { git, logger, }) => {
|
|
617
|
+
if (nodeFile.status === 'deleted') {
|
|
618
|
+
return 'This file has been deleted.';
|
|
619
|
+
}
|
|
620
|
+
if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
|
|
621
|
+
const renamedDiff = await parseRenamedFileDiff(nodeFile, git, logger);
|
|
622
|
+
return renamedDiff;
|
|
623
|
+
}
|
|
624
|
+
// If not deleted or renamed, get the diff from the index
|
|
625
|
+
const defaultDiff = await parseDefaultFileDiff(nodeFile, git);
|
|
626
|
+
return defaultDiff;
|
|
627
|
+
};
|
|
628
|
+
|
|
641
629
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
642
|
-
const fileChangeParser = async (changes, { tokenizer,
|
|
630
|
+
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
643
631
|
const logger = new Logger(config);
|
|
644
|
-
const head = await repo.getHeadCommit();
|
|
645
|
-
const headTree = await head.getTree();
|
|
646
|
-
const index = await repo.refreshIndex();
|
|
647
632
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
648
633
|
const summarizationChain = getChain(model, {
|
|
649
634
|
type: 'map_reduce',
|
|
@@ -655,7 +640,7 @@ const fileChangeParser = async (changes, { tokenizer, repo, model }) => {
|
|
|
655
640
|
logger.stopTimer('Created file hierarchy');
|
|
656
641
|
// Collect diffs
|
|
657
642
|
logger.startTimer().startSpinner(`Collecting Diffs...\n`, { color: 'blue' });
|
|
658
|
-
const diffs = await collectDiffs(rootTreeNode, (path) =>
|
|
643
|
+
const diffs = await collectDiffs(rootTreeNode, (path) => getDiff(path, { git, logger }), tokenizer, logger);
|
|
659
644
|
logger.stopSpinner('Diffs Collected').stopTimer();
|
|
660
645
|
// Summarize diffs
|
|
661
646
|
logger.startTimer();
|
|
@@ -666,6 +651,7 @@ const fileChangeParser = async (changes, { tokenizer, repo, model }) => {
|
|
|
666
651
|
chain: summarizationChain,
|
|
667
652
|
});
|
|
668
653
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
654
|
+
logger.verbose(`\nSummary:\n${summary}`, { color: 'blue' });
|
|
669
655
|
return summary;
|
|
670
656
|
};
|
|
671
657
|
|
|
@@ -699,102 +685,47 @@ const getTokenizer = () => {
|
|
|
699
685
|
return tokenizer;
|
|
700
686
|
};
|
|
701
687
|
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (patch.isAdded()) {
|
|
709
|
-
summary = `added: ${newFilePath}`;
|
|
710
|
-
}
|
|
711
|
-
else if (patch.isDeleted()) {
|
|
712
|
-
summary = `deleted: ${oldFilePath}`;
|
|
713
|
-
}
|
|
714
|
-
else if (patch.isModified()) {
|
|
715
|
-
summary = `modified: ${newFilePath}`;
|
|
716
|
-
}
|
|
717
|
-
else if (patch.isRenamed()) {
|
|
718
|
-
summary = `renamed: ${oldFilePath} -> ${newFilePath}`;
|
|
719
|
-
}
|
|
720
|
-
else if (patch.isUntracked()) {
|
|
721
|
-
summary = `untracked: ${newFilePath}`;
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
summary = `unknown: ${newFilePath}`;
|
|
725
|
-
}
|
|
726
|
-
return summary;
|
|
688
|
+
const llm = async ({ llm, prompt, variables }) => {
|
|
689
|
+
const chain = new LLMChain({ llm, prompt });
|
|
690
|
+
const res = await chain.call(variables);
|
|
691
|
+
if (res.error)
|
|
692
|
+
throw new Error(res.error);
|
|
693
|
+
return res.text.trim();
|
|
727
694
|
};
|
|
728
695
|
|
|
729
|
-
const getStatus = (
|
|
696
|
+
const getStatus = (file, location = 'index') => {
|
|
697
|
+
const statusCode = file[location] ? file[location] : file.index;
|
|
730
698
|
let status;
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
status = 'unknown';
|
|
699
|
+
switch (statusCode) {
|
|
700
|
+
case 'A':
|
|
701
|
+
status = 'added';
|
|
702
|
+
break;
|
|
703
|
+
case 'D':
|
|
704
|
+
status = 'deleted';
|
|
705
|
+
break;
|
|
706
|
+
case 'M':
|
|
707
|
+
status = 'modified';
|
|
708
|
+
break;
|
|
709
|
+
case 'R':
|
|
710
|
+
status = 'renamed';
|
|
711
|
+
break;
|
|
712
|
+
case '?':
|
|
713
|
+
status = 'untracked';
|
|
714
|
+
break;
|
|
715
|
+
default:
|
|
716
|
+
status = 'unknown';
|
|
717
|
+
break;
|
|
751
718
|
}
|
|
752
719
|
return status;
|
|
753
720
|
};
|
|
754
721
|
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
];
|
|
763
|
-
/**
|
|
764
|
-
* Parse patches from a git diff.
|
|
765
|
-
*
|
|
766
|
-
* @param {ConvenientPatch[]} patches - An array of git patches.
|
|
767
|
-
* @param {string[]} [options.ignoredFiles] - An optional array of file patterns to ignore.
|
|
768
|
-
* If not provided, it defaults to the `ignoredFiles` configuration value from the app's config.
|
|
769
|
-
* @param {string[]} [options.ignoredExtensions] - An optional array of file extensions to ignore.
|
|
770
|
-
* If not provided, it defaults to the `ignoredExtensions` configuration value from the app's config.
|
|
771
|
-
* @returns {Promise<FileChange[]>} A Promise that resolves to an array of file changes.
|
|
772
|
-
**/
|
|
773
|
-
const parsePatches = async (patches, { ignoredFiles = DEFAULT_IGNORED_FILES$1, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS$1, }) => patches
|
|
774
|
-
.map((patch) => {
|
|
775
|
-
const summary = getSummaryText(patch);
|
|
776
|
-
const status = getStatus(patch);
|
|
777
|
-
return {
|
|
778
|
-
filepath: patch.newFile().path(),
|
|
779
|
-
oldFilepath: status === 'renamed' ? patch.oldFile().path() : undefined,
|
|
780
|
-
summary,
|
|
781
|
-
status,
|
|
782
|
-
};
|
|
783
|
-
})
|
|
784
|
-
.filter(Boolean)
|
|
785
|
-
// Filter out ignored files & extensions...
|
|
786
|
-
.filter(({ filepath }) => {
|
|
787
|
-
if (!filepath)
|
|
788
|
-
return false;
|
|
789
|
-
const extension = filepath.split('.').pop();
|
|
790
|
-
// Remove ignored extensions
|
|
791
|
-
if (extension && ignoredExtensions.includes(extension))
|
|
792
|
-
return false;
|
|
793
|
-
// Remove ignored files
|
|
794
|
-
if (ignoredFiles.some((pattern) => minimatch(filepath, pattern)))
|
|
795
|
-
return false;
|
|
796
|
-
return true;
|
|
797
|
-
});
|
|
722
|
+
const getSummaryText = (file, change) => {
|
|
723
|
+
const status = change.status || getStatus(file);
|
|
724
|
+
if (change.oldFilepath) {
|
|
725
|
+
return `${status}: ${change.oldFilepath} -> ${file.path}`;
|
|
726
|
+
}
|
|
727
|
+
return `${status}: ${file.path}`;
|
|
728
|
+
};
|
|
798
729
|
|
|
799
730
|
const DEFAULT_IGNORED_FILES = [
|
|
800
731
|
...(config?.ignoredFiles?.length && config?.ignoredFiles?.length > 0 ? config.ignoredFiles : []),
|
|
@@ -804,92 +735,55 @@ const DEFAULT_IGNORED_EXTENSIONS = [
|
|
|
804
735
|
? config.ignoredExtensions
|
|
805
736
|
: []),
|
|
806
737
|
];
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
});
|
|
847
|
-
const unstagedPatches = await unstagedDiff.patches();
|
|
848
|
-
unstaged = await parsePatches(unstagedPatches, { ignoredFiles, ignoredExtensions });
|
|
849
|
-
}
|
|
850
|
-
if (!ignoreUntracked) {
|
|
851
|
-
const untrackedDiff = await Diff.treeToWorkdirWithIndex(repo, tree, {
|
|
852
|
-
flags: 33554432 /* Diff.OPTION.SHOW_UNTRACKED_CONTENT */,
|
|
853
|
-
});
|
|
854
|
-
const untrackedPatches = await untrackedDiff.patches();
|
|
855
|
-
untracked = (await parsePatches(untrackedPatches, { ignoredFiles, ignoredExtensions })).filter(({ status }) => status === 'untracked');
|
|
856
|
-
}
|
|
857
|
-
const diff = await Diff.treeToIndex(repo, tree, index);
|
|
858
|
-
await diff.findSimilar({
|
|
859
|
-
flags: 1 /* Diff.FIND.RENAMES */,
|
|
738
|
+
async function getChanges(git, options = {}) {
|
|
739
|
+
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options;
|
|
740
|
+
const staged = [];
|
|
741
|
+
const unstaged = [];
|
|
742
|
+
const untracked = [];
|
|
743
|
+
const status = await git.status();
|
|
744
|
+
status.files.forEach((file) => {
|
|
745
|
+
// console.log({ file })
|
|
746
|
+
const fileChange = {
|
|
747
|
+
filepath: file.path,
|
|
748
|
+
oldFilepath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
|
|
749
|
+
};
|
|
750
|
+
// Unstaged files
|
|
751
|
+
if (file.working_dir !== '?' && file.working_dir !== ' ') {
|
|
752
|
+
fileChange.status = getStatus(file, 'working_dir');
|
|
753
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
754
|
+
unstaged.push(fileChange);
|
|
755
|
+
}
|
|
756
|
+
// Staged files
|
|
757
|
+
if (file.index !== ' ' && file.index !== '?') {
|
|
758
|
+
fileChange.status = getStatus(file);
|
|
759
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
760
|
+
staged.push(fileChange);
|
|
761
|
+
}
|
|
762
|
+
// Untracked files
|
|
763
|
+
if (file.working_dir === '?' && file.index === '?') {
|
|
764
|
+
fileChange.status = 'added';
|
|
765
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
766
|
+
untracked.push(fileChange);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
|
|
770
|
+
const filteredStaged = staged.filter((file) => {
|
|
771
|
+
const extension = path__default.extname(file.filepath).toLowerCase();
|
|
772
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.includes(file.filepath);
|
|
773
|
+
});
|
|
774
|
+
const filteredUnstaged = unstaged.filter((file) => {
|
|
775
|
+
const extension = path__default.extname(file.filepath).toLowerCase();
|
|
776
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.includes(file.filepath);
|
|
860
777
|
});
|
|
861
|
-
const patches = await diff.patches();
|
|
862
778
|
return {
|
|
863
|
-
staged:
|
|
864
|
-
unstaged,
|
|
779
|
+
staged: filteredStaged,
|
|
780
|
+
unstaged: filteredUnstaged,
|
|
865
781
|
untracked,
|
|
866
782
|
};
|
|
867
783
|
}
|
|
868
784
|
|
|
869
|
-
async
|
|
870
|
-
const
|
|
871
|
-
const index = await repo.refreshIndex();
|
|
872
|
-
await index.addAll();
|
|
873
|
-
await index.write();
|
|
874
|
-
const oid = await index.writeTree();
|
|
875
|
-
const head = await nodegit.Reference.nameToId(repo, "HEAD");
|
|
876
|
-
const parent = await repo.getCommit(head);
|
|
877
|
-
return await repo.createCommit("HEAD", author, author, commitMsg, oid, [parent]);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const llm = async ({ llm, prompt, variables }) => {
|
|
881
|
-
const chain = new LLMChain({ llm, prompt });
|
|
882
|
-
const res = await chain.call(variables);
|
|
883
|
-
if (res.error)
|
|
884
|
-
throw new Error(res.error);
|
|
885
|
-
return res.text.trim();
|
|
886
|
-
};
|
|
887
|
-
|
|
888
|
-
const noResult = async ({ repo, logger }) => {
|
|
889
|
-
const { staged, unstaged, untracked } = await getChanges(repo, {
|
|
890
|
-
ignoreUnstaged: false,
|
|
891
|
-
ignoreUntracked: false,
|
|
892
|
-
});
|
|
785
|
+
const noResult = async ({ git, logger }) => {
|
|
786
|
+
const { staged, unstaged, untracked } = await getChanges(git);
|
|
893
787
|
if (staged.length > 0) {
|
|
894
788
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
895
789
|
logger.log(`Files are likely either:\n β’ changed files are ignored\n β’ file diff is too large.`, { color: 'yellow' });
|
|
@@ -912,25 +806,26 @@ const noResult = async ({ repo, logger }) => {
|
|
|
912
806
|
process.exit(0);
|
|
913
807
|
};
|
|
914
808
|
|
|
809
|
+
async function createCommit(commitMsg, git) {
|
|
810
|
+
return await git.commit(commitMsg);
|
|
811
|
+
}
|
|
812
|
+
|
|
915
813
|
const argv = loadArgv();
|
|
916
814
|
const tokenizer = getTokenizer();
|
|
815
|
+
const git = simpleGit();
|
|
917
816
|
async function main(options) {
|
|
918
817
|
const logger = new Logger(config);
|
|
919
818
|
if (!config.openAIApiKey) {
|
|
920
819
|
logger.log(`No API Key found. ποΈπͺ`, { color: 'red' });
|
|
921
820
|
process.exit(1);
|
|
922
821
|
}
|
|
923
|
-
const repo = await Repository.open('.');
|
|
924
822
|
const model = getModel({
|
|
925
823
|
temperature: 0.4,
|
|
926
824
|
maxConcurrency: 10,
|
|
927
825
|
openAIApiKey: config.openAIApiKey,
|
|
928
826
|
});
|
|
929
827
|
const INTERACTIVE = config?.mode === 'interactive' || options.interactive;
|
|
930
|
-
const { staged: changes } = await getChanges(
|
|
931
|
-
ignoreUnstaged: true,
|
|
932
|
-
ignoreUntracked: true,
|
|
933
|
-
});
|
|
828
|
+
const { staged: changes } = await getChanges(git);
|
|
934
829
|
let summary = '';
|
|
935
830
|
let commitMsg = '';
|
|
936
831
|
let promptTemplate = config?.prompt || '';
|
|
@@ -940,11 +835,11 @@ async function main(options) {
|
|
|
940
835
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
941
836
|
color: 'blue',
|
|
942
837
|
});
|
|
943
|
-
summary = await fileChangeParser(changes, { tokenizer,
|
|
838
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
944
839
|
}
|
|
945
840
|
// Handle empty summary
|
|
946
841
|
if (!summary.length) {
|
|
947
|
-
noResult({
|
|
842
|
+
noResult({ git, logger });
|
|
948
843
|
}
|
|
949
844
|
// Prompt user for commit template prompt, if necessary
|
|
950
845
|
if (modifyPrompt) {
|
|
@@ -1059,7 +954,7 @@ async function main(options) {
|
|
|
1059
954
|
// Handle resulting commit message
|
|
1060
955
|
switch (MODE) {
|
|
1061
956
|
case 'interactive':
|
|
1062
|
-
await createCommit(commitMsg,
|
|
957
|
+
await createCommit(commitMsg, git);
|
|
1063
958
|
logSuccess();
|
|
1064
959
|
break;
|
|
1065
960
|
case 'stdout':
|