git-coco 0.11.1 → 0.12.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/dist/index.d.ts +56 -76
- package/dist/index.esm.mjs +1230 -978
- package/dist/index.js +1267 -1014
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var prompts = require('@langchain/core/prompts');
|
|
4
|
+
var prompts$1 = require('@langchain/core/prompts');
|
|
5
5
|
var example_selectors = require('@langchain/core/example_selectors');
|
|
6
6
|
var yargs = require('yargs');
|
|
7
|
-
var fs = require('fs');
|
|
8
|
-
var prompts$1 = require('@inquirer/prompts');
|
|
9
7
|
var chalk = require('chalk');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var prompts = require('@inquirer/prompts');
|
|
10
10
|
var ini = require('ini');
|
|
11
11
|
var os = require('os');
|
|
12
12
|
var path = require('path');
|
|
@@ -14,9 +14,10 @@ var Ajv = require('ajv');
|
|
|
14
14
|
var ora = require('ora');
|
|
15
15
|
var now = require('performance-now');
|
|
16
16
|
var prettyMilliseconds = require('pretty-ms');
|
|
17
|
-
var output_parsers = require('@langchain/core/output_parsers');
|
|
18
17
|
var ollama = require('@langchain/ollama');
|
|
19
18
|
var openai = require('@langchain/openai');
|
|
19
|
+
var output_parsers = require('@langchain/core/output_parsers');
|
|
20
|
+
var simpleGit = require('simple-git');
|
|
20
21
|
var pQueue = require('p-queue');
|
|
21
22
|
var documents = require('@langchain/core/documents');
|
|
22
23
|
var outputs = require('@langchain/core/outputs');
|
|
@@ -32,7 +33,6 @@ require('@langchain/core/utils/env');
|
|
|
32
33
|
require('@langchain/core/utils/json_patch');
|
|
33
34
|
var diff = require('diff');
|
|
34
35
|
var minimatch = require('minimatch');
|
|
35
|
-
var simpleGit = require('simple-git');
|
|
36
36
|
var tiktoken = require('tiktoken');
|
|
37
37
|
var child_process = require('child_process');
|
|
38
38
|
|
|
@@ -58,6 +58,27 @@ var ini__namespace = /*#__PURE__*/_interopNamespaceDefault(ini);
|
|
|
58
58
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
59
59
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
60
60
|
|
|
61
|
+
const isInteractive = (config) => {
|
|
62
|
+
return config?.mode === 'interactive' || !!config?.interactive;
|
|
63
|
+
};
|
|
64
|
+
const SEPERATOR = chalk.blue('─────────────');
|
|
65
|
+
const LOGO = chalk.green(`┌──────┐
|
|
66
|
+
│┏┏┓┏┏┓│
|
|
67
|
+
│┗┗┛┗┗┛│
|
|
68
|
+
└──────┘`);
|
|
69
|
+
chalk.green(`┌────┐
|
|
70
|
+
│coco│
|
|
71
|
+
└────┘`);
|
|
72
|
+
const USAGE_BANNER = chalk.green(`${LOGO}
|
|
73
|
+
${chalk.bgGreen(`\xa0v${process.env.npm_package_version}\xa0`)}
|
|
74
|
+
`);
|
|
75
|
+
const getCommandUsageHeader = (command) => {
|
|
76
|
+
return chalk.green(`${USAGE_BANNER}\n${chalk.white('Command:')}\n\xa0\xa0\xa0\xa0\xa0 $0 ${chalk.greenBright(command)} [options]`);
|
|
77
|
+
};
|
|
78
|
+
const CONFIG_ALREADY_EXISTS = (path) => {
|
|
79
|
+
return `coco config found in '${path}', do you want to override it?`;
|
|
80
|
+
};
|
|
81
|
+
|
|
61
82
|
/**
|
|
62
83
|
* Returns a new object with all undefined keys removed
|
|
63
84
|
*
|
|
@@ -68,16 +89,64 @@ function removeUndefined(obj) {
|
|
|
68
89
|
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));
|
|
69
90
|
}
|
|
70
91
|
|
|
71
|
-
|
|
92
|
+
async function updateFileSection({ filePath, startComment, endComment, getNewContent, confirmUpdate = true, confirmMessage = (path) => `A section already exists in ${path}, do you want to override it?`, }) {
|
|
93
|
+
const lines = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8').split(/\r?\n/) : [];
|
|
94
|
+
const newLines = [];
|
|
95
|
+
let foundSection = false;
|
|
96
|
+
for (let i = 0; i < lines.length; i++) {
|
|
97
|
+
if (lines[i].trim() === startComment) {
|
|
98
|
+
foundSection = true;
|
|
99
|
+
if (confirmUpdate) {
|
|
100
|
+
const confirmOverwrite = await prompts.confirm({
|
|
101
|
+
message: typeof confirmMessage === 'function' ? confirmMessage(filePath) : confirmMessage,
|
|
102
|
+
default: false,
|
|
103
|
+
});
|
|
104
|
+
if (!confirmOverwrite) {
|
|
105
|
+
// keep all lines until the end comment
|
|
106
|
+
while (i < lines.length && lines[i].trim() !== endComment) {
|
|
107
|
+
newLines.push(lines[i]);
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
newLines.push(endComment);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
newLines.push(startComment);
|
|
115
|
+
// Insert the new content
|
|
116
|
+
const newContent = await getNewContent();
|
|
117
|
+
newLines.push(newContent);
|
|
118
|
+
// Skip the existing content of the section
|
|
119
|
+
while (i < lines.length && lines[i].trim() !== endComment) {
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
newLines.push(endComment);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!foundSection || lines[i].trim() !== endComment) {
|
|
126
|
+
newLines.push(lines[i]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// If section wasn't found, append it at the end
|
|
130
|
+
if (!foundSection) {
|
|
131
|
+
newLines.push('\n' + startComment);
|
|
132
|
+
const newContent = await getNewContent();
|
|
133
|
+
newLines.push(newContent);
|
|
134
|
+
newLines.push(endComment);
|
|
135
|
+
}
|
|
136
|
+
// Write the updated contents back to the file
|
|
137
|
+
fs.writeFileSync(filePath, newLines.join('\n'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const template$4 = `GOAL: Use functional abstractions to summarize the following text
|
|
72
141
|
|
|
73
142
|
RULES: Avoid phrases like "this change", "this code", or "this function" etc. Instead refer to the function, variable, or class by name.
|
|
74
143
|
|
|
75
144
|
TEXT:"""{text}"""
|
|
76
145
|
`;
|
|
77
|
-
const inputVariables$
|
|
78
|
-
const SUMMARIZE_PROMPT = new prompts.PromptTemplate({
|
|
79
|
-
inputVariables: inputVariables$
|
|
80
|
-
template: template$
|
|
146
|
+
const inputVariables$3 = ['text'];
|
|
147
|
+
const SUMMARIZE_PROMPT = new prompts$1.PromptTemplate({
|
|
148
|
+
inputVariables: inputVariables$3,
|
|
149
|
+
template: template$4,
|
|
81
150
|
});
|
|
82
151
|
|
|
83
152
|
/**
|
|
@@ -203,68 +272,6 @@ const CONFIG_KEYS = Object.keys({
|
|
|
203
272
|
prompt: '',
|
|
204
273
|
});
|
|
205
274
|
|
|
206
|
-
async function updateFileSection({ filePath, startComment, endComment, getNewContent, confirmUpdate = true, confirmMessage = (path) => `A section already exists in ${path}, do you want to override it?`, }) {
|
|
207
|
-
const lines = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8').split(/\r?\n/) : [];
|
|
208
|
-
const newLines = [];
|
|
209
|
-
let foundSection = false;
|
|
210
|
-
for (let i = 0; i < lines.length; i++) {
|
|
211
|
-
if (lines[i].trim() === startComment) {
|
|
212
|
-
foundSection = true;
|
|
213
|
-
if (confirmUpdate) {
|
|
214
|
-
const confirmOverwrite = await prompts$1.confirm({
|
|
215
|
-
message: typeof confirmMessage === 'function' ? confirmMessage(filePath) : confirmMessage,
|
|
216
|
-
default: false,
|
|
217
|
-
});
|
|
218
|
-
if (!confirmOverwrite) {
|
|
219
|
-
// keep all lines until the end comment
|
|
220
|
-
while (i < lines.length && lines[i].trim() !== endComment) {
|
|
221
|
-
newLines.push(lines[i]);
|
|
222
|
-
i++;
|
|
223
|
-
}
|
|
224
|
-
newLines.push(endComment);
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
newLines.push(startComment);
|
|
229
|
-
// Insert the new content
|
|
230
|
-
const newContent = await getNewContent();
|
|
231
|
-
newLines.push(newContent);
|
|
232
|
-
// Skip the existing content of the section
|
|
233
|
-
while (i < lines.length && lines[i].trim() !== endComment) {
|
|
234
|
-
i++;
|
|
235
|
-
}
|
|
236
|
-
newLines.push(endComment);
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
if (!foundSection || lines[i].trim() !== endComment) {
|
|
240
|
-
newLines.push(lines[i]);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// If section wasn't found, append it at the end
|
|
244
|
-
if (!foundSection) {
|
|
245
|
-
newLines.push('\n' + startComment);
|
|
246
|
-
const newContent = await getNewContent();
|
|
247
|
-
newLines.push(newContent);
|
|
248
|
-
newLines.push(endComment);
|
|
249
|
-
}
|
|
250
|
-
// Write the updated contents back to the file
|
|
251
|
-
fs.writeFileSync(filePath, newLines.join('\n'));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const isInteractive = (argv) => {
|
|
255
|
-
return argv?.mode === 'interactive' || argv.interactive;
|
|
256
|
-
};
|
|
257
|
-
const SEPERATOR = chalk.blue('─────────────');
|
|
258
|
-
const LOGO = chalk.green(`┌────────────┐
|
|
259
|
-
│┌─┐┌─┐┌─┐┌─┐│
|
|
260
|
-
││ │ ││ │ ││
|
|
261
|
-
│└─┘└─┘└─┘└─┘│
|
|
262
|
-
└────────────┘
|
|
263
|
-
`);
|
|
264
|
-
const CONFIG_ALREADY_EXISTS = (path) => {
|
|
265
|
-
return `coco config found in '${path}', do you want to override it?`;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
275
|
/**
|
|
269
276
|
* Load environment variables
|
|
270
277
|
*
|
|
@@ -288,6 +295,9 @@ function loadEnvConfig(config) {
|
|
|
288
295
|
handleServiceEnvVar(envConfig.service, key, envValue);
|
|
289
296
|
}
|
|
290
297
|
else {
|
|
298
|
+
if (key === 'service' || !envValue) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
291
301
|
envConfig[key] = envValue;
|
|
292
302
|
}
|
|
293
303
|
});
|
|
@@ -396,7 +406,6 @@ function loadGitConfig(config) {
|
|
|
396
406
|
config = {
|
|
397
407
|
...config,
|
|
398
408
|
service: service,
|
|
399
|
-
temperature: gitConfigParsed.coco?.temperature || config.temperature,
|
|
400
409
|
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
401
410
|
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
402
411
|
summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
|
|
@@ -1936,26 +1945,6 @@ function commandExecutor(handler) {
|
|
|
1936
1945
|
};
|
|
1937
1946
|
}
|
|
1938
1947
|
|
|
1939
|
-
const executeChain = async ({ llm, prompt, variables, parser, }) => {
|
|
1940
|
-
if (!llm || !prompt || !variables) {
|
|
1941
|
-
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
1942
|
-
}
|
|
1943
|
-
const chain = prompt.pipe(llm).pipe(parser);
|
|
1944
|
-
let res;
|
|
1945
|
-
try {
|
|
1946
|
-
res = await chain.invoke(variables);
|
|
1947
|
-
}
|
|
1948
|
-
catch (error) {
|
|
1949
|
-
if (error instanceof Error) {
|
|
1950
|
-
throw new Error(`LLMChain call error: ${error.message}`);
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
if (!res) {
|
|
1954
|
-
throw new Error('Empty response from LLMChain call');
|
|
1955
|
-
}
|
|
1956
|
-
return res;
|
|
1957
|
-
};
|
|
1958
|
-
|
|
1959
1948
|
/**
|
|
1960
1949
|
* Get LLM Model Based on Configuration
|
|
1961
1950
|
*
|
|
@@ -1989,87 +1978,610 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
1989
1978
|
if (!template && !fallback)
|
|
1990
1979
|
throw new Error('Must provide either a template or a fallback');
|
|
1991
1980
|
return (template
|
|
1992
|
-
? new prompts.PromptTemplate({
|
|
1981
|
+
? new prompts$1.PromptTemplate({
|
|
1993
1982
|
template,
|
|
1994
1983
|
inputVariables: variables,
|
|
1995
1984
|
})
|
|
1996
1985
|
: fallback);
|
|
1997
1986
|
}
|
|
1998
1987
|
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
1988
|
+
const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
1989
|
+
if (!llm || !prompt || !variables) {
|
|
1990
|
+
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
1991
|
+
}
|
|
1992
|
+
const chain = prompt.pipe(llm).pipe(parser);
|
|
1993
|
+
let res;
|
|
1994
|
+
try {
|
|
1995
|
+
res = await chain.invoke(variables);
|
|
1996
|
+
}
|
|
1997
|
+
catch (error) {
|
|
1998
|
+
if (error instanceof Error) {
|
|
1999
|
+
throw new Error(`LLMChain call error: ${error.message}`);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
if (!res) {
|
|
2003
|
+
throw new Error('Empty response from LLMChain call');
|
|
2004
|
+
}
|
|
2005
|
+
return res;
|
|
2006
|
+
};
|
|
2007
2007
|
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
input_documents: docs,
|
|
2013
|
-
returnIntermediateSteps,
|
|
2014
|
-
});
|
|
2015
|
-
if (res.error)
|
|
2016
|
-
throw new Error(res.error);
|
|
2017
|
-
return res.text && res.text.trim();
|
|
2008
|
+
function extractTicketIdFromBranchName(branchName) {
|
|
2009
|
+
const regex = /((?<!([A-Z]+)-?)[A-Z]+-\d+)/;
|
|
2010
|
+
const match = branchName.match(regex);
|
|
2011
|
+
return match ? match[0] : null;
|
|
2018
2012
|
}
|
|
2019
2013
|
|
|
2020
2014
|
/**
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
* @
|
|
2015
|
+
* Retrieves the commit log range between two specified commits.
|
|
2016
|
+
*
|
|
2017
|
+
* @param from - The starting commit.
|
|
2018
|
+
* @param to - The ending commit.
|
|
2019
|
+
* @param options - Additional options for retrieving the commit log range.
|
|
2020
|
+
* @returns A promise that resolves to an array of commit log messages.
|
|
2021
|
+
* @throws If there is an error retrieving the commit log range.
|
|
2024
2022
|
*/
|
|
2025
|
-
function
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
groupByPath[path].tokenCount += diff.tokenCount;
|
|
2035
|
-
});
|
|
2036
|
-
node.children.forEach(traverse);
|
|
2023
|
+
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
2024
|
+
try {
|
|
2025
|
+
const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
|
|
2026
|
+
const commitLog = await git.log(logOptions);
|
|
2027
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
2028
|
+
}
|
|
2029
|
+
catch (error) {
|
|
2030
|
+
// If there's an error, handle it appropriately
|
|
2031
|
+
throw error;
|
|
2037
2032
|
}
|
|
2038
|
-
traverse(node);
|
|
2039
|
-
return Object.values(groupByPath);
|
|
2040
2033
|
}
|
|
2034
|
+
|
|
2041
2035
|
/**
|
|
2042
|
-
*
|
|
2036
|
+
* Retrieves the name of the current branch.
|
|
2037
|
+
*
|
|
2038
|
+
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
2039
|
+
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
2043
2040
|
*/
|
|
2044
|
-
async function
|
|
2041
|
+
async function getCurrentBranchName({ git }) {
|
|
2042
|
+
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* Retrieves the commit log between the current branch and a specified target branch.
|
|
2047
|
+
*
|
|
2048
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
2049
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
2050
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
2051
|
+
* @param {string} options.targetBranch - The target branch to compare against.
|
|
2052
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
2053
|
+
*/
|
|
2054
|
+
async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
|
|
2045
2055
|
try {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
path: directory.path,
|
|
2063
|
-
summary: directorySummary,
|
|
2064
|
-
tokenCount: newTokenTotal,
|
|
2065
|
-
};
|
|
2056
|
+
// Get the current branch name
|
|
2057
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
2058
|
+
// Get the list of commits that are unique to the current branch compared to the target branch
|
|
2059
|
+
const uniqueCommits = (await git.raw(['rev-list', `${targetBranch}..${currentBranch}`]))
|
|
2060
|
+
.split('\n')
|
|
2061
|
+
.filter(Boolean)
|
|
2062
|
+
.reverse();
|
|
2063
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and "${targetBranch}"`, { color: 'blue' });
|
|
2064
|
+
const firstCommit = uniqueCommits[0];
|
|
2065
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
2066
|
+
if (!firstCommit || !lastCommit) {
|
|
2067
|
+
logger?.log('Unable to determine first and last commit between branches', { color: 'yellow' });
|
|
2068
|
+
return [];
|
|
2069
|
+
}
|
|
2070
|
+
// Retrieve commit log with messages
|
|
2071
|
+
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
2066
2072
|
}
|
|
2067
2073
|
catch (error) {
|
|
2068
|
-
|
|
2069
|
-
return directory;
|
|
2074
|
+
logger?.log('Encountered an error getting commit log between branches', { color: 'red' });
|
|
2070
2075
|
}
|
|
2076
|
+
return [];
|
|
2071
2077
|
}
|
|
2072
|
-
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* Retrieves the commit log for the current branch.
|
|
2081
|
+
*
|
|
2082
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
2083
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
2084
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
2085
|
+
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
2086
|
+
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
2087
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
2088
|
+
*/
|
|
2089
|
+
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
2090
|
+
try {
|
|
2091
|
+
// Get the current branch name
|
|
2092
|
+
const branch = await getCurrentBranchName({ git });
|
|
2093
|
+
// Check if the current branch has any commits
|
|
2094
|
+
const hasCommits = (await git.raw(['rev-list', '--count', branch])) !== '0';
|
|
2095
|
+
if (!hasCommits) {
|
|
2096
|
+
logger?.log('No commits on the current branch.');
|
|
2097
|
+
return [];
|
|
2098
|
+
}
|
|
2099
|
+
// Get the list of commits that are unique to the current branch
|
|
2100
|
+
let uniqueCommits;
|
|
2101
|
+
if (comparisonBranch === branch) {
|
|
2102
|
+
// If the comparison branch is the same as the current branch, we compare against the remote.
|
|
2103
|
+
uniqueCommits = (await git.raw(['rev-list', `${comparisonRemote}/${comparisonBranch}..${branch}`]))
|
|
2104
|
+
.split('\n')
|
|
2105
|
+
.filter(Boolean)
|
|
2106
|
+
.reverse();
|
|
2107
|
+
}
|
|
2108
|
+
else {
|
|
2109
|
+
// Your existing code for different branches
|
|
2110
|
+
uniqueCommits = (await git.raw(['rev-list', `${comparisonBranch}..${branch}`]))
|
|
2111
|
+
.split('\n')
|
|
2112
|
+
.filter(Boolean)
|
|
2113
|
+
.reverse();
|
|
2114
|
+
}
|
|
2115
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits on "${branch}"`, { color: 'blue' });
|
|
2116
|
+
const firstCommit = uniqueCommits[0];
|
|
2117
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
2118
|
+
if (!firstCommit || !lastCommit) {
|
|
2119
|
+
logger?.log('Unable to determine first and last commit on the current branch', { color: 'yellow' });
|
|
2120
|
+
return [];
|
|
2121
|
+
}
|
|
2122
|
+
// Retrieve commit log with messages
|
|
2123
|
+
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
logger?.log('Encountered an error getting commit log from current branch', { color: 'red' });
|
|
2127
|
+
}
|
|
2128
|
+
return [];
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* Retrieves the SimpleGit instance for the repository.
|
|
2133
|
+
* @returns {SimpleGit} The SimpleGit instance.
|
|
2134
|
+
*/
|
|
2135
|
+
const getRepo = () => {
|
|
2136
|
+
let git;
|
|
2137
|
+
try {
|
|
2138
|
+
git = simpleGit.simpleGit();
|
|
2139
|
+
}
|
|
2140
|
+
catch (e) {
|
|
2141
|
+
console.log('Error initializing git repo', e);
|
|
2142
|
+
process.exit(1);
|
|
2143
|
+
}
|
|
2144
|
+
return git;
|
|
2145
|
+
};
|
|
2146
|
+
|
|
2147
|
+
const template$3 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
2148
|
+
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary around 300 characters, the shorter and more concise the better.
|
|
2149
|
+
|
|
2150
|
+
Please follow the guidelines below when writing your commit message:
|
|
2151
|
+
|
|
2152
|
+
- Write concisely using an informal tone
|
|
2153
|
+
- Avoid phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
2154
|
+
- Avoid referencing specific files names or long paths in the commit message
|
|
2155
|
+
- DO NOT include any diffs or file changes in the commit message
|
|
2156
|
+
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
2157
|
+
|
|
2158
|
+
{format_instructions}
|
|
2159
|
+
|
|
2160
|
+
""""""
|
|
2161
|
+
{summary}
|
|
2162
|
+
""""""
|
|
2163
|
+
|
|
2164
|
+
{additional}
|
|
2165
|
+
`;
|
|
2166
|
+
const inputVariables$2 = ['summary', 'format_instructions', 'additional'];
|
|
2167
|
+
const COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
2168
|
+
template: template$3,
|
|
2169
|
+
inputVariables: inputVariables$2,
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
/**
|
|
2173
|
+
* Verify template string contains all required input variables
|
|
2174
|
+
*
|
|
2175
|
+
* @param text template string
|
|
2176
|
+
* @param inputVariables template variables
|
|
2177
|
+
* @returns boolean or error message
|
|
2178
|
+
*/
|
|
2179
|
+
function validatePromptTemplate(text, inputVariables) {
|
|
2180
|
+
if (!text) {
|
|
2181
|
+
return 'Prompt template cannot be empty';
|
|
2182
|
+
}
|
|
2183
|
+
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
2184
|
+
return ('Prompt template must include at least one of the following input variables: ' +
|
|
2185
|
+
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
2186
|
+
}
|
|
2187
|
+
return true;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
async function editPrompt(options) {
|
|
2191
|
+
return await prompts.editor({
|
|
2192
|
+
message: 'Edit the prompt',
|
|
2193
|
+
default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
|
|
2194
|
+
waitForUseInput: false,
|
|
2195
|
+
postfix: 'Press ENTER to continue',
|
|
2196
|
+
validate: (text) => validatePromptTemplate(text, COMMIT_PROMPT.inputVariables),
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
async function editResult(result, options) {
|
|
2201
|
+
if (options.openInEditor) {
|
|
2202
|
+
return await prompts.editor({
|
|
2203
|
+
message: 'Edit the commit message',
|
|
2204
|
+
default: result,
|
|
2205
|
+
waitForUseInput: false,
|
|
2206
|
+
validate: (text) => (text ? true : 'Commit message cannot be empty'),
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
return result;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
async function getUserReviewDecision({ label, descriptions, enableRetry = true, enableFullRetry = true, enableModifyPrompt = true, }) {
|
|
2213
|
+
const choices = [
|
|
2214
|
+
{
|
|
2215
|
+
name: '✨ Looks good!',
|
|
2216
|
+
value: 'approve',
|
|
2217
|
+
description: descriptions?.approve || `Continue with the generated ${label}`,
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
name: '📝 Edit',
|
|
2221
|
+
value: 'edit',
|
|
2222
|
+
description: descriptions?.edit || `Edit the generated ${label} before proceeding`,
|
|
2223
|
+
},
|
|
2224
|
+
];
|
|
2225
|
+
if (enableModifyPrompt) {
|
|
2226
|
+
choices.push({
|
|
2227
|
+
name: '🪶 Modify Prompt',
|
|
2228
|
+
value: 'modifyPrompt',
|
|
2229
|
+
description: descriptions?.modifyPrompt || `Modify the prompt template and regenerate the ${label}`,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
if (enableRetry) {
|
|
2233
|
+
choices.push({
|
|
2234
|
+
name: '🔄 Retry',
|
|
2235
|
+
value: 'retryMessageOnly',
|
|
2236
|
+
description: descriptions?.retryMessageOnly ||
|
|
2237
|
+
`Restart the function execution from generating the ${label}`,
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
if (enableFullRetry) {
|
|
2241
|
+
choices.push({
|
|
2242
|
+
name: '🔄 Retry Full',
|
|
2243
|
+
value: 'retryFull',
|
|
2244
|
+
description: descriptions?.retryFull ||
|
|
2245
|
+
`Restart the function execution from the beginning, regenerating both the summary and ${label}`,
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
choices.push({
|
|
2249
|
+
name: '💣 Cancel',
|
|
2250
|
+
value: 'cancel',
|
|
2251
|
+
description: descriptions?.cancel || `Cancel the ${label}`,
|
|
2252
|
+
});
|
|
2253
|
+
return (await prompts.select({
|
|
2254
|
+
message: `Would you like to make any changes to the ${label}?`,
|
|
2255
|
+
choices,
|
|
2256
|
+
}));
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function logResult(label, result) {
|
|
2260
|
+
console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
async function generateAndReviewLoop({ label, factory, parser, noResult, agent, options, }) {
|
|
2264
|
+
const { logger } = options;
|
|
2265
|
+
let continueLoop = true;
|
|
2266
|
+
let modifyPrompt = false;
|
|
2267
|
+
let context = '';
|
|
2268
|
+
let result = '';
|
|
2269
|
+
const changes = await factory();
|
|
2270
|
+
// if we don't have any changes, bail.
|
|
2271
|
+
if (!changes || !Object.keys(changes).length) {
|
|
2272
|
+
await noResult(options);
|
|
2273
|
+
}
|
|
2274
|
+
while (continueLoop) {
|
|
2275
|
+
if (!context.length) {
|
|
2276
|
+
context = await parser(changes, result, options);
|
|
2277
|
+
}
|
|
2278
|
+
// if we still don't have a context, bail.
|
|
2279
|
+
if (!context.length) {
|
|
2280
|
+
await noResult(options);
|
|
2281
|
+
}
|
|
2282
|
+
if (modifyPrompt) {
|
|
2283
|
+
options.prompt = await editPrompt(options);
|
|
2284
|
+
}
|
|
2285
|
+
logger.startTimer().startSpinner(`Generating ${label}\n`, {
|
|
2286
|
+
color: 'blue',
|
|
2287
|
+
});
|
|
2288
|
+
result = await agent(context, options);
|
|
2289
|
+
if (!result) {
|
|
2290
|
+
logger.stopSpinner('💀 Agent failed to return content.', {
|
|
2291
|
+
mode: 'fail',
|
|
2292
|
+
color: 'red',
|
|
2293
|
+
});
|
|
2294
|
+
process.exit(0);
|
|
2295
|
+
}
|
|
2296
|
+
logger
|
|
2297
|
+
.stopSpinner(`Generated ${label}`, {
|
|
2298
|
+
color: 'green',
|
|
2299
|
+
mode: 'succeed',
|
|
2300
|
+
})
|
|
2301
|
+
.stopTimer();
|
|
2302
|
+
if (options?.interactive) {
|
|
2303
|
+
logResult(label, result);
|
|
2304
|
+
const reviewAnswer = await getUserReviewDecision({
|
|
2305
|
+
label,
|
|
2306
|
+
...(options?.review || {}),
|
|
2307
|
+
});
|
|
2308
|
+
if (reviewAnswer === 'cancel') {
|
|
2309
|
+
process.exit(0);
|
|
2310
|
+
}
|
|
2311
|
+
if (reviewAnswer === 'edit') {
|
|
2312
|
+
options.openInEditor = true;
|
|
2313
|
+
}
|
|
2314
|
+
if (reviewAnswer === 'retryFull') {
|
|
2315
|
+
context = '';
|
|
2316
|
+
result = '';
|
|
2317
|
+
options.prompt = '';
|
|
2318
|
+
continue;
|
|
2319
|
+
}
|
|
2320
|
+
if (reviewAnswer === 'retryMessageOnly') {
|
|
2321
|
+
modifyPrompt = false;
|
|
2322
|
+
result = '';
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
if (reviewAnswer === 'modifyPrompt') {
|
|
2326
|
+
modifyPrompt = true;
|
|
2327
|
+
result = '';
|
|
2328
|
+
continue;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
// if we're here, we're done.
|
|
2332
|
+
result = await editResult(result, options);
|
|
2333
|
+
continueLoop = false;
|
|
2334
|
+
}
|
|
2335
|
+
return result;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
const logSuccess = () => {
|
|
2339
|
+
console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
|
|
2340
|
+
};
|
|
2341
|
+
|
|
2342
|
+
async function handleResult({ result, mode, interactiveHandler }) {
|
|
2343
|
+
switch (mode) {
|
|
2344
|
+
case 'interactive':
|
|
2345
|
+
if (interactiveHandler) {
|
|
2346
|
+
await interactiveHandler(result);
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
console.warn('No result handler provided for interactive mode.');
|
|
2350
|
+
logSuccess();
|
|
2351
|
+
}
|
|
2352
|
+
break;
|
|
2353
|
+
case 'stdout':
|
|
2354
|
+
default:
|
|
2355
|
+
process.stdout.write(result, 'utf8');
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
process.exit(0);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
const template$2 = `Write informative git changelog, in the imperative, based on a series of individual messages.
|
|
2362
|
+
|
|
2363
|
+
- Include the git commit hash as reference for each change, including just the first 7 characters
|
|
2364
|
+
- Logically group changes, and if necessary, summarize dependency updates
|
|
2365
|
+
|
|
2366
|
+
{format_instructions}
|
|
2367
|
+
|
|
2368
|
+
"""{summary}"""`;
|
|
2369
|
+
const inputVariables$1 = ['format_instructions', 'summary'];
|
|
2370
|
+
const CHANGELOG_PROMPT = new prompts$1.PromptTemplate({
|
|
2371
|
+
template: template$2,
|
|
2372
|
+
inputVariables: inputVariables$1,
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
const handler$3 = async (argv, logger) => {
|
|
2376
|
+
const config = loadConfig(argv);
|
|
2377
|
+
const git = getRepo();
|
|
2378
|
+
const key = getApiKeyForModel(config);
|
|
2379
|
+
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
2380
|
+
if (config.service.authentication.type !== 'None' && !key) {
|
|
2381
|
+
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
2382
|
+
process.exit(1);
|
|
2383
|
+
}
|
|
2384
|
+
const llm = getLlm(provider, model, config);
|
|
2385
|
+
const INTERACTIVE = isInteractive(config);
|
|
2386
|
+
if (INTERACTIVE) {
|
|
2387
|
+
logger.log(LOGO);
|
|
2388
|
+
}
|
|
2389
|
+
async function factory() {
|
|
2390
|
+
const branchName = await getCurrentBranchName({ git });
|
|
2391
|
+
if (config.range && config.range.includes(':')) {
|
|
2392
|
+
const [from, to] = config.range.split(':');
|
|
2393
|
+
if (!from || !to) {
|
|
2394
|
+
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
2395
|
+
process.exit(1);
|
|
2396
|
+
}
|
|
2397
|
+
return {
|
|
2398
|
+
branch: branchName,
|
|
2399
|
+
commits: await getCommitLogRange(from, to, { git, noMerges: true }),
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
if (argv.branch) {
|
|
2403
|
+
logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
|
|
2404
|
+
return {
|
|
2405
|
+
branch: branchName,
|
|
2406
|
+
commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
logger.verbose(`No range or branch provided. Defaulting to current branch`, { color: 'yellow' });
|
|
2410
|
+
return {
|
|
2411
|
+
branch: branchName,
|
|
2412
|
+
commits: await getCommitLogCurrentBranch({ git, logger }),
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
async function parser({ branch, commits }) {
|
|
2416
|
+
let result;
|
|
2417
|
+
if (!commits || commits.length === 0) {
|
|
2418
|
+
result = `## ${branch}\n\nNo commits found.`;
|
|
2419
|
+
}
|
|
2420
|
+
else {
|
|
2421
|
+
result = `## ${branch}\n\n${commits.map((commit) => commit.trim()).join('\n\n')}`;
|
|
2422
|
+
}
|
|
2423
|
+
return result;
|
|
2424
|
+
}
|
|
2425
|
+
const changelogMsg = await generateAndReviewLoop({
|
|
2426
|
+
label: 'changelog',
|
|
2427
|
+
factory,
|
|
2428
|
+
parser,
|
|
2429
|
+
agent: async (context, options) => {
|
|
2430
|
+
const parser = new output_parsers.JsonOutputParser();
|
|
2431
|
+
const prompt = getPrompt({
|
|
2432
|
+
template: options.prompt,
|
|
2433
|
+
variables: CHANGELOG_PROMPT.inputVariables,
|
|
2434
|
+
fallback: CHANGELOG_PROMPT,
|
|
2435
|
+
});
|
|
2436
|
+
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'header' and 'content', both strings.";
|
|
2437
|
+
const changelog = await executeChain({
|
|
2438
|
+
llm,
|
|
2439
|
+
prompt,
|
|
2440
|
+
variables: {
|
|
2441
|
+
summary: context,
|
|
2442
|
+
format_instructions: formatInstructions,
|
|
2443
|
+
},
|
|
2444
|
+
parser,
|
|
2445
|
+
});
|
|
2446
|
+
const branchName = await getCurrentBranchName({ git });
|
|
2447
|
+
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
2448
|
+
const footer = ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
2449
|
+
return `${changelog.header}\n\n${changelog.content}${footer}`;
|
|
2450
|
+
},
|
|
2451
|
+
noResult: async () => {
|
|
2452
|
+
if (config.range) {
|
|
2453
|
+
logger.log(`No commits found in the provided range.`, { color: 'red' });
|
|
2454
|
+
process.exit(0);
|
|
2455
|
+
}
|
|
2456
|
+
logger.log(`No commits found in the current branch.`, { color: 'red' });
|
|
2457
|
+
process.exit(0);
|
|
2458
|
+
},
|
|
2459
|
+
options: {
|
|
2460
|
+
...config,
|
|
2461
|
+
prompt: config.prompt || CHANGELOG_PROMPT.template,
|
|
2462
|
+
logger,
|
|
2463
|
+
interactive: INTERACTIVE,
|
|
2464
|
+
review: {
|
|
2465
|
+
enableFullRetry: false,
|
|
2466
|
+
},
|
|
2467
|
+
},
|
|
2468
|
+
});
|
|
2469
|
+
const MODE = (INTERACTIVE && 'interactive') || (config.commit && 'interactive') || config?.mode || 'stdout';
|
|
2470
|
+
handleResult({
|
|
2471
|
+
result: changelogMsg,
|
|
2472
|
+
interactiveHandler: async () => {
|
|
2473
|
+
logSuccess();
|
|
2474
|
+
},
|
|
2475
|
+
mode: MODE,
|
|
2476
|
+
});
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Command line options via yargs
|
|
2481
|
+
*/
|
|
2482
|
+
const options$3 = {
|
|
2483
|
+
range: {
|
|
2484
|
+
type: 'string',
|
|
2485
|
+
alias: 'r',
|
|
2486
|
+
description: 'Commit range e.g `HEAD~2:HEAD`',
|
|
2487
|
+
},
|
|
2488
|
+
branch: {
|
|
2489
|
+
type: 'string',
|
|
2490
|
+
alias: 'b',
|
|
2491
|
+
description: 'Target branch to compare against',
|
|
2492
|
+
},
|
|
2493
|
+
i: {
|
|
2494
|
+
type: 'boolean',
|
|
2495
|
+
alias: 'interactive',
|
|
2496
|
+
description: 'Toggle interactive mode',
|
|
2497
|
+
},
|
|
2498
|
+
};
|
|
2499
|
+
const builder$3 = (yargsInstance) => {
|
|
2500
|
+
return yargsInstance.options(options$3).usage(getCommandUsageHeader(changelog.command));
|
|
2501
|
+
};
|
|
2502
|
+
|
|
2503
|
+
var changelog = {
|
|
2504
|
+
command: 'changelog',
|
|
2505
|
+
desc: 'Generate a changelog from current or target branch or provided commit range.',
|
|
2506
|
+
builder: builder$3,
|
|
2507
|
+
handler: commandExecutor(handler$3),
|
|
2508
|
+
options: options$3,
|
|
2509
|
+
};
|
|
2510
|
+
|
|
2511
|
+
/**
|
|
2512
|
+
* Extract the path from a file path string.
|
|
2513
|
+
* @param {string} filePath - The full file path.
|
|
2514
|
+
* @returns {string} The path portion of the file path.
|
|
2515
|
+
*/
|
|
2516
|
+
function getPathFromFilePath(filePath) {
|
|
2517
|
+
return filePath.split('/').slice(0, -1).join('/');
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
async function summarize(documents$1, { chain, textSplitter, options }) {
|
|
2521
|
+
const { returnIntermediateSteps = false } = options || {};
|
|
2522
|
+
const docs = await textSplitter.splitDocuments(documents$1.map((doc) => new documents.Document(doc)));
|
|
2523
|
+
const res = await chain.invoke({
|
|
2524
|
+
input_documents: docs,
|
|
2525
|
+
returnIntermediateSteps,
|
|
2526
|
+
});
|
|
2527
|
+
if (res.error)
|
|
2528
|
+
throw new Error(res.error);
|
|
2529
|
+
return res.text && res.text.trim();
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/**
|
|
2533
|
+
* Create groups from a given node info.
|
|
2534
|
+
* @param {DiffNode} node - The node info to start grouping.
|
|
2535
|
+
* @returns {DirectoryDiff[]} The groups created.
|
|
2536
|
+
*/
|
|
2537
|
+
function createDirectoryDiffs(node) {
|
|
2538
|
+
const groupByPath = {};
|
|
2539
|
+
function traverse(node) {
|
|
2540
|
+
node.diffs.forEach((diff) => {
|
|
2541
|
+
const path = getPathFromFilePath(diff.file);
|
|
2542
|
+
if (!groupByPath[path]) {
|
|
2543
|
+
groupByPath[path] = { diffs: [], path, tokenCount: 0 };
|
|
2544
|
+
}
|
|
2545
|
+
groupByPath[path].diffs.push(diff);
|
|
2546
|
+
groupByPath[path].tokenCount += diff.tokenCount;
|
|
2547
|
+
});
|
|
2548
|
+
node.children.forEach(traverse);
|
|
2549
|
+
}
|
|
2550
|
+
traverse(node);
|
|
2551
|
+
return Object.values(groupByPath);
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Summarize a directory diff asynchronously.
|
|
2555
|
+
*/
|
|
2556
|
+
async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenizer }) {
|
|
2557
|
+
try {
|
|
2558
|
+
const directorySummary = await summarize(directory.diffs.map((diff) => ({
|
|
2559
|
+
pageContent: diff.diff,
|
|
2560
|
+
metadata: {
|
|
2561
|
+
file: diff.file,
|
|
2562
|
+
summary: diff.summary,
|
|
2563
|
+
},
|
|
2564
|
+
})), {
|
|
2565
|
+
chain,
|
|
2566
|
+
textSplitter,
|
|
2567
|
+
options: {
|
|
2568
|
+
returnIntermediateSteps: true,
|
|
2569
|
+
},
|
|
2570
|
+
});
|
|
2571
|
+
const newTokenTotal = tokenizer(directorySummary);
|
|
2572
|
+
return {
|
|
2573
|
+
diffs: directory.diffs,
|
|
2574
|
+
path: directory.path,
|
|
2575
|
+
summary: directorySummary,
|
|
2576
|
+
tokenCount: newTokenTotal,
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
catch (error) {
|
|
2580
|
+
console.error(error);
|
|
2581
|
+
return directory;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
const defaultOutputCallback = (group) => {
|
|
2073
2585
|
let output = `
|
|
2074
2586
|
-------\n* changes in "/${group.path}"\n\n`;
|
|
2075
2587
|
if (group.summary) {
|
|
@@ -2117,100 +2629,6 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 204
|
|
|
2117
2629
|
return directoryDiffs.map(handleOutput).join('');
|
|
2118
2630
|
}
|
|
2119
2631
|
|
|
2120
|
-
class DiffTreeNode {
|
|
2121
|
-
constructor(path) {
|
|
2122
|
-
this.path = [];
|
|
2123
|
-
this.files = [];
|
|
2124
|
-
this.children = new Map();
|
|
2125
|
-
if (path)
|
|
2126
|
-
this.path = path;
|
|
2127
|
-
}
|
|
2128
|
-
addFile(file) {
|
|
2129
|
-
this.files.push(file);
|
|
2130
|
-
}
|
|
2131
|
-
addChild(part, node) {
|
|
2132
|
-
this.children.set(part, node);
|
|
2133
|
-
}
|
|
2134
|
-
getChild(part) {
|
|
2135
|
-
return this.children.get(part);
|
|
2136
|
-
}
|
|
2137
|
-
getPath() {
|
|
2138
|
-
return this.path.join('/');
|
|
2139
|
-
}
|
|
2140
|
-
print(indentation = 0) {
|
|
2141
|
-
const indent = ' '.repeat(indentation);
|
|
2142
|
-
let output = `${indent}- Path: ${this.getPath()}\n`;
|
|
2143
|
-
if (this.files.length > 0) {
|
|
2144
|
-
output += `${indent} Files:\n`;
|
|
2145
|
-
for (const file of this.files) {
|
|
2146
|
-
output += `${indent} - ${file.summary}\n`;
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
if (this.children.size > 0) {
|
|
2150
|
-
output += `${indent} Children:\n`;
|
|
2151
|
-
for (const [, child] of this.children) {
|
|
2152
|
-
output += child.print(indentation + 4);
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
return output;
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
const createDiffTree = (changes) => {
|
|
2159
|
-
const root = new DiffTreeNode();
|
|
2160
|
-
for (const change of changes) {
|
|
2161
|
-
let currentParent = root;
|
|
2162
|
-
const parts = change.filePath.split('/');
|
|
2163
|
-
parts.pop();
|
|
2164
|
-
for (const part of parts) {
|
|
2165
|
-
let childNode = currentParent.getChild(part);
|
|
2166
|
-
if (!childNode) {
|
|
2167
|
-
childNode = new DiffTreeNode([...currentParent.path, part]);
|
|
2168
|
-
currentParent.addChild(part, childNode);
|
|
2169
|
-
}
|
|
2170
|
-
currentParent = childNode;
|
|
2171
|
-
}
|
|
2172
|
-
// Create a NodeFile object and add it to the parent
|
|
2173
|
-
currentParent.addFile({
|
|
2174
|
-
filePath: change.filePath,
|
|
2175
|
-
oldFilePath: change.oldFilePath,
|
|
2176
|
-
summary: change.summary,
|
|
2177
|
-
status: change.status,
|
|
2178
|
-
});
|
|
2179
|
-
}
|
|
2180
|
-
return root;
|
|
2181
|
-
};
|
|
2182
|
-
|
|
2183
|
-
/**
|
|
2184
|
-
* Asynchronously collect diffs for a given node and its children.
|
|
2185
|
-
*/
|
|
2186
|
-
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
2187
|
-
// Collect diffs for the files of the current node
|
|
2188
|
-
const diffPromises = node.files.map(async (nodeFile) => {
|
|
2189
|
-
const diff = await getFileDiff(nodeFile);
|
|
2190
|
-
const tokenCount = tokenizer(diff);
|
|
2191
|
-
logger.verbose(`Collected diff for ${nodeFile.filePath} (${tokenCount} tokens)`, {
|
|
2192
|
-
color: 'magenta',
|
|
2193
|
-
});
|
|
2194
|
-
return {
|
|
2195
|
-
file: nodeFile.filePath,
|
|
2196
|
-
summary: nodeFile.summary,
|
|
2197
|
-
diff,
|
|
2198
|
-
tokenCount,
|
|
2199
|
-
};
|
|
2200
|
-
});
|
|
2201
|
-
// Collect diffs for the children of the current node
|
|
2202
|
-
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer, logger));
|
|
2203
|
-
const [diffs, children] = await Promise.all([
|
|
2204
|
-
Promise.all(diffPromises),
|
|
2205
|
-
Promise.all(childrenPromises),
|
|
2206
|
-
]);
|
|
2207
|
-
return {
|
|
2208
|
-
path: node.getPath(),
|
|
2209
|
-
diffs,
|
|
2210
|
-
children,
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
2632
|
/**
|
|
2215
2633
|
* Base interface that all chains must implement.
|
|
2216
2634
|
*/
|
|
@@ -2645,7 +3063,7 @@ class LLMChain extends BaseChain {
|
|
|
2645
3063
|
}
|
|
2646
3064
|
return new LLMChain({
|
|
2647
3065
|
llm: await base.BaseLanguageModel.deserialize(llm),
|
|
2648
|
-
prompt: await prompts.BasePromptTemplate.deserialize(prompt),
|
|
3066
|
+
prompt: await prompts$1.BasePromptTemplate.deserialize(prompt),
|
|
2649
3067
|
});
|
|
2650
3068
|
}
|
|
2651
3069
|
/** @deprecated */
|
|
@@ -2675,7 +3093,7 @@ You should build the API url in order to get a response that is as short as poss
|
|
|
2675
3093
|
|
|
2676
3094
|
Question:{question}
|
|
2677
3095
|
API url:`;
|
|
2678
|
-
const API_URL_PROMPT_TEMPLATE = /* #__PURE__ */ new prompts.PromptTemplate({
|
|
3096
|
+
const API_URL_PROMPT_TEMPLATE = /* #__PURE__ */ new prompts$1.PromptTemplate({
|
|
2679
3097
|
inputVariables: ["api_docs", "question"],
|
|
2680
3098
|
template: API_URL_RAW_PROMPT_TEMPLATE,
|
|
2681
3099
|
});
|
|
@@ -2688,7 +3106,7 @@ Here is the response from the API:
|
|
|
2688
3106
|
Summarize this response to answer the original question.
|
|
2689
3107
|
|
|
2690
3108
|
Summary:`;
|
|
2691
|
-
const API_RESPONSE_PROMPT_TEMPLATE = /* #__PURE__ */ new prompts.PromptTemplate({
|
|
3109
|
+
const API_RESPONSE_PROMPT_TEMPLATE = /* #__PURE__ */ new prompts$1.PromptTemplate({
|
|
2692
3110
|
inputVariables: ["api_docs", "question", "api_url", "api_response"],
|
|
2693
3111
|
template: API_RESPONSE_RAW_PROMPT_TEMPLATE,
|
|
2694
3112
|
});
|
|
@@ -3413,7 +3831,7 @@ class RefineDocumentsChain extends BaseChain {
|
|
|
3413
3831
|
return "RefineDocumentsChain";
|
|
3414
3832
|
}
|
|
3415
3833
|
get defaultDocumentPrompt() {
|
|
3416
|
-
return new prompts.PromptTemplate({
|
|
3834
|
+
return new prompts$1.PromptTemplate({
|
|
3417
3835
|
inputVariables: ["page_content"],
|
|
3418
3836
|
template: "{page_content}",
|
|
3419
3837
|
});
|
|
@@ -3570,7 +3988,7 @@ var combine_docs_chain = /*#__PURE__*/Object.freeze({
|
|
|
3570
3988
|
});
|
|
3571
3989
|
|
|
3572
3990
|
/* eslint-disable spaced-comment */
|
|
3573
|
-
const DEFAULT_QA_PROMPT = /*#__PURE__*/ new prompts.PromptTemplate({
|
|
3991
|
+
const DEFAULT_QA_PROMPT = /*#__PURE__*/ new prompts$1.PromptTemplate({
|
|
3574
3992
|
template: "Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.\n\n{context}\n\nQuestion: {question}\nHelpful Answer:",
|
|
3575
3993
|
inputVariables: ["context", "question"],
|
|
3576
3994
|
});
|
|
@@ -3579,10 +3997,10 @@ If you don't know the answer, just say that you don't know, don't try to make up
|
|
|
3579
3997
|
----------------
|
|
3580
3998
|
{context}`;
|
|
3581
3999
|
const messages = [
|
|
3582
|
-
/*#__PURE__*/ prompts.SystemMessagePromptTemplate.fromTemplate(system_template),
|
|
3583
|
-
/*#__PURE__*/ prompts.HumanMessagePromptTemplate.fromTemplate("{question}"),
|
|
4000
|
+
/*#__PURE__*/ prompts$1.SystemMessagePromptTemplate.fromTemplate(system_template),
|
|
4001
|
+
/*#__PURE__*/ prompts$1.HumanMessagePromptTemplate.fromTemplate("{question}"),
|
|
3584
4002
|
];
|
|
3585
|
-
const CHAT_PROMPT = /*#__PURE__*/ prompts.ChatPromptTemplate.fromMessages(messages);
|
|
4003
|
+
const CHAT_PROMPT = /*#__PURE__*/ prompts$1.ChatPromptTemplate.fromMessages(messages);
|
|
3586
4004
|
const QA_PROMPT_SELECTOR = /*#__PURE__*/ new example_selectors.ConditionalPromptSelector(DEFAULT_QA_PROMPT, [[example_selectors.isChatModel, CHAT_PROMPT]]);
|
|
3587
4005
|
|
|
3588
4006
|
/**
|
|
@@ -4333,15 +4751,15 @@ var vector_db_qa = /*#__PURE__*/Object.freeze({
|
|
|
4333
4751
|
});
|
|
4334
4752
|
|
|
4335
4753
|
/* eslint-disable spaced-comment */
|
|
4336
|
-
const template$
|
|
4754
|
+
const template$1 = `Write a concise summary of the following:
|
|
4337
4755
|
|
|
4338
4756
|
|
|
4339
4757
|
"{text}"
|
|
4340
4758
|
|
|
4341
4759
|
|
|
4342
4760
|
CONCISE SUMMARY:`;
|
|
4343
|
-
const DEFAULT_PROMPT = /*#__PURE__*/ new prompts.PromptTemplate({
|
|
4344
|
-
template: template$
|
|
4761
|
+
const DEFAULT_PROMPT = /*#__PURE__*/ new prompts$1.PromptTemplate({
|
|
4762
|
+
template: template$1,
|
|
4345
4763
|
inputVariables: ["text"],
|
|
4346
4764
|
});
|
|
4347
4765
|
|
|
@@ -4357,7 +4775,7 @@ Given the new context, refine the original summary
|
|
|
4357
4775
|
If the context isn't useful, return the original summary.
|
|
4358
4776
|
|
|
4359
4777
|
REFINED SUMMARY:`;
|
|
4360
|
-
const REFINE_PROMPT = /* #__PURE__ */ new prompts.PromptTemplate({
|
|
4778
|
+
const REFINE_PROMPT = /* #__PURE__ */ new prompts$1.PromptTemplate({
|
|
4361
4779
|
template: refinePromptTemplate,
|
|
4362
4780
|
inputVariables: ["existing_answer", "text"],
|
|
4363
4781
|
});
|
|
@@ -5459,6 +5877,100 @@ function getTextSplitter(options = {}) {
|
|
|
5459
5877
|
return new RecursiveCharacterTextSplitter(options);
|
|
5460
5878
|
}
|
|
5461
5879
|
|
|
5880
|
+
/**
|
|
5881
|
+
* Asynchronously collect diffs for a given node and its children.
|
|
5882
|
+
*/
|
|
5883
|
+
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
5884
|
+
// Collect diffs for the files of the current node
|
|
5885
|
+
const diffPromises = node.files.map(async (nodeFile) => {
|
|
5886
|
+
const diff = await getFileDiff(nodeFile);
|
|
5887
|
+
const tokenCount = tokenizer(diff);
|
|
5888
|
+
logger.verbose(`Collected diff for ${nodeFile.filePath} (${tokenCount} tokens)`, {
|
|
5889
|
+
color: 'magenta',
|
|
5890
|
+
});
|
|
5891
|
+
return {
|
|
5892
|
+
file: nodeFile.filePath,
|
|
5893
|
+
summary: nodeFile.summary,
|
|
5894
|
+
diff,
|
|
5895
|
+
tokenCount,
|
|
5896
|
+
};
|
|
5897
|
+
});
|
|
5898
|
+
// Collect diffs for the children of the current node
|
|
5899
|
+
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer, logger));
|
|
5900
|
+
const [diffs, children] = await Promise.all([
|
|
5901
|
+
Promise.all(diffPromises),
|
|
5902
|
+
Promise.all(childrenPromises),
|
|
5903
|
+
]);
|
|
5904
|
+
return {
|
|
5905
|
+
path: node.getPath(),
|
|
5906
|
+
diffs,
|
|
5907
|
+
children,
|
|
5908
|
+
};
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
class DiffTreeNode {
|
|
5912
|
+
constructor(path) {
|
|
5913
|
+
this.path = [];
|
|
5914
|
+
this.files = [];
|
|
5915
|
+
this.children = new Map();
|
|
5916
|
+
if (path)
|
|
5917
|
+
this.path = path;
|
|
5918
|
+
}
|
|
5919
|
+
addFile(file) {
|
|
5920
|
+
this.files.push(file);
|
|
5921
|
+
}
|
|
5922
|
+
addChild(part, node) {
|
|
5923
|
+
this.children.set(part, node);
|
|
5924
|
+
}
|
|
5925
|
+
getChild(part) {
|
|
5926
|
+
return this.children.get(part);
|
|
5927
|
+
}
|
|
5928
|
+
getPath() {
|
|
5929
|
+
return this.path.join('/');
|
|
5930
|
+
}
|
|
5931
|
+
print(indentation = 0) {
|
|
5932
|
+
const indent = ' '.repeat(indentation);
|
|
5933
|
+
let output = `${indent}- Path: ${this.getPath()}\n`;
|
|
5934
|
+
if (this.files.length > 0) {
|
|
5935
|
+
output += `${indent} Files:\n`;
|
|
5936
|
+
for (const file of this.files) {
|
|
5937
|
+
output += `${indent} - ${file.summary}\n`;
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
if (this.children.size > 0) {
|
|
5941
|
+
output += `${indent} Children:\n`;
|
|
5942
|
+
for (const [, child] of this.children) {
|
|
5943
|
+
output += child.print(indentation + 4);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
return output;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
const createDiffTree = (changes) => {
|
|
5950
|
+
const root = new DiffTreeNode();
|
|
5951
|
+
for (const change of changes) {
|
|
5952
|
+
let currentParent = root;
|
|
5953
|
+
const parts = change.filePath.split('/');
|
|
5954
|
+
parts.pop();
|
|
5955
|
+
for (const part of parts) {
|
|
5956
|
+
let childNode = currentParent.getChild(part);
|
|
5957
|
+
if (!childNode) {
|
|
5958
|
+
childNode = new DiffTreeNode([...currentParent.path, part]);
|
|
5959
|
+
currentParent.addChild(part, childNode);
|
|
5960
|
+
}
|
|
5961
|
+
currentParent = childNode;
|
|
5962
|
+
}
|
|
5963
|
+
// Create a NodeFile object and add it to the parent
|
|
5964
|
+
currentParent.addFile({
|
|
5965
|
+
filePath: change.filePath,
|
|
5966
|
+
oldFilePath: change.oldFilePath,
|
|
5967
|
+
summary: change.summary,
|
|
5968
|
+
status: change.status,
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
return root;
|
|
5972
|
+
};
|
|
5973
|
+
|
|
5462
5974
|
/**
|
|
5463
5975
|
* Parses the default file diff for a given nodeFile.
|
|
5464
5976
|
*
|
|
@@ -5468,8 +5980,15 @@ function getTextSplitter(options = {}) {
|
|
|
5468
5980
|
* @returns A Promise that resolves to the file diff as a string.
|
|
5469
5981
|
*/
|
|
5470
5982
|
async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
|
|
5471
|
-
if (commit
|
|
5472
|
-
return await git.diff([
|
|
5983
|
+
if (commit === '--staged') {
|
|
5984
|
+
return await git.diff(['--staged', nodeFile.filePath]);
|
|
5985
|
+
}
|
|
5986
|
+
else if (commit === '--unstaged') {
|
|
5987
|
+
return await git.diff([nodeFile.filePath]);
|
|
5988
|
+
}
|
|
5989
|
+
else if (commit === '--untracked') {
|
|
5990
|
+
// For untracked files, return the entire file content
|
|
5991
|
+
return await git.show([`:${nodeFile.filePath}`]);
|
|
5473
5992
|
}
|
|
5474
5993
|
return await git.diff([commit, nodeFile.filePath]);
|
|
5475
5994
|
}
|
|
@@ -5532,414 +6051,187 @@ async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
|
5532
6051
|
* @param logger - The logger instance.
|
|
5533
6052
|
* @returns A promise that resolves to the diff as a string.
|
|
5534
6053
|
*/
|
|
5535
|
-
async function getDiff(nodeFile, commit, { git, logger, }) {
|
|
5536
|
-
if (nodeFile.status === 'deleted') {
|
|
5537
|
-
return 'This file has been deleted.';
|
|
5538
|
-
}
|
|
5539
|
-
if (nodeFile.status === 'renamed' && nodeFile.oldFilePath) {
|
|
5540
|
-
const renamedDiff = await parseRenamedFileDiff(nodeFile, commit, git, logger);
|
|
5541
|
-
return renamedDiff;
|
|
5542
|
-
}
|
|
5543
|
-
// If not deleted or renamed, get the diff from the index
|
|
5544
|
-
const defaultDiff = await parseDefaultFileDiff(nodeFile, commit, git);
|
|
5545
|
-
return defaultDiff;
|
|
5546
|
-
}
|
|
5547
|
-
|
|
5548
|
-
// Max tokens for GPT-3 is 4096
|
|
5549
|
-
// const MAX_TOKENS_PER_SUMMARY = 4096
|
|
5550
|
-
const MAX_TOKENS_PER_SUMMARY = 12288;
|
|
5551
|
-
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger }, }) {
|
|
5552
|
-
const textSplitter = getTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
5553
|
-
// const textSplitter = new TokenTextSplitter({
|
|
5554
|
-
// chunkSize: 10000,
|
|
5555
|
-
// chunkOverlap: 250,
|
|
5556
|
-
// });
|
|
5557
|
-
const summarizationChain = getSummarizationChain(model, {
|
|
5558
|
-
type: 'map_reduce',
|
|
5559
|
-
combineMapPrompt: SUMMARIZE_PROMPT,
|
|
5560
|
-
combinePrompt: SUMMARIZE_PROMPT,
|
|
5561
|
-
});
|
|
5562
|
-
logger.startTimer();
|
|
5563
|
-
const rootTreeNode = createDiffTree(changes);
|
|
5564
|
-
logger.stopTimer('Created file hierarchy');
|
|
5565
|
-
// Collect diffs
|
|
5566
|
-
logger.startTimer().startSpinner(`Collecting Diffs...\n`, { color: 'blue' });
|
|
5567
|
-
const diffs = await collectDiffs(rootTreeNode, (path) => getDiff(path, commit, { git, logger }), tokenizer, logger);
|
|
5568
|
-
logger.stopSpinner('Diffs Collected').stopTimer();
|
|
5569
|
-
// Summarize diffs
|
|
5570
|
-
logger.startTimer();
|
|
5571
|
-
const summary = await summarizeDiffs(diffs, {
|
|
5572
|
-
tokenizer,
|
|
5573
|
-
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
5574
|
-
textSplitter,
|
|
5575
|
-
chain: summarizationChain,
|
|
5576
|
-
logger,
|
|
5577
|
-
});
|
|
5578
|
-
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
5579
|
-
return summary;
|
|
5580
|
-
}
|
|
5581
|
-
|
|
5582
|
-
/**
|
|
5583
|
-
* Creates a commit with the specified commit message.
|
|
5584
|
-
*
|
|
5585
|
-
* @param message The commit message.
|
|
5586
|
-
* @param git The SimpleGit instance.
|
|
5587
|
-
* @returns A Promise that resolves to the CommitResult.
|
|
5588
|
-
*/
|
|
5589
|
-
async function createCommit(message, git) {
|
|
5590
|
-
return await git.commit(message);
|
|
5591
|
-
}
|
|
5592
|
-
|
|
5593
|
-
/**
|
|
5594
|
-
* Determines the status of a file based on its changes in the Git repository.
|
|
5595
|
-
*
|
|
5596
|
-
* @param file - The file to check the status of.
|
|
5597
|
-
* @param location - The location to check the status in ('index' or 'working_dir'). Defaults to 'index'.
|
|
5598
|
-
* @returns The status of the file ('added', 'deleted', 'modified', 'renamed', 'untracked', or 'unknown').
|
|
5599
|
-
* @throws Error if the file type is invalid.
|
|
5600
|
-
*/
|
|
5601
|
-
function getStatus(file, location = 'index') {
|
|
5602
|
-
if ('index' in file && 'working_dir' in file) {
|
|
5603
|
-
const statusCode = file[location];
|
|
5604
|
-
switch (statusCode) {
|
|
5605
|
-
case 'A':
|
|
5606
|
-
return 'added';
|
|
5607
|
-
case 'D':
|
|
5608
|
-
return 'deleted';
|
|
5609
|
-
case 'M':
|
|
5610
|
-
return 'modified';
|
|
5611
|
-
case 'R':
|
|
5612
|
-
return 'renamed';
|
|
5613
|
-
case '?':
|
|
5614
|
-
return 'untracked';
|
|
5615
|
-
default:
|
|
5616
|
-
return 'unknown';
|
|
5617
|
-
}
|
|
5618
|
-
}
|
|
5619
|
-
else if ('changes' in file && 'binary' in file) {
|
|
5620
|
-
if (file.changes === 0)
|
|
5621
|
-
return 'untracked';
|
|
5622
|
-
if (file.file.includes('=>'))
|
|
5623
|
-
return 'renamed';
|
|
5624
|
-
if (file.deletions === 0 && file.insertions > 0)
|
|
5625
|
-
return 'added';
|
|
5626
|
-
if (file.insertions === 0 && file.deletions > 0)
|
|
5627
|
-
return 'deleted';
|
|
5628
|
-
if ((file.insertions > 0 && file.deletions > 0) || file.changes > 0)
|
|
5629
|
-
return 'modified';
|
|
5630
|
-
return 'unknown';
|
|
5631
|
-
}
|
|
5632
|
-
else {
|
|
5633
|
-
throw new Error('Invalid file type');
|
|
5634
|
-
}
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
|
-
/**
|
|
5638
|
-
* Returns the summary text for a file change.
|
|
5639
|
-
*
|
|
5640
|
-
* @param file - The file status or diff result.
|
|
5641
|
-
* @param change - The partial file change object.
|
|
5642
|
-
* @returns The summary text for the file change.
|
|
5643
|
-
* @throws Error if the file type is invalid.
|
|
5644
|
-
*/
|
|
5645
|
-
function getSummaryText(file, change) {
|
|
5646
|
-
const status = change.status || getStatus(file);
|
|
5647
|
-
let filePath;
|
|
5648
|
-
if ('path' in file) {
|
|
5649
|
-
filePath = file.path;
|
|
5650
|
-
}
|
|
5651
|
-
else if ('file' in file) {
|
|
5652
|
-
filePath = change?.filePath || file.file;
|
|
5653
|
-
}
|
|
5654
|
-
else {
|
|
5655
|
-
throw new Error('Invalid file type');
|
|
5656
|
-
}
|
|
5657
|
-
if (change.oldFilePath) {
|
|
5658
|
-
return `${status}: ${change.oldFilePath} -> ${filePath}`;
|
|
5659
|
-
}
|
|
5660
|
-
return `${status}: ${filePath}`;
|
|
5661
|
-
}
|
|
5662
|
-
|
|
5663
|
-
/**
|
|
5664
|
-
* Retrieves the changes in the Git repository.
|
|
5665
|
-
*
|
|
5666
|
-
* @param {GetChangesInput} options - The options for retrieving the changes.
|
|
5667
|
-
* @returns {Promise<GetChangesResult>} A promise that resolves to the changes in the Git repository.
|
|
5668
|
-
*/
|
|
5669
|
-
async function getChanges({ git, options }) {
|
|
5670
|
-
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options || {};
|
|
5671
|
-
const staged = [];
|
|
5672
|
-
const unstaged = [];
|
|
5673
|
-
const untracked = [];
|
|
5674
|
-
const status = await git.status();
|
|
5675
|
-
status.files.forEach((file) => {
|
|
5676
|
-
const fileChange = {
|
|
5677
|
-
filePath: file.path,
|
|
5678
|
-
oldFilePath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
|
|
5679
|
-
};
|
|
5680
|
-
// Unstaged files
|
|
5681
|
-
if (file.working_dir !== '?' && file.working_dir !== ' ') {
|
|
5682
|
-
fileChange.status = getStatus(file, 'working_dir');
|
|
5683
|
-
fileChange.summary = getSummaryText(file, fileChange);
|
|
5684
|
-
unstaged.push(fileChange);
|
|
5685
|
-
}
|
|
5686
|
-
// Staged files
|
|
5687
|
-
if (file.index !== ' ' && file.index !== '?') {
|
|
5688
|
-
fileChange.status = getStatus(file);
|
|
5689
|
-
fileChange.summary = getSummaryText(file, fileChange);
|
|
5690
|
-
staged.push(fileChange);
|
|
5691
|
-
}
|
|
5692
|
-
// Untracked files
|
|
5693
|
-
if (file.working_dir === '?' && file.index === '?') {
|
|
5694
|
-
fileChange.status = 'added';
|
|
5695
|
-
fileChange.summary = getSummaryText(file, fileChange);
|
|
5696
|
-
untracked.push(fileChange);
|
|
5697
|
-
}
|
|
5698
|
-
});
|
|
5699
|
-
const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
|
|
5700
|
-
const filteredStaged = staged.filter((file) => {
|
|
5701
|
-
const extension = path.extname(file.filePath).toLowerCase();
|
|
5702
|
-
return (!ignoredExtensionsSet.has(extension) &&
|
|
5703
|
-
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
5704
|
-
});
|
|
5705
|
-
const filteredUnstaged = unstaged.filter((file) => {
|
|
5706
|
-
const extension = path.extname(file.filePath).toLowerCase();
|
|
5707
|
-
return (!ignoredExtensionsSet.has(extension) &&
|
|
5708
|
-
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
5709
|
-
});
|
|
5710
|
-
const filteredUntracked = untracked.filter((file) => {
|
|
5711
|
-
const extension = path.extname(file.filePath).toLowerCase();
|
|
5712
|
-
return (!ignoredExtensionsSet.has(extension) &&
|
|
5713
|
-
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
5714
|
-
});
|
|
5715
|
-
return {
|
|
5716
|
-
staged: filteredStaged,
|
|
5717
|
-
unstaged: filteredUnstaged,
|
|
5718
|
-
untracked: filteredUntracked,
|
|
5719
|
-
};
|
|
5720
|
-
}
|
|
5721
|
-
|
|
5722
|
-
/**
|
|
5723
|
-
* Retrieves the SimpleGit instance for the repository.
|
|
5724
|
-
* @returns {SimpleGit} The SimpleGit instance.
|
|
5725
|
-
*/
|
|
5726
|
-
const getRepo = () => {
|
|
5727
|
-
let git;
|
|
5728
|
-
try {
|
|
5729
|
-
git = simpleGit.simpleGit();
|
|
5730
|
-
}
|
|
5731
|
-
catch (e) {
|
|
5732
|
-
console.log('Error initializing git repo', e);
|
|
5733
|
-
process.exit(1);
|
|
5734
|
-
}
|
|
5735
|
-
return git;
|
|
5736
|
-
};
|
|
5737
|
-
|
|
5738
|
-
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
5739
|
-
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. Please follow the guidelines below when writing your commit message:
|
|
5740
|
-
|
|
5741
|
-
- Write concisely using an informal tone
|
|
5742
|
-
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
5743
|
-
- DO NOT use specific names or files from the code
|
|
5744
|
-
- DO NOT include any diffs or file changes in the commit message
|
|
5745
|
-
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
5746
|
-
|
|
5747
|
-
{format_instructions}
|
|
5748
|
-
|
|
5749
|
-
"""{summary}"""`;
|
|
5750
|
-
const inputVariables$1 = ['summary', 'format_instructions'];
|
|
5751
|
-
const COMMIT_PROMPT = new prompts.PromptTemplate({
|
|
5752
|
-
template: template$1,
|
|
5753
|
-
inputVariables: inputVariables$1,
|
|
5754
|
-
});
|
|
5755
|
-
|
|
5756
|
-
/**
|
|
5757
|
-
* Verify template string contains all required input variables
|
|
5758
|
-
*
|
|
5759
|
-
* @param text template string
|
|
5760
|
-
* @param inputVariables template variables
|
|
5761
|
-
* @returns boolean or error message
|
|
5762
|
-
*/
|
|
5763
|
-
function validatePromptTemplate(text, inputVariables) {
|
|
5764
|
-
if (!text) {
|
|
5765
|
-
return 'Prompt template cannot be empty';
|
|
6054
|
+
async function getDiff(nodeFile, commit, { git, logger, }) {
|
|
6055
|
+
if (nodeFile.status === 'deleted') {
|
|
6056
|
+
return 'This file has been deleted.';
|
|
5766
6057
|
}
|
|
5767
|
-
if (
|
|
5768
|
-
|
|
5769
|
-
|
|
6058
|
+
if (nodeFile.status === 'renamed' && nodeFile.oldFilePath) {
|
|
6059
|
+
const renamedDiff = await parseRenamedFileDiff(nodeFile, commit, git, logger);
|
|
6060
|
+
return renamedDiff;
|
|
5770
6061
|
}
|
|
5771
|
-
|
|
6062
|
+
// If not deleted or renamed, get the diff from the index
|
|
6063
|
+
const defaultDiff = await parseDefaultFileDiff(nodeFile, commit, git);
|
|
6064
|
+
return defaultDiff;
|
|
5772
6065
|
}
|
|
5773
6066
|
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
6067
|
+
// Max tokens for GPT-3 is 4096
|
|
6068
|
+
// const MAX_TOKENS_PER_SUMMARY = 4096
|
|
6069
|
+
const MAX_TOKENS_PER_SUMMARY = 12288;
|
|
6070
|
+
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger }, }) {
|
|
6071
|
+
const textSplitter = getTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
6072
|
+
const summarizationChain = getSummarizationChain(model, {
|
|
6073
|
+
type: 'map_reduce',
|
|
6074
|
+
combineMapPrompt: SUMMARIZE_PROMPT,
|
|
6075
|
+
combinePrompt: SUMMARIZE_PROMPT,
|
|
6076
|
+
});
|
|
6077
|
+
logger.startTimer();
|
|
6078
|
+
const rootTreeNode = createDiffTree(changes);
|
|
6079
|
+
logger.stopTimer('Created file hierarchy');
|
|
6080
|
+
// Collect diffs
|
|
6081
|
+
logger.startTimer().startSpinner(`Collecting Diffs...\n`, { color: 'blue' });
|
|
6082
|
+
const diffs = await collectDiffs(rootTreeNode, (path) => getDiff(path, commit, { git, logger }), tokenizer, logger);
|
|
6083
|
+
logger.stopSpinner('Diffs Collected').stopTimer();
|
|
6084
|
+
// Summarize diffs
|
|
6085
|
+
logger.startTimer();
|
|
6086
|
+
const summary = await summarizeDiffs(diffs, {
|
|
6087
|
+
tokenizer,
|
|
6088
|
+
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
6089
|
+
textSplitter,
|
|
6090
|
+
chain: summarizationChain,
|
|
6091
|
+
logger,
|
|
5781
6092
|
});
|
|
6093
|
+
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
6094
|
+
return summary;
|
|
5782
6095
|
}
|
|
5783
6096
|
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
return result;
|
|
6097
|
+
/**
|
|
6098
|
+
* Creates a commit with the specified commit message.
|
|
6099
|
+
*
|
|
6100
|
+
* @param message The commit message.
|
|
6101
|
+
* @param git The SimpleGit instance.
|
|
6102
|
+
* @returns A Promise that resolves to the CommitResult.
|
|
6103
|
+
*/
|
|
6104
|
+
async function createCommit(message, git) {
|
|
6105
|
+
return await git.commit(message);
|
|
5794
6106
|
}
|
|
5795
6107
|
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
6108
|
+
/**
|
|
6109
|
+
* Determines the status of a file based on its changes in the Git repository.
|
|
6110
|
+
*
|
|
6111
|
+
* @param file - The file to check the status of.
|
|
6112
|
+
* @param location - The location to check the status in ('index' or 'working_dir'). Defaults to 'index'.
|
|
6113
|
+
* @returns The status of the file ('added', 'deleted', 'modified', 'renamed', 'untracked', or 'unknown').
|
|
6114
|
+
* @throws Error if the file type is invalid.
|
|
6115
|
+
*/
|
|
6116
|
+
function getStatus(file, location = 'index') {
|
|
6117
|
+
if ('index' in file && 'working_dir' in file) {
|
|
6118
|
+
const statusCode = file[location];
|
|
6119
|
+
switch (statusCode) {
|
|
6120
|
+
case 'A':
|
|
6121
|
+
return 'added';
|
|
6122
|
+
case 'D':
|
|
6123
|
+
return 'deleted';
|
|
6124
|
+
case 'M':
|
|
6125
|
+
return 'modified';
|
|
6126
|
+
case 'R':
|
|
6127
|
+
return 'renamed';
|
|
6128
|
+
case '?':
|
|
6129
|
+
return 'untracked';
|
|
6130
|
+
default:
|
|
6131
|
+
return 'unknown';
|
|
6132
|
+
}
|
|
5815
6133
|
}
|
|
5816
|
-
if (
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
6134
|
+
else if ('changes' in file && 'binary' in file) {
|
|
6135
|
+
if (file.changes === 0)
|
|
6136
|
+
return 'untracked';
|
|
6137
|
+
if (file.file.includes('=>'))
|
|
6138
|
+
return 'renamed';
|
|
6139
|
+
if (file.deletions === 0 && file.insertions > 0)
|
|
6140
|
+
return 'added';
|
|
6141
|
+
if (file.insertions === 0 && file.deletions > 0)
|
|
6142
|
+
return 'deleted';
|
|
6143
|
+
if ((file.insertions > 0 && file.deletions > 0) || file.changes > 0)
|
|
6144
|
+
return 'modified';
|
|
6145
|
+
return 'unknown';
|
|
5823
6146
|
}
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
name: '🔄 Retry Full',
|
|
5827
|
-
value: 'retryFull',
|
|
5828
|
-
description: descriptions?.retryFull ||
|
|
5829
|
-
`Restart the function execution from the beginning, regenerating both the summary and ${label}`,
|
|
5830
|
-
});
|
|
6147
|
+
else {
|
|
6148
|
+
throw new Error('Invalid file type');
|
|
5831
6149
|
}
|
|
5832
|
-
choices.push({
|
|
5833
|
-
name: '💣 Cancel',
|
|
5834
|
-
value: 'cancel',
|
|
5835
|
-
description: descriptions?.cancel || `Cancel the ${label}`,
|
|
5836
|
-
});
|
|
5837
|
-
return (await prompts$1.select({
|
|
5838
|
-
message: `Would you like to make any changes to the ${label}?`,
|
|
5839
|
-
choices,
|
|
5840
|
-
}));
|
|
5841
6150
|
}
|
|
5842
6151
|
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
await noResult(options);
|
|
5857
|
-
}
|
|
5858
|
-
while (continueLoop) {
|
|
5859
|
-
if (!context.length) {
|
|
5860
|
-
context = await parser(changes, result, options);
|
|
5861
|
-
}
|
|
5862
|
-
// if we still don't have a context, bail.
|
|
5863
|
-
if (!context.length) {
|
|
5864
|
-
await noResult(options);
|
|
5865
|
-
}
|
|
5866
|
-
if (modifyPrompt) {
|
|
5867
|
-
options.prompt = await editPrompt(options);
|
|
5868
|
-
}
|
|
5869
|
-
logger.startTimer().startSpinner(`Generating ${label}\n`, {
|
|
5870
|
-
color: 'blue',
|
|
5871
|
-
});
|
|
5872
|
-
result = await agent(context, options);
|
|
5873
|
-
if (!result) {
|
|
5874
|
-
logger.stopSpinner('💀 Agent failed to return content.', {
|
|
5875
|
-
mode: 'fail',
|
|
5876
|
-
color: 'red',
|
|
5877
|
-
});
|
|
5878
|
-
process.exit(0);
|
|
5879
|
-
}
|
|
5880
|
-
logger
|
|
5881
|
-
.stopSpinner(`Generated ${label}`, {
|
|
5882
|
-
color: 'green',
|
|
5883
|
-
mode: 'succeed',
|
|
5884
|
-
})
|
|
5885
|
-
.stopTimer();
|
|
5886
|
-
if (options?.interactive) {
|
|
5887
|
-
logResult(label, result);
|
|
5888
|
-
const reviewAnswer = await getUserReviewDecision({
|
|
5889
|
-
label,
|
|
5890
|
-
...(options?.review || {}),
|
|
5891
|
-
});
|
|
5892
|
-
if (reviewAnswer === 'cancel') {
|
|
5893
|
-
process.exit(0);
|
|
5894
|
-
}
|
|
5895
|
-
if (reviewAnswer === 'edit') {
|
|
5896
|
-
options.openInEditor = true;
|
|
5897
|
-
}
|
|
5898
|
-
if (reviewAnswer === 'retryFull') {
|
|
5899
|
-
context = '';
|
|
5900
|
-
result = '';
|
|
5901
|
-
options.prompt = '';
|
|
5902
|
-
continue;
|
|
5903
|
-
}
|
|
5904
|
-
if (reviewAnswer === 'retryMessageOnly') {
|
|
5905
|
-
modifyPrompt = false;
|
|
5906
|
-
result = '';
|
|
5907
|
-
continue;
|
|
5908
|
-
}
|
|
5909
|
-
if (reviewAnswer === 'modifyPrompt') {
|
|
5910
|
-
modifyPrompt = true;
|
|
5911
|
-
result = '';
|
|
5912
|
-
continue;
|
|
5913
|
-
}
|
|
5914
|
-
}
|
|
5915
|
-
// if we're here, we're done.
|
|
5916
|
-
result = await editResult(result, options);
|
|
5917
|
-
continueLoop = false;
|
|
6152
|
+
/**
|
|
6153
|
+
* Returns the summary text for a file change.
|
|
6154
|
+
*
|
|
6155
|
+
* @param file - The file status or diff result.
|
|
6156
|
+
* @param change - The partial file change object.
|
|
6157
|
+
* @returns The summary text for the file change.
|
|
6158
|
+
* @throws Error if the file type is invalid.
|
|
6159
|
+
*/
|
|
6160
|
+
function getSummaryText(file, change) {
|
|
6161
|
+
const status = change.status || getStatus(file);
|
|
6162
|
+
let filePath;
|
|
6163
|
+
if ('path' in file) {
|
|
6164
|
+
filePath = file.path;
|
|
5918
6165
|
}
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
const logSuccess = () => {
|
|
5923
|
-
console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
|
|
5924
|
-
};
|
|
5925
|
-
|
|
5926
|
-
async function handleResult({ result, mode, interactiveHandler }) {
|
|
5927
|
-
switch (mode) {
|
|
5928
|
-
case 'interactive':
|
|
5929
|
-
if (interactiveHandler) {
|
|
5930
|
-
await interactiveHandler(result);
|
|
5931
|
-
}
|
|
5932
|
-
else {
|
|
5933
|
-
console.warn('No result handler provided for interactive mode.');
|
|
5934
|
-
logSuccess();
|
|
5935
|
-
}
|
|
5936
|
-
break;
|
|
5937
|
-
case 'stdout':
|
|
5938
|
-
default:
|
|
5939
|
-
process.stdout.write(result, 'utf8');
|
|
5940
|
-
break;
|
|
6166
|
+
else if ('file' in file) {
|
|
6167
|
+
filePath = change?.filePath || file.file;
|
|
5941
6168
|
}
|
|
5942
|
-
|
|
6169
|
+
else {
|
|
6170
|
+
throw new Error('Invalid file type');
|
|
6171
|
+
}
|
|
6172
|
+
if (change.oldFilePath) {
|
|
6173
|
+
return `${status}: ${change.oldFilePath} -> ${filePath}`;
|
|
6174
|
+
}
|
|
6175
|
+
return `${status}: ${filePath}`;
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6178
|
+
/**
|
|
6179
|
+
* Retrieves the changes in the Git repository.
|
|
6180
|
+
*
|
|
6181
|
+
* @param {GetChangesInput} options - The options for retrieving the changes.
|
|
6182
|
+
* @returns {Promise<GetChangesResult>} A promise that resolves to the changes in the Git repository.
|
|
6183
|
+
*/
|
|
6184
|
+
async function getChanges({ git, options }) {
|
|
6185
|
+
const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options || {};
|
|
6186
|
+
const staged = [];
|
|
6187
|
+
const unstaged = [];
|
|
6188
|
+
const untracked = [];
|
|
6189
|
+
const status = await git.status();
|
|
6190
|
+
status.files.forEach((file) => {
|
|
6191
|
+
const fileChange = {
|
|
6192
|
+
filePath: file.path,
|
|
6193
|
+
oldFilePath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
|
|
6194
|
+
};
|
|
6195
|
+
// Unstaged files
|
|
6196
|
+
if (file.working_dir !== '?' && file.working_dir !== ' ') {
|
|
6197
|
+
fileChange.status = getStatus(file, 'working_dir');
|
|
6198
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
6199
|
+
unstaged.push(fileChange);
|
|
6200
|
+
}
|
|
6201
|
+
// Staged files
|
|
6202
|
+
if (file.index !== ' ' && file.index !== '?') {
|
|
6203
|
+
fileChange.status = getStatus(file);
|
|
6204
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
6205
|
+
staged.push(fileChange);
|
|
6206
|
+
}
|
|
6207
|
+
// Untracked files
|
|
6208
|
+
if (file.working_dir === '?' && file.index === '?') {
|
|
6209
|
+
fileChange.status = 'added';
|
|
6210
|
+
fileChange.summary = getSummaryText(file, fileChange);
|
|
6211
|
+
untracked.push(fileChange);
|
|
6212
|
+
}
|
|
6213
|
+
});
|
|
6214
|
+
const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
|
|
6215
|
+
const filteredStaged = staged.filter((file) => {
|
|
6216
|
+
const extension = path.extname(file.filePath).toLowerCase();
|
|
6217
|
+
return (!ignoredExtensionsSet.has(extension) &&
|
|
6218
|
+
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
6219
|
+
});
|
|
6220
|
+
const filteredUnstaged = unstaged.filter((file) => {
|
|
6221
|
+
const extension = path.extname(file.filePath).toLowerCase();
|
|
6222
|
+
return (!ignoredExtensionsSet.has(extension) &&
|
|
6223
|
+
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
6224
|
+
});
|
|
6225
|
+
const filteredUntracked = untracked.filter((file) => {
|
|
6226
|
+
const extension = path.extname(file.filePath).toLowerCase();
|
|
6227
|
+
return (!ignoredExtensionsSet.has(extension) &&
|
|
6228
|
+
!ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
|
|
6229
|
+
});
|
|
6230
|
+
return {
|
|
6231
|
+
staged: filteredStaged,
|
|
6232
|
+
unstaged: filteredUnstaged,
|
|
6233
|
+
untracked: filteredUntracked,
|
|
6234
|
+
};
|
|
5943
6235
|
}
|
|
5944
6236
|
|
|
5945
6237
|
/**
|
|
@@ -5964,7 +6256,7 @@ const getTokenCounter = async (modelName) => {
|
|
|
5964
6256
|
});
|
|
5965
6257
|
};
|
|
5966
6258
|
|
|
5967
|
-
async function noResult({ git, logger }) {
|
|
6259
|
+
async function noResult$1({ git, logger }) {
|
|
5968
6260
|
const { staged, unstaged, untracked } = await getChanges({ git });
|
|
5969
6261
|
const hasStaged = staged && staged.length > 0;
|
|
5970
6262
|
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
@@ -5975,337 +6267,101 @@ async function noResult({ git, logger }) {
|
|
|
5975
6267
|
}
|
|
5976
6268
|
else if (hasUnstaged || hasUntracked) {
|
|
5977
6269
|
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
5978
|
-
if (hasUnstaged) {
|
|
5979
|
-
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
5980
|
-
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5981
|
-
color: 'red',
|
|
5982
|
-
});
|
|
5983
|
-
}
|
|
5984
|
-
if (hasUntracked) {
|
|
5985
|
-
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
5986
|
-
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5987
|
-
color: 'red',
|
|
5988
|
-
});
|
|
5989
|
-
}
|
|
5990
|
-
}
|
|
5991
|
-
else {
|
|
5992
|
-
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
5993
|
-
}
|
|
5994
|
-
}
|
|
5995
|
-
|
|
5996
|
-
const handler$2 = async (argv, logger) => {
|
|
5997
|
-
const git = getRepo();
|
|
5998
|
-
const options = loadConfig(argv);
|
|
5999
|
-
const key = getApiKeyForModel(options);
|
|
6000
|
-
const { provider, model } = getModelAndProviderFromConfig(options);
|
|
6001
|
-
if (options.service.authentication.type !== 'None' && !key) {
|
|
6002
|
-
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
6003
|
-
process.exit(1);
|
|
6004
|
-
}
|
|
6005
|
-
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4');
|
|
6006
|
-
const llm = getLlm(provider, model, options);
|
|
6007
|
-
const INTERACTIVE = isInteractive(options);
|
|
6008
|
-
if (INTERACTIVE) {
|
|
6009
|
-
logger.log(LOGO);
|
|
6010
|
-
}
|
|
6011
|
-
async function factory() {
|
|
6012
|
-
const changes = await getChanges({ git });
|
|
6013
|
-
return changes.staged;
|
|
6014
|
-
}
|
|
6015
|
-
async function parser(changes) {
|
|
6016
|
-
return await fileChangeParser({
|
|
6017
|
-
changes,
|
|
6018
|
-
commit: '--staged',
|
|
6019
|
-
options: { tokenizer, git, llm, logger },
|
|
6020
|
-
});
|
|
6021
|
-
}
|
|
6022
|
-
const commitMsg = await generateAndReviewLoop({
|
|
6023
|
-
label: 'commit message',
|
|
6024
|
-
options: {
|
|
6025
|
-
...options,
|
|
6026
|
-
prompt: options.prompt || COMMIT_PROMPT.template,
|
|
6027
|
-
logger,
|
|
6028
|
-
interactive: INTERACTIVE,
|
|
6029
|
-
review: {
|
|
6030
|
-
descriptions: {
|
|
6031
|
-
approve: `Commit staged changes with generated commit message`,
|
|
6032
|
-
edit: 'Edit the commit message before proceeding',
|
|
6033
|
-
modifyPrompt: 'Modify the prompt template and regenerate the commit message',
|
|
6034
|
-
retryMessageOnly: 'Restart the function execution from generating the commit message',
|
|
6035
|
-
retryFull: 'Restart the function execution from the beginning, regenerating both the diff summary and commit message',
|
|
6036
|
-
},
|
|
6037
|
-
},
|
|
6038
|
-
},
|
|
6039
|
-
factory,
|
|
6040
|
-
parser,
|
|
6041
|
-
agent: async (context, options) => {
|
|
6042
|
-
const parser = new output_parsers.JsonOutputParser();
|
|
6043
|
-
const prompt = getPrompt({
|
|
6044
|
-
template: options.prompt,
|
|
6045
|
-
variables: COMMIT_PROMPT.inputVariables,
|
|
6046
|
-
fallback: COMMIT_PROMPT,
|
|
6047
|
-
});
|
|
6048
|
-
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body'.";
|
|
6049
|
-
const commitMsg = await executeChain({
|
|
6050
|
-
llm,
|
|
6051
|
-
prompt,
|
|
6052
|
-
variables: { summary: context, format_instructions: formatInstructions },
|
|
6053
|
-
parser,
|
|
6054
|
-
});
|
|
6055
|
-
return `${commitMsg.title}\n\n${commitMsg.body}`;
|
|
6056
|
-
},
|
|
6057
|
-
noResult: async () => {
|
|
6058
|
-
await noResult({ git, logger });
|
|
6059
|
-
process.exit(0);
|
|
6060
|
-
},
|
|
6061
|
-
});
|
|
6062
|
-
const MODE = (INTERACTIVE && 'interactive') || (options.commit && 'interactive') || options?.mode || 'stdout';
|
|
6063
|
-
handleResult({
|
|
6064
|
-
result: commitMsg,
|
|
6065
|
-
interactiveHandler: async (result) => {
|
|
6066
|
-
await createCommit(result, git);
|
|
6067
|
-
logSuccess();
|
|
6068
|
-
},
|
|
6069
|
-
mode: MODE,
|
|
6070
|
-
});
|
|
6071
|
-
};
|
|
6072
|
-
|
|
6073
|
-
/**
|
|
6074
|
-
* Command line options via yargs
|
|
6075
|
-
*/
|
|
6076
|
-
const options$2 = {
|
|
6077
|
-
service: { type: 'string', description: 'LLM/Model-Name', choices: ['openai', 'ollama'] },
|
|
6078
|
-
openAIApiKey: {
|
|
6079
|
-
type: 'string',
|
|
6080
|
-
description: 'OpenAI API Key',
|
|
6081
|
-
},
|
|
6082
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
6083
|
-
prompt: {
|
|
6084
|
-
type: 'string',
|
|
6085
|
-
alias: 'p',
|
|
6086
|
-
description: 'Commit message prompt',
|
|
6087
|
-
},
|
|
6088
|
-
i: {
|
|
6089
|
-
type: 'boolean',
|
|
6090
|
-
alias: 'interactive',
|
|
6091
|
-
description: 'Toggle interactive mode',
|
|
6092
|
-
},
|
|
6093
|
-
s: {
|
|
6094
|
-
type: 'boolean',
|
|
6095
|
-
description: 'Automatically commit staged changes with generated commit message',
|
|
6096
|
-
default: false,
|
|
6097
|
-
},
|
|
6098
|
-
e: {
|
|
6099
|
-
type: 'boolean',
|
|
6100
|
-
alias: 'edit',
|
|
6101
|
-
description: 'Open commit message in editor before proceeding',
|
|
6102
|
-
},
|
|
6103
|
-
summarizePrompt: {
|
|
6104
|
-
type: 'string',
|
|
6105
|
-
description: 'Large file summary prompt',
|
|
6106
|
-
},
|
|
6107
|
-
ignoredFiles: {
|
|
6108
|
-
type: 'array',
|
|
6109
|
-
description: 'Ignored files',
|
|
6110
|
-
},
|
|
6111
|
-
ignoredExtensions: {
|
|
6112
|
-
type: 'array',
|
|
6113
|
-
description: 'Ignored extensions',
|
|
6114
|
-
},
|
|
6115
|
-
};
|
|
6116
|
-
const builder$2 = (yargs) => {
|
|
6117
|
-
return yargs.options(options$2);
|
|
6118
|
-
};
|
|
6119
|
-
|
|
6120
|
-
var commit = {
|
|
6121
|
-
command: 'commit',
|
|
6122
|
-
desc: 'Write a commit message summarizing the staged changes.',
|
|
6123
|
-
builder: builder$2,
|
|
6124
|
-
handler: commandExecutor(handler$2),
|
|
6125
|
-
options: options$2,
|
|
6126
|
-
};
|
|
6127
|
-
|
|
6128
|
-
/**
|
|
6129
|
-
* Retrieves the commit log range between two specified commits.
|
|
6130
|
-
*
|
|
6131
|
-
* @param from - The starting commit.
|
|
6132
|
-
* @param to - The ending commit.
|
|
6133
|
-
* @param options - Additional options for retrieving the commit log range.
|
|
6134
|
-
* @returns A promise that resolves to an array of commit log messages.
|
|
6135
|
-
* @throws If there is an error retrieving the commit log range.
|
|
6136
|
-
*/
|
|
6137
|
-
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
6138
|
-
try {
|
|
6139
|
-
const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
|
|
6140
|
-
const commitLog = await git.log(logOptions);
|
|
6141
|
-
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6142
|
-
}
|
|
6143
|
-
catch (error) {
|
|
6144
|
-
// If there's an error, handle it appropriately
|
|
6145
|
-
console.error('Error getting commit messages:', error);
|
|
6146
|
-
throw error;
|
|
6147
|
-
}
|
|
6148
|
-
}
|
|
6149
|
-
|
|
6150
|
-
/**
|
|
6151
|
-
* Retrieves the name of the current branch.
|
|
6152
|
-
*
|
|
6153
|
-
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
6154
|
-
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
6155
|
-
*/
|
|
6156
|
-
async function getCurrentBranchName({ git }) {
|
|
6157
|
-
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
6158
|
-
}
|
|
6159
|
-
|
|
6160
|
-
/**
|
|
6161
|
-
* Retrieves the commit log for the current branch.
|
|
6162
|
-
*
|
|
6163
|
-
* @param {Object} options - The options for retrieving the commit log.
|
|
6164
|
-
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
6165
|
-
* @param {Logger} options.logger - The logger for logging messages.
|
|
6166
|
-
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
6167
|
-
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
6168
|
-
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
6169
|
-
*/
|
|
6170
|
-
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
6171
|
-
try {
|
|
6172
|
-
// Get the current branch name
|
|
6173
|
-
const branch = await getCurrentBranchName({ git });
|
|
6174
|
-
// Check if the current branch has any commits
|
|
6175
|
-
const hasCommits = (await git.raw(['rev-list', '--count', branch])) !== '0';
|
|
6176
|
-
if (!hasCommits) {
|
|
6177
|
-
logger?.log('No commits on the current branch.');
|
|
6178
|
-
return [];
|
|
6179
|
-
}
|
|
6180
|
-
// Get the list of commits that are unique to the current branch
|
|
6181
|
-
let uniqueCommits;
|
|
6182
|
-
if (comparisonBranch === branch) {
|
|
6183
|
-
// If the comparison branch is the same as the current branch, we compare against the remote.
|
|
6184
|
-
uniqueCommits = (await git.raw(['rev-list', `${comparisonRemote}/${comparisonBranch}..${branch}`]))
|
|
6185
|
-
.split('\n')
|
|
6186
|
-
.filter(Boolean)
|
|
6187
|
-
.reverse();
|
|
6188
|
-
}
|
|
6189
|
-
else {
|
|
6190
|
-
// Your existing code for different branches
|
|
6191
|
-
uniqueCommits = (await git.raw(['rev-list', `${comparisonBranch}..${branch}`]))
|
|
6192
|
-
.split('\n')
|
|
6193
|
-
.filter(Boolean)
|
|
6194
|
-
.reverse();
|
|
6270
|
+
if (hasUnstaged) {
|
|
6271
|
+
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
6272
|
+
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
6273
|
+
color: 'red',
|
|
6274
|
+
});
|
|
6195
6275
|
}
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
return [];
|
|
6276
|
+
if (hasUntracked) {
|
|
6277
|
+
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
6278
|
+
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
6279
|
+
color: 'red',
|
|
6280
|
+
});
|
|
6202
6281
|
}
|
|
6203
|
-
// Retrieve commit log with messages
|
|
6204
|
-
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
6205
6282
|
}
|
|
6206
|
-
|
|
6207
|
-
logger
|
|
6283
|
+
else {
|
|
6284
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
6208
6285
|
}
|
|
6209
|
-
return [];
|
|
6210
6286
|
}
|
|
6211
6287
|
|
|
6212
|
-
const
|
|
6213
|
-
|
|
6214
|
-
- Include the git commit hash as reference for each change, including just the first 7 characters
|
|
6215
|
-
- Logically group changes, and if necessary, summarize dependency updates
|
|
6216
|
-
|
|
6217
|
-
{format_instructions}
|
|
6218
|
-
|
|
6219
|
-
"""{summary}"""`;
|
|
6220
|
-
const inputVariables = ['format_instructions', 'summary'];
|
|
6221
|
-
const CHANGELOG_PROMPT = new prompts.PromptTemplate({
|
|
6222
|
-
template,
|
|
6223
|
-
inputVariables,
|
|
6224
|
-
});
|
|
6225
|
-
|
|
6226
|
-
const handler$1 = async (argv, logger) => {
|
|
6227
|
-
const config = loadConfig(argv);
|
|
6288
|
+
const handler$2 = async (argv, logger) => {
|
|
6228
6289
|
const git = getRepo();
|
|
6290
|
+
const config = loadConfig(argv);
|
|
6229
6291
|
const key = getApiKeyForModel(config);
|
|
6230
6292
|
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
6231
6293
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
6232
6294
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
6233
6295
|
process.exit(1);
|
|
6234
6296
|
}
|
|
6297
|
+
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4');
|
|
6235
6298
|
const llm = getLlm(provider, model, config);
|
|
6236
6299
|
const INTERACTIVE = isInteractive(config);
|
|
6237
6300
|
if (INTERACTIVE) {
|
|
6238
6301
|
logger.log(LOGO);
|
|
6239
6302
|
}
|
|
6240
6303
|
async function factory() {
|
|
6241
|
-
const
|
|
6242
|
-
|
|
6243
|
-
const [from, to] = config.range.split(':');
|
|
6244
|
-
if (!from || !to) {
|
|
6245
|
-
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
6246
|
-
process.exit(1);
|
|
6247
|
-
}
|
|
6248
|
-
return {
|
|
6249
|
-
branch: branchName,
|
|
6250
|
-
commits: await getCommitLogRange(from, to, { git, noMerges: true }),
|
|
6251
|
-
};
|
|
6252
|
-
}
|
|
6253
|
-
logger.verbose(`No range provided. Defaulting to current branch`, { color: 'yellow' });
|
|
6254
|
-
return {
|
|
6255
|
-
branch: branchName,
|
|
6256
|
-
commits: await getCommitLogCurrentBranch({ git, logger }),
|
|
6257
|
-
};
|
|
6304
|
+
const changes = await getChanges({ git });
|
|
6305
|
+
return changes.staged;
|
|
6258
6306
|
}
|
|
6259
|
-
async function parser(
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6307
|
+
async function parser(changes) {
|
|
6308
|
+
return await fileChangeParser({
|
|
6309
|
+
changes,
|
|
6310
|
+
commit: '--staged',
|
|
6311
|
+
options: { tokenizer, git, llm, logger },
|
|
6312
|
+
});
|
|
6263
6313
|
}
|
|
6264
|
-
const
|
|
6265
|
-
label: '
|
|
6314
|
+
const commitMsg = await generateAndReviewLoop({
|
|
6315
|
+
label: 'commit message',
|
|
6316
|
+
options: {
|
|
6317
|
+
...config,
|
|
6318
|
+
prompt: config.prompt || COMMIT_PROMPT.template,
|
|
6319
|
+
logger,
|
|
6320
|
+
interactive: INTERACTIVE,
|
|
6321
|
+
review: {
|
|
6322
|
+
descriptions: {
|
|
6323
|
+
approve: `Commit staged changes with generated commit message`,
|
|
6324
|
+
edit: 'Edit the commit message before proceeding',
|
|
6325
|
+
modifyPrompt: 'Modify the prompt template and regenerate the commit message',
|
|
6326
|
+
retryMessageOnly: 'Restart the function execution from generating the commit message',
|
|
6327
|
+
retryFull: 'Restart the function execution from the beginning, regenerating both the diff summary and commit message',
|
|
6328
|
+
},
|
|
6329
|
+
},
|
|
6330
|
+
},
|
|
6266
6331
|
factory,
|
|
6267
6332
|
parser,
|
|
6268
6333
|
agent: async (context, options) => {
|
|
6269
6334
|
const parser = new output_parsers.JsonOutputParser();
|
|
6270
6335
|
const prompt = getPrompt({
|
|
6271
6336
|
template: options.prompt,
|
|
6272
|
-
variables:
|
|
6273
|
-
fallback:
|
|
6337
|
+
variables: COMMIT_PROMPT.inputVariables,
|
|
6338
|
+
fallback: COMMIT_PROMPT,
|
|
6274
6339
|
});
|
|
6275
|
-
const formatInstructions = "Respond with a valid JSON object, containing two fields: '
|
|
6276
|
-
const
|
|
6340
|
+
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body', both strings.";
|
|
6341
|
+
const additionalContext = argv.additional ? `${argv.additional}` : '';
|
|
6342
|
+
const commitMsg = await executeChain({
|
|
6277
6343
|
llm,
|
|
6278
6344
|
prompt,
|
|
6279
6345
|
variables: {
|
|
6280
6346
|
summary: context,
|
|
6281
6347
|
format_instructions: formatInstructions,
|
|
6348
|
+
additional: additionalContext,
|
|
6282
6349
|
},
|
|
6283
6350
|
parser,
|
|
6284
6351
|
});
|
|
6285
|
-
|
|
6352
|
+
const appendedText = argv.append ? `\n\n${argv.append}` : '';
|
|
6353
|
+
return `${commitMsg.title}\n\n${commitMsg.body}${appendedText}`;
|
|
6286
6354
|
},
|
|
6287
6355
|
noResult: async () => {
|
|
6288
|
-
|
|
6289
|
-
logger.log(`No commits found in the provided range.`, { color: 'red' });
|
|
6290
|
-
process.exit(0);
|
|
6291
|
-
}
|
|
6292
|
-
logger.log(`No commits found in the current branch.`, { color: 'red' });
|
|
6356
|
+
await noResult$1({ git, logger });
|
|
6293
6357
|
process.exit(0);
|
|
6294
6358
|
},
|
|
6295
|
-
options: {
|
|
6296
|
-
...config,
|
|
6297
|
-
prompt: config.prompt || CHANGELOG_PROMPT.template,
|
|
6298
|
-
logger,
|
|
6299
|
-
interactive: INTERACTIVE,
|
|
6300
|
-
review: {
|
|
6301
|
-
enableFullRetry: false,
|
|
6302
|
-
},
|
|
6303
|
-
},
|
|
6304
6359
|
});
|
|
6305
6360
|
const MODE = (INTERACTIVE && 'interactive') || (config.commit && 'interactive') || config?.mode || 'stdout';
|
|
6306
6361
|
handleResult({
|
|
6307
|
-
result:
|
|
6308
|
-
interactiveHandler: async () => {
|
|
6362
|
+
result: commitMsg,
|
|
6363
|
+
interactiveHandler: async (result) => {
|
|
6364
|
+
await createCommit(result, git);
|
|
6309
6365
|
logSuccess();
|
|
6310
6366
|
},
|
|
6311
6367
|
mode: MODE,
|
|
@@ -6315,43 +6371,39 @@ const handler$1 = async (argv, logger) => {
|
|
|
6315
6371
|
/**
|
|
6316
6372
|
* Command line options via yargs
|
|
6317
6373
|
*/
|
|
6318
|
-
const options$
|
|
6319
|
-
range: {
|
|
6320
|
-
type: 'string',
|
|
6321
|
-
alias: 'r',
|
|
6322
|
-
description: 'Commit range e.g `HEAD~2:HEAD`',
|
|
6323
|
-
},
|
|
6324
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
6325
|
-
prompt: {
|
|
6326
|
-
type: 'string',
|
|
6327
|
-
alias: 'p',
|
|
6328
|
-
description: 'Prompt for llm',
|
|
6329
|
-
},
|
|
6374
|
+
const options$2 = {
|
|
6330
6375
|
i: {
|
|
6331
|
-
type: 'boolean',
|
|
6332
6376
|
alias: 'interactive',
|
|
6333
6377
|
description: 'Toggle interactive mode',
|
|
6334
|
-
},
|
|
6335
|
-
e: {
|
|
6336
6378
|
type: 'boolean',
|
|
6337
|
-
alias: 'edit',
|
|
6338
|
-
description: 'Open generated changelog message in editor before proceeding',
|
|
6339
6379
|
},
|
|
6340
|
-
|
|
6380
|
+
ignoredFiles: {
|
|
6381
|
+
description: 'Ignored files',
|
|
6382
|
+
type: 'array',
|
|
6383
|
+
},
|
|
6384
|
+
ignoredExtensions: {
|
|
6385
|
+
description: 'Ignored extensions',
|
|
6386
|
+
type: 'array',
|
|
6387
|
+
},
|
|
6388
|
+
append: {
|
|
6389
|
+
description: 'Add content to the end of the generated commit message',
|
|
6390
|
+
type: 'string',
|
|
6391
|
+
},
|
|
6392
|
+
additional: {
|
|
6393
|
+
description: 'Add extra contextual information to the prompt',
|
|
6341
6394
|
type: 'string',
|
|
6342
|
-
description: 'Prompt for summarizing large files',
|
|
6343
6395
|
},
|
|
6344
6396
|
};
|
|
6345
|
-
const builder$
|
|
6346
|
-
return
|
|
6397
|
+
const builder$2 = (yargsInstance) => {
|
|
6398
|
+
return yargsInstance.options(options$2).usage(getCommandUsageHeader(commit.command));
|
|
6347
6399
|
};
|
|
6348
6400
|
|
|
6349
|
-
var
|
|
6350
|
-
command: '
|
|
6351
|
-
desc: '
|
|
6352
|
-
builder: builder$
|
|
6353
|
-
handler: commandExecutor(handler$
|
|
6354
|
-
options: options$
|
|
6401
|
+
var commit = {
|
|
6402
|
+
command: 'commit',
|
|
6403
|
+
desc: 'Summarize the staged changes in a commit message.',
|
|
6404
|
+
builder: builder$2,
|
|
6405
|
+
handler: commandExecutor(handler$2),
|
|
6406
|
+
options: options$2,
|
|
6355
6407
|
};
|
|
6356
6408
|
|
|
6357
6409
|
/**
|
|
@@ -6451,13 +6503,13 @@ async function checkAndHandlePackageInstallation({ global = false, logger, }) {
|
|
|
6451
6503
|
const projectRoot = findProjectRoot(process.cwd());
|
|
6452
6504
|
let shouldInstall = false;
|
|
6453
6505
|
if (isPackageInstalled(packageName, projectRoot)) {
|
|
6454
|
-
shouldInstall = await prompts
|
|
6506
|
+
shouldInstall = await prompts.confirm({
|
|
6455
6507
|
message: `'${packageName}' is already installed in '${projectRoot}/package.json', would you like to update?`,
|
|
6456
6508
|
default: shouldInstall,
|
|
6457
6509
|
});
|
|
6458
6510
|
}
|
|
6459
6511
|
else {
|
|
6460
|
-
shouldInstall = await prompts
|
|
6512
|
+
shouldInstall = await prompts.confirm({
|
|
6461
6513
|
message: `'${packageName}' is not installed in '${projectRoot}/package.json', would you like to install?`,
|
|
6462
6514
|
default: shouldInstall,
|
|
6463
6515
|
});
|
|
@@ -6495,7 +6547,7 @@ const questions = {
|
|
|
6495
6547
|
/**
|
|
6496
6548
|
* @description configure coco globally for the current user or project?
|
|
6497
6549
|
*/
|
|
6498
|
-
whatScope: async () => await prompts
|
|
6550
|
+
whatScope: async () => await prompts.select({
|
|
6499
6551
|
message: 'configure coco globally for the current user or for the current directory?',
|
|
6500
6552
|
choices: [
|
|
6501
6553
|
{
|
|
@@ -6510,7 +6562,7 @@ const questions = {
|
|
|
6510
6562
|
},
|
|
6511
6563
|
],
|
|
6512
6564
|
}),
|
|
6513
|
-
selectLLMProvider: async () => await prompts
|
|
6565
|
+
selectLLMProvider: async () => await prompts.select({
|
|
6514
6566
|
message: 'select language model provider:',
|
|
6515
6567
|
choices: [
|
|
6516
6568
|
{
|
|
@@ -6552,7 +6604,7 @@ const questions = {
|
|
|
6552
6604
|
})),
|
|
6553
6605
|
];
|
|
6554
6606
|
}
|
|
6555
|
-
return await prompts
|
|
6607
|
+
return await prompts.select({
|
|
6556
6608
|
message: 'select language model:',
|
|
6557
6609
|
choices: availableModels,
|
|
6558
6610
|
});
|
|
@@ -6563,7 +6615,7 @@ const questions = {
|
|
|
6563
6615
|
* print results to stdout
|
|
6564
6616
|
* @returns 'interactive' | 'stdout'
|
|
6565
6617
|
*/
|
|
6566
|
-
selectMode: async () => await prompts
|
|
6618
|
+
selectMode: async () => await prompts.select({
|
|
6567
6619
|
message: 'select mode:',
|
|
6568
6620
|
choices: [
|
|
6569
6621
|
{
|
|
@@ -6581,19 +6633,19 @@ const questions = {
|
|
|
6581
6633
|
inputOpenAIApiKey: async () => {
|
|
6582
6634
|
// check for existing env var
|
|
6583
6635
|
if (process.env.OPENAI_API_KEY) {
|
|
6584
|
-
return (await prompts
|
|
6636
|
+
return (await prompts.confirm({
|
|
6585
6637
|
message: `use existing OPENAI_API_KEY env var?`,
|
|
6586
6638
|
default: true,
|
|
6587
6639
|
}))
|
|
6588
6640
|
? process.env.OPENAI_API_KEY
|
|
6589
|
-
: await prompts
|
|
6641
|
+
: await prompts.password({
|
|
6590
6642
|
message: `enter your OpenAI API key:`,
|
|
6591
6643
|
validate(input) {
|
|
6592
6644
|
return input.length > 0 ? true : 'API key cannot be empty';
|
|
6593
6645
|
},
|
|
6594
6646
|
});
|
|
6595
6647
|
}
|
|
6596
|
-
return await prompts
|
|
6648
|
+
return await prompts.password({
|
|
6597
6649
|
message: `enter your OpenAI API key:`,
|
|
6598
6650
|
validate(input) {
|
|
6599
6651
|
return input.length > 0 ? true : 'API key cannot be empty';
|
|
@@ -6601,48 +6653,48 @@ const questions = {
|
|
|
6601
6653
|
});
|
|
6602
6654
|
},
|
|
6603
6655
|
inputTokenLimit: async () => {
|
|
6604
|
-
const tokenLimit = await prompts
|
|
6656
|
+
const tokenLimit = await prompts.input({
|
|
6605
6657
|
message: 'maximum number of tokens for generating commit messages:',
|
|
6606
6658
|
default: '300',
|
|
6607
6659
|
});
|
|
6608
6660
|
return parseInt(tokenLimit);
|
|
6609
6661
|
},
|
|
6610
6662
|
inputModelTemperature: async () => {
|
|
6611
|
-
const temperature = await prompts
|
|
6663
|
+
const temperature = await prompts.input({
|
|
6612
6664
|
message: 'model temperature for generating commit messages:',
|
|
6613
6665
|
default: '0.36',
|
|
6614
6666
|
});
|
|
6615
6667
|
return parseFloat(temperature);
|
|
6616
6668
|
},
|
|
6617
|
-
selectDefaultGitBranch: async () => (await prompts
|
|
6669
|
+
selectDefaultGitBranch: async () => (await prompts.input({
|
|
6618
6670
|
message: 'default branch for the repository:',
|
|
6619
6671
|
default: 'main',
|
|
6620
6672
|
})) || 'main',
|
|
6621
|
-
configureAdvancedOptions: async () => await prompts
|
|
6673
|
+
configureAdvancedOptions: async () => await prompts.confirm({
|
|
6622
6674
|
message: 'would you like to configure advanced options?',
|
|
6623
6675
|
default: false,
|
|
6624
6676
|
}),
|
|
6625
|
-
enableVerboseMode: async () => await prompts
|
|
6677
|
+
enableVerboseMode: async () => await prompts.confirm({
|
|
6626
6678
|
message: 'enable verbose logging:',
|
|
6627
6679
|
default: false,
|
|
6628
6680
|
}),
|
|
6629
|
-
whatFilesToIgnore: async () => (await prompts
|
|
6681
|
+
whatFilesToIgnore: async () => (await prompts.input({
|
|
6630
6682
|
message: 'paths of files to be excluded when generating commit messages (comma-separated):',
|
|
6631
6683
|
default: 'package-lock.json',
|
|
6632
6684
|
}))
|
|
6633
6685
|
?.split(',')
|
|
6634
6686
|
?.map((file) => file.trim()) || [],
|
|
6635
|
-
whatExtensionsToIgnore: async () => (await prompts
|
|
6687
|
+
whatExtensionsToIgnore: async () => (await prompts.input({
|
|
6636
6688
|
message: 'file extensions to be excluded when generating commit messages (comma-separated):',
|
|
6637
6689
|
default: '.map, .lock',
|
|
6638
6690
|
}))
|
|
6639
6691
|
?.split(',')
|
|
6640
6692
|
?.map((ext) => ext.trim()) || [],
|
|
6641
|
-
modifyCommitPrompt: async () => await prompts
|
|
6693
|
+
modifyCommitPrompt: async () => await prompts.editor({
|
|
6642
6694
|
message: 'modify default commit message prompt:',
|
|
6643
6695
|
default: COMMIT_PROMPT.template,
|
|
6644
6696
|
}),
|
|
6645
|
-
selectProjectConfigFileType: async () => await prompts
|
|
6697
|
+
selectProjectConfigFileType: async () => await prompts.select({
|
|
6646
6698
|
message: 'where would you like to store the project config?',
|
|
6647
6699
|
choices: [
|
|
6648
6700
|
{
|
|
@@ -6657,7 +6709,7 @@ const questions = {
|
|
|
6657
6709
|
}),
|
|
6658
6710
|
};
|
|
6659
6711
|
|
|
6660
|
-
const handler = async (argv, logger) => {
|
|
6712
|
+
const handler$1 = async (argv, logger) => {
|
|
6661
6713
|
const options = loadConfig(argv);
|
|
6662
6714
|
logger.log(LOGO);
|
|
6663
6715
|
let scope = options?.scope;
|
|
@@ -6695,10 +6747,13 @@ const handler = async (argv, logger) => {
|
|
|
6695
6747
|
if (advOptions) {
|
|
6696
6748
|
config.mode = await questions.selectMode();
|
|
6697
6749
|
config.defaultBranch = await questions.selectDefaultGitBranch();
|
|
6698
|
-
config.
|
|
6699
|
-
|
|
6750
|
+
config.service = {
|
|
6751
|
+
...config.service,
|
|
6752
|
+
temperature: await questions.inputModelTemperature(),
|
|
6753
|
+
tokenLimit: await questions.inputTokenLimit(),
|
|
6754
|
+
};
|
|
6700
6755
|
config.verbose = await questions.enableVerboseMode();
|
|
6701
|
-
const promptForIgnores = await prompts
|
|
6756
|
+
const promptForIgnores = await prompts.confirm({
|
|
6702
6757
|
message: 'would you like to configure ignored files and extensions?',
|
|
6703
6758
|
default: false,
|
|
6704
6759
|
});
|
|
@@ -6706,7 +6761,7 @@ const handler = async (argv, logger) => {
|
|
|
6706
6761
|
config.ignoredFiles = await questions.whatFilesToIgnore();
|
|
6707
6762
|
config.ignoredExtensions = await questions.whatExtensionsToIgnore();
|
|
6708
6763
|
}
|
|
6709
|
-
const promptForCommitPrompt = await prompts
|
|
6764
|
+
const promptForCommitPrompt = await prompts.confirm({
|
|
6710
6765
|
message: 'would you like to configure the commit message prompt?',
|
|
6711
6766
|
default: false,
|
|
6712
6767
|
});
|
|
@@ -6723,7 +6778,7 @@ const handler = async (argv, logger) => {
|
|
|
6723
6778
|
approvalMessage = 'looking good? (API key hidden for security)';
|
|
6724
6779
|
}
|
|
6725
6780
|
}
|
|
6726
|
-
const isApproved = await prompts
|
|
6781
|
+
const isApproved = await prompts.confirm({
|
|
6727
6782
|
message: approvalMessage,
|
|
6728
6783
|
});
|
|
6729
6784
|
let configFilePath = '';
|
|
@@ -6760,20 +6815,228 @@ const handler = async (argv, logger) => {
|
|
|
6760
6815
|
/**
|
|
6761
6816
|
* Command line options via yargs
|
|
6762
6817
|
*/
|
|
6763
|
-
const options = {
|
|
6818
|
+
const options$1 = {
|
|
6764
6819
|
scope: {
|
|
6765
6820
|
type: 'string',
|
|
6766
6821
|
description: 'configure coco for the current user or project?',
|
|
6767
6822
|
choices: ['global', 'project'],
|
|
6768
6823
|
},
|
|
6769
6824
|
};
|
|
6770
|
-
const builder = (yargs) => {
|
|
6771
|
-
return yargs.options(options);
|
|
6825
|
+
const builder$1 = (yargs) => {
|
|
6826
|
+
return yargs.options(options$1).usage(getCommandUsageHeader(init.command));
|
|
6772
6827
|
};
|
|
6773
6828
|
|
|
6774
6829
|
var init = {
|
|
6775
6830
|
command: 'init',
|
|
6776
6831
|
desc: 'install & configure coco globally or for the current project',
|
|
6832
|
+
builder: builder$1,
|
|
6833
|
+
handler: commandExecutor(handler$1),
|
|
6834
|
+
options: options$1,
|
|
6835
|
+
};
|
|
6836
|
+
|
|
6837
|
+
/**
|
|
6838
|
+
* Formats a commit log into a readable string format.
|
|
6839
|
+
*
|
|
6840
|
+
* @param commitLog - The commit log result containing an array of commit details.
|
|
6841
|
+
* @returns An array of formatted commit log strings.
|
|
6842
|
+
*
|
|
6843
|
+
* Each formatted string includes:
|
|
6844
|
+
* - The date of the commit in square brackets.
|
|
6845
|
+
* - The commit message.
|
|
6846
|
+
* - The commit body.
|
|
6847
|
+
* - The commit hash in parentheses.
|
|
6848
|
+
* - The author's name and email in angle brackets.
|
|
6849
|
+
*/
|
|
6850
|
+
const formatCommitLog = (commitLog) => {
|
|
6851
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6852
|
+
};
|
|
6853
|
+
|
|
6854
|
+
const getChangesByTimestamp = async ({ since, git }) => {
|
|
6855
|
+
const commitLog = await git.log({ '--since': since });
|
|
6856
|
+
return formatCommitLog(commitLog);
|
|
6857
|
+
};
|
|
6858
|
+
|
|
6859
|
+
const getChangesSinceLastTag = async ({ git }) => {
|
|
6860
|
+
const tags = await git.tags();
|
|
6861
|
+
if (tags.all.length > 0) {
|
|
6862
|
+
const lastTag = tags.latest;
|
|
6863
|
+
const commitLog = await git.log({ from: lastTag });
|
|
6864
|
+
return formatCommitLog(commitLog);
|
|
6865
|
+
}
|
|
6866
|
+
else {
|
|
6867
|
+
return ['No tags found in the repository.'];
|
|
6868
|
+
}
|
|
6869
|
+
};
|
|
6870
|
+
|
|
6871
|
+
async function noResult({ logger }) {
|
|
6872
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
6873
|
+
}
|
|
6874
|
+
|
|
6875
|
+
const template = `Following the formatting instructions, summarize the following changes in the underlying git repository/branch.
|
|
6876
|
+
The summarization should descibe in a general sense what has changed in the repository over the specified timeframe. Specific files can be mentioned, but the summary should be general enough to be useful to someone who has not seen the changes.
|
|
6877
|
+
|
|
6878
|
+
Breaking down the changes into categories (e.g. bug fixes, new features, etc.) with markdown headings is encouraged.
|
|
6879
|
+
|
|
6880
|
+
{timeframe}
|
|
6881
|
+
|
|
6882
|
+
{format_instructions}
|
|
6883
|
+
|
|
6884
|
+
"""{changes}"""`;
|
|
6885
|
+
const inputVariables = ['format_instructions', 'changes', 'timeframe'];
|
|
6886
|
+
const RECAP_PROMPT = new prompts$1.PromptTemplate({
|
|
6887
|
+
template,
|
|
6888
|
+
inputVariables,
|
|
6889
|
+
});
|
|
6890
|
+
|
|
6891
|
+
const handler = async (argv, logger) => {
|
|
6892
|
+
const git = getRepo();
|
|
6893
|
+
const config = loadConfig(argv);
|
|
6894
|
+
const key = getApiKeyForModel(config);
|
|
6895
|
+
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
6896
|
+
if (config.service.authentication.type !== 'None' && !key) {
|
|
6897
|
+
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
6898
|
+
process.exit(1);
|
|
6899
|
+
}
|
|
6900
|
+
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4');
|
|
6901
|
+
const llm = getLlm(provider, model, config);
|
|
6902
|
+
const INTERACTIVE = isInteractive(config);
|
|
6903
|
+
if (INTERACTIVE) {
|
|
6904
|
+
logger.log(LOGO);
|
|
6905
|
+
}
|
|
6906
|
+
const { 'last-month': lastMonth, 'last-tag': lastTag, yesterday, 'last-week': lastWeek } = argv;
|
|
6907
|
+
const timeframe = lastMonth ? 'last-month' : lastTag ? 'last-tag' : yesterday ? 'yesterday' : lastWeek ? 'last-week' : 'current';
|
|
6908
|
+
logger.log(`Generating recap for timeframe: ${timeframe}`);
|
|
6909
|
+
async function factory() {
|
|
6910
|
+
switch (timeframe) {
|
|
6911
|
+
case 'current':
|
|
6912
|
+
const { staged, unstaged, untracked } = await getChanges({ git });
|
|
6913
|
+
logger.log(`Staged: ${staged.length}, Unstaged: ${unstaged?.length || 0}, Untracked: ${untracked?.length || 0}`);
|
|
6914
|
+
const unstagedChanges = await fileChangeParser({
|
|
6915
|
+
changes: unstaged || [],
|
|
6916
|
+
commit: '--unstaged',
|
|
6917
|
+
options: { tokenizer, git, llm, logger },
|
|
6918
|
+
});
|
|
6919
|
+
const unstagedResponse = `Unstaged changes:\n${unstagedChanges}`;
|
|
6920
|
+
const untrackedChanges = await fileChangeParser({
|
|
6921
|
+
changes: untracked || [],
|
|
6922
|
+
commit: '--untracked',
|
|
6923
|
+
options: { tokenizer, git, llm, logger },
|
|
6924
|
+
});
|
|
6925
|
+
const untrackedResponse = `Untracked changes:\n${untrackedChanges}`;
|
|
6926
|
+
const stagedChanges = await fileChangeParser({
|
|
6927
|
+
changes: staged,
|
|
6928
|
+
commit: '--staged',
|
|
6929
|
+
options: { tokenizer, git, llm, logger },
|
|
6930
|
+
});
|
|
6931
|
+
const stagedResponse = `Staged changes:\n${stagedChanges}`;
|
|
6932
|
+
return [unstagedResponse, untrackedResponse, stagedResponse];
|
|
6933
|
+
case 'yesterday':
|
|
6934
|
+
const yesterday = new Date();
|
|
6935
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
6936
|
+
return await getChangesByTimestamp({ git, since: yesterday.toISOString().split('T')[0] });
|
|
6937
|
+
case 'last-week':
|
|
6938
|
+
const lastWeek = new Date();
|
|
6939
|
+
lastWeek.setDate(lastWeek.getDate() - 7);
|
|
6940
|
+
return await getChangesByTimestamp({ git, since: lastWeek.toISOString().split('T')[0] });
|
|
6941
|
+
case 'last-month':
|
|
6942
|
+
const lastMonth = new Date();
|
|
6943
|
+
lastMonth.setMonth(lastMonth.getMonth() - 1);
|
|
6944
|
+
return await getChangesByTimestamp({ git, since: lastMonth.toISOString().split('T')[0] });
|
|
6945
|
+
case 'last-tag':
|
|
6946
|
+
const tags = await getChangesSinceLastTag({ git });
|
|
6947
|
+
return tags;
|
|
6948
|
+
default:
|
|
6949
|
+
logger.log(`Invalid timeframe: ${timeframe}`, { color: 'red' });
|
|
6950
|
+
return [];
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
async function parser(changes) {
|
|
6954
|
+
return changes.join('\n');
|
|
6955
|
+
}
|
|
6956
|
+
const recap = await generateAndReviewLoop({
|
|
6957
|
+
label: 'recap',
|
|
6958
|
+
options: {
|
|
6959
|
+
...config,
|
|
6960
|
+
prompt: config.prompt || RECAP_PROMPT.template,
|
|
6961
|
+
logger,
|
|
6962
|
+
interactive: INTERACTIVE,
|
|
6963
|
+
review: {
|
|
6964
|
+
descriptions: {
|
|
6965
|
+
approve: `Commit staged changes with generated commit message`,
|
|
6966
|
+
edit: 'Edit the commit message before proceeding',
|
|
6967
|
+
modifyPrompt: 'Modify the prompt template and regenerate the commit message',
|
|
6968
|
+
retryMessageOnly: 'Restart the function execution from generating the commit message',
|
|
6969
|
+
retryFull: 'Restart the function execution from the beginning, regenerating both the diff summary and commit message',
|
|
6970
|
+
},
|
|
6971
|
+
},
|
|
6972
|
+
},
|
|
6973
|
+
factory,
|
|
6974
|
+
parser,
|
|
6975
|
+
agent: async (context, options) => {
|
|
6976
|
+
const parser = new output_parsers.JsonOutputParser();
|
|
6977
|
+
const formatInstructions = "Respond with a valid JSON object, containing one field: 'summary', a string.";
|
|
6978
|
+
const prompt = getPrompt({
|
|
6979
|
+
template: options.prompt,
|
|
6980
|
+
variables: RECAP_PROMPT.inputVariables,
|
|
6981
|
+
fallback: RECAP_PROMPT,
|
|
6982
|
+
});
|
|
6983
|
+
const response = await executeChain({
|
|
6984
|
+
llm,
|
|
6985
|
+
prompt,
|
|
6986
|
+
variables: {
|
|
6987
|
+
changes: context,
|
|
6988
|
+
format_instructions: formatInstructions,
|
|
6989
|
+
timeframe,
|
|
6990
|
+
},
|
|
6991
|
+
parser,
|
|
6992
|
+
});
|
|
6993
|
+
logger.log(response.summary || 'no response');
|
|
6994
|
+
return `${response.summary}`;
|
|
6995
|
+
},
|
|
6996
|
+
noResult: async () => {
|
|
6997
|
+
await noResult({ git, logger });
|
|
6998
|
+
process.exit(0);
|
|
6999
|
+
},
|
|
7000
|
+
});
|
|
7001
|
+
logger.log(`Recap generated: ${recap}`, { color: 'green' });
|
|
7002
|
+
};
|
|
7003
|
+
|
|
7004
|
+
/**
|
|
7005
|
+
* Command line options via yargs
|
|
7006
|
+
*/
|
|
7007
|
+
const options = {
|
|
7008
|
+
yesterday: {
|
|
7009
|
+
type: 'boolean',
|
|
7010
|
+
description: 'Recap for yesterday',
|
|
7011
|
+
},
|
|
7012
|
+
"last-week": {
|
|
7013
|
+
alias: 'week',
|
|
7014
|
+
type: 'boolean',
|
|
7015
|
+
description: 'Recap for last week',
|
|
7016
|
+
},
|
|
7017
|
+
"last-month": {
|
|
7018
|
+
alias: 'month',
|
|
7019
|
+
type: 'boolean',
|
|
7020
|
+
description: 'Recap for last month',
|
|
7021
|
+
},
|
|
7022
|
+
"last-tag": {
|
|
7023
|
+
alias: 'tag',
|
|
7024
|
+
type: 'boolean',
|
|
7025
|
+
description: 'Recap for last tag',
|
|
7026
|
+
},
|
|
7027
|
+
i: {
|
|
7028
|
+
type: 'boolean',
|
|
7029
|
+
alias: 'interactive',
|
|
7030
|
+
description: 'Toggle interactive mode',
|
|
7031
|
+
},
|
|
7032
|
+
};
|
|
7033
|
+
const builder = (yargsInstance) => {
|
|
7034
|
+
return yargsInstance.options(options).usage(getCommandUsageHeader(recap.command));
|
|
7035
|
+
};
|
|
7036
|
+
|
|
7037
|
+
var recap = {
|
|
7038
|
+
command: 'recap',
|
|
7039
|
+
desc: 'Summarize the changes in the repository over a specified timeframe.',
|
|
6777
7040
|
builder,
|
|
6778
7041
|
handler: commandExecutor(handler),
|
|
6779
7042
|
options,
|
|
@@ -6784,25 +7047,15 @@ var types = /*#__PURE__*/Object.freeze({
|
|
|
6784
7047
|
});
|
|
6785
7048
|
|
|
6786
7049
|
const y = yargs();
|
|
6787
|
-
y.scriptName('coco')
|
|
6788
|
-
y.command([commit.command, '$0'], commit.desc,
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
y.command(changelog.command, changelog.desc,
|
|
6794
|
-
// TODO: fix type on builder
|
|
6795
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
6796
|
-
// @ts-ignore
|
|
6797
|
-
changelog.builder, changelog.handler).options(changelog.options);
|
|
6798
|
-
y.command(init.command, init.desc,
|
|
6799
|
-
// TODO: fix type on builder
|
|
6800
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
6801
|
-
// @ts-ignore
|
|
6802
|
-
init.builder, init.handler).options(init.options);
|
|
6803
|
-
y.parse(process.argv.slice(2));
|
|
7050
|
+
y.scriptName('coco');
|
|
7051
|
+
y.command([commit.command, '$0'], commit.desc, commit.builder, commit.handler);
|
|
7052
|
+
y.command(changelog.command, changelog.desc, changelog.builder, changelog.handler);
|
|
7053
|
+
y.command(recap.command, recap.desc, recap.builder, recap.handler);
|
|
7054
|
+
y.command(init.command, init.desc, init.builder, init.handler);
|
|
7055
|
+
y.help().parse(process.argv.slice(2));
|
|
6804
7056
|
|
|
6805
7057
|
exports.changelog = changelog;
|
|
6806
7058
|
exports.commit = commit;
|
|
6807
7059
|
exports.init = init;
|
|
7060
|
+
exports.recap = recap;
|
|
6808
7061
|
exports.types = types;
|