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.js CHANGED
@@ -7,30 +7,30 @@ var yargs = require('yargs');
7
7
  var fs = require('fs');
8
8
  var prompts$1 = require('@inquirer/prompts');
9
9
  var chalk = require('chalk');
10
+ var ini = require('ini');
10
11
  var os = require('os');
11
12
  var path = require('path');
12
- var ini = require('ini');
13
13
  var Ajv = require('ajv');
14
14
  var ora = require('ora');
15
15
  var now = require('performance-now');
16
16
  var prettyMilliseconds = require('pretty-ms');
17
+ var output_parsers = require('@langchain/core/output_parsers');
18
+ var ollama = require('@langchain/ollama');
19
+ var openai = require('@langchain/openai');
17
20
  var pQueue = require('p-queue');
18
21
  var documents = require('@langchain/core/documents');
19
22
  var outputs = require('@langchain/core/outputs');
20
23
  var manager = require('@langchain/core/callbacks/manager');
21
24
  var runnables = require('@langchain/core/runnables');
22
25
  var base = require('@langchain/core/language_models/base');
23
- var output_parsers = require('@langchain/core/output_parsers');
24
26
  require('@langchain/core/messages');
25
27
  require('@langchain/core/memory');
26
28
  require('@langchain/core/chat_history');
27
29
  require('@langchain/core/utils/tiktoken');
28
- var openai = require('@langchain/openai');
29
30
  require('@langchain/core/utils/async_caller');
30
31
  require('@langchain/core/utils/env');
31
32
  require('@langchain/core/utils/json_patch');
32
33
  var diff = require('diff');
33
- var ollama = require('@langchain/ollama');
34
34
  var minimatch = require('minimatch');
35
35
  var simpleGit = require('simple-git');
36
36
  var tiktoken = require('tiktoken');
@@ -54,9 +54,9 @@ function _interopNamespaceDefault(e) {
54
54
  }
55
55
 
56
56
  var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
57
+ var ini__namespace = /*#__PURE__*/_interopNamespaceDefault(ini);
57
58
  var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
58
59
  var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
59
- var ini__namespace = /*#__PURE__*/_interopNamespaceDefault(ini);
60
60
 
61
61
  /**
62
62
  * Returns a new object with all undefined keys removed
@@ -128,9 +128,9 @@ function getDefaultServiceApiKey(config) {
128
128
  }
129
129
  const DEFAULT_OPENAI_LLM_SERVICE = {
130
130
  provider: 'openai',
131
- model: 'gpt-4-turbo',
131
+ model: 'gpt-4',
132
132
  tokenLimit: 2024,
133
- temperature: 0.4,
133
+ temperature: 0.32,
134
134
  authentication: {
135
135
  type: 'APIKey',
136
136
  credentials: {
@@ -280,12 +280,35 @@ function loadEnvConfig(config) {
280
280
  if (envValue === undefined) {
281
281
  return;
282
282
  }
283
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
284
- // @ts-ignore
285
- envConfig[key] = envValue;
283
+ if (key === 'COCO_SERVICE_PROVIDER' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
284
+ // NOTE: We want to ensure that the service object is always defined
285
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
286
+ // @ts-ignore
287
+ envConfig.service = envConfig.service || {};
288
+ handleServiceEnvVar(envConfig.service, key, envValue);
289
+ }
290
+ else {
291
+ envConfig[key] = envValue;
292
+ }
286
293
  });
287
294
  return { ...config, ...removeUndefined(envConfig) };
288
295
  }
296
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
+ function handleServiceEnvVar(service, key, value) {
298
+ switch (key) {
299
+ case 'COCO_SERVICE_PROVIDER':
300
+ service.provider = value;
301
+ break;
302
+ case 'COCO_SERVICE_MODEL':
303
+ service.model = value;
304
+ break;
305
+ case 'OPEN_AI_KEY':
306
+ if (service.provider === 'openai') {
307
+ service.fields = { apiKey: value };
308
+ }
309
+ break;
310
+ }
311
+ }
289
312
  function parseEnvValue(key, value) {
290
313
  switch (true) {
291
314
  // Handle undefined values
@@ -364,7 +387,12 @@ function loadGitConfig(config) {
364
387
  if (fs__namespace.existsSync(gitConfigPath)) {
365
388
  const gitConfigRaw = fs__namespace.readFileSync(gitConfigPath, 'utf-8');
366
389
  const gitConfigParsed = ini__namespace.parse(gitConfigRaw);
367
- const service = gitConfigParsed.coco?.service || config.service;
390
+ const gitServiceAlias = gitConfigParsed.coco?.service;
391
+ let service = config.service;
392
+ if (gitServiceAlias) {
393
+ const gitServiceConfig = getDefaultServiceConfigFromAlias(gitServiceAlias);
394
+ service = parseServiceConfig$1(gitServiceConfig || config.service);
395
+ }
368
396
  config = {
369
397
  ...config,
370
398
  service: service,
@@ -379,6 +407,28 @@ function loadGitConfig(config) {
379
407
  }
380
408
  return removeUndefined(config);
381
409
  }
410
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
411
+ function parseServiceConfig$1(service) {
412
+ if (!service)
413
+ return undefined;
414
+ switch (service.provider) {
415
+ case 'openai':
416
+ return {
417
+ provider: 'openai',
418
+ model: service.model,
419
+ fields: { apiKey: service.apiKey },
420
+ };
421
+ case 'ollama':
422
+ return {
423
+ provider: 'ollama',
424
+ model: service.model,
425
+ endpoint: service.endpoint,
426
+ fields: service.fields,
427
+ };
428
+ default:
429
+ return undefined;
430
+ }
431
+ }
382
432
  /**
383
433
  * Appends the provided configuration to a git config file.
384
434
  *
@@ -1753,10 +1803,42 @@ function loadXDGConfig(config) {
1753
1803
  const xdgConfigPath = path__namespace.join(xdgConfigHome, 'coco', 'config.json');
1754
1804
  if (fs__namespace.existsSync(xdgConfigPath)) {
1755
1805
  const xdgConfig = JSON.parse(fs__namespace.readFileSync(xdgConfigPath, 'utf-8'));
1756
- config = { ...config, ...xdgConfig };
1806
+ const service = parseServiceConfig(xdgConfig.service || config.service);
1807
+ config = {
1808
+ ...config,
1809
+ ...xdgConfig,
1810
+ service: service
1811
+ };
1757
1812
  }
1758
1813
  return config;
1759
1814
  }
1815
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1816
+ function parseServiceConfig(service) {
1817
+ if (!service)
1818
+ return undefined;
1819
+ switch (service.provider) {
1820
+ case 'openai':
1821
+ return {
1822
+ provider: 'openai',
1823
+ model: service.model,
1824
+ authentication: {
1825
+ type: 'APIKey',
1826
+ credentials: {
1827
+ apiKey: service.apiKey
1828
+ }
1829
+ }
1830
+ };
1831
+ case 'ollama':
1832
+ return {
1833
+ provider: 'ollama',
1834
+ model: service.model,
1835
+ endpoint: service.endpoint,
1836
+ fields: service.fields
1837
+ };
1838
+ default:
1839
+ return undefined;
1840
+ }
1841
+ }
1760
1842
 
1761
1843
  /**
1762
1844
  * Load application config
@@ -1854,6 +1936,66 @@ function commandExecutor(handler) {
1854
1936
  };
1855
1937
  }
1856
1938
 
1939
+ const executeChain = async ({ llm, prompt, variables, parser, }) => {
1940
+ if (!llm || !prompt || !variables) {
1941
+ throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
1942
+ }
1943
+ const chain = prompt.pipe(llm).pipe(parser);
1944
+ let res;
1945
+ try {
1946
+ res = await chain.invoke(variables);
1947
+ }
1948
+ catch (error) {
1949
+ if (error instanceof Error) {
1950
+ throw new Error(`LLMChain call error: ${error.message}`);
1951
+ }
1952
+ }
1953
+ if (!res) {
1954
+ throw new Error('Empty response from LLMChain call');
1955
+ }
1956
+ return res;
1957
+ };
1958
+
1959
+ /**
1960
+ * Get LLM Model Based on Configuration
1961
+ *
1962
+ * @param fields
1963
+ * @param configuration
1964
+ * @returns LLM Model
1965
+ */
1966
+ function getLlm(provider, model, config) {
1967
+ if (!model) {
1968
+ throw new Error(`Invalid LLM Service: ${provider}/${model}`);
1969
+ }
1970
+ switch (provider) {
1971
+ case 'ollama':
1972
+ return new ollama.ChatOllama({
1973
+ baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
1974
+ maxConcurrency: config.service.maxConcurrent,
1975
+ model,
1976
+ });
1977
+ case 'openai':
1978
+ default:
1979
+ const openAiModel = new openai.ChatOpenAI({
1980
+ openAIApiKey: getApiKeyForModel(config),
1981
+ model,
1982
+ temperature: config.service.temperature || 0.2,
1983
+ });
1984
+ return openAiModel;
1985
+ }
1986
+ }
1987
+
1988
+ function getPrompt({ template, variables, fallback }) {
1989
+ if (!template && !fallback)
1990
+ throw new Error('Must provide either a template or a fallback');
1991
+ return (template
1992
+ ? new prompts.PromptTemplate({
1993
+ template,
1994
+ inputVariables: variables,
1995
+ })
1996
+ : fallback);
1997
+ }
1998
+
1857
1999
  /**
1858
2000
  * Extract the path from a file path string.
1859
2001
  * @param {string} filePath - The full file path.
@@ -5437,64 +5579,15 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
5437
5579
  return summary;
5438
5580
  }
5439
5581
 
5440
- const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
5441
- 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:
5442
-
5443
- - Write concisely using an informal tone
5444
- - DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
5445
- - DO NOT use specific names or files from the code
5446
- - DO NOT include any diffs or file changes in the commit message
5447
- - Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
5448
- - ONLY respond with the resulting commit message.
5449
-
5450
- --------
5451
- {summary}
5452
- --------
5453
-
5454
- COMMIT MESSAGE:`;
5455
- const inputVariables$1 = ['summary'];
5456
- const COMMIT_PROMPT = new prompts.PromptTemplate({
5457
- template: template$1,
5458
- inputVariables: inputVariables$1,
5459
- });
5460
-
5461
5582
  /**
5462
- * Get LLM Model Based on Configuration
5583
+ * Creates a commit with the specified commit message.
5463
5584
  *
5464
- * @param fields
5465
- * @param configuration
5466
- * @returns LLM Model
5585
+ * @param message The commit message.
5586
+ * @param git The SimpleGit instance.
5587
+ * @returns A Promise that resolves to the CommitResult.
5467
5588
  */
5468
- function getLlm(provider, model, config) {
5469
- if (!model) {
5470
- throw new Error(`Invalid LLM Service: ${provider}/${model}`);
5471
- }
5472
- switch (provider) {
5473
- case 'ollama':
5474
- return new ollama.ChatOllama({
5475
- baseUrl: DEFAULT_OLLAMA_LLM_SERVICE.endpoint,
5476
- maxConcurrency: config.service.maxConcurrent,
5477
- model,
5478
- });
5479
- case 'openai':
5480
- default:
5481
- const openAiModel = new openai.ChatOpenAI({
5482
- openAIApiKey: getApiKeyForModel(config),
5483
- model,
5484
- });
5485
- return openAiModel;
5486
- }
5487
- }
5488
-
5489
- function getPrompt({ template, variables, fallback }) {
5490
- if (!template && !fallback)
5491
- throw new Error('Must provide either a template or a fallback');
5492
- return (template
5493
- ? new prompts.PromptTemplate({
5494
- template,
5495
- inputVariables: variables,
5496
- })
5497
- : fallback);
5589
+ async function createCommit(message, git) {
5590
+ return await git.commit(message);
5498
5591
  }
5499
5592
 
5500
5593
  /**
@@ -5626,37 +5719,66 @@ async function getChanges({ git, options }) {
5626
5719
  };
5627
5720
  }
5628
5721
 
5629
- async function noResult({ git, logger }) {
5630
- const { staged, unstaged, untracked } = await getChanges({ git });
5631
- const hasStaged = staged && staged.length > 0;
5632
- const hasUnstaged = unstaged && unstaged.length > 0;
5633
- const hasUntracked = untracked && untracked.length > 0;
5634
- if (hasStaged) {
5635
- logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
5636
- logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
5722
+ /**
5723
+ * Retrieves the SimpleGit instance for the repository.
5724
+ * @returns {SimpleGit} The SimpleGit instance.
5725
+ */
5726
+ const getRepo = () => {
5727
+ let git;
5728
+ try {
5729
+ git = simpleGit.simpleGit();
5637
5730
  }
5638
- else if (hasUnstaged || hasUntracked) {
5639
- logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
5640
- if (hasUnstaged) {
5641
- logger.log('\nChanges not staged for commit:', { color: 'yellow' });
5642
- logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
5643
- color: 'red',
5644
- });
5645
- }
5646
- if (hasUntracked) {
5647
- logger.log('\nUntracked changes:', { color: 'yellow' });
5648
- logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
5649
- color: 'red',
5650
- });
5651
- }
5731
+ catch (e) {
5732
+ console.log('Error initializing git repo', e);
5733
+ process.exit(1);
5652
5734
  }
5653
- else {
5654
- logger.log('No repo changes detected. 👀', { color: 'blue' });
5735
+ return git;
5736
+ };
5737
+
5738
+ const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
5739
+ 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:
5740
+
5741
+ - Write concisely using an informal tone
5742
+ - DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
5743
+ - DO NOT use specific names or files from the code
5744
+ - DO NOT include any diffs or file changes in the commit message
5745
+ - Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
5746
+
5747
+ {format_instructions}
5748
+
5749
+ """{summary}"""`;
5750
+ const inputVariables$1 = ['summary', 'format_instructions'];
5751
+ const COMMIT_PROMPT = new prompts.PromptTemplate({
5752
+ template: template$1,
5753
+ inputVariables: inputVariables$1,
5754
+ });
5755
+
5756
+ /**
5757
+ * Verify template string contains all required input variables
5758
+ *
5759
+ * @param text template string
5760
+ * @param inputVariables template variables
5761
+ * @returns boolean or error message
5762
+ */
5763
+ function validatePromptTemplate(text, inputVariables) {
5764
+ if (!text) {
5765
+ return 'Prompt template cannot be empty';
5655
5766
  }
5767
+ if (!inputVariables.some((entry) => text.includes(entry))) {
5768
+ return ('Prompt template must include at least one of the following input variables: ' +
5769
+ inputVariables.map((value) => `{${value}}`).join(', '));
5770
+ }
5771
+ return true;
5656
5772
  }
5657
5773
 
5658
- function logResult(label, result) {
5659
- console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
5774
+ async function editPrompt(options) {
5775
+ return await prompts$1.editor({
5776
+ message: 'Edit the prompt',
5777
+ default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
5778
+ waitForUseInput: false,
5779
+ postfix: 'Press ENTER to continue',
5780
+ validate: (text) => validatePromptTemplate(text, COMMIT_PROMPT.inputVariables),
5781
+ });
5660
5782
  }
5661
5783
 
5662
5784
  async function editResult(result, options) {
@@ -5718,32 +5840,8 @@ async function getUserReviewDecision({ label, descriptions, enableRetry = true,
5718
5840
  }));
5719
5841
  }
5720
5842
 
5721
- /**
5722
- * Verify template string contains all required input variables
5723
- *
5724
- * @param text template string
5725
- * @param inputVariables template variables
5726
- * @returns boolean or error message
5727
- */
5728
- function validatePromptTemplate(text, inputVariables) {
5729
- if (!text) {
5730
- return 'Prompt template cannot be empty';
5731
- }
5732
- if (!inputVariables.some((entry) => text.includes(entry))) {
5733
- return ('Prompt template must include at least one of the following input variables: ' +
5734
- inputVariables.map((value) => `{${value}}`).join(', '));
5735
- }
5736
- return true;
5737
- }
5738
-
5739
- async function editPrompt(options) {
5740
- return await prompts$1.editor({
5741
- message: 'Edit the prompt',
5742
- default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
5743
- waitForUseInput: false,
5744
- postfix: 'Press ENTER to continue',
5745
- validate: (text) => validatePromptTemplate(text, COMMIT_PROMPT.inputVariables),
5746
- });
5843
+ function logResult(label, result) {
5844
+ console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
5747
5845
  }
5748
5846
 
5749
5847
  async function generateAndReviewLoop({ label, factory, parser, noResult, agent, options, }) {
@@ -5754,7 +5852,7 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
5754
5852
  let result = '';
5755
5853
  const changes = await factory();
5756
5854
  // if we don't have any changes, bail.
5757
- if (!changes || !changes.length) {
5855
+ if (!changes || !Object.keys(changes).length) {
5758
5856
  await noResult(options);
5759
5857
  }
5760
5858
  while (continueLoop) {
@@ -5789,7 +5887,7 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
5789
5887
  logResult(label, result);
5790
5888
  const reviewAnswer = await getUserReviewDecision({
5791
5889
  label,
5792
- ...options?.review || {},
5890
+ ...(options?.review || {}),
5793
5891
  });
5794
5892
  if (reviewAnswer === 'cancel') {
5795
5893
  process.exit(0);
@@ -5821,26 +5919,6 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
5821
5919
  return result;
5822
5920
  }
5823
5921
 
5824
- const executeChain = async ({ llm, prompt, variables }) => {
5825
- if (!llm || !prompt || !variables) {
5826
- throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
5827
- }
5828
- const chain = prompt.pipe(llm);
5829
- let res;
5830
- try {
5831
- res = await chain.invoke(variables);
5832
- }
5833
- catch (error) {
5834
- if (error instanceof Error) {
5835
- throw new Error(`LLMChain call error: ${error.message}`);
5836
- }
5837
- }
5838
- if (!res) {
5839
- throw new Error('Empty response from LLMChain call');
5840
- }
5841
- return res.content;
5842
- };
5843
-
5844
5922
  const logSuccess = () => {
5845
5923
  console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
5846
5924
  };
@@ -5864,22 +5942,6 @@ async function handleResult({ result, mode, interactiveHandler }) {
5864
5942
  process.exit(0);
5865
5943
  }
5866
5944
 
5867
- /**
5868
- * Retrieves the SimpleGit instance for the repository.
5869
- * @returns {SimpleGit} The SimpleGit instance.
5870
- */
5871
- const getRepo = () => {
5872
- let git;
5873
- try {
5874
- git = simpleGit.simpleGit();
5875
- }
5876
- catch (e) {
5877
- console.log('Error initializing git repo', e);
5878
- process.exit(1);
5879
- }
5880
- return git;
5881
- };
5882
-
5883
5945
  /**
5884
5946
  * Retrieves a TikToken for the specified model.
5885
5947
  *
@@ -5902,15 +5964,33 @@ const getTokenCounter = async (modelName) => {
5902
5964
  });
5903
5965
  };
5904
5966
 
5905
- /**
5906
- * Creates a commit with the specified commit message.
5907
- *
5908
- * @param message The commit message.
5909
- * @param git The SimpleGit instance.
5910
- * @returns A Promise that resolves to the CommitResult.
5911
- */
5912
- async function createCommit(message, git) {
5913
- return await git.commit(message);
5967
+ async function noResult({ git, logger }) {
5968
+ const { staged, unstaged, untracked } = await getChanges({ git });
5969
+ const hasStaged = staged && staged.length > 0;
5970
+ const hasUnstaged = unstaged && unstaged.length > 0;
5971
+ const hasUntracked = untracked && untracked.length > 0;
5972
+ if (hasStaged) {
5973
+ logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
5974
+ logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
5975
+ }
5976
+ else if (hasUnstaged || hasUntracked) {
5977
+ logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
5978
+ if (hasUnstaged) {
5979
+ logger.log('\nChanges not staged for commit:', { color: 'yellow' });
5980
+ logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
5981
+ color: 'red',
5982
+ });
5983
+ }
5984
+ if (hasUntracked) {
5985
+ logger.log('\nUntracked changes:', { color: 'yellow' });
5986
+ logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
5987
+ color: 'red',
5988
+ });
5989
+ }
5990
+ }
5991
+ else {
5992
+ logger.log('No repo changes detected. 👀', { color: 'blue' });
5993
+ }
5914
5994
  }
5915
5995
 
5916
5996
  const handler$2 = async (argv, logger) => {
@@ -5959,16 +6039,20 @@ const handler$2 = async (argv, logger) => {
5959
6039
  factory,
5960
6040
  parser,
5961
6041
  agent: async (context, options) => {
6042
+ const parser = new output_parsers.JsonOutputParser();
5962
6043
  const prompt = getPrompt({
5963
6044
  template: options.prompt,
5964
6045
  variables: COMMIT_PROMPT.inputVariables,
5965
6046
  fallback: COMMIT_PROMPT,
5966
6047
  });
5967
- return await executeChain({
6048
+ const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body'.";
6049
+ const commitMsg = await executeChain({
5968
6050
  llm,
5969
6051
  prompt,
5970
- variables: { summary: context },
6052
+ variables: { summary: context, format_instructions: formatInstructions },
6053
+ parser,
5971
6054
  });
6055
+ return `${commitMsg.title}\n\n${commitMsg.body}`;
5972
6056
  },
5973
6057
  noResult: async () => {
5974
6058
  await noResult({ git, logger });
@@ -6041,20 +6125,6 @@ var commit = {
6041
6125
  options: options$2,
6042
6126
  };
6043
6127
 
6044
- const template = `Write informative git changelog, in the imperative, based on a series of individual messages.
6045
-
6046
- - Typically a hyphen or asterisk is used for the bullet
6047
- - Summarize dependency updates
6048
-
6049
- """{summary}"""
6050
-
6051
- Changelog:`;
6052
- const inputVariables = ['summary'];
6053
- const CHANGELOG_PROMPT = new prompts.PromptTemplate({
6054
- template,
6055
- inputVariables,
6056
- });
6057
-
6058
6128
  /**
6059
6129
  * Retrieves the commit log range between two specified commits.
6060
6130
  *
@@ -6068,7 +6138,7 @@ async function getCommitLogRange(from, to, { noMerges, git }) {
6068
6138
  try {
6069
6139
  const logOptions = { from: `${from}^1`, to, '--no-merges': noMerges };
6070
6140
  const commitLog = await git.log(logOptions);
6071
- return commitLog.all.map(({ message, date, body, author_name }) => `[${date}] ${message}\n${body}\n - ${author_name}`);
6141
+ return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
6072
6142
  }
6073
6143
  catch (error) {
6074
6144
  // If there's an error, handle it appropriately
@@ -6139,6 +6209,20 @@ async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main
6139
6209
  return [];
6140
6210
  }
6141
6211
 
6212
+ const template = `Write informative git changelog, in the imperative, based on a series of individual messages.
6213
+
6214
+ - Include the git commit hash as reference for each change, including just the first 7 characters
6215
+ - Logically group changes, and if necessary, summarize dependency updates
6216
+
6217
+ {format_instructions}
6218
+
6219
+ """{summary}"""`;
6220
+ const inputVariables = ['format_instructions', 'summary'];
6221
+ const CHANGELOG_PROMPT = new prompts.PromptTemplate({
6222
+ template,
6223
+ inputVariables,
6224
+ });
6225
+
6142
6226
  const handler$1 = async (argv, logger) => {
6143
6227
  const config = loadConfig(argv);
6144
6228
  const git = getRepo();
@@ -6154,19 +6238,27 @@ const handler$1 = async (argv, logger) => {
6154
6238
  logger.log(LOGO);
6155
6239
  }
6156
6240
  async function factory() {
6241
+ const branchName = await getCurrentBranchName({ git });
6157
6242
  if (config.range && config.range.includes(':')) {
6158
6243
  const [from, to] = config.range.split(':');
6159
6244
  if (!from || !to) {
6160
6245
  logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
6161
6246
  process.exit(1);
6162
6247
  }
6163
- return await getCommitLogRange(from, to, { git, noMerges: true });
6248
+ return {
6249
+ branch: branchName,
6250
+ commits: await getCommitLogRange(from, to, { git, noMerges: true }),
6251
+ };
6164
6252
  }
6165
6253
  logger.verbose(`No range provided. Defaulting to current branch`, { color: 'yellow' });
6166
- return await getCommitLogCurrentBranch({ git, logger });
6254
+ return {
6255
+ branch: branchName,
6256
+ commits: await getCommitLogCurrentBranch({ git, logger }),
6257
+ };
6167
6258
  }
6168
- async function parser(messages) {
6169
- const result = messages.join('\n');
6259
+ async function parser({ branch, commits }) {
6260
+ const result = `## ${branch}\n\n${commits.map((commit) => `${commit}`).join('\n\n\n')}`;
6261
+ console.log({ result });
6170
6262
  return result;
6171
6263
  }
6172
6264
  const changelogMsg = await generateAndReviewLoop({
@@ -6174,16 +6266,23 @@ const handler$1 = async (argv, logger) => {
6174
6266
  factory,
6175
6267
  parser,
6176
6268
  agent: async (context, options) => {
6269
+ const parser = new output_parsers.JsonOutputParser();
6177
6270
  const prompt = getPrompt({
6178
6271
  template: options.prompt,
6179
6272
  variables: CHANGELOG_PROMPT.inputVariables,
6180
6273
  fallback: CHANGELOG_PROMPT,
6181
6274
  });
6182
- return await executeChain({
6275
+ const formatInstructions = "Respond with a valid JSON object, containing two fields: 'header' and 'content', both strings.";
6276
+ const changelog = await executeChain({
6183
6277
  llm,
6184
6278
  prompt,
6185
- variables: { summary: context },
6279
+ variables: {
6280
+ summary: context,
6281
+ format_instructions: formatInstructions,
6282
+ },
6283
+ parser,
6186
6284
  });
6285
+ return `${changelog.header}\n\n${changelog.content}`;
6187
6286
  },
6188
6287
  noResult: async () => {
6189
6288
  if (config.range) {
@@ -6222,17 +6321,6 @@ const options$1 = {
6222
6321
  alias: 'r',
6223
6322
  description: 'Commit range e.g `HEAD~2:HEAD`',
6224
6323
  },
6225
- model: { type: 'string', description: 'LLM/Model-Name' },
6226
- openAIApiKey: {
6227
- type: 'string',
6228
- description: 'OpenAI API Key',
6229
- conflicts: 'huggingFaceHubApiKey',
6230
- },
6231
- huggingFaceHubApiKey: {
6232
- type: 'string',
6233
- description: 'HuggingFace Hub API Key',
6234
- conflicts: 'openAIApiKey',
6235
- },
6236
6324
  tokenLimit: { type: 'number', description: 'Token limit' },
6237
6325
  prompt: {
6238
6326
  type: 'string',
@@ -6253,14 +6341,6 @@ const options$1 = {
6253
6341
  type: 'string',
6254
6342
  description: 'Prompt for summarizing large files',
6255
6343
  },
6256
- ignoredFiles: {
6257
- type: 'array',
6258
- description: 'Ignored files',
6259
- },
6260
- ignoredExtensions: {
6261
- type: 'array',
6262
- description: 'Ignored extensions',
6263
- },
6264
6344
  };
6265
6345
  const builder$1 = (yargs) => {
6266
6346
  return yargs.options(options$1);