git-coco 0.7.6 → 0.8.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 +1 -1
- package/dist/index.d.ts +125 -63
- package/dist/index.esm.mjs +318 -155
- package/dist/index.js +318 -155
- package/package.json +4 -3
package/dist/index.esm.mjs
CHANGED
|
@@ -15,11 +15,11 @@ import now from 'performance-now';
|
|
|
15
15
|
import prettyMilliseconds from 'pretty-ms';
|
|
16
16
|
import pQueue from 'p-queue';
|
|
17
17
|
import { Document } from 'langchain/document';
|
|
18
|
-
import { HuggingFaceInference } from 'langchain/llms/hf';
|
|
19
18
|
import { loadSummarizationChain, LLMChain } from 'langchain/chains';
|
|
20
|
-
import { OpenAI } from 'langchain/llms/openai';
|
|
21
19
|
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
|
|
22
20
|
import { createTwoFilesPatch } from 'diff';
|
|
21
|
+
import { Ollama } from 'langchain/llms/ollama';
|
|
22
|
+
import { OpenAI } from 'langchain/llms/openai';
|
|
23
23
|
import { minimatch } from 'minimatch';
|
|
24
24
|
import { simpleGit } from 'simple-git';
|
|
25
25
|
import { encoding_for_model } from 'tiktoken';
|
|
@@ -47,33 +47,34 @@ const SUMMARIZE_PROMPT = new PromptTemplate({
|
|
|
47
47
|
inputVariables: inputVariables$2,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
const DEFAULT_IGNORED_FILES = ['package-lock.json'];
|
|
51
|
+
const DEFAULT_IGNORED_EXTENSIONS = ['.map', '.lock'];
|
|
50
52
|
/**
|
|
51
53
|
* Default Config
|
|
52
54
|
*
|
|
53
55
|
* @type {Config}
|
|
54
56
|
*/
|
|
55
57
|
const DEFAULT_CONFIG = {
|
|
56
|
-
service: 'openai
|
|
58
|
+
service: 'openai',
|
|
57
59
|
verbose: false,
|
|
58
60
|
tokenLimit: 1024,
|
|
59
61
|
summarizePrompt: SUMMARIZE_PROMPT.template,
|
|
60
62
|
temperature: 0.4,
|
|
61
63
|
mode: 'stdout',
|
|
62
|
-
ignoredFiles:
|
|
63
|
-
ignoredExtensions:
|
|
64
|
+
ignoredFiles: DEFAULT_IGNORED_FILES,
|
|
65
|
+
ignoredExtensions: DEFAULT_IGNORED_EXTENSIONS,
|
|
64
66
|
defaultBranch: 'main',
|
|
65
67
|
};
|
|
66
68
|
/**
|
|
67
69
|
* Create a named export of all config keys for use in other modules.
|
|
68
70
|
*
|
|
69
|
-
* @see
|
|
71
|
+
* @see Used in `src/lib/config/services/env.ts` to validate all env vars.
|
|
70
72
|
*
|
|
71
73
|
* @type {string[]}
|
|
72
74
|
*/
|
|
73
75
|
const CONFIG_KEYS = Object.keys({
|
|
74
76
|
...DEFAULT_CONFIG,
|
|
75
|
-
|
|
76
|
-
openAIApiKey: '',
|
|
77
|
+
endpoint: '',
|
|
77
78
|
prompt: '',
|
|
78
79
|
});
|
|
79
80
|
const COCO_CONFIG_START_COMMENT = '# -- start coco config --';
|
|
@@ -152,8 +153,9 @@ function loadEnvConfig(config) {
|
|
|
152
153
|
CONFIG_KEYS.forEach((key) => {
|
|
153
154
|
const envVarName = toEnvVarName(key);
|
|
154
155
|
const envValue = parseEnvValue(key, process.env[envVarName]);
|
|
155
|
-
if (envValue === undefined)
|
|
156
|
+
if (envValue === undefined) {
|
|
156
157
|
return;
|
|
158
|
+
}
|
|
157
159
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
158
160
|
// @ts-ignore
|
|
159
161
|
envConfig[key] = envValue;
|
|
@@ -161,27 +163,28 @@ function loadEnvConfig(config) {
|
|
|
161
163
|
return { ...config, ...removeUndefined(envConfig) };
|
|
162
164
|
}
|
|
163
165
|
function parseEnvValue(key, value) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
switch (true) {
|
|
167
|
+
// Handle undefined values
|
|
168
|
+
case value === undefined:
|
|
169
|
+
return undefined;
|
|
170
|
+
// Handle comma separated strings for ignoredFiles and ignoredExtensions arrays
|
|
171
|
+
case (key === 'ignoredFiles' || key === 'ignoredExtensions') &&
|
|
172
|
+
typeof value === 'string' &&
|
|
173
|
+
value.includes(','):
|
|
174
|
+
return value.split(',');
|
|
175
|
+
// Handle boolean values
|
|
176
|
+
case typeof value === 'string' && (value === 'false' || value === 'true'):
|
|
177
|
+
return value === 'true';
|
|
178
|
+
default:
|
|
179
|
+
return value;
|
|
174
180
|
}
|
|
175
|
-
return value;
|
|
176
181
|
}
|
|
177
182
|
function toEnvVarName(key) {
|
|
178
183
|
switch (key) {
|
|
179
184
|
case 'openAIApiKey':
|
|
180
185
|
return 'OPENAI_API_KEY';
|
|
181
|
-
case 'huggingFaceHubApiKey':
|
|
182
|
-
return 'HUGGINGFACE_HUB_API_KEY';
|
|
183
186
|
default:
|
|
184
|
-
return
|
|
187
|
+
return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
function formatEnvValue(value) {
|
|
@@ -225,13 +228,19 @@ function loadGitConfig(config) {
|
|
|
225
228
|
const gitConfigParsed = ini.parse(gitConfigRaw);
|
|
226
229
|
config = {
|
|
227
230
|
...config,
|
|
228
|
-
service: gitConfigParsed.coco?.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
service: gitConfigParsed.coco?.service || config.service,
|
|
232
|
+
...(config.service === 'ollama'
|
|
233
|
+
? {
|
|
234
|
+
endpoint: gitConfigParsed.coco?.endpoint || config?.endpoint,
|
|
235
|
+
}
|
|
236
|
+
: {
|
|
237
|
+
openAIApiKey: gitConfigParsed.coco?.openAIApiKey || config?.openAIApiKey,
|
|
238
|
+
}),
|
|
239
|
+
model: gitConfigParsed.coco?.model || config?.model,
|
|
240
|
+
temperature: gitConfigParsed.coco?.temperature || config?.temperature,
|
|
241
|
+
tokenLimit: gitConfigParsed.coco?.tokenLimit || config?.tokenLimit,
|
|
232
242
|
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
233
243
|
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
234
|
-
temperature: gitConfigParsed.coco?.temperature || config.temperature,
|
|
235
244
|
summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
|
|
236
245
|
ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
|
|
237
246
|
ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
|
|
@@ -661,114 +670,49 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
|
661
670
|
};
|
|
662
671
|
}
|
|
663
672
|
|
|
664
|
-
function getModelAndProviderFromService(service) {
|
|
665
|
-
if (!service) {
|
|
666
|
-
throw new Error(`Missing service`);
|
|
667
|
-
}
|
|
668
|
-
const [provider, model] = service.split(/\/(.*)/s);
|
|
669
|
-
if (!model || !provider) {
|
|
670
|
-
throw new Error(`Invalid service: ${service}`);
|
|
671
|
-
}
|
|
672
|
-
return { provider, model };
|
|
673
|
-
}
|
|
674
|
-
function getModelFromService(service) {
|
|
675
|
-
const { model } = getModelAndProviderFromService(service);
|
|
676
|
-
return model;
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Get LLM Model Based on Configuration
|
|
680
|
-
* @param fields
|
|
681
|
-
* @param configuration
|
|
682
|
-
* @returns LLM Model
|
|
683
|
-
*/
|
|
684
|
-
function getLlm(service, key, fields) {
|
|
685
|
-
const { provider, model } = getModelAndProviderFromService(service);
|
|
686
|
-
if (!model) {
|
|
687
|
-
throw new Error(`Invalid LLM Service: ${service}`);
|
|
688
|
-
}
|
|
689
|
-
switch (provider) {
|
|
690
|
-
case 'huggingface':
|
|
691
|
-
return new HuggingFaceInference({
|
|
692
|
-
model: model,
|
|
693
|
-
apiKey: key,
|
|
694
|
-
maxConcurrency: 4,
|
|
695
|
-
...fields,
|
|
696
|
-
});
|
|
697
|
-
case 'openai':
|
|
698
|
-
default:
|
|
699
|
-
return new OpenAI({
|
|
700
|
-
openAIApiKey: key,
|
|
701
|
-
modelName: model,
|
|
702
|
-
...fields,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
673
|
/**
|
|
707
|
-
*
|
|
708
|
-
* @param
|
|
674
|
+
* Get Summarization Chain
|
|
675
|
+
* @param model
|
|
709
676
|
* @param options
|
|
710
677
|
* @returns
|
|
711
678
|
*/
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
switch (provider) {
|
|
715
|
-
case 'huggingface':
|
|
716
|
-
return options.huggingFaceHubApiKey;
|
|
717
|
-
case 'openai':
|
|
718
|
-
default:
|
|
719
|
-
return options.openAIApiKey;
|
|
720
|
-
}
|
|
679
|
+
function getSummarizationChain(model, options = { type: 'map_reduce' }) {
|
|
680
|
+
return loadSummarizationChain(model, options);
|
|
721
681
|
}
|
|
682
|
+
|
|
722
683
|
/**
|
|
723
684
|
* Get Recursive Character Text Splitter
|
|
685
|
+
*
|
|
724
686
|
* @param options
|
|
725
687
|
* @returns
|
|
726
688
|
*/
|
|
727
689
|
function getTextSplitter(options = {}) {
|
|
728
690
|
return new RecursiveCharacterTextSplitter(options);
|
|
729
691
|
}
|
|
692
|
+
|
|
730
693
|
/**
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
* @param
|
|
734
|
-
* @
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return loadSummarizationChain(model, options);
|
|
738
|
-
}
|
|
739
|
-
function getPrompt({ template, variables, fallback }) {
|
|
740
|
-
if (!template && !fallback)
|
|
741
|
-
throw new Error('Must provide either a template or a fallback');
|
|
742
|
-
return (template
|
|
743
|
-
? new PromptTemplate({
|
|
744
|
-
template,
|
|
745
|
-
inputVariables: variables,
|
|
746
|
-
})
|
|
747
|
-
: fallback);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Verify template string contains all required input variables
|
|
751
|
-
* @param text template string
|
|
752
|
-
* @param inputVariables template variables
|
|
753
|
-
* @returns boolean or error message
|
|
694
|
+
* Parses the default file diff for a given nodeFile.
|
|
695
|
+
*
|
|
696
|
+
* @param nodeFile - The file change object.
|
|
697
|
+
* @param commit - The commit to diff against. Defaults to '--staged'.
|
|
698
|
+
* @param git - The SimpleGit instance.
|
|
699
|
+
* @returns A Promise that resolves to the file diff as a string.
|
|
754
700
|
*/
|
|
755
|
-
function validatePromptTemplate(text, inputVariables) {
|
|
756
|
-
if (!text) {
|
|
757
|
-
return 'Prompt template cannot be empty';
|
|
758
|
-
}
|
|
759
|
-
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
760
|
-
return ('Prompt template must include at least one of the following input variables: ' +
|
|
761
|
-
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
762
|
-
}
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
701
|
async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
|
|
767
702
|
if (commit !== '--staged') {
|
|
768
703
|
return await git.diff([`${commit}~1..${commit}`, '--', nodeFile.filePath]);
|
|
769
704
|
}
|
|
770
705
|
return await git.diff([commit, nodeFile.filePath]);
|
|
771
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* Parses the diff for a renamed file.
|
|
709
|
+
*
|
|
710
|
+
* @param nodeFile - The file change object.
|
|
711
|
+
* @param commit - The commit hash or '--staged'.
|
|
712
|
+
* @param git - The SimpleGit instance.
|
|
713
|
+
* @param logger - The logger instance.
|
|
714
|
+
* @returns A Promise that resolves to the diff string.
|
|
715
|
+
*/
|
|
772
716
|
async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
773
717
|
let result = '';
|
|
774
718
|
const oldFilePath = nodeFile?.oldFilePath || nodeFile.filePath;
|
|
@@ -807,6 +751,18 @@ async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
|
807
751
|
}
|
|
808
752
|
return result;
|
|
809
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Retrieves the diff for a given file change in a specific commit.
|
|
756
|
+
* If the file is deleted, it returns a message indicating that the file has been deleted.
|
|
757
|
+
* If the file is renamed, it parses the renamed file diff and returns it.
|
|
758
|
+
* Otherwise, it retrieves the default diff from the index and returns it.
|
|
759
|
+
*
|
|
760
|
+
* @param nodeFile - The file change object.
|
|
761
|
+
* @param commit - The commit hash.
|
|
762
|
+
* @param git - The SimpleGit instance.
|
|
763
|
+
* @param logger - The logger instance.
|
|
764
|
+
* @returns A promise that resolves to the diff as a string.
|
|
765
|
+
*/
|
|
810
766
|
async function getDiff(nodeFile, commit, { git, logger, }) {
|
|
811
767
|
if (nodeFile.status === 'deleted') {
|
|
812
768
|
return 'This file has been deleted.';
|
|
@@ -849,14 +805,14 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
849
805
|
}
|
|
850
806
|
|
|
851
807
|
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
852
|
-
Commit Messages must have a short description that is less than 50 characters
|
|
808
|
+
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary no more than 300 characters, the shorter and more concise the better. The detailed summary should be separated from the short description by a blank line. Please follow the guidelines below when writing your commit message:
|
|
853
809
|
|
|
854
|
-
- Typically a hyphen or asterisk is used for the bullet
|
|
855
810
|
- Write concisely using an informal tone
|
|
856
811
|
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
857
|
-
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
858
812
|
- DO NOT use specific names or files from the code
|
|
813
|
+
- DO NOT include any diffs or file changes in the commit message
|
|
859
814
|
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
815
|
+
- ONLY respond with the resulting commit message.
|
|
860
816
|
|
|
861
817
|
"""{summary}"""
|
|
862
818
|
|
|
@@ -867,6 +823,151 @@ const COMMIT_PROMPT = new PromptTemplate({
|
|
|
867
823
|
inputVariables: inputVariables$1,
|
|
868
824
|
});
|
|
869
825
|
|
|
826
|
+
const DEFAULT_OLLAMA_LLM_SERVICE = {
|
|
827
|
+
provider: 'ollama',
|
|
828
|
+
model: 'codellama',
|
|
829
|
+
endpoint: 'http://localhost:11434',
|
|
830
|
+
maxConcurrent: 1,
|
|
831
|
+
tokenLimit: 1024,
|
|
832
|
+
};
|
|
833
|
+
const DEFAULT_OPENAI_LLM_SERVICE = {
|
|
834
|
+
provider: 'openai',
|
|
835
|
+
model: 'gpt-4',
|
|
836
|
+
authentication: {
|
|
837
|
+
type: 'APIKey',
|
|
838
|
+
credentials: {
|
|
839
|
+
apiKey: '',
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
tokenLimit: 1024,
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Retrieves the provider and model from the given configuration object.
|
|
847
|
+
* @param config The configuration object.
|
|
848
|
+
* @returns An object containing the provider and model.
|
|
849
|
+
* @throws Error if the configuration is invalid or missing required properties.
|
|
850
|
+
*/
|
|
851
|
+
function getModelAndProviderFromConfig(config) {
|
|
852
|
+
if (!config.service) {
|
|
853
|
+
throw new Error('Invalid service: undefined');
|
|
854
|
+
}
|
|
855
|
+
let result;
|
|
856
|
+
switch (typeof config.service) {
|
|
857
|
+
case 'string':
|
|
858
|
+
result = getDefaultServiceConfigFromAlias(config.service, config?.model);
|
|
859
|
+
break;
|
|
860
|
+
case 'object':
|
|
861
|
+
default:
|
|
862
|
+
result = config.service;
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
const { provider, model } = result;
|
|
866
|
+
if (!model || !provider) {
|
|
867
|
+
throw new Error(`Invalid service: ${config.service}`);
|
|
868
|
+
}
|
|
869
|
+
return { provider, model };
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Retrieve appropriate API key based on selected model
|
|
873
|
+
* @param service
|
|
874
|
+
* @param options
|
|
875
|
+
* @returns API Key
|
|
876
|
+
*/
|
|
877
|
+
function getApiKeyForModel(config) {
|
|
878
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
879
|
+
switch (provider) {
|
|
880
|
+
case 'openai':
|
|
881
|
+
return config.openAIApiKey || getDefaultServiceApiKey(config);
|
|
882
|
+
default:
|
|
883
|
+
return getDefaultServiceApiKey(config);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Retrieves the default service API key from the given configuration.
|
|
888
|
+
* @param config The configuration object.
|
|
889
|
+
* @returns The default service API key.
|
|
890
|
+
*/
|
|
891
|
+
function getDefaultServiceApiKey(config) {
|
|
892
|
+
const service = config.service;
|
|
893
|
+
if (service.authentication.type === 'APIKey') {
|
|
894
|
+
return service.authentication.credentials?.apiKey;
|
|
895
|
+
}
|
|
896
|
+
else if (service.authentication.type === 'OAuth') {
|
|
897
|
+
return service.authentication.credentials?.token;
|
|
898
|
+
}
|
|
899
|
+
return '';
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Retrieves the default service configuration based on the provided alias and optional model.
|
|
903
|
+
* @param alias - The alias of the service.
|
|
904
|
+
* @param model - The optional model to be used.
|
|
905
|
+
* @returns The default service configuration.
|
|
906
|
+
* @throws Error if the alias is invalid or undefined.
|
|
907
|
+
*/
|
|
908
|
+
function getDefaultServiceConfigFromAlias(alias, model) {
|
|
909
|
+
if (!alias) {
|
|
910
|
+
throw new Error('Invalid alias: undefined');
|
|
911
|
+
}
|
|
912
|
+
switch (alias) {
|
|
913
|
+
case 'openai':
|
|
914
|
+
return {
|
|
915
|
+
...DEFAULT_OPENAI_LLM_SERVICE,
|
|
916
|
+
model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
|
|
917
|
+
};
|
|
918
|
+
case 'ollama':
|
|
919
|
+
return {
|
|
920
|
+
...DEFAULT_OLLAMA_LLM_SERVICE,
|
|
921
|
+
model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Get LLM Model Based on Configuration
|
|
928
|
+
*
|
|
929
|
+
* @param fields
|
|
930
|
+
* @param configuration
|
|
931
|
+
* @returns LLM Model
|
|
932
|
+
*/
|
|
933
|
+
function getLlm(provider, model, config) {
|
|
934
|
+
if (!model) {
|
|
935
|
+
throw new Error(`Invalid LLM Service: ${provider}/${model}`);
|
|
936
|
+
}
|
|
937
|
+
switch (provider) {
|
|
938
|
+
case 'ollama':
|
|
939
|
+
return new Ollama({
|
|
940
|
+
baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
|
|
941
|
+
model,
|
|
942
|
+
});
|
|
943
|
+
case 'openai':
|
|
944
|
+
default:
|
|
945
|
+
return new OpenAI({
|
|
946
|
+
openAIApiKey: config.openAIApiKey,
|
|
947
|
+
modelName: model,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function getPrompt({ template, variables, fallback }) {
|
|
953
|
+
if (!template && !fallback)
|
|
954
|
+
throw new Error('Must provide either a template or a fallback');
|
|
955
|
+
return (template
|
|
956
|
+
? new PromptTemplate({
|
|
957
|
+
template,
|
|
958
|
+
inputVariables: variables,
|
|
959
|
+
})
|
|
960
|
+
: fallback);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Determines the status of a file based on its changes in the Git repository.
|
|
965
|
+
*
|
|
966
|
+
* @param file - The file to check the status of.
|
|
967
|
+
* @param location - The location to check the status in ('index' or 'working_dir'). Defaults to 'index'.
|
|
968
|
+
* @returns The status of the file ('added', 'deleted', 'modified', 'renamed', 'untracked', or 'unknown').
|
|
969
|
+
* @throws Error if the file type is invalid.
|
|
970
|
+
*/
|
|
870
971
|
function getStatus(file, location = 'index') {
|
|
871
972
|
if ('index' in file && 'working_dir' in file) {
|
|
872
973
|
const statusCode = file[location];
|
|
@@ -903,6 +1004,14 @@ function getStatus(file, location = 'index') {
|
|
|
903
1004
|
}
|
|
904
1005
|
}
|
|
905
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Returns the summary text for a file change.
|
|
1009
|
+
*
|
|
1010
|
+
* @param file - The file status or diff result.
|
|
1011
|
+
* @param change - The partial file change object.
|
|
1012
|
+
* @returns The summary text for the file change.
|
|
1013
|
+
* @throws Error if the file type is invalid.
|
|
1014
|
+
*/
|
|
906
1015
|
function getSummaryText(file, change) {
|
|
907
1016
|
const status = change.status || getStatus(file);
|
|
908
1017
|
let filePath;
|
|
@@ -921,9 +1030,12 @@ function getSummaryText(file, change) {
|
|
|
921
1030
|
return `${status}: ${filePath}`;
|
|
922
1031
|
}
|
|
923
1032
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1033
|
+
/**
|
|
1034
|
+
* Retrieves the changes in the Git repository.
|
|
1035
|
+
*
|
|
1036
|
+
* @param {GetChangesInput} options - The options for retrieving the changes.
|
|
1037
|
+
* @returns {Promise<GetChangesResult>} A promise that resolves to the changes in the Git repository.
|
|
1038
|
+
*/
|
|
927
1039
|
async function getChanges({ git, options }) {
|
|
928
1040
|
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options || {};
|
|
929
1041
|
const staged = [];
|
|
@@ -989,13 +1101,13 @@ async function noResult({ git, logger }) {
|
|
|
989
1101
|
else if (hasUnstaged || hasUntracked) {
|
|
990
1102
|
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
991
1103
|
if (hasUnstaged) {
|
|
992
|
-
logger.log('\
|
|
1104
|
+
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
993
1105
|
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
994
1106
|
color: 'red',
|
|
995
1107
|
});
|
|
996
1108
|
}
|
|
997
1109
|
if (hasUntracked) {
|
|
998
|
-
logger.log('\
|
|
1110
|
+
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
999
1111
|
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
1000
1112
|
color: 'red',
|
|
1001
1113
|
});
|
|
@@ -1069,6 +1181,24 @@ async function getUserReviewDecision({ label, descriptions, enableRetry = true,
|
|
|
1069
1181
|
}));
|
|
1070
1182
|
}
|
|
1071
1183
|
|
|
1184
|
+
/**
|
|
1185
|
+
* Verify template string contains all required input variables
|
|
1186
|
+
*
|
|
1187
|
+
* @param text template string
|
|
1188
|
+
* @param inputVariables template variables
|
|
1189
|
+
* @returns boolean or error message
|
|
1190
|
+
*/
|
|
1191
|
+
function validatePromptTemplate(text, inputVariables) {
|
|
1192
|
+
if (!text) {
|
|
1193
|
+
return 'Prompt template cannot be empty';
|
|
1194
|
+
}
|
|
1195
|
+
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
1196
|
+
return ('Prompt template must include at least one of the following input variables: ' +
|
|
1197
|
+
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
1198
|
+
}
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1072
1202
|
async function editPrompt(options) {
|
|
1073
1203
|
return await editor({
|
|
1074
1204
|
message: 'Edit the prompt',
|
|
@@ -1199,6 +1329,10 @@ async function handleResult({ result, mode, interactiveHandler }) {
|
|
|
1199
1329
|
process.exit(0);
|
|
1200
1330
|
}
|
|
1201
1331
|
|
|
1332
|
+
/**
|
|
1333
|
+
* Retrieves the SimpleGit instance for the repository.
|
|
1334
|
+
* @returns {SimpleGit} The SimpleGit instance.
|
|
1335
|
+
*/
|
|
1202
1336
|
const getRepo = () => {
|
|
1203
1337
|
let git;
|
|
1204
1338
|
try {
|
|
@@ -1211,34 +1345,50 @@ const getRepo = () => {
|
|
|
1211
1345
|
return git;
|
|
1212
1346
|
};
|
|
1213
1347
|
|
|
1348
|
+
/**
|
|
1349
|
+
* Retrieves a TikToken for the specified model.
|
|
1350
|
+
*
|
|
1351
|
+
* @param {TiktokenModel} modelName - The name of the TiktokenModel.
|
|
1352
|
+
* @returns A Promise that resolves to the TikToken.
|
|
1353
|
+
*/
|
|
1214
1354
|
const getTikToken = async (modelName) => {
|
|
1215
1355
|
return await encoding_for_model(modelName);
|
|
1216
1356
|
};
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1357
|
+
/**
|
|
1358
|
+
* Retrieves the token counter for a given model name.
|
|
1359
|
+
*
|
|
1360
|
+
* @param {TikTokenModel} modelName - The name of the Tiktoken model.
|
|
1361
|
+
* @returns A promise that resolves to a function that calculates the number of tokens in a given text.
|
|
1362
|
+
*/
|
|
1363
|
+
const getTokenCounter = async (modelName) => {
|
|
1364
|
+
return getTikToken(modelName).then((tokenizer) => (text) => {
|
|
1365
|
+
const tokens = tokenizer.encode(text);
|
|
1366
|
+
return tokens.length;
|
|
1367
|
+
});
|
|
1368
|
+
};
|
|
1223
1369
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1370
|
+
/**
|
|
1371
|
+
* Creates a commit with the specified commit message.
|
|
1372
|
+
*
|
|
1373
|
+
* @param message The commit message.
|
|
1374
|
+
* @param git The SimpleGit instance.
|
|
1375
|
+
* @returns A Promise that resolves to the CommitResult.
|
|
1376
|
+
*/
|
|
1377
|
+
async function createCommit(message, git) {
|
|
1378
|
+
return await git.commit(message);
|
|
1226
1379
|
}
|
|
1227
1380
|
|
|
1228
1381
|
const handler$2 = async (argv, logger) => {
|
|
1229
1382
|
const git = getRepo();
|
|
1230
1383
|
const options = loadConfig(argv);
|
|
1231
|
-
const
|
|
1232
|
-
const key = getApiKeyForModel(service, options);
|
|
1233
|
-
const tokenizer = await getTokenCounter(getModelFromService(service));
|
|
1384
|
+
const key = getApiKeyForModel(options);
|
|
1234
1385
|
if (!key) {
|
|
1235
1386
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
1236
1387
|
process.exit(1);
|
|
1237
1388
|
}
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
});
|
|
1389
|
+
const { provider, model } = getModelAndProviderFromConfig(options);
|
|
1390
|
+
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4');
|
|
1391
|
+
const llm = getLlm(provider, model, options);
|
|
1242
1392
|
const INTERACTIVE = isInteractive(options);
|
|
1243
1393
|
if (INTERACTIVE) {
|
|
1244
1394
|
logger.log(LOGO);
|
|
@@ -1304,16 +1454,10 @@ const handler$2 = async (argv, logger) => {
|
|
|
1304
1454
|
* Command line options via yargs
|
|
1305
1455
|
*/
|
|
1306
1456
|
const options$2 = {
|
|
1307
|
-
|
|
1457
|
+
service: { type: 'string', description: 'LLM/Model-Name', choices: ['openai', 'ollama'] },
|
|
1308
1458
|
openAIApiKey: {
|
|
1309
1459
|
type: 'string',
|
|
1310
1460
|
description: 'OpenAI API Key',
|
|
1311
|
-
conflicts: 'huggingFaceHubApiKey',
|
|
1312
|
-
},
|
|
1313
|
-
huggingFaceHubApiKey: {
|
|
1314
|
-
type: 'string',
|
|
1315
|
-
description: 'HuggingFace Hub API Key',
|
|
1316
|
-
conflicts: 'openAIApiKey',
|
|
1317
1461
|
},
|
|
1318
1462
|
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
1319
1463
|
prompt: {
|
|
@@ -1375,6 +1519,15 @@ const CHANGELOG_PROMPT = new PromptTemplate({
|
|
|
1375
1519
|
inputVariables,
|
|
1376
1520
|
});
|
|
1377
1521
|
|
|
1522
|
+
/**
|
|
1523
|
+
* Retrieves the commit log range between two specified commits.
|
|
1524
|
+
*
|
|
1525
|
+
* @param from - The starting commit.
|
|
1526
|
+
* @param to - The ending commit.
|
|
1527
|
+
* @param options - Additional options for retrieving the commit log range.
|
|
1528
|
+
* @returns A promise that resolves to an array of commit log messages.
|
|
1529
|
+
* @throws If there is an error retrieving the commit log range.
|
|
1530
|
+
*/
|
|
1378
1531
|
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
1379
1532
|
try {
|
|
1380
1533
|
const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
|
|
@@ -1388,10 +1541,26 @@ async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
|
1388
1541
|
}
|
|
1389
1542
|
}
|
|
1390
1543
|
|
|
1544
|
+
/**
|
|
1545
|
+
* Retrieves the name of the current branch.
|
|
1546
|
+
*
|
|
1547
|
+
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
1548
|
+
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
1549
|
+
*/
|
|
1391
1550
|
async function getCurrentBranchName({ git }) {
|
|
1392
1551
|
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
1393
1552
|
}
|
|
1394
1553
|
|
|
1554
|
+
/**
|
|
1555
|
+
* Retrieves the commit log for the current branch.
|
|
1556
|
+
*
|
|
1557
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
1558
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
1559
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
1560
|
+
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
1561
|
+
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
1562
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
1563
|
+
*/
|
|
1395
1564
|
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
1396
1565
|
try {
|
|
1397
1566
|
// Get the current branch name
|
|
@@ -1437,15 +1606,13 @@ async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main
|
|
|
1437
1606
|
const handler$1 = async (argv, logger) => {
|
|
1438
1607
|
const options = loadConfig(argv);
|
|
1439
1608
|
const git = getRepo();
|
|
1440
|
-
const key = getApiKeyForModel(options
|
|
1609
|
+
const key = getApiKeyForModel(options);
|
|
1441
1610
|
if (!key) {
|
|
1442
1611
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
1443
1612
|
process.exit(1);
|
|
1444
1613
|
}
|
|
1445
|
-
const model =
|
|
1446
|
-
|
|
1447
|
-
maxConcurrency: 10,
|
|
1448
|
-
});
|
|
1614
|
+
const { provider, model } = getModelAndProviderFromConfig(options);
|
|
1615
|
+
const llm = getLlm(provider, model, options);
|
|
1449
1616
|
const INTERACTIVE = isInteractive(options);
|
|
1450
1617
|
if (INTERACTIVE) {
|
|
1451
1618
|
logger.log(LOGO);
|
|
@@ -1477,7 +1644,7 @@ const handler$1 = async (argv, logger) => {
|
|
|
1477
1644
|
fallback: CHANGELOG_PROMPT,
|
|
1478
1645
|
});
|
|
1479
1646
|
return await executeChain({
|
|
1480
|
-
llm
|
|
1647
|
+
llm,
|
|
1481
1648
|
prompt,
|
|
1482
1649
|
variables: { summary: context },
|
|
1483
1650
|
});
|
|
@@ -1634,14 +1801,10 @@ async function installNpmPackage({ name, flags = [], cwd = process.cwd(), }) {
|
|
|
1634
1801
|
*/
|
|
1635
1802
|
function isPackageInstalled(packageName, projectPath) {
|
|
1636
1803
|
try {
|
|
1637
|
-
// Construct the path to the package.json file
|
|
1638
1804
|
const packageJsonPath = path__default.join(projectPath, 'package.json');
|
|
1639
|
-
// Read the package.json file
|
|
1640
1805
|
const packageJson = JSON.parse(fs__default.readFileSync(packageJsonPath, 'utf8'));
|
|
1641
|
-
// Check both dependencies and devDependencies
|
|
1642
1806
|
const dependencies = packageJson.dependencies || {};
|
|
1643
1807
|
const devDependencies = packageJson.devDependencies || {};
|
|
1644
|
-
// Return true if the package is found in either
|
|
1645
1808
|
return dependencies.hasOwnProperty(packageName) || devDependencies.hasOwnProperty(packageName);
|
|
1646
1809
|
}
|
|
1647
1810
|
catch (error) {
|