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.
@@ -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-turbo',
110
+ model: 'gpt-4',
111
111
  tokenLimit: 2024,
112
- temperature: 0.4,
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
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
263
- // @ts-ignore
264
- envConfig[key] = envValue;
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 service = gitConfigParsed.coco?.service || config.service;
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
- config = { ...config, ...xdgConfig };
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
- * Get LLM Model Based on Configuration
5562
+ * Creates a commit with the specified commit message.
5442
5563
  *
5443
- * @param fields
5444
- * @param configuration
5445
- * @returns LLM Model
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 getLlm(provider, model, config) {
5448
- if (!model) {
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
- async function noResult({ git, logger }) {
5609
- const { staged, unstaged, untracked } = await getChanges({ git });
5610
- const hasStaged = staged && staged.length > 0;
5611
- const hasUnstaged = unstaged && unstaged.length > 0;
5612
- const hasUntracked = untracked && untracked.length > 0;
5613
- if (hasStaged) {
5614
- logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
5615
- logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
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
- else if (hasUnstaged || hasUntracked) {
5618
- logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
5619
- if (hasUnstaged) {
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
- else {
5633
- logger.log('No repo changes detected. 👀', { color: 'blue' });
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 logResult(label, result) {
5638
- console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
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
- * Verify template string contains all required input variables
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
- * Creates a commit with the specified commit message.
5886
- *
5887
- * @param message The commit message.
5888
- * @param git The SimpleGit instance.
5889
- * @returns A Promise that resolves to the CommitResult.
5890
- */
5891
- async function createCommit(message, git) {
5892
- return await git.commit(message);
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
- return await executeChain({
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 await getCommitLogRange(from, to, { git, noMerges: true });
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 await getCommitLogCurrentBranch({ git, logger });
6233
+ return {
6234
+ branch: branchName,
6235
+ commits: await getCommitLogCurrentBranch({ git, logger }),
6236
+ };
6146
6237
  }
6147
- async function parser(messages) {
6148
- const result = messages.join('\n');
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
- return await executeChain({
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: { summary: context },
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);