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/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');
|
|
@@ -10,19 +9,18 @@ var ini = require('ini');
|
|
|
10
9
|
var yargs = require('yargs');
|
|
11
10
|
var helpers = require('yargs/helpers');
|
|
12
11
|
var prompts = require('langchain/prompts');
|
|
13
|
-
var
|
|
12
|
+
var pQueue = require('p-queue');
|
|
14
13
|
var chalk = require('chalk');
|
|
15
14
|
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
|
-
var
|
|
23
|
+
var simpleGit = require('simple-git');
|
|
26
24
|
|
|
27
25
|
function _interopNamespaceDefault(e) {
|
|
28
26
|
var n = Object.create(null);
|
|
@@ -41,12 +39,10 @@ function _interopNamespaceDefault(e) {
|
|
|
41
39
|
return Object.freeze(n);
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
var nodegit__namespace = /*#__PURE__*/_interopNamespaceDefault(nodegit);
|
|
45
42
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
46
43
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
47
44
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
48
45
|
var ini__namespace = /*#__PURE__*/_interopNamespaceDefault(ini);
|
|
49
|
-
var util__namespace = /*#__PURE__*/_interopNamespaceDefault(util);
|
|
50
46
|
|
|
51
47
|
/**
|
|
52
48
|
* Returns a new object with all undefined keys removed
|
|
@@ -444,7 +440,7 @@ const defaultOutputCallback = (group) => {
|
|
|
444
440
|
let output = `
|
|
445
441
|
-------\n* changes in "/${group.path}"\n\n`;
|
|
446
442
|
if (group.summary) {
|
|
447
|
-
output += `${group.diffs.map((diff) => ` • ${diff.summary}`).join('\n')}\n\nSummary
|
|
443
|
+
output += `${group.diffs.map((diff) => ` • ${diff.summary}`).join('\n')}\n\nSummary:\n\n${group.summary}\n\n`;
|
|
448
444
|
}
|
|
449
445
|
else {
|
|
450
446
|
output += `${group.diffs.map((diff) => ` • ${diff.summary}\n\n${diff.diff}`).join('\n\n')}\n\n`;
|
|
@@ -453,7 +449,7 @@ const defaultOutputCallback = (group) => {
|
|
|
453
449
|
};
|
|
454
450
|
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
455
451
|
const logger = new Logger(config);
|
|
456
|
-
const queue = new
|
|
452
|
+
const queue = new pQueue({ concurrency: 8 });
|
|
457
453
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
458
454
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
459
455
|
// Sort by token count descending
|
|
@@ -568,61 +564,6 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
568
564
|
};
|
|
569
565
|
}
|
|
570
566
|
|
|
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
567
|
// TODO: Extend this to support other models! 🎉
|
|
627
568
|
function getModel(fields, configuration) {
|
|
628
569
|
return new openai.OpenAI(fields, configuration);
|
|
@@ -663,12 +604,54 @@ function validatePromptTemplate(text, inputVariables) {
|
|
|
663
604
|
return true;
|
|
664
605
|
}
|
|
665
606
|
|
|
607
|
+
const parseDefaultFileDiff = async (nodeFile, git) => {
|
|
608
|
+
return await git.diff(['--staged', nodeFile.filepath]);
|
|
609
|
+
};
|
|
610
|
+
const parseRenamedFileDiff = async (nodeFile, git, logger) => {
|
|
611
|
+
let result = '';
|
|
612
|
+
const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
|
|
613
|
+
try {
|
|
614
|
+
const [headContent, indexContent] = await Promise.all([
|
|
615
|
+
// git.diff(['HEAD', '-M', '--', oldFilepath]),
|
|
616
|
+
// git.diff(['-z', '-M', '--staged', nodeFile.filepath]),
|
|
617
|
+
git.show([`HEAD:${oldFilepath}`]),
|
|
618
|
+
git.show([`:${nodeFile.filepath}`]),
|
|
619
|
+
// readFile(nodeFile.filepath),
|
|
620
|
+
]);
|
|
621
|
+
if (headContent !== indexContent) {
|
|
622
|
+
result = diff.createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent.toString(), '', '', {
|
|
623
|
+
context: 3,
|
|
624
|
+
});
|
|
625
|
+
// remove the first 4 lines of the patch (they contain the old and new file names)
|
|
626
|
+
result = result.split('\n').slice(4).join('\n');
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
result = 'File contents are unchanged.';
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
logger.verbose(`Error comparing file contents for ${nodeFile.filepath}`, { color: 'red' });
|
|
634
|
+
console.log(err);
|
|
635
|
+
result = 'Error comparing file contents.';
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
638
|
+
};
|
|
639
|
+
const getDiff = async (nodeFile, { git, logger, }) => {
|
|
640
|
+
if (nodeFile.status === 'deleted') {
|
|
641
|
+
return 'This file has been deleted.';
|
|
642
|
+
}
|
|
643
|
+
if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
|
|
644
|
+
const renamedDiff = await parseRenamedFileDiff(nodeFile, git, logger);
|
|
645
|
+
return renamedDiff;
|
|
646
|
+
}
|
|
647
|
+
// If not deleted or renamed, get the diff from the index
|
|
648
|
+
const defaultDiff = await parseDefaultFileDiff(nodeFile, git);
|
|
649
|
+
return defaultDiff;
|
|
650
|
+
};
|
|
651
|
+
|
|
666
652
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
667
|
-
const fileChangeParser = async (changes, { tokenizer,
|
|
653
|
+
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
668
654
|
const logger = new Logger(config);
|
|
669
|
-
const head = await repo.getHeadCommit();
|
|
670
|
-
const headTree = await head.getTree();
|
|
671
|
-
const index = await repo.refreshIndex();
|
|
672
655
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
673
656
|
const summarizationChain = getChain(model, {
|
|
674
657
|
type: 'map_reduce',
|
|
@@ -680,7 +663,7 @@ const fileChangeParser = async (changes, { tokenizer, repo, model }) => {
|
|
|
680
663
|
logger.stopTimer('Created file hierarchy');
|
|
681
664
|
// Collect diffs
|
|
682
665
|
logger.startTimer().startSpinner(`Collecting Diffs...\n`, { color: 'blue' });
|
|
683
|
-
const diffs = await collectDiffs(rootTreeNode, (path) =>
|
|
666
|
+
const diffs = await collectDiffs(rootTreeNode, (path) => getDiff(path, { git, logger }), tokenizer, logger);
|
|
684
667
|
logger.stopSpinner('Diffs Collected').stopTimer();
|
|
685
668
|
// Summarize diffs
|
|
686
669
|
logger.startTimer();
|
|
@@ -691,6 +674,7 @@ const fileChangeParser = async (changes, { tokenizer, repo, model }) => {
|
|
|
691
674
|
chain: summarizationChain,
|
|
692
675
|
});
|
|
693
676
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
677
|
+
logger.verbose(`\nSummary:\n${summary}`, { color: 'blue' });
|
|
694
678
|
return summary;
|
|
695
679
|
};
|
|
696
680
|
|
|
@@ -724,102 +708,47 @@ const getTokenizer = () => {
|
|
|
724
708
|
return tokenizer;
|
|
725
709
|
};
|
|
726
710
|
|
|
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;
|
|
711
|
+
const llm = async ({ llm, prompt, variables }) => {
|
|
712
|
+
const chain = new chains.LLMChain({ llm, prompt });
|
|
713
|
+
const res = await chain.call(variables);
|
|
714
|
+
if (res.error)
|
|
715
|
+
throw new Error(res.error);
|
|
716
|
+
return res.text.trim();
|
|
752
717
|
};
|
|
753
718
|
|
|
754
|
-
const getStatus = (
|
|
719
|
+
const getStatus = (file, location = 'index') => {
|
|
720
|
+
const statusCode = file[location] ? file[location] : file.index;
|
|
755
721
|
let status;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
status = 'unknown';
|
|
722
|
+
switch (statusCode) {
|
|
723
|
+
case 'A':
|
|
724
|
+
status = 'added';
|
|
725
|
+
break;
|
|
726
|
+
case 'D':
|
|
727
|
+
status = 'deleted';
|
|
728
|
+
break;
|
|
729
|
+
case 'M':
|
|
730
|
+
status = 'modified';
|
|
731
|
+
break;
|
|
732
|
+
case 'R':
|
|
733
|
+
status = 'renamed';
|
|
734
|
+
break;
|
|
735
|
+
case '?':
|
|
736
|
+
status = 'untracked';
|
|
737
|
+
break;
|
|
738
|
+
default:
|
|
739
|
+
status = 'unknown';
|
|
740
|
+
break;
|
|
776
741
|
}
|
|
777
742
|
return status;
|
|
778
743
|
};
|
|
779
744
|
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
});
|
|
745
|
+
const getSummaryText = (file, change) => {
|
|
746
|
+
const status = change.status || getStatus(file);
|
|
747
|
+
if (change.oldFilepath) {
|
|
748
|
+
return `${status}: ${change.oldFilepath} -> ${file.path}`;
|
|
749
|
+
}
|
|
750
|
+
return `${status}: ${file.path}`;
|
|
751
|
+
};
|
|
823
752
|
|
|
824
753
|
const DEFAULT_IGNORED_FILES = [
|
|
825
754
|
...(config?.ignoredFiles?.length && config?.ignoredFiles?.length > 0 ? config.ignoredFiles : []),
|
|
@@ -829,92 +758,55 @@ const DEFAULT_IGNORED_EXTENSIONS = [
|
|
|
829
758
|
? config.ignoredExtensions
|
|
830
759
|
: []),
|
|
831
760
|
];
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
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');
|
|
881
|
-
}
|
|
882
|
-
const diff = await nodegit.Diff.treeToIndex(repo, tree, index);
|
|
883
|
-
await diff.findSimilar({
|
|
884
|
-
flags: 1 /* Diff.FIND.RENAMES */,
|
|
761
|
+
async function getChanges(git, options = {}) {
|
|
762
|
+
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options;
|
|
763
|
+
const staged = [];
|
|
764
|
+
const unstaged = [];
|
|
765
|
+
const untracked = [];
|
|
766
|
+
const status = await git.status();
|
|
767
|
+
status.files.forEach((file) => {
|
|
768
|
+
// console.log({ file })
|
|
769
|
+
const fileChange = {
|
|
770
|
+
filepath: file.path,
|
|
771
|
+
oldFilepath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
|
|
772
|
+
};
|
|
773
|
+
// Unstaged files
|
|
774
|
+
if (file.working_dir !== '?' && file.working_dir !== ' ') {
|
|
775
|
+
fileChange.status = getStatus(file, 'working_dir');
|
|
776
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
777
|
+
unstaged.push(fileChange);
|
|
778
|
+
}
|
|
779
|
+
// Staged files
|
|
780
|
+
if (file.index !== ' ' && file.index !== '?') {
|
|
781
|
+
fileChange.status = getStatus(file);
|
|
782
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
783
|
+
staged.push(fileChange);
|
|
784
|
+
}
|
|
785
|
+
// Untracked files
|
|
786
|
+
if (file.working_dir === '?' && file.index === '?') {
|
|
787
|
+
fileChange.status = 'added';
|
|
788
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
789
|
+
untracked.push(fileChange);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
|
|
793
|
+
const filteredStaged = staged.filter((file) => {
|
|
794
|
+
const extension = path.extname(file.filepath).toLowerCase();
|
|
795
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.includes(file.filepath);
|
|
796
|
+
});
|
|
797
|
+
const filteredUnstaged = unstaged.filter((file) => {
|
|
798
|
+
const extension = path.extname(file.filepath).toLowerCase();
|
|
799
|
+
return !ignoredExtensionsSet.has(extension) && !ignoredFiles.includes(file.filepath);
|
|
885
800
|
});
|
|
886
|
-
const patches = await diff.patches();
|
|
887
801
|
return {
|
|
888
|
-
staged:
|
|
889
|
-
unstaged,
|
|
802
|
+
staged: filteredStaged,
|
|
803
|
+
unstaged: filteredUnstaged,
|
|
890
804
|
untracked,
|
|
891
805
|
};
|
|
892
806
|
}
|
|
893
807
|
|
|
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
|
-
});
|
|
808
|
+
const noResult = async ({ git, logger }) => {
|
|
809
|
+
const { staged, unstaged, untracked } = await getChanges(git);
|
|
918
810
|
if (staged.length > 0) {
|
|
919
811
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
920
812
|
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
@@ -937,25 +829,26 @@ const noResult = async ({ repo, logger }) => {
|
|
|
937
829
|
process.exit(0);
|
|
938
830
|
};
|
|
939
831
|
|
|
832
|
+
async function createCommit(commitMsg, git) {
|
|
833
|
+
return await git.commit(commitMsg);
|
|
834
|
+
}
|
|
835
|
+
|
|
940
836
|
const argv = loadArgv();
|
|
941
837
|
const tokenizer = getTokenizer();
|
|
838
|
+
const git = simpleGit.simpleGit();
|
|
942
839
|
async function main(options) {
|
|
943
840
|
const logger = new Logger(config);
|
|
944
841
|
if (!config.openAIApiKey) {
|
|
945
842
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
946
843
|
process.exit(1);
|
|
947
844
|
}
|
|
948
|
-
const repo = await nodegit.Repository.open('.');
|
|
949
845
|
const model = getModel({
|
|
950
846
|
temperature: 0.4,
|
|
951
847
|
maxConcurrency: 10,
|
|
952
848
|
openAIApiKey: config.openAIApiKey,
|
|
953
849
|
});
|
|
954
850
|
const INTERACTIVE = config?.mode === 'interactive' || options.interactive;
|
|
955
|
-
const { staged: changes } = await getChanges(
|
|
956
|
-
ignoreUnstaged: true,
|
|
957
|
-
ignoreUntracked: true,
|
|
958
|
-
});
|
|
851
|
+
const { staged: changes } = await getChanges(git);
|
|
959
852
|
let summary = '';
|
|
960
853
|
let commitMsg = '';
|
|
961
854
|
let promptTemplate = config?.prompt || '';
|
|
@@ -965,11 +858,11 @@ async function main(options) {
|
|
|
965
858
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
966
859
|
color: 'blue',
|
|
967
860
|
});
|
|
968
|
-
summary = await fileChangeParser(changes, { tokenizer,
|
|
861
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
969
862
|
}
|
|
970
863
|
// Handle empty summary
|
|
971
864
|
if (!summary.length) {
|
|
972
|
-
noResult({
|
|
865
|
+
noResult({ git, logger });
|
|
973
866
|
}
|
|
974
867
|
// Prompt user for commit template prompt, if necessary
|
|
975
868
|
if (modifyPrompt) {
|
|
@@ -1084,7 +977,7 @@ async function main(options) {
|
|
|
1084
977
|
// Handle resulting commit message
|
|
1085
978
|
switch (MODE) {
|
|
1086
979
|
case 'interactive':
|
|
1087
|
-
await createCommit(commitMsg,
|
|
980
|
+
await createCommit(commitMsg, git);
|
|
1088
981
|
logSuccess();
|
|
1089
982
|
break;
|
|
1090
983
|
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
|
}
|