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/README.md
CHANGED
|
@@ -113,7 +113,7 @@ Remember, command line flags and environment variables should be defined in `UPP
|
|
|
113
113
|
- [x] LangChain integration ๐ฆ
|
|
114
114
|
- [ ] Additional tests! ๐งช
|
|
115
115
|
- [ ] Conventional commits ๐
|
|
116
|
-
- [
|
|
116
|
+
- [x] HuggingFace integration ๐
|
|
117
117
|
- [ ] Google Vertex AI integration (?)
|
|
118
118
|
- [ ] Automatic changelog generation ๐ซฃ
|
|
119
119
|
- [ ] Rebase support ๐
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Argv, CommandBuilder } from 'yargs';
|
|
2
|
+
import { BaseCommandOptions } from '../types';
|
|
3
|
+
export interface CommitOptions extends BaseCommandOptions {
|
|
4
|
+
interactive: boolean;
|
|
5
|
+
tokenLimit: number;
|
|
6
|
+
prompt: string;
|
|
7
|
+
commit: boolean;
|
|
8
|
+
summarizePrompt: string;
|
|
9
|
+
openInEditor: boolean;
|
|
10
|
+
ignoredFiles: string[];
|
|
11
|
+
ignoredExtensions: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const command: string[];
|
|
14
|
+
export declare const description = "Generate a commit message based on the diff summary";
|
|
15
|
+
export declare const builder: CommandBuilder<CommitOptions>;
|
|
16
|
+
export declare function handler(argv: Argv<CommitOptions>["argv"]): Promise<void>;
|
package/dist/index.d.ts
CHANGED
package/dist/index.esm.mjs
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import yargs from 'yargs';
|
|
2
3
|
import { select, editor } from '@inquirer/prompts';
|
|
4
|
+
import { simpleGit } from 'simple-git';
|
|
3
5
|
import * as fs from 'fs';
|
|
4
6
|
import * as os from 'os';
|
|
5
7
|
import * as path from 'path';
|
|
6
8
|
import path__default from 'path';
|
|
7
9
|
import * as ini from 'ini';
|
|
8
|
-
import yargs from 'yargs';
|
|
9
|
-
import { hideBin } from 'yargs/helpers';
|
|
10
10
|
import { PromptTemplate } from 'langchain/prompts';
|
|
11
11
|
import pQueue from 'p-queue';
|
|
12
|
-
import chalk from 'chalk';
|
|
13
|
-
import ora from 'ora';
|
|
14
|
-
import now from 'performance-now';
|
|
15
|
-
import prettyMilliseconds from 'pretty-ms';
|
|
16
12
|
import { Document } from 'langchain/document';
|
|
13
|
+
import { HuggingFaceInference } from 'langchain/llms/hf';
|
|
17
14
|
import { loadSummarizationChain, LLMChain } from 'langchain/chains';
|
|
18
15
|
import { OpenAI } from 'langchain/llms/openai';
|
|
19
16
|
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
|
|
20
17
|
import { createTwoFilesPatch } from 'diff';
|
|
18
|
+
import chalk from 'chalk';
|
|
21
19
|
import GPT3NodeTokenizer from 'gpt3-tokenizer';
|
|
20
|
+
import ora from 'ora';
|
|
21
|
+
import now from 'performance-now';
|
|
22
|
+
import prettyMilliseconds from 'pretty-ms';
|
|
22
23
|
import { minimatch } from 'minimatch';
|
|
23
|
-
import { simpleGit } from 'simple-git';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Returns a new object with all undefined keys removed
|
|
@@ -40,7 +40,9 @@ function removeUndefined(obj) {
|
|
|
40
40
|
**/
|
|
41
41
|
function loadEnvConfig(config) {
|
|
42
42
|
const envConfig = {
|
|
43
|
+
model: process.env.COCO_MODEL || undefined,
|
|
43
44
|
openAIApiKey: process.env.OPENAI_API_KEY || undefined,
|
|
45
|
+
huggingFaceHubApiKey: process.env.HUGGINGFACE_HUB_API_KEY || undefined,
|
|
44
46
|
tokenLimit: process.env.COCO_TOKEN_LIMIT
|
|
45
47
|
? parseInt(process.env.COCO_TOKEN_LIMIT)
|
|
46
48
|
: undefined,
|
|
@@ -71,7 +73,9 @@ function loadGitConfig(config) {
|
|
|
71
73
|
const gitConfigParsed = ini.parse(gitConfigRaw);
|
|
72
74
|
config = {
|
|
73
75
|
...config,
|
|
76
|
+
model: gitConfigParsed.coco?.model || config.model,
|
|
74
77
|
openAIApiKey: gitConfigParsed.coco?.openAIApiKey || config.openAIApiKey,
|
|
78
|
+
huggingFaceHubApiKey: gitConfigParsed.coco?.huggingFaceHubApiKey || config.huggingFaceHubApiKey,
|
|
75
79
|
tokenLimit: parseInt(gitConfigParsed.coco?.tokenLimit) || config.tokenLimit,
|
|
76
80
|
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
77
81
|
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
@@ -147,72 +151,6 @@ function loadXDGConfig(config) {
|
|
|
147
151
|
return config;
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
/**
|
|
151
|
-
* Command line options via yargs
|
|
152
|
-
*/
|
|
153
|
-
const options = {
|
|
154
|
-
openAIApiKey: { type: 'string', description: 'OpenAI API Key' },
|
|
155
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
156
|
-
prompt: {
|
|
157
|
-
type: 'string',
|
|
158
|
-
alias: 'p',
|
|
159
|
-
description: 'Commit message prompt',
|
|
160
|
-
},
|
|
161
|
-
interactive: {
|
|
162
|
-
type: 'boolean',
|
|
163
|
-
alias: 'i',
|
|
164
|
-
description: 'Toggle interactive mode',
|
|
165
|
-
},
|
|
166
|
-
commit: {
|
|
167
|
-
type: 'boolean',
|
|
168
|
-
alias: 's',
|
|
169
|
-
description: 'Commit staged changes with generated commit message',
|
|
170
|
-
default: false,
|
|
171
|
-
},
|
|
172
|
-
openInEditor: {
|
|
173
|
-
type: 'boolean',
|
|
174
|
-
alias: 'e',
|
|
175
|
-
description: 'Open commit message in editor before proceeding',
|
|
176
|
-
},
|
|
177
|
-
verbose: {
|
|
178
|
-
type: 'boolean',
|
|
179
|
-
description: 'Enable verbose logging',
|
|
180
|
-
},
|
|
181
|
-
summarizePrompt: {
|
|
182
|
-
type: 'string',
|
|
183
|
-
description: 'Large file summary prompt',
|
|
184
|
-
},
|
|
185
|
-
ignoredFiles: {
|
|
186
|
-
type: 'array',
|
|
187
|
-
description: 'Ignored files',
|
|
188
|
-
},
|
|
189
|
-
ignoredExtensions: {
|
|
190
|
-
type: 'array',
|
|
191
|
-
description: 'Ignored extensions',
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
/**
|
|
195
|
-
* Load command line flags via yargs
|
|
196
|
-
*
|
|
197
|
-
* @returns {Partial<Config>} Updated config
|
|
198
|
-
*/
|
|
199
|
-
const loadArgv = () => {
|
|
200
|
-
return yargs(hideBin(process.argv)).options(options).parseSync();
|
|
201
|
-
};
|
|
202
|
-
/**
|
|
203
|
-
* Load command line flags
|
|
204
|
-
*
|
|
205
|
-
* Note: Arugments are parsed using yargs.
|
|
206
|
-
*
|
|
207
|
-
* @param {Config} config
|
|
208
|
-
* @returns {Config} Updated config
|
|
209
|
-
**/
|
|
210
|
-
function loadCmdLineFlags(config) {
|
|
211
|
-
const argv = loadArgv();
|
|
212
|
-
config = { ...config, ...argv };
|
|
213
|
-
return config;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
154
|
const template$1 = `Write informative git commit message based on the diffs & file changes provided in the "Diff Summary" section.
|
|
217
155
|
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.
|
|
218
156
|
- Write concisely using an informal tone
|
|
@@ -248,7 +186,7 @@ const SUMMARIZE_PROMPT = new PromptTemplate({
|
|
|
248
186
|
* @type {Config}
|
|
249
187
|
*/
|
|
250
188
|
const DEFAULT_CONFIG = {
|
|
251
|
-
|
|
189
|
+
model: 'openai/gpt-3.5-turbo',
|
|
252
190
|
verbose: false,
|
|
253
191
|
tokenLimit: 1024,
|
|
254
192
|
prompt: COMMIT_PROMPT.template,
|
|
@@ -258,7 +196,6 @@ const DEFAULT_CONFIG = {
|
|
|
258
196
|
ignoredFiles: ['package-lock.json'],
|
|
259
197
|
ignoredExtensions: ['.map', '.lock'],
|
|
260
198
|
};
|
|
261
|
-
|
|
262
199
|
/**
|
|
263
200
|
* Load application config
|
|
264
201
|
*
|
|
@@ -276,7 +213,7 @@ const DEFAULT_CONFIG = {
|
|
|
276
213
|
*
|
|
277
214
|
* @returns {Config} application config
|
|
278
215
|
**/
|
|
279
|
-
function loadConfig() {
|
|
216
|
+
function loadConfig(argv = {}) {
|
|
280
217
|
// Default config
|
|
281
218
|
let config = DEFAULT_CONFIG;
|
|
282
219
|
config = loadGitignore(config);
|
|
@@ -285,60 +222,7 @@ function loadConfig() {
|
|
|
285
222
|
config = loadGitConfig(config);
|
|
286
223
|
config = loadProjectConfig(config);
|
|
287
224
|
config = loadEnvConfig(config);
|
|
288
|
-
config
|
|
289
|
-
return config;
|
|
290
|
-
}
|
|
291
|
-
const config = loadConfig();
|
|
292
|
-
|
|
293
|
-
class Logger {
|
|
294
|
-
constructor(config) {
|
|
295
|
-
this.config = config;
|
|
296
|
-
this.spinner = null;
|
|
297
|
-
}
|
|
298
|
-
log(message, options = { color: 'blue' }) {
|
|
299
|
-
let outputMessage = message;
|
|
300
|
-
if (options.color) {
|
|
301
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
302
|
-
}
|
|
303
|
-
console.log(outputMessage);
|
|
304
|
-
return this;
|
|
305
|
-
}
|
|
306
|
-
verbose(message, options = {}) {
|
|
307
|
-
if (!this.config?.verbose) {
|
|
308
|
-
return this;
|
|
309
|
-
}
|
|
310
|
-
this.log(message, options);
|
|
311
|
-
return this;
|
|
312
|
-
}
|
|
313
|
-
startTimer() {
|
|
314
|
-
this.timerStart = now();
|
|
315
|
-
return this;
|
|
316
|
-
}
|
|
317
|
-
stopTimer(message, options = { color: 'yellow' }) {
|
|
318
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
319
|
-
return this;
|
|
320
|
-
}
|
|
321
|
-
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
322
|
-
let outputMessage = message
|
|
323
|
-
? `${message} (โฒ ${elapsedTime})`
|
|
324
|
-
: `โฒ ${elapsedTime}`;
|
|
325
|
-
if (options.color) {
|
|
326
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
327
|
-
}
|
|
328
|
-
console.log(outputMessage);
|
|
329
|
-
return this;
|
|
330
|
-
}
|
|
331
|
-
startSpinner(message, options = { color: 'green' }) {
|
|
332
|
-
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
333
|
-
this.spinner = ora(spinnerMessage).start();
|
|
334
|
-
return this;
|
|
335
|
-
}
|
|
336
|
-
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
337
|
-
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
338
|
-
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
339
|
-
this.spinner = null;
|
|
340
|
-
return this;
|
|
341
|
-
}
|
|
225
|
+
return { ...config, ...argv };
|
|
342
226
|
}
|
|
343
227
|
|
|
344
228
|
/**
|
|
@@ -425,8 +309,7 @@ const defaultOutputCallback = (group) => {
|
|
|
425
309
|
}
|
|
426
310
|
return output;
|
|
427
311
|
};
|
|
428
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
429
|
-
const logger = new Logger(config);
|
|
312
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
430
313
|
const queue = new pQueue({ concurrency: 8 });
|
|
431
314
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
432
315
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
@@ -512,7 +395,7 @@ const createDiffTree = (changes) => {
|
|
|
512
395
|
/**
|
|
513
396
|
* Asynchronously collect diffs for a given node and its children.
|
|
514
397
|
*/
|
|
515
|
-
async function collectDiffs(node, getFileDiff, tokenizer, logger
|
|
398
|
+
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
516
399
|
// Collect diffs for the files of the current node
|
|
517
400
|
const diffPromises = node.files.map(async (nodeFile) => {
|
|
518
401
|
const diff = await getFileDiff(nodeFile);
|
|
@@ -530,7 +413,7 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
530
413
|
};
|
|
531
414
|
});
|
|
532
415
|
// Collect diffs for the children of the current node
|
|
533
|
-
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer));
|
|
416
|
+
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer, logger));
|
|
534
417
|
const [diffs, children] = await Promise.all([
|
|
535
418
|
Promise.all(diffPromises),
|
|
536
419
|
Promise.all(childrenPromises),
|
|
@@ -542,22 +425,67 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
542
425
|
};
|
|
543
426
|
}
|
|
544
427
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Get LLM Model Based on Configuration
|
|
430
|
+
* @param fields
|
|
431
|
+
* @param configuration
|
|
432
|
+
* @returns LLM Model
|
|
433
|
+
*/
|
|
434
|
+
function getModel(name, key, fields, configuration) {
|
|
435
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
436
|
+
if (!model) {
|
|
437
|
+
throw new Error(`Invalid model: ${name}`);
|
|
438
|
+
}
|
|
439
|
+
switch (llm) {
|
|
440
|
+
case 'huggingface':
|
|
441
|
+
return new HuggingFaceInference({
|
|
442
|
+
model: model,
|
|
443
|
+
apiKey: key,
|
|
444
|
+
maxConcurrency: 4,
|
|
445
|
+
...fields,
|
|
446
|
+
});
|
|
447
|
+
case 'openai':
|
|
448
|
+
default:
|
|
449
|
+
return new OpenAI({
|
|
450
|
+
openAIApiKey: key,
|
|
451
|
+
modelName: model,
|
|
452
|
+
...fields,
|
|
453
|
+
}, configuration);
|
|
454
|
+
}
|
|
557
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Retrieve appropriate API key based on selected model
|
|
458
|
+
* @param name
|
|
459
|
+
* @param options
|
|
460
|
+
* @returns
|
|
461
|
+
*/
|
|
462
|
+
function getModelAPIKey(name, options) {
|
|
463
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
464
|
+
if (!model) {
|
|
465
|
+
throw new Error(`Invalid model: ${name}`);
|
|
466
|
+
}
|
|
467
|
+
switch (llm) {
|
|
468
|
+
case 'huggingface':
|
|
469
|
+
return options.huggingFaceHubApiKey;
|
|
470
|
+
case 'openai':
|
|
471
|
+
default:
|
|
472
|
+
return options.openAIApiKey;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Get Recursive Character Text Splitter
|
|
477
|
+
* @param options
|
|
478
|
+
* @returns
|
|
479
|
+
*/
|
|
558
480
|
function getTextSplitter(options = {}) {
|
|
559
481
|
return new RecursiveCharacterTextSplitter(options);
|
|
560
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Get Summarization Chain
|
|
485
|
+
* @param model
|
|
486
|
+
* @param options
|
|
487
|
+
* @returns
|
|
488
|
+
*/
|
|
561
489
|
function getChain(model, options = { type: 'map_reduce' }) {
|
|
562
490
|
return loadSummarizationChain(model, options);
|
|
563
491
|
}
|
|
@@ -571,6 +499,12 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
571
499
|
})
|
|
572
500
|
: fallback);
|
|
573
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Verify template string contains all required input variables
|
|
504
|
+
* @param text template string
|
|
505
|
+
* @param inputVariables template variables
|
|
506
|
+
* @returns boolean or error message
|
|
507
|
+
*/
|
|
574
508
|
function validatePromptTemplate(text, inputVariables) {
|
|
575
509
|
if (!text) {
|
|
576
510
|
return 'Prompt template cannot be empty';
|
|
@@ -624,8 +558,7 @@ const getDiff = async (nodeFile, { git, logger, }) => {
|
|
|
624
558
|
};
|
|
625
559
|
|
|
626
560
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
627
|
-
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
628
|
-
const logger = new Logger(config);
|
|
561
|
+
const fileChangeParser = async (changes, { tokenizer, git, model, logger }) => {
|
|
629
562
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
630
563
|
const summarizationChain = getChain(model, {
|
|
631
564
|
type: 'map_reduce',
|
|
@@ -646,6 +579,7 @@ const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
|
646
579
|
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
647
580
|
textSplitter,
|
|
648
581
|
chain: summarizationChain,
|
|
582
|
+
logger
|
|
649
583
|
});
|
|
650
584
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
651
585
|
return summary;
|
|
@@ -681,11 +615,77 @@ const getTokenizer = () => {
|
|
|
681
615
|
return tokenizer;
|
|
682
616
|
};
|
|
683
617
|
|
|
618
|
+
class Logger {
|
|
619
|
+
constructor(config) {
|
|
620
|
+
this.config = config;
|
|
621
|
+
this.spinner = null;
|
|
622
|
+
}
|
|
623
|
+
log(message, options = { color: 'blue' }) {
|
|
624
|
+
let outputMessage = message;
|
|
625
|
+
if (options.color) {
|
|
626
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
627
|
+
}
|
|
628
|
+
console.log(outputMessage);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
verbose(message, options = {}) {
|
|
632
|
+
if (!this.config?.verbose) {
|
|
633
|
+
return this;
|
|
634
|
+
}
|
|
635
|
+
this.log(message, options);
|
|
636
|
+
return this;
|
|
637
|
+
}
|
|
638
|
+
startTimer() {
|
|
639
|
+
this.timerStart = now();
|
|
640
|
+
return this;
|
|
641
|
+
}
|
|
642
|
+
stopTimer(message, options = { color: 'yellow' }) {
|
|
643
|
+
if (!this.config?.verbose || !this.timerStart) {
|
|
644
|
+
return this;
|
|
645
|
+
}
|
|
646
|
+
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
647
|
+
let outputMessage = message
|
|
648
|
+
? `${message} (โฒ ${elapsedTime})`
|
|
649
|
+
: `โฒ ${elapsedTime}`;
|
|
650
|
+
if (options.color) {
|
|
651
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
652
|
+
}
|
|
653
|
+
console.log(outputMessage);
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
656
|
+
startSpinner(message, options = { color: 'green' }) {
|
|
657
|
+
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
658
|
+
this.spinner = ora(spinnerMessage).start();
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
662
|
+
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
663
|
+
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
664
|
+
this.spinner = null;
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
684
669
|
const llm = async ({ llm, prompt, variables }) => {
|
|
670
|
+
if (!llm || !prompt || !variables) {
|
|
671
|
+
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
672
|
+
}
|
|
685
673
|
const chain = new LLMChain({ llm, prompt });
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
674
|
+
let res;
|
|
675
|
+
try {
|
|
676
|
+
res = await chain.call(variables);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
if (error instanceof Error) {
|
|
680
|
+
throw new Error(`LLMChain call error: ${error.message}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (!res) {
|
|
684
|
+
throw new Error('Empty response from LLMChain call');
|
|
685
|
+
}
|
|
686
|
+
if (res.error) {
|
|
687
|
+
throw new Error(`LLMChain response error: ${res.error}`);
|
|
688
|
+
}
|
|
689
689
|
return res.text.trim();
|
|
690
690
|
};
|
|
691
691
|
|
|
@@ -723,6 +723,7 @@ const getSummaryText = (file, change) => {
|
|
|
723
723
|
return `${status}: ${file.path}`;
|
|
724
724
|
};
|
|
725
725
|
|
|
726
|
+
const config = loadConfig();
|
|
726
727
|
const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
|
|
727
728
|
const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
|
|
728
729
|
async function getChanges(git, options = {}) {
|
|
@@ -777,62 +778,117 @@ async function getChanges(git, options = {}) {
|
|
|
777
778
|
|
|
778
779
|
const noResult = async ({ git, logger }) => {
|
|
779
780
|
const { staged, unstaged, untracked } = await getChanges(git);
|
|
780
|
-
|
|
781
|
+
const hasStaged = staged && staged.length > 0;
|
|
782
|
+
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
783
|
+
const hasUntracked = untracked && untracked.length > 0;
|
|
784
|
+
if (hasStaged) {
|
|
781
785
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
782
786
|
logger.log(`Files are likely either:\n โข changed files are ignored\n โข file diff is too large.`, { color: 'yellow' });
|
|
783
787
|
}
|
|
784
|
-
else if (
|
|
785
|
-
logger.log('No staged
|
|
786
|
-
|
|
787
|
-
color: 'yellow'
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
color: 'yellow'
|
|
794
|
-
|
|
788
|
+
else if (hasUnstaged || hasUntracked) {
|
|
789
|
+
logger.log('Forget something? No staged changes found... ๐ป', { color: 'red' });
|
|
790
|
+
if (hasUnstaged) {
|
|
791
|
+
logger.log('\nDetected unstaged changes', { color: 'yellow' });
|
|
792
|
+
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
793
|
+
color: 'red',
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (hasUntracked) {
|
|
797
|
+
logger.log('\nDetected untracked changes', { color: 'yellow' });
|
|
798
|
+
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
799
|
+
color: 'red',
|
|
800
|
+
});
|
|
801
|
+
}
|
|
795
802
|
}
|
|
796
803
|
else {
|
|
797
|
-
logger.log('No repo changes detected.', { color: '
|
|
804
|
+
logger.log('No repo changes detected. ๐', { color: 'blue' });
|
|
798
805
|
}
|
|
799
|
-
process.exit(0);
|
|
800
806
|
};
|
|
801
807
|
|
|
802
808
|
async function createCommit(commitMsg, git) {
|
|
803
809
|
return await git.commit(commitMsg);
|
|
804
810
|
}
|
|
805
811
|
|
|
806
|
-
const argv = loadArgv()
|
|
812
|
+
// const argv = loadArgv()
|
|
807
813
|
const tokenizer = getTokenizer();
|
|
808
814
|
const git = simpleGit();
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
815
|
+
const command = ['commit', '$0'];
|
|
816
|
+
const description = 'Generate a commit message based on the diff summary';
|
|
817
|
+
const builder = {
|
|
818
|
+
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
819
|
+
openAIApiKey: {
|
|
820
|
+
type: 'string',
|
|
821
|
+
description: 'OpenAI API Key',
|
|
822
|
+
conflicts: 'huggingFaceHubApiKey',
|
|
823
|
+
},
|
|
824
|
+
huggingFaceHubApiKey: {
|
|
825
|
+
type: 'string',
|
|
826
|
+
description: 'HuggingFace Hub API Key',
|
|
827
|
+
conflicts: 'openAIApiKey',
|
|
828
|
+
},
|
|
829
|
+
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
830
|
+
prompt: {
|
|
831
|
+
type: 'string',
|
|
832
|
+
alias: 'p',
|
|
833
|
+
description: 'Commit message prompt',
|
|
834
|
+
},
|
|
835
|
+
i: {
|
|
836
|
+
type: 'boolean',
|
|
837
|
+
alias: 'interactive',
|
|
838
|
+
description: 'Toggle interactive mode',
|
|
839
|
+
},
|
|
840
|
+
s: {
|
|
841
|
+
type: 'boolean',
|
|
842
|
+
description: 'Automatically commit staged changes with generated commit message',
|
|
843
|
+
default: false,
|
|
844
|
+
},
|
|
845
|
+
e: {
|
|
846
|
+
type: 'boolean',
|
|
847
|
+
alias: 'edit',
|
|
848
|
+
description: 'Open commit message in editor before proceeding',
|
|
849
|
+
},
|
|
850
|
+
summarizePrompt: {
|
|
851
|
+
type: 'string',
|
|
852
|
+
description: 'Large file summary prompt',
|
|
853
|
+
},
|
|
854
|
+
ignoredFiles: {
|
|
855
|
+
type: 'array',
|
|
856
|
+
description: 'Ignored files',
|
|
857
|
+
},
|
|
858
|
+
ignoredExtensions: {
|
|
859
|
+
type: 'array',
|
|
860
|
+
description: 'Ignored extensions',
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
async function handler(argv) {
|
|
864
|
+
const options = loadConfig(argv);
|
|
865
|
+
const logger = new Logger(options);
|
|
866
|
+
const key = getModelAPIKey(options.model, options);
|
|
867
|
+
if (!key) {
|
|
812
868
|
logger.log(`No API Key found. ๐๏ธ๐ช`, { color: 'red' });
|
|
813
869
|
process.exit(1);
|
|
814
870
|
}
|
|
815
|
-
const model = getModel({
|
|
871
|
+
const model = getModel(options.model, key, {
|
|
816
872
|
temperature: 0.4,
|
|
817
873
|
maxConcurrency: 10,
|
|
818
|
-
openAIApiKey: config.openAIApiKey,
|
|
819
874
|
});
|
|
820
|
-
const INTERACTIVE =
|
|
875
|
+
const INTERACTIVE = options?.mode === 'interactive' || options.interactive;
|
|
821
876
|
const { staged: changes } = await getChanges(git);
|
|
822
877
|
let summary = '';
|
|
823
878
|
let commitMsg = '';
|
|
824
|
-
let promptTemplate =
|
|
879
|
+
let promptTemplate = options?.prompt || '';
|
|
825
880
|
let modifyPrompt = false;
|
|
826
881
|
while (true) {
|
|
827
882
|
if (changes.length !== 0 && !summary.length) {
|
|
828
883
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
829
884
|
color: 'blue',
|
|
830
885
|
});
|
|
831
|
-
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
886
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model, logger });
|
|
832
887
|
}
|
|
833
888
|
// Handle empty summary
|
|
834
889
|
if (!summary.length) {
|
|
835
|
-
noResult({ git, logger });
|
|
890
|
+
await noResult({ git, logger });
|
|
891
|
+
process.exit(0);
|
|
836
892
|
}
|
|
837
893
|
// Prompt user for commit template prompt, if necessary
|
|
838
894
|
if (modifyPrompt) {
|
|
@@ -908,7 +964,7 @@ async function main(options) {
|
|
|
908
964
|
process.exit(0);
|
|
909
965
|
}
|
|
910
966
|
if (reviewAnswer === 'edit') {
|
|
911
|
-
|
|
967
|
+
options.openInEditor = true;
|
|
912
968
|
}
|
|
913
969
|
if (reviewAnswer === 'retryFull') {
|
|
914
970
|
summary = '';
|
|
@@ -927,7 +983,7 @@ async function main(options) {
|
|
|
927
983
|
continue;
|
|
928
984
|
}
|
|
929
985
|
}
|
|
930
|
-
if (
|
|
986
|
+
if (options.openInEditor) {
|
|
931
987
|
commitMsg = await editor({
|
|
932
988
|
message: 'Edit the commit message',
|
|
933
989
|
default: commitMsg,
|
|
@@ -942,7 +998,7 @@ async function main(options) {
|
|
|
942
998
|
}
|
|
943
999
|
const MODE = (options.interactive && 'interactive') ||
|
|
944
1000
|
(options.commit && 'interactive') ||
|
|
945
|
-
|
|
1001
|
+
options?.mode ||
|
|
946
1002
|
'stdout';
|
|
947
1003
|
// Handle resulting commit message
|
|
948
1004
|
switch (MODE) {
|
|
@@ -958,5 +1014,28 @@ async function main(options) {
|
|
|
958
1014
|
process.exit(0);
|
|
959
1015
|
}
|
|
960
1016
|
}
|
|
961
|
-
|
|
1017
|
+
|
|
1018
|
+
var commit = /*#__PURE__*/Object.freeze({
|
|
1019
|
+
__proto__: null,
|
|
1020
|
+
builder: builder,
|
|
1021
|
+
command: command,
|
|
1022
|
+
description: description,
|
|
1023
|
+
handler: handler
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
yargs
|
|
1027
|
+
.scriptName('coco')
|
|
1028
|
+
.commandDir('./commands', {
|
|
1029
|
+
extensions: ['ts'],
|
|
1030
|
+
})
|
|
1031
|
+
.demandCommand()
|
|
1032
|
+
.strict()
|
|
1033
|
+
.option('h', { alias: 'help' })
|
|
1034
|
+
.option('v', {
|
|
1035
|
+
alias: 'verbose',
|
|
1036
|
+
type: 'boolean',
|
|
1037
|
+
description: 'Run with verbose logging',
|
|
1038
|
+
}).argv;
|
|
1039
|
+
|
|
1040
|
+
export { commit };
|
|
962
1041
|
//# sourceMappingURL=index.esm.mjs.map
|