git-coco 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/commit.d.ts +16 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.mjs +258 -179
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +258 -179
- package/dist/lib/config/{index.d.ts → loadConfig.d.ts} +7 -3
- package/dist/lib/config/types.d.ts +15 -1
- package/dist/lib/langchain/utils.d.ts +32 -1
- package/dist/lib/parsers/default/utils/collectDiffs.d.ts +1 -1
- package/dist/lib/parsers/default/utils/summarizeDiffs.d.ts +3 -1
- package/dist/lib/parsers/noResult.d.ts +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/stats.html +2 -2
- package/dist/types.d.ts +8 -0
- package/package.json +4 -4
- package/dist/lib/config/default.d.ts +0 -7
- package/dist/lib/config/services/yargs.d.ts +0 -25
- package/dist/lib/utils/getTruncatedFilePath.d.ts +0 -1
- package/dist/lib/utils/readFile.d.ts +0 -3
package/dist/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
var yargs = require('yargs');
|
|
4
5
|
var prompts$1 = require('@inquirer/prompts');
|
|
6
|
+
var simpleGit = require('simple-git');
|
|
5
7
|
var fs = require('fs');
|
|
6
8
|
var os = require('os');
|
|
7
9
|
var path = require('path');
|
|
8
10
|
var ini = require('ini');
|
|
9
|
-
var yargs = require('yargs');
|
|
10
|
-
var helpers = require('yargs/helpers');
|
|
11
11
|
var prompts = require('langchain/prompts');
|
|
12
12
|
var pQueue = require('p-queue');
|
|
13
|
-
var chalk = require('chalk');
|
|
14
|
-
var ora = require('ora');
|
|
15
|
-
var now = require('performance-now');
|
|
16
|
-
var prettyMilliseconds = require('pretty-ms');
|
|
17
13
|
var document = require('langchain/document');
|
|
14
|
+
var hf = require('langchain/llms/hf');
|
|
18
15
|
var chains = require('langchain/chains');
|
|
19
16
|
var openai = require('langchain/llms/openai');
|
|
20
17
|
var text_splitter = require('langchain/text_splitter');
|
|
21
18
|
var diff = require('diff');
|
|
19
|
+
var chalk = require('chalk');
|
|
22
20
|
var GPT3NodeTokenizer = require('gpt3-tokenizer');
|
|
21
|
+
var ora = require('ora');
|
|
22
|
+
var now = require('performance-now');
|
|
23
|
+
var prettyMilliseconds = require('pretty-ms');
|
|
23
24
|
var minimatch = require('minimatch');
|
|
24
|
-
var simpleGit = require('simple-git');
|
|
25
25
|
|
|
26
26
|
function _interopNamespaceDefault(e) {
|
|
27
27
|
var n = Object.create(null);
|
|
@@ -63,7 +63,9 @@ function removeUndefined(obj) {
|
|
|
63
63
|
**/
|
|
64
64
|
function loadEnvConfig(config) {
|
|
65
65
|
const envConfig = {
|
|
66
|
+
model: process.env.COCO_MODEL || undefined,
|
|
66
67
|
openAIApiKey: process.env.OPENAI_API_KEY || undefined,
|
|
68
|
+
huggingFaceHubApiKey: process.env.HUGGINGFACE_HUB_API_KEY || undefined,
|
|
67
69
|
tokenLimit: process.env.COCO_TOKEN_LIMIT
|
|
68
70
|
? parseInt(process.env.COCO_TOKEN_LIMIT)
|
|
69
71
|
: undefined,
|
|
@@ -94,7 +96,9 @@ function loadGitConfig(config) {
|
|
|
94
96
|
const gitConfigParsed = ini__namespace.parse(gitConfigRaw);
|
|
95
97
|
config = {
|
|
96
98
|
...config,
|
|
99
|
+
model: gitConfigParsed.coco?.model || config.model,
|
|
97
100
|
openAIApiKey: gitConfigParsed.coco?.openAIApiKey || config.openAIApiKey,
|
|
101
|
+
huggingFaceHubApiKey: gitConfigParsed.coco?.huggingFaceHubApiKey || config.huggingFaceHubApiKey,
|
|
98
102
|
tokenLimit: parseInt(gitConfigParsed.coco?.tokenLimit) || config.tokenLimit,
|
|
99
103
|
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
100
104
|
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
@@ -170,72 +174,6 @@ function loadXDGConfig(config) {
|
|
|
170
174
|
return config;
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
/**
|
|
174
|
-
* Command line options via yargs
|
|
175
|
-
*/
|
|
176
|
-
const options = {
|
|
177
|
-
openAIApiKey: { type: 'string', description: 'OpenAI API Key' },
|
|
178
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
179
|
-
prompt: {
|
|
180
|
-
type: 'string',
|
|
181
|
-
alias: 'p',
|
|
182
|
-
description: 'Commit message prompt',
|
|
183
|
-
},
|
|
184
|
-
interactive: {
|
|
185
|
-
type: 'boolean',
|
|
186
|
-
alias: 'i',
|
|
187
|
-
description: 'Toggle interactive mode',
|
|
188
|
-
},
|
|
189
|
-
commit: {
|
|
190
|
-
type: 'boolean',
|
|
191
|
-
alias: 's',
|
|
192
|
-
description: 'Commit staged changes with generated commit message',
|
|
193
|
-
default: false,
|
|
194
|
-
},
|
|
195
|
-
openInEditor: {
|
|
196
|
-
type: 'boolean',
|
|
197
|
-
alias: 'e',
|
|
198
|
-
description: 'Open commit message in editor before proceeding',
|
|
199
|
-
},
|
|
200
|
-
verbose: {
|
|
201
|
-
type: 'boolean',
|
|
202
|
-
description: 'Enable verbose logging',
|
|
203
|
-
},
|
|
204
|
-
summarizePrompt: {
|
|
205
|
-
type: 'string',
|
|
206
|
-
description: 'Large file summary prompt',
|
|
207
|
-
},
|
|
208
|
-
ignoredFiles: {
|
|
209
|
-
type: 'array',
|
|
210
|
-
description: 'Ignored files',
|
|
211
|
-
},
|
|
212
|
-
ignoredExtensions: {
|
|
213
|
-
type: 'array',
|
|
214
|
-
description: 'Ignored extensions',
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
/**
|
|
218
|
-
* Load command line flags via yargs
|
|
219
|
-
*
|
|
220
|
-
* @returns {Partial<Config>} Updated config
|
|
221
|
-
*/
|
|
222
|
-
const loadArgv = () => {
|
|
223
|
-
return yargs(helpers.hideBin(process.argv)).options(options).parseSync();
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* Load command line flags
|
|
227
|
-
*
|
|
228
|
-
* Note: Arugments are parsed using yargs.
|
|
229
|
-
*
|
|
230
|
-
* @param {Config} config
|
|
231
|
-
* @returns {Config} Updated config
|
|
232
|
-
**/
|
|
233
|
-
function loadCmdLineFlags(config) {
|
|
234
|
-
const argv = loadArgv();
|
|
235
|
-
config = { ...config, ...argv };
|
|
236
|
-
return config;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
177
|
const template$1 = `Write informative git commit message based on the diffs & file changes provided in the "Diff Summary" section.
|
|
240
178
|
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.
|
|
241
179
|
- Write concisely using an informal tone
|
|
@@ -271,7 +209,7 @@ const SUMMARIZE_PROMPT = new prompts.PromptTemplate({
|
|
|
271
209
|
* @type {Config}
|
|
272
210
|
*/
|
|
273
211
|
const DEFAULT_CONFIG = {
|
|
274
|
-
|
|
212
|
+
model: 'openai/gpt-3.5-turbo',
|
|
275
213
|
verbose: false,
|
|
276
214
|
tokenLimit: 1024,
|
|
277
215
|
prompt: COMMIT_PROMPT.template,
|
|
@@ -281,7 +219,6 @@ const DEFAULT_CONFIG = {
|
|
|
281
219
|
ignoredFiles: ['package-lock.json'],
|
|
282
220
|
ignoredExtensions: ['.map', '.lock'],
|
|
283
221
|
};
|
|
284
|
-
|
|
285
222
|
/**
|
|
286
223
|
* Load application config
|
|
287
224
|
*
|
|
@@ -299,7 +236,7 @@ const DEFAULT_CONFIG = {
|
|
|
299
236
|
*
|
|
300
237
|
* @returns {Config} application config
|
|
301
238
|
**/
|
|
302
|
-
function loadConfig() {
|
|
239
|
+
function loadConfig(argv = {}) {
|
|
303
240
|
// Default config
|
|
304
241
|
let config = DEFAULT_CONFIG;
|
|
305
242
|
config = loadGitignore(config);
|
|
@@ -308,60 +245,7 @@ function loadConfig() {
|
|
|
308
245
|
config = loadGitConfig(config);
|
|
309
246
|
config = loadProjectConfig(config);
|
|
310
247
|
config = loadEnvConfig(config);
|
|
311
|
-
config
|
|
312
|
-
return config;
|
|
313
|
-
}
|
|
314
|
-
const config = loadConfig();
|
|
315
|
-
|
|
316
|
-
class Logger {
|
|
317
|
-
constructor(config) {
|
|
318
|
-
this.config = config;
|
|
319
|
-
this.spinner = null;
|
|
320
|
-
}
|
|
321
|
-
log(message, options = { color: 'blue' }) {
|
|
322
|
-
let outputMessage = message;
|
|
323
|
-
if (options.color) {
|
|
324
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
325
|
-
}
|
|
326
|
-
console.log(outputMessage);
|
|
327
|
-
return this;
|
|
328
|
-
}
|
|
329
|
-
verbose(message, options = {}) {
|
|
330
|
-
if (!this.config?.verbose) {
|
|
331
|
-
return this;
|
|
332
|
-
}
|
|
333
|
-
this.log(message, options);
|
|
334
|
-
return this;
|
|
335
|
-
}
|
|
336
|
-
startTimer() {
|
|
337
|
-
this.timerStart = now();
|
|
338
|
-
return this;
|
|
339
|
-
}
|
|
340
|
-
stopTimer(message, options = { color: 'yellow' }) {
|
|
341
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
342
|
-
return this;
|
|
343
|
-
}
|
|
344
|
-
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
345
|
-
let outputMessage = message
|
|
346
|
-
? `${message} (⏲ ${elapsedTime})`
|
|
347
|
-
: `⏲ ${elapsedTime}`;
|
|
348
|
-
if (options.color) {
|
|
349
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
350
|
-
}
|
|
351
|
-
console.log(outputMessage);
|
|
352
|
-
return this;
|
|
353
|
-
}
|
|
354
|
-
startSpinner(message, options = { color: 'green' }) {
|
|
355
|
-
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
356
|
-
this.spinner = ora(spinnerMessage).start();
|
|
357
|
-
return this;
|
|
358
|
-
}
|
|
359
|
-
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
360
|
-
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
361
|
-
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
362
|
-
this.spinner = null;
|
|
363
|
-
return this;
|
|
364
|
-
}
|
|
248
|
+
return { ...config, ...argv };
|
|
365
249
|
}
|
|
366
250
|
|
|
367
251
|
/**
|
|
@@ -448,8 +332,7 @@ const defaultOutputCallback = (group) => {
|
|
|
448
332
|
}
|
|
449
333
|
return output;
|
|
450
334
|
};
|
|
451
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
452
|
-
const logger = new Logger(config);
|
|
335
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
453
336
|
const queue = new pQueue({ concurrency: 8 });
|
|
454
337
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
455
338
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
@@ -535,7 +418,7 @@ const createDiffTree = (changes) => {
|
|
|
535
418
|
/**
|
|
536
419
|
* Asynchronously collect diffs for a given node and its children.
|
|
537
420
|
*/
|
|
538
|
-
async function collectDiffs(node, getFileDiff, tokenizer, logger
|
|
421
|
+
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
539
422
|
// Collect diffs for the files of the current node
|
|
540
423
|
const diffPromises = node.files.map(async (nodeFile) => {
|
|
541
424
|
const diff = await getFileDiff(nodeFile);
|
|
@@ -553,7 +436,7 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
553
436
|
};
|
|
554
437
|
});
|
|
555
438
|
// Collect diffs for the children of the current node
|
|
556
|
-
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer));
|
|
439
|
+
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer, logger));
|
|
557
440
|
const [diffs, children] = await Promise.all([
|
|
558
441
|
Promise.all(diffPromises),
|
|
559
442
|
Promise.all(childrenPromises),
|
|
@@ -565,22 +448,67 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
565
448
|
};
|
|
566
449
|
}
|
|
567
450
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Get LLM Model Based on Configuration
|
|
453
|
+
* @param fields
|
|
454
|
+
* @param configuration
|
|
455
|
+
* @returns LLM Model
|
|
456
|
+
*/
|
|
457
|
+
function getModel(name, key, fields, configuration) {
|
|
458
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
459
|
+
if (!model) {
|
|
460
|
+
throw new Error(`Invalid model: ${name}`);
|
|
461
|
+
}
|
|
462
|
+
switch (llm) {
|
|
463
|
+
case 'huggingface':
|
|
464
|
+
return new hf.HuggingFaceInference({
|
|
465
|
+
model: model,
|
|
466
|
+
apiKey: key,
|
|
467
|
+
maxConcurrency: 4,
|
|
468
|
+
...fields,
|
|
469
|
+
});
|
|
470
|
+
case 'openai':
|
|
471
|
+
default:
|
|
472
|
+
return new openai.OpenAI({
|
|
473
|
+
openAIApiKey: key,
|
|
474
|
+
modelName: model,
|
|
475
|
+
...fields,
|
|
476
|
+
}, configuration);
|
|
477
|
+
}
|
|
580
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Retrieve appropriate API key based on selected model
|
|
481
|
+
* @param name
|
|
482
|
+
* @param options
|
|
483
|
+
* @returns
|
|
484
|
+
*/
|
|
485
|
+
function getModelAPIKey(name, options) {
|
|
486
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
487
|
+
if (!model) {
|
|
488
|
+
throw new Error(`Invalid model: ${name}`);
|
|
489
|
+
}
|
|
490
|
+
switch (llm) {
|
|
491
|
+
case 'huggingface':
|
|
492
|
+
return options.huggingFaceHubApiKey;
|
|
493
|
+
case 'openai':
|
|
494
|
+
default:
|
|
495
|
+
return options.openAIApiKey;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Get Recursive Character Text Splitter
|
|
500
|
+
* @param options
|
|
501
|
+
* @returns
|
|
502
|
+
*/
|
|
581
503
|
function getTextSplitter(options = {}) {
|
|
582
504
|
return new text_splitter.RecursiveCharacterTextSplitter(options);
|
|
583
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Get Summarization Chain
|
|
508
|
+
* @param model
|
|
509
|
+
* @param options
|
|
510
|
+
* @returns
|
|
511
|
+
*/
|
|
584
512
|
function getChain(model, options = { type: 'map_reduce' }) {
|
|
585
513
|
return chains.loadSummarizationChain(model, options);
|
|
586
514
|
}
|
|
@@ -594,6 +522,12 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
594
522
|
})
|
|
595
523
|
: fallback);
|
|
596
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* Verify template string contains all required input variables
|
|
527
|
+
* @param text template string
|
|
528
|
+
* @param inputVariables template variables
|
|
529
|
+
* @returns boolean or error message
|
|
530
|
+
*/
|
|
597
531
|
function validatePromptTemplate(text, inputVariables) {
|
|
598
532
|
if (!text) {
|
|
599
533
|
return 'Prompt template cannot be empty';
|
|
@@ -647,8 +581,7 @@ const getDiff = async (nodeFile, { git, logger, }) => {
|
|
|
647
581
|
};
|
|
648
582
|
|
|
649
583
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
650
|
-
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
651
|
-
const logger = new Logger(config);
|
|
584
|
+
const fileChangeParser = async (changes, { tokenizer, git, model, logger }) => {
|
|
652
585
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
653
586
|
const summarizationChain = getChain(model, {
|
|
654
587
|
type: 'map_reduce',
|
|
@@ -669,6 +602,7 @@ const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
|
669
602
|
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
670
603
|
textSplitter,
|
|
671
604
|
chain: summarizationChain,
|
|
605
|
+
logger
|
|
672
606
|
});
|
|
673
607
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
674
608
|
return summary;
|
|
@@ -704,11 +638,77 @@ const getTokenizer = () => {
|
|
|
704
638
|
return tokenizer;
|
|
705
639
|
};
|
|
706
640
|
|
|
641
|
+
class Logger {
|
|
642
|
+
constructor(config) {
|
|
643
|
+
this.config = config;
|
|
644
|
+
this.spinner = null;
|
|
645
|
+
}
|
|
646
|
+
log(message, options = { color: 'blue' }) {
|
|
647
|
+
let outputMessage = message;
|
|
648
|
+
if (options.color) {
|
|
649
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
650
|
+
}
|
|
651
|
+
console.log(outputMessage);
|
|
652
|
+
return this;
|
|
653
|
+
}
|
|
654
|
+
verbose(message, options = {}) {
|
|
655
|
+
if (!this.config?.verbose) {
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
this.log(message, options);
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
startTimer() {
|
|
662
|
+
this.timerStart = now();
|
|
663
|
+
return this;
|
|
664
|
+
}
|
|
665
|
+
stopTimer(message, options = { color: 'yellow' }) {
|
|
666
|
+
if (!this.config?.verbose || !this.timerStart) {
|
|
667
|
+
return this;
|
|
668
|
+
}
|
|
669
|
+
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
670
|
+
let outputMessage = message
|
|
671
|
+
? `${message} (⏲ ${elapsedTime})`
|
|
672
|
+
: `⏲ ${elapsedTime}`;
|
|
673
|
+
if (options.color) {
|
|
674
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
675
|
+
}
|
|
676
|
+
console.log(outputMessage);
|
|
677
|
+
return this;
|
|
678
|
+
}
|
|
679
|
+
startSpinner(message, options = { color: 'green' }) {
|
|
680
|
+
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
681
|
+
this.spinner = ora(spinnerMessage).start();
|
|
682
|
+
return this;
|
|
683
|
+
}
|
|
684
|
+
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
685
|
+
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
686
|
+
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
687
|
+
this.spinner = null;
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
707
692
|
const llm = async ({ llm, prompt, variables }) => {
|
|
693
|
+
if (!llm || !prompt || !variables) {
|
|
694
|
+
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
695
|
+
}
|
|
708
696
|
const chain = new chains.LLMChain({ llm, prompt });
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
697
|
+
let res;
|
|
698
|
+
try {
|
|
699
|
+
res = await chain.call(variables);
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
if (error instanceof Error) {
|
|
703
|
+
throw new Error(`LLMChain call error: ${error.message}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!res) {
|
|
707
|
+
throw new Error('Empty response from LLMChain call');
|
|
708
|
+
}
|
|
709
|
+
if (res.error) {
|
|
710
|
+
throw new Error(`LLMChain response error: ${res.error}`);
|
|
711
|
+
}
|
|
712
712
|
return res.text.trim();
|
|
713
713
|
};
|
|
714
714
|
|
|
@@ -746,6 +746,7 @@ const getSummaryText = (file, change) => {
|
|
|
746
746
|
return `${status}: ${file.path}`;
|
|
747
747
|
};
|
|
748
748
|
|
|
749
|
+
const config = loadConfig();
|
|
749
750
|
const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
|
|
750
751
|
const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
|
|
751
752
|
async function getChanges(git, options = {}) {
|
|
@@ -800,62 +801,117 @@ async function getChanges(git, options = {}) {
|
|
|
800
801
|
|
|
801
802
|
const noResult = async ({ git, logger }) => {
|
|
802
803
|
const { staged, unstaged, untracked } = await getChanges(git);
|
|
803
|
-
|
|
804
|
+
const hasStaged = staged && staged.length > 0;
|
|
805
|
+
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
806
|
+
const hasUntracked = untracked && untracked.length > 0;
|
|
807
|
+
if (hasStaged) {
|
|
804
808
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
805
809
|
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
806
810
|
}
|
|
807
|
-
else if (
|
|
808
|
-
logger.log('No staged
|
|
809
|
-
|
|
810
|
-
color: 'yellow'
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
color: 'yellow'
|
|
817
|
-
|
|
811
|
+
else if (hasUnstaged || hasUntracked) {
|
|
812
|
+
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
813
|
+
if (hasUnstaged) {
|
|
814
|
+
logger.log('\nDetected unstaged changes', { color: 'yellow' });
|
|
815
|
+
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
816
|
+
color: 'red',
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
if (hasUntracked) {
|
|
820
|
+
logger.log('\nDetected untracked changes', { color: 'yellow' });
|
|
821
|
+
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
822
|
+
color: 'red',
|
|
823
|
+
});
|
|
824
|
+
}
|
|
818
825
|
}
|
|
819
826
|
else {
|
|
820
|
-
logger.log('No repo changes detected.', { color: '
|
|
827
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
821
828
|
}
|
|
822
|
-
process.exit(0);
|
|
823
829
|
};
|
|
824
830
|
|
|
825
831
|
async function createCommit(commitMsg, git) {
|
|
826
832
|
return await git.commit(commitMsg);
|
|
827
833
|
}
|
|
828
834
|
|
|
829
|
-
const argv = loadArgv()
|
|
835
|
+
// const argv = loadArgv()
|
|
830
836
|
const tokenizer = getTokenizer();
|
|
831
837
|
const git = simpleGit.simpleGit();
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
838
|
+
const command = ['commit', '$0'];
|
|
839
|
+
const description = 'Generate a commit message based on the diff summary';
|
|
840
|
+
const builder = {
|
|
841
|
+
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
842
|
+
openAIApiKey: {
|
|
843
|
+
type: 'string',
|
|
844
|
+
description: 'OpenAI API Key',
|
|
845
|
+
conflicts: 'huggingFaceHubApiKey',
|
|
846
|
+
},
|
|
847
|
+
huggingFaceHubApiKey: {
|
|
848
|
+
type: 'string',
|
|
849
|
+
description: 'HuggingFace Hub API Key',
|
|
850
|
+
conflicts: 'openAIApiKey',
|
|
851
|
+
},
|
|
852
|
+
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
853
|
+
prompt: {
|
|
854
|
+
type: 'string',
|
|
855
|
+
alias: 'p',
|
|
856
|
+
description: 'Commit message prompt',
|
|
857
|
+
},
|
|
858
|
+
i: {
|
|
859
|
+
type: 'boolean',
|
|
860
|
+
alias: 'interactive',
|
|
861
|
+
description: 'Toggle interactive mode',
|
|
862
|
+
},
|
|
863
|
+
s: {
|
|
864
|
+
type: 'boolean',
|
|
865
|
+
description: 'Automatically commit staged changes with generated commit message',
|
|
866
|
+
default: false,
|
|
867
|
+
},
|
|
868
|
+
e: {
|
|
869
|
+
type: 'boolean',
|
|
870
|
+
alias: 'edit',
|
|
871
|
+
description: 'Open commit message in editor before proceeding',
|
|
872
|
+
},
|
|
873
|
+
summarizePrompt: {
|
|
874
|
+
type: 'string',
|
|
875
|
+
description: 'Large file summary prompt',
|
|
876
|
+
},
|
|
877
|
+
ignoredFiles: {
|
|
878
|
+
type: 'array',
|
|
879
|
+
description: 'Ignored files',
|
|
880
|
+
},
|
|
881
|
+
ignoredExtensions: {
|
|
882
|
+
type: 'array',
|
|
883
|
+
description: 'Ignored extensions',
|
|
884
|
+
},
|
|
885
|
+
};
|
|
886
|
+
async function handler(argv) {
|
|
887
|
+
const options = loadConfig(argv);
|
|
888
|
+
const logger = new Logger(options);
|
|
889
|
+
const key = getModelAPIKey(options.model, options);
|
|
890
|
+
if (!key) {
|
|
835
891
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
836
892
|
process.exit(1);
|
|
837
893
|
}
|
|
838
|
-
const model = getModel({
|
|
894
|
+
const model = getModel(options.model, key, {
|
|
839
895
|
temperature: 0.4,
|
|
840
896
|
maxConcurrency: 10,
|
|
841
|
-
openAIApiKey: config.openAIApiKey,
|
|
842
897
|
});
|
|
843
|
-
const INTERACTIVE =
|
|
898
|
+
const INTERACTIVE = options?.mode === 'interactive' || options.interactive;
|
|
844
899
|
const { staged: changes } = await getChanges(git);
|
|
845
900
|
let summary = '';
|
|
846
901
|
let commitMsg = '';
|
|
847
|
-
let promptTemplate =
|
|
902
|
+
let promptTemplate = options?.prompt || '';
|
|
848
903
|
let modifyPrompt = false;
|
|
849
904
|
while (true) {
|
|
850
905
|
if (changes.length !== 0 && !summary.length) {
|
|
851
906
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
852
907
|
color: 'blue',
|
|
853
908
|
});
|
|
854
|
-
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
909
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model, logger });
|
|
855
910
|
}
|
|
856
911
|
// Handle empty summary
|
|
857
912
|
if (!summary.length) {
|
|
858
|
-
noResult({ git, logger });
|
|
913
|
+
await noResult({ git, logger });
|
|
914
|
+
process.exit(0);
|
|
859
915
|
}
|
|
860
916
|
// Prompt user for commit template prompt, if necessary
|
|
861
917
|
if (modifyPrompt) {
|
|
@@ -931,7 +987,7 @@ async function main(options) {
|
|
|
931
987
|
process.exit(0);
|
|
932
988
|
}
|
|
933
989
|
if (reviewAnswer === 'edit') {
|
|
934
|
-
|
|
990
|
+
options.openInEditor = true;
|
|
935
991
|
}
|
|
936
992
|
if (reviewAnswer === 'retryFull') {
|
|
937
993
|
summary = '';
|
|
@@ -950,7 +1006,7 @@ async function main(options) {
|
|
|
950
1006
|
continue;
|
|
951
1007
|
}
|
|
952
1008
|
}
|
|
953
|
-
if (
|
|
1009
|
+
if (options.openInEditor) {
|
|
954
1010
|
commitMsg = await prompts$1.editor({
|
|
955
1011
|
message: 'Edit the commit message',
|
|
956
1012
|
default: commitMsg,
|
|
@@ -965,7 +1021,7 @@ async function main(options) {
|
|
|
965
1021
|
}
|
|
966
1022
|
const MODE = (options.interactive && 'interactive') ||
|
|
967
1023
|
(options.commit && 'interactive') ||
|
|
968
|
-
|
|
1024
|
+
options?.mode ||
|
|
969
1025
|
'stdout';
|
|
970
1026
|
// Handle resulting commit message
|
|
971
1027
|
switch (MODE) {
|
|
@@ -981,4 +1037,27 @@ async function main(options) {
|
|
|
981
1037
|
process.exit(0);
|
|
982
1038
|
}
|
|
983
1039
|
}
|
|
984
|
-
|
|
1040
|
+
|
|
1041
|
+
var commit = /*#__PURE__*/Object.freeze({
|
|
1042
|
+
__proto__: null,
|
|
1043
|
+
builder: builder,
|
|
1044
|
+
command: command,
|
|
1045
|
+
description: description,
|
|
1046
|
+
handler: handler
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
yargs
|
|
1050
|
+
.scriptName('coco')
|
|
1051
|
+
.commandDir('./commands', {
|
|
1052
|
+
extensions: ['ts'],
|
|
1053
|
+
})
|
|
1054
|
+
.demandCommand()
|
|
1055
|
+
.strict()
|
|
1056
|
+
.option('h', { alias: 'help' })
|
|
1057
|
+
.option('v', {
|
|
1058
|
+
alias: 'verbose',
|
|
1059
|
+
type: 'boolean',
|
|
1060
|
+
description: 'Run with verbose logging',
|
|
1061
|
+
}).argv;
|
|
1062
|
+
|
|
1063
|
+
exports.commit = commit;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Config } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Default Config
|
|
4
|
+
*
|
|
5
|
+
* @type {Config}
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
2
8
|
/**
|
|
3
9
|
* Load application config
|
|
4
10
|
*
|
|
@@ -16,6 +22,4 @@ import { Config } from './types';
|
|
|
16
22
|
*
|
|
17
23
|
* @returns {Config} application config
|
|
18
24
|
**/
|
|
19
|
-
export declare function loadConfig(): Config;
|
|
20
|
-
declare const config: Config;
|
|
21
|
-
export default config;
|
|
25
|
+
export declare function loadConfig(argv?: {}): Config;
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
export interface Config {
|
|
2
|
+
/**
|
|
3
|
+
* The LLM model to use for generating results.
|
|
4
|
+
*
|
|
5
|
+
* @default 'openai/gpt-3.5-turbo'
|
|
6
|
+
*
|
|
7
|
+
* @example 'openai/gpt-4'
|
|
8
|
+
* @example 'openai/gpt-3.5-turbo'
|
|
9
|
+
* @example 'huggingface/bigscience/bloom'
|
|
10
|
+
**/
|
|
11
|
+
model: string;
|
|
2
12
|
/**
|
|
3
13
|
* The OpenAI API key.
|
|
4
14
|
*/
|
|
5
|
-
openAIApiKey
|
|
15
|
+
openAIApiKey?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The HuggingFace Hub API key.
|
|
18
|
+
*/
|
|
19
|
+
huggingFaceHubApiKey?: string;
|
|
6
20
|
/**
|
|
7
21
|
* The maximum number of tokens per request.
|
|
8
22
|
*
|