git-coco 0.3.4 → 0.4.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/commands/changelog/handler.d.ts +3 -0
- package/dist/commands/changelog/index.d.ts +10 -0
- package/dist/commands/changelog/options.d.ts +16 -0
- package/dist/index.esm.mjs +203 -36
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +203 -36
- package/dist/lib/langchain/prompts/changelog.d.ts +3 -0
- package/dist/lib/simple-git/getCommitLogRange.d.ts +7 -0
- package/dist/lib/simple-git/getDiff.d.ts +2 -2
- package/dist/lib/simple-git/getStatus.d.ts +1 -1
- package/dist/lib/simple-git/getSummaryText.d.ts +1 -1
- package/dist/lib/ui/generateAndReviewLoop.d.ts +2 -1
- package/dist/stats.html +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="yargs" />
|
|
2
|
+
import { handler } from './handler';
|
|
3
|
+
declare const _default: {
|
|
4
|
+
command: string;
|
|
5
|
+
desc: string;
|
|
6
|
+
builder: (yargs: import("yargs").Argv<{}>) => import("yargs").Argv<import("yargs").Omit<{}, string> & import("yargs").InferredOptionTypes<Record<string, import("yargs").Options>>>;
|
|
7
|
+
handler: typeof handler;
|
|
8
|
+
options: Record<string, import("yargs").Options>;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Options, Argv } from 'yargs';
|
|
2
|
+
import { BaseCommandOptions } from '../types';
|
|
3
|
+
export interface ChangelogOptions extends BaseCommandOptions {
|
|
4
|
+
range: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
commit: boolean;
|
|
7
|
+
summarizePrompt: string;
|
|
8
|
+
openInEditor: boolean;
|
|
9
|
+
ignoredFiles: string[];
|
|
10
|
+
ignoredExtensions: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Command line options via yargs
|
|
14
|
+
*/
|
|
15
|
+
export declare const options: Record<string, Options>;
|
|
16
|
+
export declare const builder: (yargs: Argv) => Argv<import("yargs").Omit<{}, string> & import("yargs").InferredOptionTypes<Record<string, Options>>>;
|
package/dist/index.esm.mjs
CHANGED
|
@@ -330,25 +330,25 @@ function validatePromptTemplate(text, inputVariables) {
|
|
|
330
330
|
return true;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
const template$
|
|
333
|
+
const template$2 = `GOAL: Use functional abstractions to summarize the following text
|
|
334
334
|
|
|
335
335
|
RULES: Avoid phrases like "this change", "this code", or "this function" etc. Instead refer to the function, variable, or class by name.
|
|
336
336
|
|
|
337
337
|
TEXT:"""{text}"""
|
|
338
338
|
`;
|
|
339
|
-
const inputVariables$
|
|
339
|
+
const inputVariables$2 = ['text'];
|
|
340
340
|
const SUMMARIZE_PROMPT = new PromptTemplate({
|
|
341
|
-
template: template$
|
|
342
|
-
inputVariables: inputVariables$
|
|
341
|
+
template: template$2,
|
|
342
|
+
inputVariables: inputVariables$2,
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
-
|
|
345
|
+
async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
|
|
346
346
|
if (commit !== '--staged') {
|
|
347
347
|
return await git.diff([`${commit}~1..${commit}`, '--', nodeFile.filePath]);
|
|
348
348
|
}
|
|
349
349
|
return await git.diff([commit, nodeFile.filePath]);
|
|
350
|
-
}
|
|
351
|
-
|
|
350
|
+
}
|
|
351
|
+
async function parseRenamedFileDiff(nodeFile, commit, git, logger) {
|
|
352
352
|
let result = '';
|
|
353
353
|
const oldFilePath = nodeFile?.oldFilePath || nodeFile.filePath;
|
|
354
354
|
let previousCommitHash = 'HEAD';
|
|
@@ -385,8 +385,8 @@ const parseRenamedFileDiff = async (nodeFile, commit, git, logger) => {
|
|
|
385
385
|
result = 'Error comparing file contents.';
|
|
386
386
|
}
|
|
387
387
|
return result;
|
|
388
|
-
}
|
|
389
|
-
|
|
388
|
+
}
|
|
389
|
+
async function getDiff(nodeFile, commit, { git, logger, }) {
|
|
390
390
|
if (nodeFile.status === 'deleted') {
|
|
391
391
|
return 'This file has been deleted.';
|
|
392
392
|
}
|
|
@@ -397,7 +397,7 @@ const getDiff = async (nodeFile, commit, { git, logger, }) => {
|
|
|
397
397
|
// If not deleted or renamed, get the diff from the index
|
|
398
398
|
const defaultDiff = await parseDefaultFileDiff(nodeFile, commit, git);
|
|
399
399
|
return defaultDiff;
|
|
400
|
-
}
|
|
400
|
+
}
|
|
401
401
|
|
|
402
402
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
403
403
|
async function fileChangeParser({ changes, commit, options: { tokenizer, git, model, logger }, }) {
|
|
@@ -500,7 +500,7 @@ class Logger {
|
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
const template = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
503
|
+
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
504
504
|
Commit Messages must have a short description that is less than 50 characters followed by a newline character and then a more verbose detailed description.
|
|
505
505
|
|
|
506
506
|
- Typically a hyphen or asterisk is used for the bullet
|
|
@@ -513,13 +513,13 @@ Commit Messages must have a short description that is less than 50 characters fo
|
|
|
513
513
|
"""{summary}"""
|
|
514
514
|
|
|
515
515
|
Commit:`;
|
|
516
|
-
const inputVariables = ['summary'];
|
|
516
|
+
const inputVariables$1 = ['summary'];
|
|
517
517
|
const COMMIT_PROMPT = new PromptTemplate({
|
|
518
|
-
template,
|
|
519
|
-
inputVariables,
|
|
518
|
+
template: template$1,
|
|
519
|
+
inputVariables: inputVariables$1,
|
|
520
520
|
});
|
|
521
521
|
|
|
522
|
-
|
|
522
|
+
function getStatus(file, location = 'index') {
|
|
523
523
|
if ('index' in file && 'working_dir' in file) {
|
|
524
524
|
const statusCode = file[location];
|
|
525
525
|
switch (statusCode) {
|
|
@@ -551,11 +551,11 @@ const getStatus = (file, location = 'index') => {
|
|
|
551
551
|
return 'unknown';
|
|
552
552
|
}
|
|
553
553
|
else {
|
|
554
|
-
throw new Error(
|
|
554
|
+
throw new Error('Invalid file type');
|
|
555
555
|
}
|
|
556
|
-
}
|
|
556
|
+
}
|
|
557
557
|
|
|
558
|
-
|
|
558
|
+
function getSummaryText(file, change) {
|
|
559
559
|
const status = change.status || getStatus(file);
|
|
560
560
|
let filePath;
|
|
561
561
|
if ('path' in file) {
|
|
@@ -565,13 +565,13 @@ const getSummaryText = (file, change) => {
|
|
|
565
565
|
filePath = change?.filePath || file.file;
|
|
566
566
|
}
|
|
567
567
|
else {
|
|
568
|
-
throw new Error(
|
|
568
|
+
throw new Error('Invalid file type');
|
|
569
569
|
}
|
|
570
570
|
if (change.oldFilePath) {
|
|
571
571
|
return `${status}: ${change.oldFilePath} -> ${filePath}`;
|
|
572
572
|
}
|
|
573
573
|
return `${status}: ${filePath}`;
|
|
574
|
-
}
|
|
574
|
+
}
|
|
575
575
|
|
|
576
576
|
/**
|
|
577
577
|
* Returns a new object with all undefined keys removed
|
|
@@ -711,7 +711,6 @@ const DEFAULT_CONFIG = {
|
|
|
711
711
|
model: 'openai/gpt-4',
|
|
712
712
|
verbose: false,
|
|
713
713
|
tokenLimit: 1024,
|
|
714
|
-
prompt: COMMIT_PROMPT.template,
|
|
715
714
|
summarizePrompt: SUMMARIZE_PROMPT.template,
|
|
716
715
|
temperature: 0.4,
|
|
717
716
|
mode: 'stdout',
|
|
@@ -899,7 +898,7 @@ async function editPrompt(options) {
|
|
|
899
898
|
});
|
|
900
899
|
}
|
|
901
900
|
|
|
902
|
-
async function generateAndReviewLoop({ factory, parser, noResult, agent, options, }) {
|
|
901
|
+
async function generateAndReviewLoop({ label, factory, parser, noResult, agent, options, }) {
|
|
903
902
|
const { logger } = options;
|
|
904
903
|
let continueLoop = true;
|
|
905
904
|
let modifyPrompt = false;
|
|
@@ -921,19 +920,19 @@ async function generateAndReviewLoop({ factory, parser, noResult, agent, options
|
|
|
921
920
|
if (modifyPrompt) {
|
|
922
921
|
options.prompt = await editPrompt(options);
|
|
923
922
|
}
|
|
924
|
-
logger.startTimer().startSpinner(`Generating
|
|
923
|
+
logger.startTimer().startSpinner(`Generating ${label}\n`, {
|
|
925
924
|
color: 'blue',
|
|
926
925
|
});
|
|
927
926
|
result = await agent(context, options);
|
|
928
927
|
if (!result) {
|
|
929
|
-
logger.stopSpinner('💀 Agent failed to
|
|
928
|
+
logger.stopSpinner('💀 Agent failed to return content.', {
|
|
930
929
|
mode: 'fail',
|
|
931
930
|
color: 'red',
|
|
932
931
|
});
|
|
933
932
|
process.exit(0);
|
|
934
933
|
}
|
|
935
934
|
logger
|
|
936
|
-
.stopSpinner(
|
|
935
|
+
.stopSpinner(`Generated ${label}`, {
|
|
937
936
|
color: 'green',
|
|
938
937
|
mode: 'succeed',
|
|
939
938
|
})
|
|
@@ -1018,8 +1017,8 @@ const handleResult = async (result, { mode, git }) => {
|
|
|
1018
1017
|
};
|
|
1019
1018
|
|
|
1020
1019
|
const tokenizer = getTokenizer();
|
|
1021
|
-
const git = simpleGit();
|
|
1022
|
-
async function handler(argv) {
|
|
1020
|
+
const git$1 = simpleGit();
|
|
1021
|
+
async function handler$1(argv) {
|
|
1023
1022
|
const options = loadConfig(argv);
|
|
1024
1023
|
const logger = new Logger(options);
|
|
1025
1024
|
const key = getApiKeyForModel(options.model, options);
|
|
@@ -1033,17 +1032,18 @@ async function handler(argv) {
|
|
|
1033
1032
|
});
|
|
1034
1033
|
const INTERACTIVE = isInteractive(options);
|
|
1035
1034
|
async function factory() {
|
|
1036
|
-
const changes = await getChanges({ git });
|
|
1035
|
+
const changes = await getChanges({ git: git$1 });
|
|
1037
1036
|
return changes.staged;
|
|
1038
1037
|
}
|
|
1039
1038
|
async function parser(changes) {
|
|
1040
1039
|
return await fileChangeParser({
|
|
1041
1040
|
changes,
|
|
1042
1041
|
commit: '--staged',
|
|
1043
|
-
options: { tokenizer, git, model, logger },
|
|
1042
|
+
options: { tokenizer, git: git$1, model, logger },
|
|
1044
1043
|
});
|
|
1045
1044
|
}
|
|
1046
1045
|
const commitMsg = await generateAndReviewLoop({
|
|
1046
|
+
label: 'Commit Message',
|
|
1047
1047
|
factory,
|
|
1048
1048
|
parser,
|
|
1049
1049
|
agent: async (context, options) => {
|
|
@@ -1058,11 +1058,12 @@ async function handler(argv) {
|
|
|
1058
1058
|
});
|
|
1059
1059
|
},
|
|
1060
1060
|
noResult: async () => {
|
|
1061
|
-
await noResult({ git, logger });
|
|
1061
|
+
await noResult({ git: git$1, logger });
|
|
1062
1062
|
process.exit(0);
|
|
1063
1063
|
},
|
|
1064
1064
|
options: {
|
|
1065
1065
|
...options,
|
|
1066
|
+
prompt: options.prompt || COMMIT_PROMPT.template,
|
|
1066
1067
|
logger,
|
|
1067
1068
|
interactive: INTERACTIVE,
|
|
1068
1069
|
},
|
|
@@ -1070,14 +1071,14 @@ async function handler(argv) {
|
|
|
1070
1071
|
const MODE = (INTERACTIVE && 'interactive') || (options.commit && 'interactive') || options?.mode || 'stdout';
|
|
1071
1072
|
handleResult(commitMsg, {
|
|
1072
1073
|
mode: MODE,
|
|
1073
|
-
git,
|
|
1074
|
+
git: git$1,
|
|
1074
1075
|
});
|
|
1075
1076
|
}
|
|
1076
1077
|
|
|
1077
1078
|
/**
|
|
1078
1079
|
* Command line options via yargs
|
|
1079
1080
|
*/
|
|
1080
|
-
const options = {
|
|
1081
|
+
const options$1 = {
|
|
1081
1082
|
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
1082
1083
|
openAIApiKey: {
|
|
1083
1084
|
type: 'string',
|
|
@@ -1123,21 +1124,187 @@ const options = {
|
|
|
1123
1124
|
description: 'Ignored extensions',
|
|
1124
1125
|
},
|
|
1125
1126
|
};
|
|
1126
|
-
const builder = (yargs) => {
|
|
1127
|
-
return yargs.options(options);
|
|
1127
|
+
const builder$1 = (yargs) => {
|
|
1128
|
+
return yargs.options(options$1);
|
|
1128
1129
|
};
|
|
1129
1130
|
|
|
1130
1131
|
var commit = {
|
|
1131
1132
|
command: 'commit',
|
|
1132
1133
|
desc: 'Generate commit message',
|
|
1134
|
+
builder: builder$1,
|
|
1135
|
+
handler: handler$1,
|
|
1136
|
+
options: options$1,
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
const template = `Write informative git changelog, in the imperative, based on a series of individual messages.
|
|
1140
|
+
|
|
1141
|
+
- Typically a hyphen or asterisk is used for the bullet
|
|
1142
|
+
- Summarize dependency updates
|
|
1143
|
+
|
|
1144
|
+
"""{summary}"""
|
|
1145
|
+
|
|
1146
|
+
Changelog:`;
|
|
1147
|
+
const inputVariables = ['summary'];
|
|
1148
|
+
const CHANGELOG_PROMPT = new PromptTemplate({
|
|
1149
|
+
template,
|
|
1150
|
+
inputVariables,
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
1154
|
+
try {
|
|
1155
|
+
const output = await git.raw([
|
|
1156
|
+
'log',
|
|
1157
|
+
`${from}..${to}`,
|
|
1158
|
+
'--pretty=format:%s',
|
|
1159
|
+
// Include '--no-merges' here if you want to exclude merge commits.
|
|
1160
|
+
noMerges ? '--no-merges' : null,
|
|
1161
|
+
].filter(Boolean)); // filter(Boolean) removes any null values from the array
|
|
1162
|
+
const messages = output.split('\n').filter(Boolean);
|
|
1163
|
+
return messages;
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
// If there's an error, handle it appropriately
|
|
1167
|
+
console.error('Error getting commit messages:', error);
|
|
1168
|
+
throw error;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const git = simpleGit();
|
|
1173
|
+
async function handler(argv) {
|
|
1174
|
+
const options = loadConfig(argv);
|
|
1175
|
+
const logger = new Logger(options);
|
|
1176
|
+
const key = getApiKeyForModel(options.model, options);
|
|
1177
|
+
if (!key) {
|
|
1178
|
+
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
1179
|
+
process.exit(1);
|
|
1180
|
+
}
|
|
1181
|
+
const model = getModel(options.model, key, {
|
|
1182
|
+
temperature: 0.4,
|
|
1183
|
+
maxConcurrency: 10,
|
|
1184
|
+
});
|
|
1185
|
+
const INTERACTIVE = isInteractive(options);
|
|
1186
|
+
const [from, to] = options.range?.split(':');
|
|
1187
|
+
if (!from || !to) {
|
|
1188
|
+
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
1189
|
+
process.exit(1);
|
|
1190
|
+
}
|
|
1191
|
+
async function factory() {
|
|
1192
|
+
const messages = await getCommitLogRange(from, to, { git, noMerges: true });
|
|
1193
|
+
return messages;
|
|
1194
|
+
}
|
|
1195
|
+
async function parser(messages) {
|
|
1196
|
+
const result = messages.join('\n');
|
|
1197
|
+
return result;
|
|
1198
|
+
}
|
|
1199
|
+
const changelogMsg = await generateAndReviewLoop({
|
|
1200
|
+
label: 'Changelog',
|
|
1201
|
+
factory,
|
|
1202
|
+
parser,
|
|
1203
|
+
agent: async (context, options) => {
|
|
1204
|
+
const prompt = getPrompt({
|
|
1205
|
+
template: options.prompt,
|
|
1206
|
+
variables: CHANGELOG_PROMPT.inputVariables,
|
|
1207
|
+
fallback: CHANGELOG_PROMPT,
|
|
1208
|
+
});
|
|
1209
|
+
return await executeChain({
|
|
1210
|
+
llm: model,
|
|
1211
|
+
prompt,
|
|
1212
|
+
variables: { summary: context },
|
|
1213
|
+
});
|
|
1214
|
+
},
|
|
1215
|
+
noResult: async () => {
|
|
1216
|
+
await noResult({ git, logger });
|
|
1217
|
+
process.exit(0);
|
|
1218
|
+
},
|
|
1219
|
+
options: {
|
|
1220
|
+
...options,
|
|
1221
|
+
prompt: options.prompt || CHANGELOG_PROMPT.template,
|
|
1222
|
+
logger,
|
|
1223
|
+
interactive: INTERACTIVE,
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1226
|
+
const MODE = (INTERACTIVE && 'interactive') || (options.commit && 'interactive') || options?.mode || 'stdout';
|
|
1227
|
+
handleResult(changelogMsg, {
|
|
1228
|
+
mode: MODE,
|
|
1229
|
+
git,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Command line options via yargs
|
|
1235
|
+
*/
|
|
1236
|
+
const options = {
|
|
1237
|
+
range: {
|
|
1238
|
+
type: 'string',
|
|
1239
|
+
alias: 'r',
|
|
1240
|
+
description: 'Commit range e.g `HEAD~2:HEAD`',
|
|
1241
|
+
demandOption: true,
|
|
1242
|
+
},
|
|
1243
|
+
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
1244
|
+
openAIApiKey: {
|
|
1245
|
+
type: 'string',
|
|
1246
|
+
description: 'OpenAI API Key',
|
|
1247
|
+
conflicts: 'huggingFaceHubApiKey',
|
|
1248
|
+
},
|
|
1249
|
+
huggingFaceHubApiKey: {
|
|
1250
|
+
type: 'string',
|
|
1251
|
+
description: 'HuggingFace Hub API Key',
|
|
1252
|
+
conflicts: 'openAIApiKey',
|
|
1253
|
+
},
|
|
1254
|
+
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
1255
|
+
prompt: {
|
|
1256
|
+
type: 'string',
|
|
1257
|
+
alias: 'p',
|
|
1258
|
+
description: 'Prompt for llm',
|
|
1259
|
+
},
|
|
1260
|
+
i: {
|
|
1261
|
+
type: 'boolean',
|
|
1262
|
+
alias: 'interactive',
|
|
1263
|
+
description: 'Toggle interactive mode',
|
|
1264
|
+
},
|
|
1265
|
+
e: {
|
|
1266
|
+
type: 'boolean',
|
|
1267
|
+
alias: 'edit',
|
|
1268
|
+
description: 'Open generated changelog message in editor before proceeding',
|
|
1269
|
+
},
|
|
1270
|
+
summarizePrompt: {
|
|
1271
|
+
type: 'string',
|
|
1272
|
+
description: 'Prompt for summarizing large files',
|
|
1273
|
+
},
|
|
1274
|
+
ignoredFiles: {
|
|
1275
|
+
type: 'array',
|
|
1276
|
+
description: 'Ignored files',
|
|
1277
|
+
},
|
|
1278
|
+
ignoredExtensions: {
|
|
1279
|
+
type: 'array',
|
|
1280
|
+
description: 'Ignored extensions',
|
|
1281
|
+
},
|
|
1282
|
+
};
|
|
1283
|
+
const builder = (yargs) => {
|
|
1284
|
+
return yargs.options(options);
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
var changelog = {
|
|
1288
|
+
command: 'changelog',
|
|
1289
|
+
desc: 'Generate a changelog from a commit range',
|
|
1133
1290
|
builder,
|
|
1134
1291
|
handler,
|
|
1135
1292
|
options,
|
|
1136
1293
|
};
|
|
1137
1294
|
|
|
1138
|
-
yargs
|
|
1295
|
+
yargs
|
|
1296
|
+
.scriptName('coco')
|
|
1297
|
+
.usage('$0 <cmd> [args]')
|
|
1298
|
+
.command([commit.command, '$0'], commit.desc,
|
|
1299
|
+
// TODO: fix type on builder
|
|
1300
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1301
|
+
// @ts-ignore
|
|
1302
|
+
commit.builder, commit.handler)
|
|
1303
|
+
.command(changelog.command, changelog.desc,
|
|
1139
1304
|
// TODO: fix type on builder
|
|
1140
1305
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1141
1306
|
// @ts-ignore
|
|
1142
|
-
|
|
1307
|
+
changelog.builder, changelog.handler)
|
|
1308
|
+
.demandCommand()
|
|
1309
|
+
.help().argv;
|
|
1143
1310
|
//# sourceMappingURL=index.esm.mjs.map
|