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.js
CHANGED
|
@@ -14,11 +14,11 @@ var now = require('performance-now');
|
|
|
14
14
|
var prettyMilliseconds = require('pretty-ms');
|
|
15
15
|
var pQueue = require('p-queue');
|
|
16
16
|
var document = require('langchain/document');
|
|
17
|
-
var hf = require('langchain/llms/hf');
|
|
18
17
|
var chains = require('langchain/chains');
|
|
19
|
-
var openai = require('langchain/llms/openai');
|
|
20
18
|
var text_splitter = require('langchain/text_splitter');
|
|
21
19
|
var diff = require('diff');
|
|
20
|
+
var ollama = require('langchain/llms/ollama');
|
|
21
|
+
var openai = require('langchain/llms/openai');
|
|
22
22
|
var minimatch = require('minimatch');
|
|
23
23
|
var simpleGit = require('simple-git');
|
|
24
24
|
var tiktoken = require('tiktoken');
|
|
@@ -68,33 +68,34 @@ const SUMMARIZE_PROMPT = new prompts.PromptTemplate({
|
|
|
68
68
|
inputVariables: inputVariables$2,
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
const DEFAULT_IGNORED_FILES = ['package-lock.json'];
|
|
72
|
+
const DEFAULT_IGNORED_EXTENSIONS = ['.map', '.lock'];
|
|
71
73
|
/**
|
|
72
74
|
* Default Config
|
|
73
75
|
*
|
|
74
76
|
* @type {Config}
|
|
75
77
|
*/
|
|
76
78
|
const DEFAULT_CONFIG = {
|
|
77
|
-
service: 'openai
|
|
79
|
+
service: 'openai',
|
|
78
80
|
verbose: false,
|
|
79
81
|
tokenLimit: 1024,
|
|
80
82
|
summarizePrompt: SUMMARIZE_PROMPT.template,
|
|
81
83
|
temperature: 0.4,
|
|
82
84
|
mode: 'stdout',
|
|
83
|
-
ignoredFiles:
|
|
84
|
-
ignoredExtensions:
|
|
85
|
+
ignoredFiles: DEFAULT_IGNORED_FILES,
|
|
86
|
+
ignoredExtensions: DEFAULT_IGNORED_EXTENSIONS,
|
|
85
87
|
defaultBranch: 'main',
|
|
86
88
|
};
|
|
87
89
|
/**
|
|
88
90
|
* Create a named export of all config keys for use in other modules.
|
|
89
91
|
*
|
|
90
|
-
* @see
|
|
92
|
+
* @see Used in `src/lib/config/services/env.ts` to validate all env vars.
|
|
91
93
|
*
|
|
92
94
|
* @type {string[]}
|
|
93
95
|
*/
|
|
94
96
|
const CONFIG_KEYS = Object.keys({
|
|
95
97
|
...DEFAULT_CONFIG,
|
|
96
|
-
|
|
97
|
-
openAIApiKey: '',
|
|
98
|
+
endpoint: '',
|
|
98
99
|
prompt: '',
|
|
99
100
|
});
|
|
100
101
|
const COCO_CONFIG_START_COMMENT = '# -- start coco config --';
|
|
@@ -173,8 +174,9 @@ function loadEnvConfig(config) {
|
|
|
173
174
|
CONFIG_KEYS.forEach((key) => {
|
|
174
175
|
const envVarName = toEnvVarName(key);
|
|
175
176
|
const envValue = parseEnvValue(key, process.env[envVarName]);
|
|
176
|
-
if (envValue === undefined)
|
|
177
|
+
if (envValue === undefined) {
|
|
177
178
|
return;
|
|
179
|
+
}
|
|
178
180
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
179
181
|
// @ts-ignore
|
|
180
182
|
envConfig[key] = envValue;
|
|
@@ -182,27 +184,28 @@ function loadEnvConfig(config) {
|
|
|
182
184
|
return { ...config, ...removeUndefined(envConfig) };
|
|
183
185
|
}
|
|
184
186
|
function parseEnvValue(key, value) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
switch (true) {
|
|
188
|
+
// Handle undefined values
|
|
189
|
+
case value === undefined:
|
|
190
|
+
return undefined;
|
|
191
|
+
// Handle comma separated strings for ignoredFiles and ignoredExtensions arrays
|
|
192
|
+
case (key === 'ignoredFiles' || key === 'ignoredExtensions') &&
|
|
193
|
+
typeof value === 'string' &&
|
|
194
|
+
value.includes(','):
|
|
195
|
+
return value.split(',');
|
|
196
|
+
// Handle boolean values
|
|
197
|
+
case typeof value === 'string' && (value === 'false' || value === 'true'):
|
|
198
|
+
return value === 'true';
|
|
199
|
+
default:
|
|
200
|
+
return value;
|
|
195
201
|
}
|
|
196
|
-
return value;
|
|
197
202
|
}
|
|
198
203
|
function toEnvVarName(key) {
|
|
199
204
|
switch (key) {
|
|
200
205
|
case 'openAIApiKey':
|
|
201
206
|
return 'OPENAI_API_KEY';
|
|
202
|
-
case 'huggingFaceHubApiKey':
|
|
203
|
-
return 'HUGGINGFACE_HUB_API_KEY';
|
|
204
207
|
default:
|
|
205
|
-
return
|
|
208
|
+
return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
function formatEnvValue(value) {
|
|
@@ -246,13 +249,19 @@ function loadGitConfig(config) {
|
|
|
246
249
|
const gitConfigParsed = ini__namespace.parse(gitConfigRaw);
|
|
247
250
|
config = {
|
|
248
251
|
...config,
|
|
249
|
-
service: gitConfigParsed.coco?.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
service: gitConfigParsed.coco?.service || config.service,
|
|
253
|
+
...(config.service === 'ollama'
|
|
254
|
+
? {
|
|
255
|
+
endpoint: gitConfigParsed.coco?.endpoint || config?.endpoint,
|
|
256
|
+
}
|
|
257
|
+
: {
|
|
258
|
+
openAIApiKey: gitConfigParsed.coco?.openAIApiKey || config?.openAIApiKey,
|
|
259
|
+
}),
|
|
260
|
+
model: gitConfigParsed.coco?.model || config?.model,
|
|
261
|
+
temperature: gitConfigParsed.coco?.temperature || config?.temperature,
|
|
262
|
+
tokenLimit: gitConfigParsed.coco?.tokenLimit || config?.tokenLimit,
|
|
253
263
|
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
254
264
|
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
255
|
-
temperature: gitConfigParsed.coco?.temperature || config.temperature,
|
|
256
265
|
summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
|
|
257
266
|
ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
|
|
258
267
|
ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
|
|
@@ -682,114 +691,49 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
|
682
691
|
};
|
|
683
692
|
}
|
|
684
693
|
|
|
685
|
-
function getModelAndProviderFromService(service) {
|
|
686
|
-
if (!service) {
|
|
687
|
-
throw new Error(`Missing service`);
|
|
688
|
-
}
|
|
689
|
-
const [provider, model] = service.split(/\/(.*)/s);
|
|
690
|
-
if (!model || !provider) {
|
|
691
|
-
throw new Error(`Invalid service: ${service}`);
|
|
692
|
-
}
|
|
693
|
-
return { provider, model };
|
|
694
|
-
}
|
|
695
|
-
function getModelFromService(service) {
|
|
696
|
-
const { model } = getModelAndProviderFromService(service);
|
|
697
|
-
return model;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Get LLM Model Based on Configuration
|
|
701
|
-
* @param fields
|
|
702
|
-
* @param configuration
|
|
703
|
-
* @returns LLM Model
|
|
704
|
-
*/
|
|
705
|
-
function getLlm(service, key, fields) {
|
|
706
|
-
const { provider, model } = getModelAndProviderFromService(service);
|
|
707
|
-
if (!model) {
|
|
708
|
-
throw new Error(`Invalid LLM Service: ${service}`);
|
|
709
|
-
}
|
|
710
|
-
switch (provider) {
|
|
711
|
-
case 'huggingface':
|
|
712
|
-
return new hf.HuggingFaceInference({
|
|
713
|
-
model: model,
|
|
714
|
-
apiKey: key,
|
|
715
|
-
maxConcurrency: 4,
|
|
716
|
-
...fields,
|
|
717
|
-
});
|
|
718
|
-
case 'openai':
|
|
719
|
-
default:
|
|
720
|
-
return new openai.OpenAI({
|
|
721
|
-
openAIApiKey: key,
|
|
722
|
-
modelName: model,
|
|
723
|
-
...fields,
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
694
|
/**
|
|
728
|
-
*
|
|
729
|
-
* @param
|
|
695
|
+
* Get Summarization Chain
|
|
696
|
+
* @param model
|
|
730
697
|
* @param options
|
|
731
698
|
* @returns
|
|
732
699
|
*/
|
|
733
|
-
function
|
|
734
|
-
|
|
735
|
-
switch (provider) {
|
|
736
|
-
case 'huggingface':
|
|
737
|
-
return options.huggingFaceHubApiKey;
|
|
738
|
-
case 'openai':
|
|
739
|
-
default:
|
|
740
|
-
return options.openAIApiKey;
|
|
741
|
-
}
|
|
700
|
+
function getSummarizationChain(model, options = { type: 'map_reduce' }) {
|
|
701
|
+
return chains.loadSummarizationChain(model, options);
|
|
742
702
|
}
|
|
703
|
+
|
|
743
704
|
/**
|
|
744
705
|
* Get Recursive Character Text Splitter
|
|
706
|
+
*
|
|
745
707
|
* @param options
|
|
746
708
|
* @returns
|
|
747
709
|
*/
|
|
748
710
|
function getTextSplitter(options = {}) {
|
|
749
711
|
return new text_splitter.RecursiveCharacterTextSplitter(options);
|
|
750
712
|
}
|
|
713
|
+
|
|
751
714
|
/**
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
* @param
|
|
755
|
-
* @
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return chains.loadSummarizationChain(model, options);
|
|
759
|
-
}
|
|
760
|
-
function getPrompt({ template, variables, fallback }) {
|
|
761
|
-
if (!template && !fallback)
|
|
762
|
-
throw new Error('Must provide either a template or a fallback');
|
|
763
|
-
return (template
|
|
764
|
-
? new prompts.PromptTemplate({
|
|
765
|
-
template,
|
|
766
|
-
inputVariables: variables,
|
|
767
|
-
})
|
|
768
|
-
: fallback);
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Verify template string contains all required input variables
|
|
772
|
-
* @param text template string
|
|
773
|
-
* @param inputVariables template variables
|
|
774
|
-
* @returns boolean or error message
|
|
715
|
+
* Parses the default file diff for a given nodeFile.
|
|
716
|
+
*
|
|
717
|
+
* @param nodeFile - The file change object.
|
|
718
|
+
* @param commit - The commit to diff against. Defaults to '--staged'.
|
|
719
|
+
* @param git - The SimpleGit instance.
|
|
720
|
+
* @returns A Promise that resolves to the file diff as a string.
|
|
775
721
|
*/
|
|
776
|
-
function validatePromptTemplate(text, inputVariables) {
|
|
777
|
-
if (!text) {
|
|
778
|
-
return 'Prompt template cannot be empty';
|
|
779
|
-
}
|
|
780
|
-
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
781
|
-
return ('Prompt template must include at least one of the following input variables: ' +
|
|
782
|
-
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
783
|
-
}
|
|
784
|
-
return true;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
722
|
async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
|
|
788
723
|
if (commit !== '--staged') {
|
|
789
724
|
return await git.diff([`${commit}~1..${commit}`, '--', nodeFile.filePath]);
|
|
790
725
|
}
|
|
791
726
|
return await git.diff([commit, nodeFile.filePath]);
|
|
792
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Parses the diff for a renamed file.
|
|
730
|
+
*
|
|
731
|
+
* @param nodeFile - The file change object.
|
|
732
|
+
* @param commit - The commit hash or '--staged'.
|
|
733
|
+
* @param git - The SimpleGit instance.
|
|
734
|
+
* @param logger - The logger instance.
|
|
735
|
+
* @returns A Promise that resolves to the diff string.
|
|
736
|
+
*/
|
|
793
737
|
async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
794
738
|
let result = '';
|
|
795
739
|
const oldFilePath = nodeFile?.oldFilePath || nodeFile.filePath;
|
|
@@ -828,6 +772,18 @@ async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
|
828
772
|
}
|
|
829
773
|
return result;
|
|
830
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Retrieves the diff for a given file change in a specific commit.
|
|
777
|
+
* If the file is deleted, it returns a message indicating that the file has been deleted.
|
|
778
|
+
* If the file is renamed, it parses the renamed file diff and returns it.
|
|
779
|
+
* Otherwise, it retrieves the default diff from the index and returns it.
|
|
780
|
+
*
|
|
781
|
+
* @param nodeFile - The file change object.
|
|
782
|
+
* @param commit - The commit hash.
|
|
783
|
+
* @param git - The SimpleGit instance.
|
|
784
|
+
* @param logger - The logger instance.
|
|
785
|
+
* @returns A promise that resolves to the diff as a string.
|
|
786
|
+
*/
|
|
831
787
|
async function getDiff(nodeFile, commit, { git, logger, }) {
|
|
832
788
|
if (nodeFile.status === 'deleted') {
|
|
833
789
|
return 'This file has been deleted.';
|
|
@@ -870,14 +826,14 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
870
826
|
}
|
|
871
827
|
|
|
872
828
|
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
873
|
-
Commit Messages must have a short description that is less than 50 characters
|
|
829
|
+
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:
|
|
874
830
|
|
|
875
|
-
- Typically a hyphen or asterisk is used for the bullet
|
|
876
831
|
- Write concisely using an informal tone
|
|
877
832
|
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
878
|
-
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
879
833
|
- DO NOT use specific names or files from the code
|
|
834
|
+
- DO NOT include any diffs or file changes in the commit message
|
|
880
835
|
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
836
|
+
- ONLY respond with the resulting commit message.
|
|
881
837
|
|
|
882
838
|
"""{summary}"""
|
|
883
839
|
|
|
@@ -888,6 +844,151 @@ const COMMIT_PROMPT = new prompts.PromptTemplate({
|
|
|
888
844
|
inputVariables: inputVariables$1,
|
|
889
845
|
});
|
|
890
846
|
|
|
847
|
+
const DEFAULT_OLLAMA_LLM_SERVICE = {
|
|
848
|
+
provider: 'ollama',
|
|
849
|
+
model: 'codellama',
|
|
850
|
+
endpoint: 'http://localhost:11434',
|
|
851
|
+
maxConcurrent: 1,
|
|
852
|
+
tokenLimit: 1024,
|
|
853
|
+
};
|
|
854
|
+
const DEFAULT_OPENAI_LLM_SERVICE = {
|
|
855
|
+
provider: 'openai',
|
|
856
|
+
model: 'gpt-4',
|
|
857
|
+
authentication: {
|
|
858
|
+
type: 'APIKey',
|
|
859
|
+
credentials: {
|
|
860
|
+
apiKey: '',
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
tokenLimit: 1024,
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Retrieves the provider and model from the given configuration object.
|
|
868
|
+
* @param config The configuration object.
|
|
869
|
+
* @returns An object containing the provider and model.
|
|
870
|
+
* @throws Error if the configuration is invalid or missing required properties.
|
|
871
|
+
*/
|
|
872
|
+
function getModelAndProviderFromConfig(config) {
|
|
873
|
+
if (!config.service) {
|
|
874
|
+
throw new Error('Invalid service: undefined');
|
|
875
|
+
}
|
|
876
|
+
let result;
|
|
877
|
+
switch (typeof config.service) {
|
|
878
|
+
case 'string':
|
|
879
|
+
result = getDefaultServiceConfigFromAlias(config.service, config?.model);
|
|
880
|
+
break;
|
|
881
|
+
case 'object':
|
|
882
|
+
default:
|
|
883
|
+
result = config.service;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
const { provider, model } = result;
|
|
887
|
+
if (!model || !provider) {
|
|
888
|
+
throw new Error(`Invalid service: ${config.service}`);
|
|
889
|
+
}
|
|
890
|
+
return { provider, model };
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Retrieve appropriate API key based on selected model
|
|
894
|
+
* @param service
|
|
895
|
+
* @param options
|
|
896
|
+
* @returns API Key
|
|
897
|
+
*/
|
|
898
|
+
function getApiKeyForModel(config) {
|
|
899
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
900
|
+
switch (provider) {
|
|
901
|
+
case 'openai':
|
|
902
|
+
return config.openAIApiKey || getDefaultServiceApiKey(config);
|
|
903
|
+
default:
|
|
904
|
+
return getDefaultServiceApiKey(config);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Retrieves the default service API key from the given configuration.
|
|
909
|
+
* @param config The configuration object.
|
|
910
|
+
* @returns The default service API key.
|
|
911
|
+
*/
|
|
912
|
+
function getDefaultServiceApiKey(config) {
|
|
913
|
+
const service = config.service;
|
|
914
|
+
if (service.authentication.type === 'APIKey') {
|
|
915
|
+
return service.authentication.credentials?.apiKey;
|
|
916
|
+
}
|
|
917
|
+
else if (service.authentication.type === 'OAuth') {
|
|
918
|
+
return service.authentication.credentials?.token;
|
|
919
|
+
}
|
|
920
|
+
return '';
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Retrieves the default service configuration based on the provided alias and optional model.
|
|
924
|
+
* @param alias - The alias of the service.
|
|
925
|
+
* @param model - The optional model to be used.
|
|
926
|
+
* @returns The default service configuration.
|
|
927
|
+
* @throws Error if the alias is invalid or undefined.
|
|
928
|
+
*/
|
|
929
|
+
function getDefaultServiceConfigFromAlias(alias, model) {
|
|
930
|
+
if (!alias) {
|
|
931
|
+
throw new Error('Invalid alias: undefined');
|
|
932
|
+
}
|
|
933
|
+
switch (alias) {
|
|
934
|
+
case 'openai':
|
|
935
|
+
return {
|
|
936
|
+
...DEFAULT_OPENAI_LLM_SERVICE,
|
|
937
|
+
model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
|
|
938
|
+
};
|
|
939
|
+
case 'ollama':
|
|
940
|
+
return {
|
|
941
|
+
...DEFAULT_OLLAMA_LLM_SERVICE,
|
|
942
|
+
model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Get LLM Model Based on Configuration
|
|
949
|
+
*
|
|
950
|
+
* @param fields
|
|
951
|
+
* @param configuration
|
|
952
|
+
* @returns LLM Model
|
|
953
|
+
*/
|
|
954
|
+
function getLlm(provider, model, config) {
|
|
955
|
+
if (!model) {
|
|
956
|
+
throw new Error(`Invalid LLM Service: ${provider}/${model}`);
|
|
957
|
+
}
|
|
958
|
+
switch (provider) {
|
|
959
|
+
case 'ollama':
|
|
960
|
+
return new ollama.Ollama({
|
|
961
|
+
baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
|
|
962
|
+
model,
|
|
963
|
+
});
|
|
964
|
+
case 'openai':
|
|
965
|
+
default:
|
|
966
|
+
return new openai.OpenAI({
|
|
967
|
+
openAIApiKey: config.openAIApiKey,
|
|
968
|
+
modelName: model,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function getPrompt({ template, variables, fallback }) {
|
|
974
|
+
if (!template && !fallback)
|
|
975
|
+
throw new Error('Must provide either a template or a fallback');
|
|
976
|
+
return (template
|
|
977
|
+
? new prompts.PromptTemplate({
|
|
978
|
+
template,
|
|
979
|
+
inputVariables: variables,
|
|
980
|
+
})
|
|
981
|
+
: fallback);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Determines the status of a file based on its changes in the Git repository.
|
|
986
|
+
*
|
|
987
|
+
* @param file - The file to check the status of.
|
|
988
|
+
* @param location - The location to check the status in ('index' or 'working_dir'). Defaults to 'index'.
|
|
989
|
+
* @returns The status of the file ('added', 'deleted', 'modified', 'renamed', 'untracked', or 'unknown').
|
|
990
|
+
* @throws Error if the file type is invalid.
|
|
991
|
+
*/
|
|
891
992
|
function getStatus(file, location = 'index') {
|
|
892
993
|
if ('index' in file && 'working_dir' in file) {
|
|
893
994
|
const statusCode = file[location];
|
|
@@ -924,6 +1025,14 @@ function getStatus(file, location = 'index') {
|
|
|
924
1025
|
}
|
|
925
1026
|
}
|
|
926
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* Returns the summary text for a file change.
|
|
1030
|
+
*
|
|
1031
|
+
* @param file - The file status or diff result.
|
|
1032
|
+
* @param change - The partial file change object.
|
|
1033
|
+
* @returns The summary text for the file change.
|
|
1034
|
+
* @throws Error if the file type is invalid.
|
|
1035
|
+
*/
|
|
927
1036
|
function getSummaryText(file, change) {
|
|
928
1037
|
const status = change.status || getStatus(file);
|
|
929
1038
|
let filePath;
|
|
@@ -942,9 +1051,12 @@ function getSummaryText(file, change) {
|
|
|
942
1051
|
return `${status}: ${filePath}`;
|
|
943
1052
|
}
|
|
944
1053
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* Retrieves the changes in the Git repository.
|
|
1056
|
+
*
|
|
1057
|
+
* @param {GetChangesInput} options - The options for retrieving the changes.
|
|
1058
|
+
* @returns {Promise<GetChangesResult>} A promise that resolves to the changes in the Git repository.
|
|
1059
|
+
*/
|
|
948
1060
|
async function getChanges({ git, options }) {
|
|
949
1061
|
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options || {};
|
|
950
1062
|
const staged = [];
|
|
@@ -1010,13 +1122,13 @@ async function noResult({ git, logger }) {
|
|
|
1010
1122
|
else if (hasUnstaged || hasUntracked) {
|
|
1011
1123
|
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
1012
1124
|
if (hasUnstaged) {
|
|
1013
|
-
logger.log('\
|
|
1125
|
+
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
1014
1126
|
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
1015
1127
|
color: 'red',
|
|
1016
1128
|
});
|
|
1017
1129
|
}
|
|
1018
1130
|
if (hasUntracked) {
|
|
1019
|
-
logger.log('\
|
|
1131
|
+
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
1020
1132
|
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
1021
1133
|
color: 'red',
|
|
1022
1134
|
});
|
|
@@ -1090,6 +1202,24 @@ async function getUserReviewDecision({ label, descriptions, enableRetry = true,
|
|
|
1090
1202
|
}));
|
|
1091
1203
|
}
|
|
1092
1204
|
|
|
1205
|
+
/**
|
|
1206
|
+
* Verify template string contains all required input variables
|
|
1207
|
+
*
|
|
1208
|
+
* @param text template string
|
|
1209
|
+
* @param inputVariables template variables
|
|
1210
|
+
* @returns boolean or error message
|
|
1211
|
+
*/
|
|
1212
|
+
function validatePromptTemplate(text, inputVariables) {
|
|
1213
|
+
if (!text) {
|
|
1214
|
+
return 'Prompt template cannot be empty';
|
|
1215
|
+
}
|
|
1216
|
+
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
1217
|
+
return ('Prompt template must include at least one of the following input variables: ' +
|
|
1218
|
+
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
1219
|
+
}
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1093
1223
|
async function editPrompt(options) {
|
|
1094
1224
|
return await prompts$1.editor({
|
|
1095
1225
|
message: 'Edit the prompt',
|
|
@@ -1220,6 +1350,10 @@ async function handleResult({ result, mode, interactiveHandler }) {
|
|
|
1220
1350
|
process.exit(0);
|
|
1221
1351
|
}
|
|
1222
1352
|
|
|
1353
|
+
/**
|
|
1354
|
+
* Retrieves the SimpleGit instance for the repository.
|
|
1355
|
+
* @returns {SimpleGit} The SimpleGit instance.
|
|
1356
|
+
*/
|
|
1223
1357
|
const getRepo = () => {
|
|
1224
1358
|
let git;
|
|
1225
1359
|
try {
|
|
@@ -1232,34 +1366,50 @@ const getRepo = () => {
|
|
|
1232
1366
|
return git;
|
|
1233
1367
|
};
|
|
1234
1368
|
|
|
1369
|
+
/**
|
|
1370
|
+
* Retrieves a TikToken for the specified model.
|
|
1371
|
+
*
|
|
1372
|
+
* @param {TiktokenModel} modelName - The name of the TiktokenModel.
|
|
1373
|
+
* @returns A Promise that resolves to the TikToken.
|
|
1374
|
+
*/
|
|
1235
1375
|
const getTikToken = async (modelName) => {
|
|
1236
1376
|
return await tiktoken.encoding_for_model(modelName);
|
|
1237
1377
|
};
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1378
|
+
/**
|
|
1379
|
+
* Retrieves the token counter for a given model name.
|
|
1380
|
+
*
|
|
1381
|
+
* @param {TikTokenModel} modelName - The name of the Tiktoken model.
|
|
1382
|
+
* @returns A promise that resolves to a function that calculates the number of tokens in a given text.
|
|
1383
|
+
*/
|
|
1384
|
+
const getTokenCounter = async (modelName) => {
|
|
1385
|
+
return getTikToken(modelName).then((tokenizer) => (text) => {
|
|
1386
|
+
const tokens = tokenizer.encode(text);
|
|
1387
|
+
return tokens.length;
|
|
1388
|
+
});
|
|
1389
|
+
};
|
|
1244
1390
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1391
|
+
/**
|
|
1392
|
+
* Creates a commit with the specified commit message.
|
|
1393
|
+
*
|
|
1394
|
+
* @param message The commit message.
|
|
1395
|
+
* @param git The SimpleGit instance.
|
|
1396
|
+
* @returns A Promise that resolves to the CommitResult.
|
|
1397
|
+
*/
|
|
1398
|
+
async function createCommit(message, git) {
|
|
1399
|
+
return await git.commit(message);
|
|
1247
1400
|
}
|
|
1248
1401
|
|
|
1249
1402
|
const handler$2 = async (argv, logger) => {
|
|
1250
1403
|
const git = getRepo();
|
|
1251
1404
|
const options = loadConfig(argv);
|
|
1252
|
-
const
|
|
1253
|
-
const key = getApiKeyForModel(service, options);
|
|
1254
|
-
const tokenizer = await getTokenCounter(getModelFromService(service));
|
|
1405
|
+
const key = getApiKeyForModel(options);
|
|
1255
1406
|
if (!key) {
|
|
1256
1407
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
1257
1408
|
process.exit(1);
|
|
1258
1409
|
}
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
});
|
|
1410
|
+
const { provider, model } = getModelAndProviderFromConfig(options);
|
|
1411
|
+
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4');
|
|
1412
|
+
const llm = getLlm(provider, model, options);
|
|
1263
1413
|
const INTERACTIVE = isInteractive(options);
|
|
1264
1414
|
if (INTERACTIVE) {
|
|
1265
1415
|
logger.log(LOGO);
|
|
@@ -1325,16 +1475,10 @@ const handler$2 = async (argv, logger) => {
|
|
|
1325
1475
|
* Command line options via yargs
|
|
1326
1476
|
*/
|
|
1327
1477
|
const options$2 = {
|
|
1328
|
-
|
|
1478
|
+
service: { type: 'string', description: 'LLM/Model-Name', choices: ['openai', 'ollama'] },
|
|
1329
1479
|
openAIApiKey: {
|
|
1330
1480
|
type: 'string',
|
|
1331
1481
|
description: 'OpenAI API Key',
|
|
1332
|
-
conflicts: 'huggingFaceHubApiKey',
|
|
1333
|
-
},
|
|
1334
|
-
huggingFaceHubApiKey: {
|
|
1335
|
-
type: 'string',
|
|
1336
|
-
description: 'HuggingFace Hub API Key',
|
|
1337
|
-
conflicts: 'openAIApiKey',
|
|
1338
1482
|
},
|
|
1339
1483
|
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
1340
1484
|
prompt: {
|
|
@@ -1396,6 +1540,15 @@ const CHANGELOG_PROMPT = new prompts.PromptTemplate({
|
|
|
1396
1540
|
inputVariables,
|
|
1397
1541
|
});
|
|
1398
1542
|
|
|
1543
|
+
/**
|
|
1544
|
+
* Retrieves the commit log range between two specified commits.
|
|
1545
|
+
*
|
|
1546
|
+
* @param from - The starting commit.
|
|
1547
|
+
* @param to - The ending commit.
|
|
1548
|
+
* @param options - Additional options for retrieving the commit log range.
|
|
1549
|
+
* @returns A promise that resolves to an array of commit log messages.
|
|
1550
|
+
* @throws If there is an error retrieving the commit log range.
|
|
1551
|
+
*/
|
|
1399
1552
|
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
1400
1553
|
try {
|
|
1401
1554
|
const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
|
|
@@ -1409,10 +1562,26 @@ async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
|
1409
1562
|
}
|
|
1410
1563
|
}
|
|
1411
1564
|
|
|
1565
|
+
/**
|
|
1566
|
+
* Retrieves the name of the current branch.
|
|
1567
|
+
*
|
|
1568
|
+
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
1569
|
+
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
1570
|
+
*/
|
|
1412
1571
|
async function getCurrentBranchName({ git }) {
|
|
1413
1572
|
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
1414
1573
|
}
|
|
1415
1574
|
|
|
1575
|
+
/**
|
|
1576
|
+
* Retrieves the commit log for the current branch.
|
|
1577
|
+
*
|
|
1578
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
1579
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
1580
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
1581
|
+
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
1582
|
+
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
1583
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
1584
|
+
*/
|
|
1416
1585
|
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
1417
1586
|
try {
|
|
1418
1587
|
// Get the current branch name
|
|
@@ -1458,15 +1627,13 @@ async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main
|
|
|
1458
1627
|
const handler$1 = async (argv, logger) => {
|
|
1459
1628
|
const options = loadConfig(argv);
|
|
1460
1629
|
const git = getRepo();
|
|
1461
|
-
const key = getApiKeyForModel(options
|
|
1630
|
+
const key = getApiKeyForModel(options);
|
|
1462
1631
|
if (!key) {
|
|
1463
1632
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
1464
1633
|
process.exit(1);
|
|
1465
1634
|
}
|
|
1466
|
-
const model =
|
|
1467
|
-
|
|
1468
|
-
maxConcurrency: 10,
|
|
1469
|
-
});
|
|
1635
|
+
const { provider, model } = getModelAndProviderFromConfig(options);
|
|
1636
|
+
const llm = getLlm(provider, model, options);
|
|
1470
1637
|
const INTERACTIVE = isInteractive(options);
|
|
1471
1638
|
if (INTERACTIVE) {
|
|
1472
1639
|
logger.log(LOGO);
|
|
@@ -1498,7 +1665,7 @@ const handler$1 = async (argv, logger) => {
|
|
|
1498
1665
|
fallback: CHANGELOG_PROMPT,
|
|
1499
1666
|
});
|
|
1500
1667
|
return await executeChain({
|
|
1501
|
-
llm
|
|
1668
|
+
llm,
|
|
1502
1669
|
prompt,
|
|
1503
1670
|
variables: { summary: context },
|
|
1504
1671
|
});
|
|
@@ -1655,14 +1822,10 @@ async function installNpmPackage({ name, flags = [], cwd = process.cwd(), }) {
|
|
|
1655
1822
|
*/
|
|
1656
1823
|
function isPackageInstalled(packageName, projectPath) {
|
|
1657
1824
|
try {
|
|
1658
|
-
// Construct the path to the package.json file
|
|
1659
1825
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
1660
|
-
// Read the package.json file
|
|
1661
1826
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1662
|
-
// Check both dependencies and devDependencies
|
|
1663
1827
|
const dependencies = packageJson.dependencies || {};
|
|
1664
1828
|
const devDependencies = packageJson.devDependencies || {};
|
|
1665
|
-
// Return true if the package is found in either
|
|
1666
1829
|
return dependencies.hasOwnProperty(packageName) || devDependencies.hasOwnProperty(packageName);
|
|
1667
1830
|
}
|
|
1668
1831
|
catch (error) {
|