git-coco 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -5
- package/dist/index.esm.mjs +287 -207
- package/dist/index.js +288 -208
- package/package.json +7 -7
package/dist/index.esm.mjs
CHANGED
|
@@ -6,32 +6,32 @@ import * as fs from 'fs';
|
|
|
6
6
|
import fs__default from 'fs';
|
|
7
7
|
import { confirm, editor, select, password, input } from '@inquirer/prompts';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
+
import * as ini from 'ini';
|
|
9
10
|
import * as os from 'os';
|
|
10
11
|
import os__default from 'os';
|
|
11
12
|
import * as path from 'path';
|
|
12
13
|
import path__default from 'path';
|
|
13
|
-
import * as ini from 'ini';
|
|
14
14
|
import Ajv from 'ajv';
|
|
15
15
|
import ora from 'ora';
|
|
16
16
|
import now from 'performance-now';
|
|
17
17
|
import prettyMilliseconds from 'pretty-ms';
|
|
18
|
+
import { BaseOutputParser, JsonOutputParser } from '@langchain/core/output_parsers';
|
|
19
|
+
import { ChatOllama } from '@langchain/ollama';
|
|
20
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
18
21
|
import pQueue from 'p-queue';
|
|
19
22
|
import { Document, BaseDocumentTransformer } from '@langchain/core/documents';
|
|
20
23
|
import { RUN_KEY } from '@langchain/core/outputs';
|
|
21
24
|
import { CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager';
|
|
22
25
|
import { ensureConfig, Runnable } from '@langchain/core/runnables';
|
|
23
26
|
import { BaseLangChain, BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
24
|
-
import { BaseOutputParser } from '@langchain/core/output_parsers';
|
|
25
27
|
import '@langchain/core/messages';
|
|
26
28
|
import '@langchain/core/memory';
|
|
27
29
|
import '@langchain/core/chat_history';
|
|
28
30
|
import '@langchain/core/utils/tiktoken';
|
|
29
|
-
import { ChatOpenAI } from '@langchain/openai';
|
|
30
31
|
import '@langchain/core/utils/async_caller';
|
|
31
32
|
import '@langchain/core/utils/env';
|
|
32
33
|
import '@langchain/core/utils/json_patch';
|
|
33
34
|
import { createTwoFilesPatch } from 'diff';
|
|
34
|
-
import { ChatOllama } from '@langchain/ollama';
|
|
35
35
|
import { minimatch } from 'minimatch';
|
|
36
36
|
import { simpleGit } from 'simple-git';
|
|
37
37
|
import { encoding_for_model } from 'tiktoken';
|
|
@@ -107,9 +107,9 @@ function getDefaultServiceApiKey(config) {
|
|
|
107
107
|
}
|
|
108
108
|
const DEFAULT_OPENAI_LLM_SERVICE = {
|
|
109
109
|
provider: 'openai',
|
|
110
|
-
model: 'gpt-4
|
|
110
|
+
model: 'gpt-4',
|
|
111
111
|
tokenLimit: 2024,
|
|
112
|
-
temperature: 0.
|
|
112
|
+
temperature: 0.32,
|
|
113
113
|
authentication: {
|
|
114
114
|
type: 'APIKey',
|
|
115
115
|
credentials: {
|
|
@@ -259,12 +259,35 @@ function loadEnvConfig(config) {
|
|
|
259
259
|
if (envValue === undefined) {
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
if (key === 'COCO_SERVICE_PROVIDER' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
|
|
263
|
+
// NOTE: We want to ensure that the service object is always defined
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
envConfig.service = envConfig.service || {};
|
|
267
|
+
handleServiceEnvVar(envConfig.service, key, envValue);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
envConfig[key] = envValue;
|
|
271
|
+
}
|
|
265
272
|
});
|
|
266
273
|
return { ...config, ...removeUndefined(envConfig) };
|
|
267
274
|
}
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
function handleServiceEnvVar(service, key, value) {
|
|
277
|
+
switch (key) {
|
|
278
|
+
case 'COCO_SERVICE_PROVIDER':
|
|
279
|
+
service.provider = value;
|
|
280
|
+
break;
|
|
281
|
+
case 'COCO_SERVICE_MODEL':
|
|
282
|
+
service.model = value;
|
|
283
|
+
break;
|
|
284
|
+
case 'OPEN_AI_KEY':
|
|
285
|
+
if (service.provider === 'openai') {
|
|
286
|
+
service.fields = { apiKey: value };
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
268
291
|
function parseEnvValue(key, value) {
|
|
269
292
|
switch (true) {
|
|
270
293
|
// Handle undefined values
|
|
@@ -343,7 +366,12 @@ function loadGitConfig(config) {
|
|
|
343
366
|
if (fs.existsSync(gitConfigPath)) {
|
|
344
367
|
const gitConfigRaw = fs.readFileSync(gitConfigPath, 'utf-8');
|
|
345
368
|
const gitConfigParsed = ini.parse(gitConfigRaw);
|
|
346
|
-
const
|
|
369
|
+
const gitServiceAlias = gitConfigParsed.coco?.service;
|
|
370
|
+
let service = config.service;
|
|
371
|
+
if (gitServiceAlias) {
|
|
372
|
+
const gitServiceConfig = getDefaultServiceConfigFromAlias(gitServiceAlias);
|
|
373
|
+
service = parseServiceConfig$1(gitServiceConfig || config.service);
|
|
374
|
+
}
|
|
347
375
|
config = {
|
|
348
376
|
...config,
|
|
349
377
|
service: service,
|
|
@@ -358,6 +386,28 @@ function loadGitConfig(config) {
|
|
|
358
386
|
}
|
|
359
387
|
return removeUndefined(config);
|
|
360
388
|
}
|
|
389
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
390
|
+
function parseServiceConfig$1(service) {
|
|
391
|
+
if (!service)
|
|
392
|
+
return undefined;
|
|
393
|
+
switch (service.provider) {
|
|
394
|
+
case 'openai':
|
|
395
|
+
return {
|
|
396
|
+
provider: 'openai',
|
|
397
|
+
model: service.model,
|
|
398
|
+
fields: { apiKey: service.apiKey },
|
|
399
|
+
};
|
|
400
|
+
case 'ollama':
|
|
401
|
+
return {
|
|
402
|
+
provider: 'ollama',
|
|
403
|
+
model: service.model,
|
|
404
|
+
endpoint: service.endpoint,
|
|
405
|
+
fields: service.fields,
|
|
406
|
+
};
|
|
407
|
+
default:
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
361
411
|
/**
|
|
362
412
|
* Appends the provided configuration to a git config file.
|
|
363
413
|
*
|
|
@@ -1732,10 +1782,42 @@ function loadXDGConfig(config) {
|
|
|
1732
1782
|
const xdgConfigPath = path.join(xdgConfigHome, 'coco', 'config.json');
|
|
1733
1783
|
if (fs.existsSync(xdgConfigPath)) {
|
|
1734
1784
|
const xdgConfig = JSON.parse(fs.readFileSync(xdgConfigPath, 'utf-8'));
|
|
1735
|
-
|
|
1785
|
+
const service = parseServiceConfig(xdgConfig.service || config.service);
|
|
1786
|
+
config = {
|
|
1787
|
+
...config,
|
|
1788
|
+
...xdgConfig,
|
|
1789
|
+
service: service
|
|
1790
|
+
};
|
|
1736
1791
|
}
|
|
1737
1792
|
return config;
|
|
1738
1793
|
}
|
|
1794
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1795
|
+
function parseServiceConfig(service) {
|
|
1796
|
+
if (!service)
|
|
1797
|
+
return undefined;
|
|
1798
|
+
switch (service.provider) {
|
|
1799
|
+
case 'openai':
|
|
1800
|
+
return {
|
|
1801
|
+
provider: 'openai',
|
|
1802
|
+
model: service.model,
|
|
1803
|
+
authentication: {
|
|
1804
|
+
type: 'APIKey',
|
|
1805
|
+
credentials: {
|
|
1806
|
+
apiKey: service.apiKey
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
case 'ollama':
|
|
1811
|
+
return {
|
|
1812
|
+
provider: 'ollama',
|
|
1813
|
+
model: service.model,
|
|
1814
|
+
endpoint: service.endpoint,
|
|
1815
|
+
fields: service.fields
|
|
1816
|
+
};
|
|
1817
|
+
default:
|
|
1818
|
+
return undefined;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1739
1821
|
|
|
1740
1822
|
/**
|
|
1741
1823
|
* Load application config
|
|
@@ -1833,6 +1915,66 @@ function commandExecutor(handler) {
|
|
|
1833
1915
|
};
|
|
1834
1916
|
}
|
|
1835
1917
|
|
|
1918
|
+
const executeChain = async ({ llm, prompt, variables, parser, }) => {
|
|
1919
|
+
if (!llm || !prompt || !variables) {
|
|
1920
|
+
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
1921
|
+
}
|
|
1922
|
+
const chain = prompt.pipe(llm).pipe(parser);
|
|
1923
|
+
let res;
|
|
1924
|
+
try {
|
|
1925
|
+
res = await chain.invoke(variables);
|
|
1926
|
+
}
|
|
1927
|
+
catch (error) {
|
|
1928
|
+
if (error instanceof Error) {
|
|
1929
|
+
throw new Error(`LLMChain call error: ${error.message}`);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (!res) {
|
|
1933
|
+
throw new Error('Empty response from LLMChain call');
|
|
1934
|
+
}
|
|
1935
|
+
return res;
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Get LLM Model Based on Configuration
|
|
1940
|
+
*
|
|
1941
|
+
* @param fields
|
|
1942
|
+
* @param configuration
|
|
1943
|
+
* @returns LLM Model
|
|
1944
|
+
*/
|
|
1945
|
+
function getLlm(provider, model, config) {
|
|
1946
|
+
if (!model) {
|
|
1947
|
+
throw new Error(`Invalid LLM Service: ${provider}/${model}`);
|
|
1948
|
+
}
|
|
1949
|
+
switch (provider) {
|
|
1950
|
+
case 'ollama':
|
|
1951
|
+
return new ChatOllama({
|
|
1952
|
+
baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
|
|
1953
|
+
maxConcurrency: config.service.maxConcurrent,
|
|
1954
|
+
model,
|
|
1955
|
+
});
|
|
1956
|
+
case 'openai':
|
|
1957
|
+
default:
|
|
1958
|
+
const openAiModel = new ChatOpenAI({
|
|
1959
|
+
openAIApiKey: getApiKeyForModel(config),
|
|
1960
|
+
model,
|
|
1961
|
+
temperature: config.service.temperature || 0.2,
|
|
1962
|
+
});
|
|
1963
|
+
return openAiModel;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
function getPrompt({ template, variables, fallback }) {
|
|
1968
|
+
if (!template && !fallback)
|
|
1969
|
+
throw new Error('Must provide either a template or a fallback');
|
|
1970
|
+
return (template
|
|
1971
|
+
? new PromptTemplate({
|
|
1972
|
+
template,
|
|
1973
|
+
inputVariables: variables,
|
|
1974
|
+
})
|
|
1975
|
+
: fallback);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1836
1978
|
/**
|
|
1837
1979
|
* Extract the path from a file path string.
|
|
1838
1980
|
* @param {string} filePath - The full file path.
|
|
@@ -5416,64 +5558,15 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
5416
5558
|
return summary;
|
|
5417
5559
|
}
|
|
5418
5560
|
|
|
5419
|
-
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
5420
|
-
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary no more than 300 characters, the shorter and more concise the better. The detailed summary should be separated from the short description by a blank line. Please follow the guidelines below when writing your commit message:
|
|
5421
|
-
|
|
5422
|
-
- Write concisely using an informal tone
|
|
5423
|
-
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
5424
|
-
- DO NOT use specific names or files from the code
|
|
5425
|
-
- DO NOT include any diffs or file changes in the commit message
|
|
5426
|
-
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
5427
|
-
- ONLY respond with the resulting commit message.
|
|
5428
|
-
|
|
5429
|
-
--------
|
|
5430
|
-
{summary}
|
|
5431
|
-
--------
|
|
5432
|
-
|
|
5433
|
-
COMMIT MESSAGE:`;
|
|
5434
|
-
const inputVariables$1 = ['summary'];
|
|
5435
|
-
const COMMIT_PROMPT = new PromptTemplate({
|
|
5436
|
-
template: template$1,
|
|
5437
|
-
inputVariables: inputVariables$1,
|
|
5438
|
-
});
|
|
5439
|
-
|
|
5440
5561
|
/**
|
|
5441
|
-
*
|
|
5562
|
+
* Creates a commit with the specified commit message.
|
|
5442
5563
|
*
|
|
5443
|
-
* @param
|
|
5444
|
-
* @param
|
|
5445
|
-
* @returns
|
|
5564
|
+
* @param message The commit message.
|
|
5565
|
+
* @param git The SimpleGit instance.
|
|
5566
|
+
* @returns A Promise that resolves to the CommitResult.
|
|
5446
5567
|
*/
|
|
5447
|
-
function
|
|
5448
|
-
|
|
5449
|
-
throw new Error(`Invalid LLM Service: ${provider}/${model}`);
|
|
5450
|
-
}
|
|
5451
|
-
switch (provider) {
|
|
5452
|
-
case 'ollama':
|
|
5453
|
-
return new ChatOllama({
|
|
5454
|
-
baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
|
|
5455
|
-
maxConcurrency: config.service.maxConcurrent,
|
|
5456
|
-
model,
|
|
5457
|
-
});
|
|
5458
|
-
case 'openai':
|
|
5459
|
-
default:
|
|
5460
|
-
const openAiModel = new ChatOpenAI({
|
|
5461
|
-
openAIApiKey: getApiKeyForModel(config),
|
|
5462
|
-
model,
|
|
5463
|
-
});
|
|
5464
|
-
return openAiModel;
|
|
5465
|
-
}
|
|
5466
|
-
}
|
|
5467
|
-
|
|
5468
|
-
function getPrompt({ template, variables, fallback }) {
|
|
5469
|
-
if (!template && !fallback)
|
|
5470
|
-
throw new Error('Must provide either a template or a fallback');
|
|
5471
|
-
return (template
|
|
5472
|
-
? new PromptTemplate({
|
|
5473
|
-
template,
|
|
5474
|
-
inputVariables: variables,
|
|
5475
|
-
})
|
|
5476
|
-
: fallback);
|
|
5568
|
+
async function createCommit(message, git) {
|
|
5569
|
+
return await git.commit(message);
|
|
5477
5570
|
}
|
|
5478
5571
|
|
|
5479
5572
|
/**
|
|
@@ -5605,37 +5698,66 @@ async function getChanges({ git, options }) {
|
|
|
5605
5698
|
};
|
|
5606
5699
|
}
|
|
5607
5700
|
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5701
|
+
/**
|
|
5702
|
+
* Retrieves the SimpleGit instance for the repository.
|
|
5703
|
+
* @returns {SimpleGit} The SimpleGit instance.
|
|
5704
|
+
*/
|
|
5705
|
+
const getRepo = () => {
|
|
5706
|
+
let git;
|
|
5707
|
+
try {
|
|
5708
|
+
git = simpleGit();
|
|
5616
5709
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
5621
|
-
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5622
|
-
color: 'red',
|
|
5623
|
-
});
|
|
5624
|
-
}
|
|
5625
|
-
if (hasUntracked) {
|
|
5626
|
-
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
5627
|
-
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5628
|
-
color: 'red',
|
|
5629
|
-
});
|
|
5630
|
-
}
|
|
5710
|
+
catch (e) {
|
|
5711
|
+
console.log('Error initializing git repo', e);
|
|
5712
|
+
process.exit(1);
|
|
5631
5713
|
}
|
|
5632
|
-
|
|
5633
|
-
|
|
5714
|
+
return git;
|
|
5715
|
+
};
|
|
5716
|
+
|
|
5717
|
+
const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
5718
|
+
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary no more than 300 characters, the shorter and more concise the better. Please follow the guidelines below when writing your commit message:
|
|
5719
|
+
|
|
5720
|
+
- Write concisely using an informal tone
|
|
5721
|
+
- DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
5722
|
+
- DO NOT use specific names or files from the code
|
|
5723
|
+
- DO NOT include any diffs or file changes in the commit message
|
|
5724
|
+
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
5725
|
+
|
|
5726
|
+
{format_instructions}
|
|
5727
|
+
|
|
5728
|
+
"""{summary}"""`;
|
|
5729
|
+
const inputVariables$1 = ['summary', 'format_instructions'];
|
|
5730
|
+
const COMMIT_PROMPT = new PromptTemplate({
|
|
5731
|
+
template: template$1,
|
|
5732
|
+
inputVariables: inputVariables$1,
|
|
5733
|
+
});
|
|
5734
|
+
|
|
5735
|
+
/**
|
|
5736
|
+
* Verify template string contains all required input variables
|
|
5737
|
+
*
|
|
5738
|
+
* @param text template string
|
|
5739
|
+
* @param inputVariables template variables
|
|
5740
|
+
* @returns boolean or error message
|
|
5741
|
+
*/
|
|
5742
|
+
function validatePromptTemplate(text, inputVariables) {
|
|
5743
|
+
if (!text) {
|
|
5744
|
+
return 'Prompt template cannot be empty';
|
|
5634
5745
|
}
|
|
5746
|
+
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
5747
|
+
return ('Prompt template must include at least one of the following input variables: ' +
|
|
5748
|
+
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
5749
|
+
}
|
|
5750
|
+
return true;
|
|
5635
5751
|
}
|
|
5636
5752
|
|
|
5637
|
-
function
|
|
5638
|
-
|
|
5753
|
+
async function editPrompt(options) {
|
|
5754
|
+
return await editor({
|
|
5755
|
+
message: 'Edit the prompt',
|
|
5756
|
+
default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
|
|
5757
|
+
waitForUseInput: false,
|
|
5758
|
+
postfix: 'Press ENTER to continue',
|
|
5759
|
+
validate: (text) => validatePromptTemplate(text, COMMIT_PROMPT.inputVariables),
|
|
5760
|
+
});
|
|
5639
5761
|
}
|
|
5640
5762
|
|
|
5641
5763
|
async function editResult(result, options) {
|
|
@@ -5697,32 +5819,8 @@ async function getUserReviewDecision({ label, descriptions, enableRetry = true,
|
|
|
5697
5819
|
}));
|
|
5698
5820
|
}
|
|
5699
5821
|
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
*
|
|
5703
|
-
* @param text template string
|
|
5704
|
-
* @param inputVariables template variables
|
|
5705
|
-
* @returns boolean or error message
|
|
5706
|
-
*/
|
|
5707
|
-
function validatePromptTemplate(text, inputVariables) {
|
|
5708
|
-
if (!text) {
|
|
5709
|
-
return 'Prompt template cannot be empty';
|
|
5710
|
-
}
|
|
5711
|
-
if (!inputVariables.some((entry) => text.includes(entry))) {
|
|
5712
|
-
return ('Prompt template must include at least one of the following input variables: ' +
|
|
5713
|
-
inputVariables.map((value) => `{${value}}`).join(', '));
|
|
5714
|
-
}
|
|
5715
|
-
return true;
|
|
5716
|
-
}
|
|
5717
|
-
|
|
5718
|
-
async function editPrompt(options) {
|
|
5719
|
-
return await editor({
|
|
5720
|
-
message: 'Edit the prompt',
|
|
5721
|
-
default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
|
|
5722
|
-
waitForUseInput: false,
|
|
5723
|
-
postfix: 'Press ENTER to continue',
|
|
5724
|
-
validate: (text) => validatePromptTemplate(text, COMMIT_PROMPT.inputVariables),
|
|
5725
|
-
});
|
|
5822
|
+
function logResult(label, result) {
|
|
5823
|
+
console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
|
|
5726
5824
|
}
|
|
5727
5825
|
|
|
5728
5826
|
async function generateAndReviewLoop({ label, factory, parser, noResult, agent, options, }) {
|
|
@@ -5733,7 +5831,7 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
5733
5831
|
let result = '';
|
|
5734
5832
|
const changes = await factory();
|
|
5735
5833
|
// if we don't have any changes, bail.
|
|
5736
|
-
if (!changes || !changes.length) {
|
|
5834
|
+
if (!changes || !Object.keys(changes).length) {
|
|
5737
5835
|
await noResult(options);
|
|
5738
5836
|
}
|
|
5739
5837
|
while (continueLoop) {
|
|
@@ -5768,7 +5866,7 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
5768
5866
|
logResult(label, result);
|
|
5769
5867
|
const reviewAnswer = await getUserReviewDecision({
|
|
5770
5868
|
label,
|
|
5771
|
-
...options?.review || {},
|
|
5869
|
+
...(options?.review || {}),
|
|
5772
5870
|
});
|
|
5773
5871
|
if (reviewAnswer === 'cancel') {
|
|
5774
5872
|
process.exit(0);
|
|
@@ -5800,26 +5898,6 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
5800
5898
|
return result;
|
|
5801
5899
|
}
|
|
5802
5900
|
|
|
5803
|
-
const executeChain = async ({ llm, prompt, variables }) => {
|
|
5804
|
-
if (!llm || !prompt || !variables) {
|
|
5805
|
-
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
5806
|
-
}
|
|
5807
|
-
const chain = prompt.pipe(llm);
|
|
5808
|
-
let res;
|
|
5809
|
-
try {
|
|
5810
|
-
res = await chain.invoke(variables);
|
|
5811
|
-
}
|
|
5812
|
-
catch (error) {
|
|
5813
|
-
if (error instanceof Error) {
|
|
5814
|
-
throw new Error(`LLMChain call error: ${error.message}`);
|
|
5815
|
-
}
|
|
5816
|
-
}
|
|
5817
|
-
if (!res) {
|
|
5818
|
-
throw new Error('Empty response from LLMChain call');
|
|
5819
|
-
}
|
|
5820
|
-
return res.content;
|
|
5821
|
-
};
|
|
5822
|
-
|
|
5823
5901
|
const logSuccess = () => {
|
|
5824
5902
|
console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
|
|
5825
5903
|
};
|
|
@@ -5843,22 +5921,6 @@ async function handleResult({ result, mode, interactiveHandler }) {
|
|
|
5843
5921
|
process.exit(0);
|
|
5844
5922
|
}
|
|
5845
5923
|
|
|
5846
|
-
/**
|
|
5847
|
-
* Retrieves the SimpleGit instance for the repository.
|
|
5848
|
-
* @returns {SimpleGit} The SimpleGit instance.
|
|
5849
|
-
*/
|
|
5850
|
-
const getRepo = () => {
|
|
5851
|
-
let git;
|
|
5852
|
-
try {
|
|
5853
|
-
git = simpleGit();
|
|
5854
|
-
}
|
|
5855
|
-
catch (e) {
|
|
5856
|
-
console.log('Error initializing git repo', e);
|
|
5857
|
-
process.exit(1);
|
|
5858
|
-
}
|
|
5859
|
-
return git;
|
|
5860
|
-
};
|
|
5861
|
-
|
|
5862
5924
|
/**
|
|
5863
5925
|
* Retrieves a TikToken for the specified model.
|
|
5864
5926
|
*
|
|
@@ -5881,15 +5943,33 @@ const getTokenCounter = async (modelName) => {
|
|
|
5881
5943
|
});
|
|
5882
5944
|
};
|
|
5883
5945
|
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5946
|
+
async function noResult({ git, logger }) {
|
|
5947
|
+
const { staged, unstaged, untracked } = await getChanges({ git });
|
|
5948
|
+
const hasStaged = staged && staged.length > 0;
|
|
5949
|
+
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
5950
|
+
const hasUntracked = untracked && untracked.length > 0;
|
|
5951
|
+
if (hasStaged) {
|
|
5952
|
+
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
5953
|
+
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
5954
|
+
}
|
|
5955
|
+
else if (hasUnstaged || hasUntracked) {
|
|
5956
|
+
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
5957
|
+
if (hasUnstaged) {
|
|
5958
|
+
logger.log('\nChanges not staged for commit:', { color: 'yellow' });
|
|
5959
|
+
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5960
|
+
color: 'red',
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
if (hasUntracked) {
|
|
5964
|
+
logger.log('\nUntracked changes:', { color: 'yellow' });
|
|
5965
|
+
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
5966
|
+
color: 'red',
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
else {
|
|
5971
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
5972
|
+
}
|
|
5893
5973
|
}
|
|
5894
5974
|
|
|
5895
5975
|
const handler$2 = async (argv, logger) => {
|
|
@@ -5938,16 +6018,20 @@ const handler$2 = async (argv, logger) => {
|
|
|
5938
6018
|
factory,
|
|
5939
6019
|
parser,
|
|
5940
6020
|
agent: async (context, options) => {
|
|
6021
|
+
const parser = new JsonOutputParser();
|
|
5941
6022
|
const prompt = getPrompt({
|
|
5942
6023
|
template: options.prompt,
|
|
5943
6024
|
variables: COMMIT_PROMPT.inputVariables,
|
|
5944
6025
|
fallback: COMMIT_PROMPT,
|
|
5945
6026
|
});
|
|
5946
|
-
|
|
6027
|
+
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body'.";
|
|
6028
|
+
const commitMsg = await executeChain({
|
|
5947
6029
|
llm,
|
|
5948
6030
|
prompt,
|
|
5949
|
-
variables: { summary: context },
|
|
6031
|
+
variables: { summary: context, format_instructions: formatInstructions },
|
|
6032
|
+
parser,
|
|
5950
6033
|
});
|
|
6034
|
+
return `${commitMsg.title}\n\n${commitMsg.body}`;
|
|
5951
6035
|
},
|
|
5952
6036
|
noResult: async () => {
|
|
5953
6037
|
await noResult({ git, logger });
|
|
@@ -6020,20 +6104,6 @@ var commit = {
|
|
|
6020
6104
|
options: options$2,
|
|
6021
6105
|
};
|
|
6022
6106
|
|
|
6023
|
-
const template = `Write informative git changelog, in the imperative, based on a series of individual messages.
|
|
6024
|
-
|
|
6025
|
-
- Typically a hyphen or asterisk is used for the bullet
|
|
6026
|
-
- Summarize dependency updates
|
|
6027
|
-
|
|
6028
|
-
"""{summary}"""
|
|
6029
|
-
|
|
6030
|
-
Changelog:`;
|
|
6031
|
-
const inputVariables = ['summary'];
|
|
6032
|
-
const CHANGELOG_PROMPT = new PromptTemplate({
|
|
6033
|
-
template,
|
|
6034
|
-
inputVariables,
|
|
6035
|
-
});
|
|
6036
|
-
|
|
6037
6107
|
/**
|
|
6038
6108
|
* Retrieves the commit log range between two specified commits.
|
|
6039
6109
|
*
|
|
@@ -6047,7 +6117,7 @@ async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
|
6047
6117
|
try {
|
|
6048
6118
|
const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
|
|
6049
6119
|
const commitLog = await git.log(logOptions);
|
|
6050
|
-
return commitLog.all.map(({ message, date, body, author_name }) => `[${date}] ${message}\n${body}\n - ${author_name}
|
|
6120
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6051
6121
|
}
|
|
6052
6122
|
catch (error) {
|
|
6053
6123
|
// If there's an error, handle it appropriately
|
|
@@ -6118,6 +6188,20 @@ async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main
|
|
|
6118
6188
|
return [];
|
|
6119
6189
|
}
|
|
6120
6190
|
|
|
6191
|
+
const template = `Write informative git changelog, in the imperative, based on a series of individual messages.
|
|
6192
|
+
|
|
6193
|
+
- Include the git commit hash as reference for each change, including just the first 7 characters
|
|
6194
|
+
- Logically group changes, and if necessary, summarize dependency updates
|
|
6195
|
+
|
|
6196
|
+
{format_instructions}
|
|
6197
|
+
|
|
6198
|
+
"""{summary}"""`;
|
|
6199
|
+
const inputVariables = ['format_instructions', 'summary'];
|
|
6200
|
+
const CHANGELOG_PROMPT = new PromptTemplate({
|
|
6201
|
+
template,
|
|
6202
|
+
inputVariables,
|
|
6203
|
+
});
|
|
6204
|
+
|
|
6121
6205
|
const handler$1 = async (argv, logger) => {
|
|
6122
6206
|
const config = loadConfig(argv);
|
|
6123
6207
|
const git = getRepo();
|
|
@@ -6133,19 +6217,27 @@ const handler$1 = async (argv, logger) => {
|
|
|
6133
6217
|
logger.log(LOGO);
|
|
6134
6218
|
}
|
|
6135
6219
|
async function factory() {
|
|
6220
|
+
const branchName = await getCurrentBranchName({ git });
|
|
6136
6221
|
if (config.range && config.range.includes(':')) {
|
|
6137
6222
|
const [from, to] = config.range.split(':');
|
|
6138
6223
|
if (!from || !to) {
|
|
6139
6224
|
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
6140
6225
|
process.exit(1);
|
|
6141
6226
|
}
|
|
6142
|
-
return
|
|
6227
|
+
return {
|
|
6228
|
+
branch: branchName,
|
|
6229
|
+
commits: await getCommitLogRange(from, to, { git, noMerges: true }),
|
|
6230
|
+
};
|
|
6143
6231
|
}
|
|
6144
6232
|
logger.verbose(`No range provided. Defaulting to current branch`, { color: 'yellow' });
|
|
6145
|
-
return
|
|
6233
|
+
return {
|
|
6234
|
+
branch: branchName,
|
|
6235
|
+
commits: await getCommitLogCurrentBranch({ git, logger }),
|
|
6236
|
+
};
|
|
6146
6237
|
}
|
|
6147
|
-
async function parser(
|
|
6148
|
-
const result =
|
|
6238
|
+
async function parser({ branch, commits }) {
|
|
6239
|
+
const result = `## ${branch}\n\n${commits.map((commit) => `${commit}`).join('\n\n\n')}`;
|
|
6240
|
+
console.log({ result });
|
|
6149
6241
|
return result;
|
|
6150
6242
|
}
|
|
6151
6243
|
const changelogMsg = await generateAndReviewLoop({
|
|
@@ -6153,16 +6245,23 @@ const handler$1 = async (argv, logger) => {
|
|
|
6153
6245
|
factory,
|
|
6154
6246
|
parser,
|
|
6155
6247
|
agent: async (context, options) => {
|
|
6248
|
+
const parser = new JsonOutputParser();
|
|
6156
6249
|
const prompt = getPrompt({
|
|
6157
6250
|
template: options.prompt,
|
|
6158
6251
|
variables: CHANGELOG_PROMPT.inputVariables,
|
|
6159
6252
|
fallback: CHANGELOG_PROMPT,
|
|
6160
6253
|
});
|
|
6161
|
-
|
|
6254
|
+
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'header' and 'content', both strings.";
|
|
6255
|
+
const changelog = await executeChain({
|
|
6162
6256
|
llm,
|
|
6163
6257
|
prompt,
|
|
6164
|
-
variables: {
|
|
6258
|
+
variables: {
|
|
6259
|
+
summary: context,
|
|
6260
|
+
format_instructions: formatInstructions,
|
|
6261
|
+
},
|
|
6262
|
+
parser,
|
|
6165
6263
|
});
|
|
6264
|
+
return `${changelog.header}\n\n${changelog.content}`;
|
|
6166
6265
|
},
|
|
6167
6266
|
noResult: async () => {
|
|
6168
6267
|
if (config.range) {
|
|
@@ -6201,17 +6300,6 @@ const options$1 = {
|
|
|
6201
6300
|
alias: 'r',
|
|
6202
6301
|
description: 'Commit range e.g `HEAD~2:HEAD`',
|
|
6203
6302
|
},
|
|
6204
|
-
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
6205
|
-
openAIApiKey: {
|
|
6206
|
-
type: 'string',
|
|
6207
|
-
description: 'OpenAI API Key',
|
|
6208
|
-
conflicts: 'huggingFaceHubApiKey',
|
|
6209
|
-
},
|
|
6210
|
-
huggingFaceHubApiKey: {
|
|
6211
|
-
type: 'string',
|
|
6212
|
-
description: 'HuggingFace Hub API Key',
|
|
6213
|
-
conflicts: 'openAIApiKey',
|
|
6214
|
-
},
|
|
6215
6303
|
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
6216
6304
|
prompt: {
|
|
6217
6305
|
type: 'string',
|
|
@@ -6232,14 +6320,6 @@ const options$1 = {
|
|
|
6232
6320
|
type: 'string',
|
|
6233
6321
|
description: 'Prompt for summarizing large files',
|
|
6234
6322
|
},
|
|
6235
|
-
ignoredFiles: {
|
|
6236
|
-
type: 'array',
|
|
6237
|
-
description: 'Ignored files',
|
|
6238
|
-
},
|
|
6239
|
-
ignoredExtensions: {
|
|
6240
|
-
type: 'array',
|
|
6241
|
-
description: 'Ignored extensions',
|
|
6242
|
-
},
|
|
6243
6323
|
};
|
|
6244
6324
|
const builder$1 = (yargs) => {
|
|
6245
6325
|
return yargs.options(options$1);
|