git-coco 0.3.0 → 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/dist/commands/commit.d.ts +16 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.mjs +212 -170
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +212 -170
- package/dist/lib/config/{index.d.ts → loadConfig.d.ts} +1 -3
- package/dist/lib/langchain/utils.d.ts +26 -2
- 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/services/yargs.d.ts +0 -25
package/dist/index.js
CHANGED
|
@@ -1,28 +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');
|
|
18
14
|
var hf = require('langchain/llms/hf');
|
|
19
15
|
var chains = require('langchain/chains');
|
|
20
16
|
var openai = require('langchain/llms/openai');
|
|
21
17
|
var text_splitter = require('langchain/text_splitter');
|
|
22
18
|
var diff = require('diff');
|
|
19
|
+
var chalk = require('chalk');
|
|
23
20
|
var GPT3NodeTokenizer = require('gpt3-tokenizer');
|
|
21
|
+
var ora = require('ora');
|
|
22
|
+
var now = require('performance-now');
|
|
23
|
+
var prettyMilliseconds = require('pretty-ms');
|
|
24
24
|
var minimatch = require('minimatch');
|
|
25
|
-
var simpleGit = require('simple-git');
|
|
26
25
|
|
|
27
26
|
function _interopNamespaceDefault(e) {
|
|
28
27
|
var n = Object.create(null);
|
|
@@ -175,74 +174,6 @@ function loadXDGConfig(config) {
|
|
|
175
174
|
return config;
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
/**
|
|
179
|
-
* Command line options via yargs
|
|
180
|
-
*/
|
|
181
|
-
const options = {
|
|
182
|
-
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
183
|
-
openAIApiKey: { type: 'string', description: 'OpenAI API Key' },
|
|
184
|
-
huggingFaceHubApiKey: { type: 'string', description: 'HuggingFace Hub API Key' },
|
|
185
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
186
|
-
prompt: {
|
|
187
|
-
type: 'string',
|
|
188
|
-
alias: 'p',
|
|
189
|
-
description: 'Commit message prompt',
|
|
190
|
-
},
|
|
191
|
-
interactive: {
|
|
192
|
-
type: 'boolean',
|
|
193
|
-
alias: 'i',
|
|
194
|
-
description: 'Toggle interactive mode',
|
|
195
|
-
},
|
|
196
|
-
commit: {
|
|
197
|
-
type: 'boolean',
|
|
198
|
-
alias: 's',
|
|
199
|
-
description: 'Commit staged changes with generated commit message',
|
|
200
|
-
default: false,
|
|
201
|
-
},
|
|
202
|
-
openInEditor: {
|
|
203
|
-
type: 'boolean',
|
|
204
|
-
alias: 'e',
|
|
205
|
-
description: 'Open commit message in editor before proceeding',
|
|
206
|
-
},
|
|
207
|
-
verbose: {
|
|
208
|
-
type: 'boolean',
|
|
209
|
-
description: 'Enable verbose logging',
|
|
210
|
-
},
|
|
211
|
-
summarizePrompt: {
|
|
212
|
-
type: 'string',
|
|
213
|
-
description: 'Large file summary prompt',
|
|
214
|
-
},
|
|
215
|
-
ignoredFiles: {
|
|
216
|
-
type: 'array',
|
|
217
|
-
description: 'Ignored files',
|
|
218
|
-
},
|
|
219
|
-
ignoredExtensions: {
|
|
220
|
-
type: 'array',
|
|
221
|
-
description: 'Ignored extensions',
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
/**
|
|
225
|
-
* Load command line flags via yargs
|
|
226
|
-
*
|
|
227
|
-
* @returns {Partial<Config>} Updated config
|
|
228
|
-
*/
|
|
229
|
-
const loadArgv = () => {
|
|
230
|
-
return yargs(helpers.hideBin(process.argv)).options(options).parseSync();
|
|
231
|
-
};
|
|
232
|
-
/**
|
|
233
|
-
* Load command line flags
|
|
234
|
-
*
|
|
235
|
-
* Note: Arugments are parsed using yargs.
|
|
236
|
-
*
|
|
237
|
-
* @param {Config} config
|
|
238
|
-
* @returns {Config} Updated config
|
|
239
|
-
**/
|
|
240
|
-
function loadCmdLineFlags(config) {
|
|
241
|
-
const argv = loadArgv();
|
|
242
|
-
config = { ...config, ...argv };
|
|
243
|
-
return config;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
177
|
const template$1 = `Write informative git commit message based on the diffs & file changes provided in the "Diff Summary" section.
|
|
247
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.
|
|
248
179
|
- Write concisely using an informal tone
|
|
@@ -305,7 +236,7 @@ const DEFAULT_CONFIG = {
|
|
|
305
236
|
*
|
|
306
237
|
* @returns {Config} application config
|
|
307
238
|
**/
|
|
308
|
-
function loadConfig() {
|
|
239
|
+
function loadConfig(argv = {}) {
|
|
309
240
|
// Default config
|
|
310
241
|
let config = DEFAULT_CONFIG;
|
|
311
242
|
config = loadGitignore(config);
|
|
@@ -314,60 +245,7 @@ function loadConfig() {
|
|
|
314
245
|
config = loadGitConfig(config);
|
|
315
246
|
config = loadProjectConfig(config);
|
|
316
247
|
config = loadEnvConfig(config);
|
|
317
|
-
config
|
|
318
|
-
return config;
|
|
319
|
-
}
|
|
320
|
-
const config = loadConfig();
|
|
321
|
-
|
|
322
|
-
class Logger {
|
|
323
|
-
constructor(config) {
|
|
324
|
-
this.config = config;
|
|
325
|
-
this.spinner = null;
|
|
326
|
-
}
|
|
327
|
-
log(message, options = { color: 'blue' }) {
|
|
328
|
-
let outputMessage = message;
|
|
329
|
-
if (options.color) {
|
|
330
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
331
|
-
}
|
|
332
|
-
console.log(outputMessage);
|
|
333
|
-
return this;
|
|
334
|
-
}
|
|
335
|
-
verbose(message, options = {}) {
|
|
336
|
-
if (!this.config?.verbose) {
|
|
337
|
-
return this;
|
|
338
|
-
}
|
|
339
|
-
this.log(message, options);
|
|
340
|
-
return this;
|
|
341
|
-
}
|
|
342
|
-
startTimer() {
|
|
343
|
-
this.timerStart = now();
|
|
344
|
-
return this;
|
|
345
|
-
}
|
|
346
|
-
stopTimer(message, options = { color: 'yellow' }) {
|
|
347
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
348
|
-
return this;
|
|
349
|
-
}
|
|
350
|
-
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
351
|
-
let outputMessage = message
|
|
352
|
-
? `${message} (⏲ ${elapsedTime})`
|
|
353
|
-
: `⏲ ${elapsedTime}`;
|
|
354
|
-
if (options.color) {
|
|
355
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
356
|
-
}
|
|
357
|
-
console.log(outputMessage);
|
|
358
|
-
return this;
|
|
359
|
-
}
|
|
360
|
-
startSpinner(message, options = { color: 'green' }) {
|
|
361
|
-
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
362
|
-
this.spinner = ora(spinnerMessage).start();
|
|
363
|
-
return this;
|
|
364
|
-
}
|
|
365
|
-
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
366
|
-
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
367
|
-
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
368
|
-
this.spinner = null;
|
|
369
|
-
return this;
|
|
370
|
-
}
|
|
248
|
+
return { ...config, ...argv };
|
|
371
249
|
}
|
|
372
250
|
|
|
373
251
|
/**
|
|
@@ -454,8 +332,7 @@ const defaultOutputCallback = (group) => {
|
|
|
454
332
|
}
|
|
455
333
|
return output;
|
|
456
334
|
};
|
|
457
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
458
|
-
const logger = new Logger(config);
|
|
335
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
459
336
|
const queue = new pQueue({ concurrency: 8 });
|
|
460
337
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
461
338
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
@@ -541,7 +418,7 @@ const createDiffTree = (changes) => {
|
|
|
541
418
|
/**
|
|
542
419
|
* Asynchronously collect diffs for a given node and its children.
|
|
543
420
|
*/
|
|
544
|
-
async function collectDiffs(node, getFileDiff, tokenizer, logger
|
|
421
|
+
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
545
422
|
// Collect diffs for the files of the current node
|
|
546
423
|
const diffPromises = node.files.map(async (nodeFile) => {
|
|
547
424
|
const diff = await getFileDiff(nodeFile);
|
|
@@ -559,7 +436,7 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
559
436
|
};
|
|
560
437
|
});
|
|
561
438
|
// Collect diffs for the children of the current node
|
|
562
|
-
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));
|
|
563
440
|
const [diffs, children] = await Promise.all([
|
|
564
441
|
Promise.all(diffPromises),
|
|
565
442
|
Promise.all(childrenPromises),
|
|
@@ -573,36 +450,65 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
573
450
|
|
|
574
451
|
/**
|
|
575
452
|
* Get LLM Model Based on Configuration
|
|
576
|
-
*
|
|
577
453
|
* @param fields
|
|
578
454
|
* @param configuration
|
|
579
455
|
* @returns LLM Model
|
|
580
456
|
*/
|
|
581
|
-
function getModel(fields, configuration) {
|
|
582
|
-
const [llm, model] =
|
|
457
|
+
function getModel(name, key, fields, configuration) {
|
|
458
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
583
459
|
if (!model) {
|
|
584
|
-
throw new Error(`Invalid model: ${
|
|
460
|
+
throw new Error(`Invalid model: ${name}`);
|
|
585
461
|
}
|
|
586
462
|
switch (llm) {
|
|
587
463
|
case 'huggingface':
|
|
588
464
|
return new hf.HuggingFaceInference({
|
|
589
465
|
model: model,
|
|
590
|
-
apiKey:
|
|
466
|
+
apiKey: key,
|
|
591
467
|
maxConcurrency: 4,
|
|
592
468
|
...fields,
|
|
593
469
|
});
|
|
594
470
|
case 'openai':
|
|
595
471
|
default:
|
|
596
472
|
return new openai.OpenAI({
|
|
597
|
-
openAIApiKey:
|
|
473
|
+
openAIApiKey: key,
|
|
598
474
|
modelName: model,
|
|
599
475
|
...fields,
|
|
600
476
|
}, configuration);
|
|
601
477
|
}
|
|
602
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
|
+
*/
|
|
603
503
|
function getTextSplitter(options = {}) {
|
|
604
504
|
return new text_splitter.RecursiveCharacterTextSplitter(options);
|
|
605
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Get Summarization Chain
|
|
508
|
+
* @param model
|
|
509
|
+
* @param options
|
|
510
|
+
* @returns
|
|
511
|
+
*/
|
|
606
512
|
function getChain(model, options = { type: 'map_reduce' }) {
|
|
607
513
|
return chains.loadSummarizationChain(model, options);
|
|
608
514
|
}
|
|
@@ -616,6 +522,12 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
616
522
|
})
|
|
617
523
|
: fallback);
|
|
618
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
|
+
*/
|
|
619
531
|
function validatePromptTemplate(text, inputVariables) {
|
|
620
532
|
if (!text) {
|
|
621
533
|
return 'Prompt template cannot be empty';
|
|
@@ -669,8 +581,7 @@ const getDiff = async (nodeFile, { git, logger, }) => {
|
|
|
669
581
|
};
|
|
670
582
|
|
|
671
583
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
672
|
-
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
673
|
-
const logger = new Logger(config);
|
|
584
|
+
const fileChangeParser = async (changes, { tokenizer, git, model, logger }) => {
|
|
674
585
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
675
586
|
const summarizationChain = getChain(model, {
|
|
676
587
|
type: 'map_reduce',
|
|
@@ -691,6 +602,7 @@ const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
|
691
602
|
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
692
603
|
textSplitter,
|
|
693
604
|
chain: summarizationChain,
|
|
605
|
+
logger
|
|
694
606
|
});
|
|
695
607
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
696
608
|
return summary;
|
|
@@ -726,6 +638,57 @@ const getTokenizer = () => {
|
|
|
726
638
|
return tokenizer;
|
|
727
639
|
};
|
|
728
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
|
+
|
|
729
692
|
const llm = async ({ llm, prompt, variables }) => {
|
|
730
693
|
if (!llm || !prompt || !variables) {
|
|
731
694
|
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
@@ -783,6 +746,7 @@ const getSummaryText = (file, change) => {
|
|
|
783
746
|
return `${status}: ${file.path}`;
|
|
784
747
|
};
|
|
785
748
|
|
|
749
|
+
const config = loadConfig();
|
|
786
750
|
const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
|
|
787
751
|
const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
|
|
788
752
|
async function getChanges(git, options = {}) {
|
|
@@ -837,62 +801,117 @@ async function getChanges(git, options = {}) {
|
|
|
837
801
|
|
|
838
802
|
const noResult = async ({ git, logger }) => {
|
|
839
803
|
const { staged, unstaged, untracked } = await getChanges(git);
|
|
840
|
-
|
|
804
|
+
const hasStaged = staged && staged.length > 0;
|
|
805
|
+
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
806
|
+
const hasUntracked = untracked && untracked.length > 0;
|
|
807
|
+
if (hasStaged) {
|
|
841
808
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
842
809
|
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
843
810
|
}
|
|
844
|
-
else if (
|
|
845
|
-
logger.log('No staged
|
|
846
|
-
|
|
847
|
-
color: 'yellow'
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
color: 'yellow'
|
|
854
|
-
|
|
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
|
+
}
|
|
855
825
|
}
|
|
856
826
|
else {
|
|
857
|
-
logger.log('No repo changes detected.', { color: '
|
|
827
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
858
828
|
}
|
|
859
|
-
process.exit(0);
|
|
860
829
|
};
|
|
861
830
|
|
|
862
831
|
async function createCommit(commitMsg, git) {
|
|
863
832
|
return await git.commit(commitMsg);
|
|
864
833
|
}
|
|
865
834
|
|
|
866
|
-
const argv = loadArgv()
|
|
835
|
+
// const argv = loadArgv()
|
|
867
836
|
const tokenizer = getTokenizer();
|
|
868
837
|
const git = simpleGit.simpleGit();
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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) {
|
|
872
891
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
873
892
|
process.exit(1);
|
|
874
893
|
}
|
|
875
|
-
const model = getModel({
|
|
894
|
+
const model = getModel(options.model, key, {
|
|
876
895
|
temperature: 0.4,
|
|
877
896
|
maxConcurrency: 10,
|
|
878
|
-
openAIApiKey: config.openAIApiKey,
|
|
879
897
|
});
|
|
880
|
-
const INTERACTIVE =
|
|
898
|
+
const INTERACTIVE = options?.mode === 'interactive' || options.interactive;
|
|
881
899
|
const { staged: changes } = await getChanges(git);
|
|
882
900
|
let summary = '';
|
|
883
901
|
let commitMsg = '';
|
|
884
|
-
let promptTemplate =
|
|
902
|
+
let promptTemplate = options?.prompt || '';
|
|
885
903
|
let modifyPrompt = false;
|
|
886
904
|
while (true) {
|
|
887
905
|
if (changes.length !== 0 && !summary.length) {
|
|
888
906
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
889
907
|
color: 'blue',
|
|
890
908
|
});
|
|
891
|
-
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
909
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model, logger });
|
|
892
910
|
}
|
|
893
911
|
// Handle empty summary
|
|
894
912
|
if (!summary.length) {
|
|
895
|
-
noResult({ git, logger });
|
|
913
|
+
await noResult({ git, logger });
|
|
914
|
+
process.exit(0);
|
|
896
915
|
}
|
|
897
916
|
// Prompt user for commit template prompt, if necessary
|
|
898
917
|
if (modifyPrompt) {
|
|
@@ -968,7 +987,7 @@ async function main(options) {
|
|
|
968
987
|
process.exit(0);
|
|
969
988
|
}
|
|
970
989
|
if (reviewAnswer === 'edit') {
|
|
971
|
-
|
|
990
|
+
options.openInEditor = true;
|
|
972
991
|
}
|
|
973
992
|
if (reviewAnswer === 'retryFull') {
|
|
974
993
|
summary = '';
|
|
@@ -987,7 +1006,7 @@ async function main(options) {
|
|
|
987
1006
|
continue;
|
|
988
1007
|
}
|
|
989
1008
|
}
|
|
990
|
-
if (
|
|
1009
|
+
if (options.openInEditor) {
|
|
991
1010
|
commitMsg = await prompts$1.editor({
|
|
992
1011
|
message: 'Edit the commit message',
|
|
993
1012
|
default: commitMsg,
|
|
@@ -1002,7 +1021,7 @@ async function main(options) {
|
|
|
1002
1021
|
}
|
|
1003
1022
|
const MODE = (options.interactive && 'interactive') ||
|
|
1004
1023
|
(options.commit && 'interactive') ||
|
|
1005
|
-
|
|
1024
|
+
options?.mode ||
|
|
1006
1025
|
'stdout';
|
|
1007
1026
|
// Handle resulting commit message
|
|
1008
1027
|
switch (MODE) {
|
|
@@ -1018,4 +1037,27 @@ async function main(options) {
|
|
|
1018
1037
|
process.exit(0);
|
|
1019
1038
|
}
|
|
1020
1039
|
}
|
|
1021
|
-
|
|
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;
|
|
@@ -5,17 +5,35 @@ import { BaseLLMParams } from 'langchain/llms/base';
|
|
|
5
5
|
import { AzureOpenAIInput, OpenAIInput, OpenAI } from 'langchain/llms/openai';
|
|
6
6
|
import { RecursiveCharacterTextSplitter, RecursiveCharacterTextSplitterParams } from 'langchain/text_splitter';
|
|
7
7
|
import { ConfigurationParameters } from 'openai';
|
|
8
|
+
import { BaseCommandOptions } from '../../types';
|
|
8
9
|
/**
|
|
9
10
|
* Get LLM Model Based on Configuration
|
|
10
|
-
*
|
|
11
11
|
* @param fields
|
|
12
12
|
* @param configuration
|
|
13
13
|
* @returns LLM Model
|
|
14
14
|
*/
|
|
15
|
-
export declare function getModel(fields?: (Partial<OpenAIInput> & Partial<AzureOpenAIInput> & BaseLLMParams & {
|
|
15
|
+
export declare function getModel(name: string, key: string, fields?: (Partial<OpenAIInput> & Partial<AzureOpenAIInput> & BaseLLMParams & {
|
|
16
16
|
configuration?: ConfigurationParameters | undefined;
|
|
17
17
|
}) | undefined, configuration?: ConfigurationParameters | undefined): OpenAI | HuggingFaceInference;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve appropriate API key based on selected model
|
|
20
|
+
* @param name
|
|
21
|
+
* @param options
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
export declare function getModelAPIKey(name: string, options: BaseCommandOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* Get Recursive Character Text Splitter
|
|
27
|
+
* @param options
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
18
30
|
export declare function getTextSplitter(options?: Partial<RecursiveCharacterTextSplitterParams>): RecursiveCharacterTextSplitter;
|
|
31
|
+
/**
|
|
32
|
+
* Get Summarization Chain
|
|
33
|
+
* @param model
|
|
34
|
+
* @param options
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
19
37
|
export declare function getChain(model: ReturnType<typeof getModel>, options?: SummarizationChainParams): import("langchain/chains").StuffDocumentsChain | import("langchain/chains").MapReduceDocumentsChain | import("langchain/chains").RefineDocumentsChain;
|
|
20
38
|
type CreatePromptInput = {
|
|
21
39
|
template?: string;
|
|
@@ -23,5 +41,11 @@ type CreatePromptInput = {
|
|
|
23
41
|
fallback?: PromptTemplate;
|
|
24
42
|
};
|
|
25
43
|
export declare function getPrompt({ template, variables, fallback }: CreatePromptInput): PromptTemplate;
|
|
44
|
+
/**
|
|
45
|
+
* Verify template string contains all required input variables
|
|
46
|
+
* @param text template string
|
|
47
|
+
* @param inputVariables template variables
|
|
48
|
+
* @returns boolean or error message
|
|
49
|
+
*/
|
|
26
50
|
export declare function validatePromptTemplate(text: string, inputVariables: string[]): string | true;
|
|
27
51
|
export {};
|
|
@@ -5,4 +5,4 @@ import { DiffTreeNode } from './createDiffTree';
|
|
|
5
5
|
/**
|
|
6
6
|
* Asynchronously collect diffs for a given node and its children.
|
|
7
7
|
*/
|
|
8
|
-
export declare function collectDiffs(node: DiffTreeNode, getFileDiff: (change: FileChange) => Promise<string>, tokenizer: GPT3Tokenizer, logger
|
|
8
|
+
export declare function collectDiffs(node: DiffTreeNode, getFileDiff: (change: FileChange) => Promise<string>, tokenizer: GPT3Tokenizer, logger: Logger): Promise<DiffNode>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import GPT3Tokenizer from 'gpt3-tokenizer';
|
|
2
2
|
import { DirectoryDiff, DiffNode } from '../../../types';
|
|
3
|
+
import { Logger } from '../../../utils/logger';
|
|
3
4
|
import { SummarizeContext } from '../../../langchain/chains/summarize';
|
|
4
5
|
/**
|
|
5
6
|
* Create groups from a given node info.
|
|
@@ -17,8 +18,9 @@ export declare function summarizeDirectoryDiff(directory: DirectoryDiff, { chain
|
|
|
17
18
|
declare const defaultOutputCallback: (group: DirectoryDiff) => string;
|
|
18
19
|
type SummarizeDiffsOptions = {
|
|
19
20
|
tokenizer: GPT3Tokenizer;
|
|
21
|
+
logger: Logger;
|
|
20
22
|
maxTokens: number;
|
|
21
23
|
handleOutput?: typeof defaultOutputCallback;
|
|
22
24
|
} & SummarizeContext;
|
|
23
|
-
export declare function summarizeDiffs(rootDiffNode: DiffNode, { tokenizer, maxTokens, textSplitter, chain, handleOutput, }: SummarizeDiffsOptions): Promise<string>;
|
|
25
|
+
export declare function summarizeDiffs(rootDiffNode: DiffNode, { tokenizer, logger, maxTokens, textSplitter, chain, handleOutput, }: SummarizeDiffsOptions): Promise<string>;
|
|
24
26
|
export {};
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import GPT3Tokenizer from 'gpt3-tokenizer';
|
|
2
2
|
import { getModel } from './langchain/utils';
|
|
3
3
|
import { SimpleGit } from 'simple-git';
|
|
4
|
+
import { Logger } from './utils/logger';
|
|
4
5
|
export type FileChangeStatus = 'modified' | 'renamed' | 'added' | 'deleted' | 'untracked' | 'unknown';
|
|
5
6
|
export interface FileChange {
|
|
6
7
|
summary: string;
|
|
@@ -30,5 +31,6 @@ export interface BaseParser {
|
|
|
30
31
|
tokenizer: GPT3Tokenizer;
|
|
31
32
|
model: ReturnType<typeof getModel>;
|
|
32
33
|
git: SimpleGit;
|
|
34
|
+
logger: Logger;
|
|
33
35
|
}): Promise<string>;
|
|
34
36
|
}
|