git-coco 0.1.1 → 0.2.1
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 +10 -2
- package/dist/index.esm.mjs +139 -251
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +138 -252
- 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 +10 -11
- 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/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var nodegit = require('nodegit');
|
|
5
4
|
var prompts$1 = require('@inquirer/prompts');
|
|
6
5
|
var fs = require('fs');
|
|
7
6
|
var os = require('os');
|
|
@@ -16,13 +15,13 @@ var ora = require('ora');
|
|
|
16
15
|
var now = require('performance-now');
|
|
17
16
|
var prettyMilliseconds = require('pretty-ms');
|
|
18
17
|
var document = require('langchain/document');
|
|
19
|
-
var diff = require('diff');
|
|
20
|
-
var util = require('util');
|
|
21
18
|
var chains = require('langchain/chains');
|
|
22
19
|
var openai = require('langchain/llms/openai');
|
|
23
20
|
var text_splitter = require('langchain/text_splitter');
|
|
21
|
+
var diff = require('diff');
|
|
24
22
|
var GPT3NodeTokenizer = require('gpt3-tokenizer');
|
|
25
23
|
var minimatch = require('minimatch');
|
|
24
|
+
var simpleGit = require('simple-git');
|
|
26
25
|
|
|
27
26
|
function _interopNamespaceDefault(e) {
|
|
28
27
|
var n = Object.create(null);
|
|
@@ -41,12 +40,10 @@ function _interopNamespaceDefault(e) {
|
|
|
41
40
|
return Object.freeze(n);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
var nodegit__namespace = /*#__PURE__*/_interopNamespaceDefault(nodegit);
|
|
45
43
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
46
44
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
47
45
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
48
46
|
var ini__namespace = /*#__PURE__*/_interopNamespaceDefault(ini);
|
|
49
|
-
var util__namespace = /*#__PURE__*/_interopNamespaceDefault(util);
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
49
|
* Returns a new object with all undefined keys removed
|
|
@@ -444,7 +441,7 @@ const defaultOutputCallback = (group) => {
|
|
|
444
441
|
let output = `
|
|
445
442
|
-------\n* changes in "/${group.path}"\n\n`;
|
|
446
443
|
if (group.summary) {
|
|
447
|
-
output += `${group.diffs.map((diff) => ` • ${diff.summary}`).join('\n')}\n\nSummary
|
|
444
|
+
output += `${group.diffs.map((diff) => ` • ${diff.summary}`).join('\n')}\n\nSummary:\n\n${group.summary}\n\n`;
|
|
448
445
|
}
|
|
449
446
|
else {
|
|
450
447
|
output += `${group.diffs.map((diff) => ` • ${diff.summary}\n\n${diff.diff}`).join('\n\n')}\n\n`;
|
|
@@ -568,61 +565,6 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
568
565
|
};
|
|
569
566
|
}
|
|
570
567
|
|
|
571
|
-
const readFile = util__namespace.promisify(fs__namespace.readFile);
|
|
572
|
-
|
|
573
|
-
const parseDefaultFileDiff = async (nodeFile, repo, headTree, index) => {
|
|
574
|
-
let result = '';
|
|
575
|
-
const diff = await nodegit.Diff.treeToIndex(repo, headTree, index, {
|
|
576
|
-
flags: 33554432 /* Diff.OPTION.SHOW_UNTRACKED_CONTENT */ | 16 /* Diff.OPTION.RECURSE_UNTRACKED_DIRS */,
|
|
577
|
-
pathspec: nodeFile.filepath,
|
|
578
|
-
});
|
|
579
|
-
const patches = await diff.patches();
|
|
580
|
-
for (const patch of patches) {
|
|
581
|
-
const hunks = await patch.hunks();
|
|
582
|
-
for (const hunk of hunks) {
|
|
583
|
-
const lines = await hunk.lines();
|
|
584
|
-
result += lines.map((line) => String.fromCharCode(line.origin()) + line.content()).join('');
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return result;
|
|
588
|
-
};
|
|
589
|
-
const parseRenamedFileDiff = async (nodeFile, repo, headTree, index, logger) => {
|
|
590
|
-
let result = '';
|
|
591
|
-
const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
|
|
592
|
-
try {
|
|
593
|
-
const headEntry = await headTree.entryByPath(oldFilepath); // use old name to look up in latest commit
|
|
594
|
-
const indexEntry = index.getByPath(nodeFile.filepath); // use new name to look up in index
|
|
595
|
-
// Compare the file contents in the latest commit and index
|
|
596
|
-
const headBlob = await nodegit.Blob.lookup(repo, headEntry.sha());
|
|
597
|
-
const indexBlobContent = await readFile(indexEntry.path); // read file from filesystem
|
|
598
|
-
const headContent = headBlob.content().toString();
|
|
599
|
-
const indexContent = indexBlobContent.toString();
|
|
600
|
-
if (headContent !== indexContent) {
|
|
601
|
-
result = diff.createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent, '', '', { context: 3 });
|
|
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
|
-
result = 'Error comparing file contents.';
|
|
612
|
-
}
|
|
613
|
-
return result;
|
|
614
|
-
};
|
|
615
|
-
const parseFileDiff = async (nodeFile, repo, headTree, index, logger) => {
|
|
616
|
-
if (nodeFile.status === 'deleted') {
|
|
617
|
-
return 'This file has been deleted.';
|
|
618
|
-
}
|
|
619
|
-
if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
|
|
620
|
-
return parseRenamedFileDiff(nodeFile, repo, headTree, index, logger);
|
|
621
|
-
}
|
|
622
|
-
// If not deleted or renamed, get the diff from the index
|
|
623
|
-
return parseDefaultFileDiff(nodeFile, repo, headTree, index);
|
|
624
|
-
};
|
|
625
|
-
|
|
626
568
|
// TODO: Extend this to support other models! 🎉
|
|
627
569
|
function getModel(fields, configuration) {
|
|
628
570
|
return new openai.OpenAI(fields, configuration);
|
|
@@ -663,12 +605,50 @@ function validatePromptTemplate(text, inputVariables) {
|
|
|
663
605
|
return true;
|
|
664
606
|
}
|
|
665
607
|
|
|
608
|
+
const parseDefaultFileDiff = async (nodeFile, git) => {
|
|
609
|
+
return await git.diff(['--staged', nodeFile.filepath]);
|
|
610
|
+
};
|
|
611
|
+
const parseRenamedFileDiff = async (nodeFile, git, logger) => {
|
|
612
|
+
let result = '';
|
|
613
|
+
const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
|
|
614
|
+
try {
|
|
615
|
+
const [headContent, indexContent] = await Promise.all([
|
|
616
|
+
git.show([`HEAD:${oldFilepath}`]),
|
|
617
|
+
git.show([`:${nodeFile.filepath}`]),
|
|
618
|
+
]);
|
|
619
|
+
if (headContent !== indexContent) {
|
|
620
|
+
result = diff.createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent, '', '', {
|
|
621
|
+
context: 3,
|
|
622
|
+
});
|
|
623
|
+
// remove the first 4 lines of the patch (they contain the old and new file names)
|
|
624
|
+
result = result.split('\n').slice(4).join('\n');
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
result = 'File contents are unchanged.';
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
logger.verbose(`Error comparing file contents for ${nodeFile.filepath}`, { color: 'red' });
|
|
632
|
+
result = 'Error comparing file contents.';
|
|
633
|
+
}
|
|
634
|
+
return result;
|
|
635
|
+
};
|
|
636
|
+
const getDiff = async (nodeFile, { git, logger, }) => {
|
|
637
|
+
if (nodeFile.status === 'deleted') {
|
|
638
|
+
return 'This file has been deleted.';
|
|
639
|
+
}
|
|
640
|
+
if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
|
|
641
|
+
const renamedDiff = await parseRenamedFileDiff(nodeFile, git, logger);
|
|
642
|
+
return renamedDiff;
|
|
643
|
+
}
|
|
644
|
+
// If not deleted or renamed, get the diff from the index
|
|
645
|
+
const defaultDiff = await parseDefaultFileDiff(nodeFile, git);
|
|
646
|
+
return defaultDiff;
|
|
647
|
+
};
|
|
648
|
+
|
|
666
649
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
667
|
-
const fileChangeParser = async (changes, { tokenizer,
|
|
650
|
+
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
668
651
|
const logger = new Logger(config);
|
|
669
|
-
const head = await repo.getHeadCommit();
|
|
670
|
-
const headTree = await head.getTree();
|
|
671
|
-
const index = await repo.refreshIndex();
|
|
672
652
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
673
653
|
const summarizationChain = getChain(model, {
|
|
674
654
|
type: 'map_reduce',
|
|
@@ -680,7 +660,7 @@ const fileChangeParser = async (changes, { tokenizer, repo, model }) => {
|
|
|
680
660
|
logger.stopTimer('Created file hierarchy');
|
|
681
661
|
// Collect diffs
|
|
682
662
|
logger.startTimer().startSpinner(`Collecting Diffs...\n`, { color: 'blue' });
|
|
683
|
-
const diffs = await collectDiffs(rootTreeNode, (path) =>
|
|
663
|
+
const diffs = await collectDiffs(rootTreeNode, (path) => getDiff(path, { git, logger }), tokenizer, logger);
|
|
684
664
|
logger.stopSpinner('Diffs Collected').stopTimer();
|
|
685
665
|
// Summarize diffs
|
|
686
666
|
logger.startTimer();
|
|
@@ -724,197 +704,102 @@ const getTokenizer = () => {
|
|
|
724
704
|
return tokenizer;
|
|
725
705
|
};
|
|
726
706
|
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (patch.isAdded()) {
|
|
734
|
-
summary = `added: ${newFilePath}`;
|
|
735
|
-
}
|
|
736
|
-
else if (patch.isDeleted()) {
|
|
737
|
-
summary = `deleted: ${oldFilePath}`;
|
|
738
|
-
}
|
|
739
|
-
else if (patch.isModified()) {
|
|
740
|
-
summary = `modified: ${newFilePath}`;
|
|
741
|
-
}
|
|
742
|
-
else if (patch.isRenamed()) {
|
|
743
|
-
summary = `renamed: ${oldFilePath} -> ${newFilePath}`;
|
|
744
|
-
}
|
|
745
|
-
else if (patch.isUntracked()) {
|
|
746
|
-
summary = `untracked: ${newFilePath}`;
|
|
747
|
-
}
|
|
748
|
-
else {
|
|
749
|
-
summary = `unknown: ${newFilePath}`;
|
|
750
|
-
}
|
|
751
|
-
return summary;
|
|
707
|
+
const llm = async ({ llm, prompt, variables }) => {
|
|
708
|
+
const chain = new chains.LLMChain({ llm, prompt });
|
|
709
|
+
const res = await chain.call(variables);
|
|
710
|
+
if (res.error)
|
|
711
|
+
throw new Error(res.error);
|
|
712
|
+
return res.text.trim();
|
|
752
713
|
};
|
|
753
714
|
|
|
754
|
-
const getStatus = (
|
|
715
|
+
const getStatus = (file, location = 'index') => {
|
|
716
|
+
const statusCode = file[location] ? file[location] : file.index;
|
|
755
717
|
let status;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
status = 'unknown';
|
|
718
|
+
switch (statusCode) {
|
|
719
|
+
case 'A':
|
|
720
|
+
status = 'added';
|
|
721
|
+
break;
|
|
722
|
+
case 'D':
|
|
723
|
+
status = 'deleted';
|
|
724
|
+
break;
|
|
725
|
+
case 'M':
|
|
726
|
+
status = 'modified';
|
|
727
|
+
break;
|
|
728
|
+
case 'R':
|
|
729
|
+
status = 'renamed';
|
|
730
|
+
break;
|
|
731
|
+
case '?':
|
|
732
|
+
status = 'untracked';
|
|
733
|
+
break;
|
|
734
|
+
default:
|
|
735
|
+
status = 'unknown';
|
|
736
|
+
break;
|
|
776
737
|
}
|
|
777
738
|
return status;
|
|
778
739
|
};
|
|
779
740
|
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
...(config?.ignoredExtensions?.length && config?.ignoredExtensions?.length > 0
|
|
785
|
-
? config.ignoredExtensions
|
|
786
|
-
: []),
|
|
787
|
-
];
|
|
788
|
-
/**
|
|
789
|
-
* Parse patches from a git diff.
|
|
790
|
-
*
|
|
791
|
-
* @param {ConvenientPatch[]} patches - An array of git patches.
|
|
792
|
-
* @param {string[]} [options.ignoredFiles] - An optional array of file patterns to ignore.
|
|
793
|
-
* If not provided, it defaults to the `ignoredFiles` configuration value from the app's config.
|
|
794
|
-
* @param {string[]} [options.ignoredExtensions] - An optional array of file extensions to ignore.
|
|
795
|
-
* If not provided, it defaults to the `ignoredExtensions` configuration value from the app's config.
|
|
796
|
-
* @returns {Promise<FileChange[]>} A Promise that resolves to an array of file changes.
|
|
797
|
-
**/
|
|
798
|
-
const parsePatches = async (patches, { ignoredFiles = DEFAULT_IGNORED_FILES$1, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS$1, }) => patches
|
|
799
|
-
.map((patch) => {
|
|
800
|
-
const summary = getSummaryText(patch);
|
|
801
|
-
const status = getStatus(patch);
|
|
802
|
-
return {
|
|
803
|
-
filepath: patch.newFile().path(),
|
|
804
|
-
oldFilepath: status === 'renamed' ? patch.oldFile().path() : undefined,
|
|
805
|
-
summary,
|
|
806
|
-
status,
|
|
807
|
-
};
|
|
808
|
-
})
|
|
809
|
-
.filter(Boolean)
|
|
810
|
-
// Filter out ignored files & extensions...
|
|
811
|
-
.filter(({ filepath }) => {
|
|
812
|
-
if (!filepath)
|
|
813
|
-
return false;
|
|
814
|
-
const extension = filepath.split('.').pop();
|
|
815
|
-
// Remove ignored extensions
|
|
816
|
-
if (extension && ignoredExtensions.includes(extension))
|
|
817
|
-
return false;
|
|
818
|
-
// Remove ignored files
|
|
819
|
-
if (ignoredFiles.some((pattern) => minimatch.minimatch(filepath, pattern)))
|
|
820
|
-
return false;
|
|
821
|
-
return true;
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
const DEFAULT_IGNORED_FILES = [
|
|
825
|
-
...(config?.ignoredFiles?.length && config?.ignoredFiles?.length > 0 ? config.ignoredFiles : []),
|
|
826
|
-
];
|
|
827
|
-
const DEFAULT_IGNORED_EXTENSIONS = [
|
|
828
|
-
...(config?.ignoredExtensions?.length && config?.ignoredExtensions?.length > 0
|
|
829
|
-
? config.ignoredExtensions
|
|
830
|
-
: []),
|
|
831
|
-
];
|
|
832
|
-
/**
|
|
833
|
-
* The 'git status' for coco
|
|
834
|
-
*
|
|
835
|
-
* Get paths of changed files in the Git repository, excluding ignored files and extensions.
|
|
836
|
-
*
|
|
837
|
-
* @param {string[]} [options.ignoredFiles] - An optional array of file patterns to ignore.
|
|
838
|
-
* If not provided, it defaults to the `ignoredFiles` configuration value from the app's config.
|
|
839
|
-
* @param {string[]} [options.ignoredExtensions] - An optional array of file extensions to ignore.
|
|
840
|
-
* If not provided, it defaults to the `ignoredExtensions` configuration value from the app's config.
|
|
841
|
-
* @returns {Promise<GetChangesResult>} A Promise that resolves to an array of changed file paths.
|
|
842
|
-
*
|
|
843
|
-
* @example
|
|
844
|
-
* const changes = await getStagedChanges()
|
|
845
|
-
* console.log(changes)
|
|
846
|
-
* // {
|
|
847
|
-
* // staged: [
|
|
848
|
-
* // {
|
|
849
|
-
* // filepath: 'src/index.ts',
|
|
850
|
-
* // action: 'modified'
|
|
851
|
-
* // },
|
|
852
|
-
* // ],
|
|
853
|
-
* // unstaged: [
|
|
854
|
-
* // {
|
|
855
|
-
* // filepath: 'src/index.test.ts',
|
|
856
|
-
* // action: 'added'
|
|
857
|
-
* // }
|
|
858
|
-
* // ]
|
|
859
|
-
* // }
|
|
860
|
-
*/
|
|
861
|
-
async function getChanges(repo, options = {}) {
|
|
862
|
-
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS, ignoreUnstaged, ignoreUntracked, } = options;
|
|
863
|
-
const head = await repo.getHeadCommit();
|
|
864
|
-
const index = await repo.refreshIndex();
|
|
865
|
-
const tree = await (head ? await head.getTree() : nodegit.Tree.lookup(repo, EMPTY_GIT_TREE_HASH));
|
|
866
|
-
let unstaged = [];
|
|
867
|
-
let untracked = [];
|
|
868
|
-
if (!ignoreUnstaged) {
|
|
869
|
-
const unstagedDiff = await nodegit.Diff.indexToWorkdir(repo, index, {
|
|
870
|
-
flags: 16 /* Diff.OPTION.RECURSE_UNTRACKED_DIRS */,
|
|
871
|
-
});
|
|
872
|
-
const unstagedPatches = await unstagedDiff.patches();
|
|
873
|
-
unstaged = await parsePatches(unstagedPatches, { ignoredFiles, ignoredExtensions });
|
|
874
|
-
}
|
|
875
|
-
if (!ignoreUntracked) {
|
|
876
|
-
const untrackedDiff = await nodegit.Diff.treeToWorkdirWithIndex(repo, tree, {
|
|
877
|
-
flags: 33554432 /* Diff.OPTION.SHOW_UNTRACKED_CONTENT */,
|
|
878
|
-
});
|
|
879
|
-
const untrackedPatches = await untrackedDiff.patches();
|
|
880
|
-
untracked = (await parsePatches(untrackedPatches, { ignoredFiles, ignoredExtensions })).filter(({ status }) => status === 'untracked');
|
|
741
|
+
const getSummaryText = (file, change) => {
|
|
742
|
+
const status = change.status || getStatus(file);
|
|
743
|
+
if (change.oldFilepath) {
|
|
744
|
+
return `${status}: ${change.oldFilepath} -> ${file.path}`;
|
|
881
745
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
746
|
+
return `${status}: ${file.path}`;
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
|
|
750
|
+
const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
|
|
751
|
+
async function getChanges(git, options = {}) {
|
|
752
|
+
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options;
|
|
753
|
+
const staged = [];
|
|
754
|
+
const unstaged = [];
|
|
755
|
+
const untracked = [];
|
|
756
|
+
const status = await git.status();
|
|
757
|
+
status.files.forEach((file) => {
|
|
758
|
+
const fileChange = {
|
|
759
|
+
filepath: file.path,
|
|
760
|
+
oldFilepath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
|
|
761
|
+
};
|
|
762
|
+
// Unstaged files
|
|
763
|
+
if (file.working_dir !== '?' && file.working_dir !== ' ') {
|
|
764
|
+
fileChange.status = getStatus(file, 'working_dir');
|
|
765
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
766
|
+
unstaged.push(fileChange);
|
|
767
|
+
}
|
|
768
|
+
// Staged files
|
|
769
|
+
if (file.index !== ' ' && file.index !== '?') {
|
|
770
|
+
fileChange.status = getStatus(file);
|
|
771
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
772
|
+
staged.push(fileChange);
|
|
773
|
+
}
|
|
774
|
+
// Untracked files
|
|
775
|
+
if (file.working_dir === '?' && file.index === '?') {
|
|
776
|
+
fileChange.status = 'added';
|
|
777
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
778
|
+
untracked.push(fileChange);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
|
|
782
|
+
const filteredStaged = staged.filter((file) => {
|
|
783
|
+
const extension = path.extname(file.filepath).toLowerCase();
|
|
784
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
|
|
785
|
+
});
|
|
786
|
+
const filteredUnstaged = unstaged.filter((file) => {
|
|
787
|
+
const extension = path.extname(file.filepath).toLowerCase();
|
|
788
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
|
|
789
|
+
});
|
|
790
|
+
const filteredUntracked = untracked.filter((file) => {
|
|
791
|
+
const extension = path.extname(file.filepath).toLowerCase();
|
|
792
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
|
|
885
793
|
});
|
|
886
|
-
const patches = await diff.patches();
|
|
887
794
|
return {
|
|
888
|
-
staged:
|
|
889
|
-
unstaged,
|
|
890
|
-
untracked,
|
|
795
|
+
staged: filteredStaged,
|
|
796
|
+
unstaged: filteredUnstaged,
|
|
797
|
+
untracked: filteredUntracked,
|
|
891
798
|
};
|
|
892
799
|
}
|
|
893
800
|
|
|
894
|
-
async
|
|
895
|
-
const
|
|
896
|
-
const index = await repo.refreshIndex();
|
|
897
|
-
await index.addAll();
|
|
898
|
-
await index.write();
|
|
899
|
-
const oid = await index.writeTree();
|
|
900
|
-
const head = await nodegit__namespace.Reference.nameToId(repo, "HEAD");
|
|
901
|
-
const parent = await repo.getCommit(head);
|
|
902
|
-
return await repo.createCommit("HEAD", author, author, commitMsg, oid, [parent]);
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const llm = async ({ llm, prompt, variables }) => {
|
|
906
|
-
const chain = new chains.LLMChain({ llm, prompt });
|
|
907
|
-
const res = await chain.call(variables);
|
|
908
|
-
if (res.error)
|
|
909
|
-
throw new Error(res.error);
|
|
910
|
-
return res.text.trim();
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
const noResult = async ({ repo, logger }) => {
|
|
914
|
-
const { staged, unstaged, untracked } = await getChanges(repo, {
|
|
915
|
-
ignoreUnstaged: false,
|
|
916
|
-
ignoreUntracked: false,
|
|
917
|
-
});
|
|
801
|
+
const noResult = async ({ git, logger }) => {
|
|
802
|
+
const { staged, unstaged, untracked } = await getChanges(git);
|
|
918
803
|
if (staged.length > 0) {
|
|
919
804
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
920
805
|
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
@@ -937,25 +822,26 @@ const noResult = async ({ repo, logger }) => {
|
|
|
937
822
|
process.exit(0);
|
|
938
823
|
};
|
|
939
824
|
|
|
825
|
+
async function createCommit(commitMsg, git) {
|
|
826
|
+
return await git.commit(commitMsg);
|
|
827
|
+
}
|
|
828
|
+
|
|
940
829
|
const argv = loadArgv();
|
|
941
830
|
const tokenizer = getTokenizer();
|
|
831
|
+
const git = simpleGit.simpleGit();
|
|
942
832
|
async function main(options) {
|
|
943
833
|
const logger = new Logger(config);
|
|
944
834
|
if (!config.openAIApiKey) {
|
|
945
835
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
946
836
|
process.exit(1);
|
|
947
837
|
}
|
|
948
|
-
const repo = await nodegit.Repository.open('.');
|
|
949
838
|
const model = getModel({
|
|
950
839
|
temperature: 0.4,
|
|
951
840
|
maxConcurrency: 10,
|
|
952
841
|
openAIApiKey: config.openAIApiKey,
|
|
953
842
|
});
|
|
954
843
|
const INTERACTIVE = config?.mode === 'interactive' || options.interactive;
|
|
955
|
-
const { staged: changes } = await getChanges(
|
|
956
|
-
ignoreUnstaged: true,
|
|
957
|
-
ignoreUntracked: true,
|
|
958
|
-
});
|
|
844
|
+
const { staged: changes } = await getChanges(git);
|
|
959
845
|
let summary = '';
|
|
960
846
|
let commitMsg = '';
|
|
961
847
|
let promptTemplate = config?.prompt || '';
|
|
@@ -965,11 +851,11 @@ async function main(options) {
|
|
|
965
851
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
966
852
|
color: 'blue',
|
|
967
853
|
});
|
|
968
|
-
summary = await fileChangeParser(changes, { tokenizer,
|
|
854
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
969
855
|
}
|
|
970
856
|
// Handle empty summary
|
|
971
857
|
if (!summary.length) {
|
|
972
|
-
noResult({
|
|
858
|
+
noResult({ git, logger });
|
|
973
859
|
}
|
|
974
860
|
// Prompt user for commit template prompt, if necessary
|
|
975
861
|
if (modifyPrompt) {
|
|
@@ -1084,7 +970,7 @@ async function main(options) {
|
|
|
1084
970
|
// Handle resulting commit message
|
|
1085
971
|
switch (MODE) {
|
|
1086
972
|
case 'interactive':
|
|
1087
|
-
await createCommit(commitMsg,
|
|
973
|
+
await createCommit(commitMsg, git);
|
|
1088
974
|
logSuccess();
|
|
1089
975
|
break;
|
|
1090
976
|
case 'stdout':
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Repository } from 'nodegit';
|
|
2
1
|
import { Logger } from '../utils/logger';
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { SimpleGit } from 'simple-git';
|
|
3
|
+
export declare const noResult: ({ git, logger }: {
|
|
4
|
+
git: SimpleGit;
|
|
5
5
|
logger: Logger;
|
|
6
|
-
}
|
|
7
|
-
export declare const noResult: ({ repo, logger }: NoResultInput) => Promise<never>;
|
|
8
|
-
export {};
|
|
6
|
+
}) => Promise<never>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SimpleGit } from 'simple-git';
|
|
2
|
+
import { FileChange } from '../types';
|
|
3
|
+
export type GetChangesArgs = {
|
|
4
|
+
ignoredFiles?: string[];
|
|
5
|
+
ignoredExtensions?: string[];
|
|
6
|
+
};
|
|
7
|
+
export type GetChangesResult = {
|
|
8
|
+
staged: FileChange[];
|
|
9
|
+
unstaged?: FileChange[];
|
|
10
|
+
untracked?: FileChange[];
|
|
11
|
+
};
|
|
12
|
+
export declare function getChanges(git: SimpleGit, options?: GetChangesArgs): Promise<GetChangesResult>;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import GPT3Tokenizer from 'gpt3-tokenizer';
|
|
2
|
-
import { Repository } from 'nodegit';
|
|
3
2
|
import { getModel } from './langchain/utils';
|
|
4
|
-
|
|
3
|
+
import { SimpleGit } from 'simple-git';
|
|
4
|
+
export type FileChangeStatus = 'modified' | 'renamed' | 'added' | 'deleted' | 'untracked' | 'unknown';
|
|
5
5
|
export interface FileChange {
|
|
6
6
|
summary: string;
|
|
7
7
|
filepath: string;
|
|
@@ -29,6 +29,6 @@ export interface BaseParser {
|
|
|
29
29
|
(changes: FileChange[], options: {
|
|
30
30
|
tokenizer: GPT3Tokenizer;
|
|
31
31
|
model: ReturnType<typeof getModel>;
|
|
32
|
-
|
|
32
|
+
git: SimpleGit;
|
|
33
33
|
}): Promise<string>;
|
|
34
34
|
}
|