git-coco 0.14.7 → 0.14.9
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 +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.mjs +215 -66
- package/dist/index.js +214 -65
- package/package.json +6 -6
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -150,6 +150,7 @@ interface BaseCommandOptions extends BaseArgvOptions {
|
|
|
150
150
|
interface ChangelogOptions extends BaseCommandOptions {
|
|
151
151
|
range: string;
|
|
152
152
|
branch: string;
|
|
153
|
+
sinceLastTag: boolean;
|
|
153
154
|
}
|
|
154
155
|
type ChangelogArgv = Arguments<ChangelogOptions>;
|
|
155
156
|
|
|
@@ -166,6 +167,7 @@ interface CommitOptions extends BaseCommandOptions {
|
|
|
166
167
|
openInEditor: boolean;
|
|
167
168
|
ignoredFiles: string[];
|
|
168
169
|
ignoredExtensions: string[];
|
|
170
|
+
withPreviousCommits: number;
|
|
169
171
|
}
|
|
170
172
|
type CommitArgv = Arguments<CommitOptions>;
|
|
171
173
|
|
|
@@ -239,12 +241,14 @@ interface SpinnerOptions {
|
|
|
239
241
|
}
|
|
240
242
|
interface Config {
|
|
241
243
|
verbose?: boolean;
|
|
244
|
+
silent?: boolean;
|
|
242
245
|
}
|
|
243
246
|
declare class Logger {
|
|
244
247
|
private config;
|
|
245
248
|
private timerStart;
|
|
246
249
|
private spinner;
|
|
247
250
|
constructor(config: Config);
|
|
251
|
+
setConfig(config: Config): Logger;
|
|
248
252
|
log(message: string, options?: LoggerOptions): Logger;
|
|
249
253
|
verbose(message: string, options?: LoggerOptions): Logger;
|
|
250
254
|
startTimer(): Logger;
|
package/dist/index.esm.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import prettyMilliseconds from 'pretty-ms';
|
|
|
18
18
|
import { ChatAnthropic } from '@langchain/anthropic';
|
|
19
19
|
import { ChatOllama } from '@langchain/ollama';
|
|
20
20
|
import { ChatOpenAI } from '@langchain/openai';
|
|
21
|
-
import { JsonOutputParser, BaseOutputParser } from '@langchain/core/output_parsers';
|
|
21
|
+
import { JsonOutputParser, BaseOutputParser, StringOutputParser } from '@langchain/core/output_parsers';
|
|
22
22
|
import { simpleGit } from 'simple-git';
|
|
23
23
|
import pQueue from 'p-queue';
|
|
24
24
|
import { Document, BaseDocumentTransformer } from '@langchain/core/documents';
|
|
@@ -55,7 +55,7 @@ import * as readline from 'readline';
|
|
|
55
55
|
/**
|
|
56
56
|
* Current build version from package.json
|
|
57
57
|
*/
|
|
58
|
-
const BUILD_VERSION = "0.14.
|
|
58
|
+
const BUILD_VERSION = "0.14.9";
|
|
59
59
|
|
|
60
60
|
const isInteractive = (config) => {
|
|
61
61
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -1769,7 +1769,17 @@ class Logger {
|
|
|
1769
1769
|
this.config = config;
|
|
1770
1770
|
this.spinner = null;
|
|
1771
1771
|
}
|
|
1772
|
+
setConfig(config) {
|
|
1773
|
+
this.config = {
|
|
1774
|
+
...this.config,
|
|
1775
|
+
...config,
|
|
1776
|
+
};
|
|
1777
|
+
return this;
|
|
1778
|
+
}
|
|
1772
1779
|
log(message, options = { color: 'blue' }) {
|
|
1780
|
+
if (this.config?.silent) {
|
|
1781
|
+
return this;
|
|
1782
|
+
}
|
|
1773
1783
|
let outputMessage = message;
|
|
1774
1784
|
if (options.color) {
|
|
1775
1785
|
outputMessage = chalk[options.color](outputMessage);
|
|
@@ -1778,7 +1788,7 @@ class Logger {
|
|
|
1778
1788
|
return this;
|
|
1779
1789
|
}
|
|
1780
1790
|
verbose(message, options = {}) {
|
|
1781
|
-
if (!this.config?.verbose) {
|
|
1791
|
+
if (!this.config?.verbose || this.config?.silent) {
|
|
1782
1792
|
return this;
|
|
1783
1793
|
}
|
|
1784
1794
|
this.log(message, options);
|
|
@@ -1789,13 +1799,11 @@ class Logger {
|
|
|
1789
1799
|
return this;
|
|
1790
1800
|
}
|
|
1791
1801
|
stopTimer(message, options = { color: 'yellow' }) {
|
|
1792
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
1802
|
+
if (!this.config?.verbose || !this.timerStart || this.config?.silent) {
|
|
1793
1803
|
return this;
|
|
1794
1804
|
}
|
|
1795
1805
|
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
1796
|
-
let outputMessage = message
|
|
1797
|
-
? `${message} (⏲ ${elapsedTime})`
|
|
1798
|
-
: `⏲ ${elapsedTime}`;
|
|
1806
|
+
let outputMessage = message ? `${message} (⏲ ${elapsedTime})` : `⏲ ${elapsedTime}`;
|
|
1799
1807
|
if (options.color) {
|
|
1800
1808
|
outputMessage = chalk[options.color](outputMessage);
|
|
1801
1809
|
}
|
|
@@ -1803,11 +1811,17 @@ class Logger {
|
|
|
1803
1811
|
return this;
|
|
1804
1812
|
}
|
|
1805
1813
|
startSpinner(message, options = { color: 'green' }) {
|
|
1814
|
+
if (this.config?.silent) {
|
|
1815
|
+
return this;
|
|
1816
|
+
}
|
|
1806
1817
|
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
1807
1818
|
this.spinner = ora(spinnerMessage).start();
|
|
1808
1819
|
return this;
|
|
1809
1820
|
}
|
|
1810
1821
|
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
1822
|
+
if (this.config?.silent) {
|
|
1823
|
+
return this;
|
|
1824
|
+
}
|
|
1811
1825
|
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
1812
1826
|
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
1813
1827
|
this.spinner = null;
|
|
@@ -1846,6 +1860,12 @@ const options$4 = {
|
|
|
1846
1860
|
alias: 'b',
|
|
1847
1861
|
description: 'Target branch to compare against',
|
|
1848
1862
|
},
|
|
1863
|
+
sinceLastTag: {
|
|
1864
|
+
type: 'boolean',
|
|
1865
|
+
alias: 't',
|
|
1866
|
+
description: 'Generate changelog for all commits since the last tag',
|
|
1867
|
+
default: false,
|
|
1868
|
+
},
|
|
1849
1869
|
i: {
|
|
1850
1870
|
type: 'boolean',
|
|
1851
1871
|
alias: 'interactive',
|
|
@@ -1898,6 +1918,7 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
1898
1918
|
? new PromptTemplate({
|
|
1899
1919
|
template,
|
|
1900
1920
|
inputVariables: variables,
|
|
1921
|
+
templateFormat: 'mustache',
|
|
1901
1922
|
})
|
|
1902
1923
|
: fallback);
|
|
1903
1924
|
}
|
|
@@ -1928,6 +1949,35 @@ function extractTicketIdFromBranchName(branchName) {
|
|
|
1928
1949
|
return match ? match[0] : null;
|
|
1929
1950
|
}
|
|
1930
1951
|
|
|
1952
|
+
/**
|
|
1953
|
+
* Formats a commit log into a readable string format.
|
|
1954
|
+
*
|
|
1955
|
+
* @param commitLog - The commit log result containing an array of commit details.
|
|
1956
|
+
* @returns An array of formatted commit log strings.
|
|
1957
|
+
*
|
|
1958
|
+
* Each formatted string includes:
|
|
1959
|
+
* - The date of the commit in square brackets.
|
|
1960
|
+
* - The commit message.
|
|
1961
|
+
* - The commit body.
|
|
1962
|
+
* - The commit hash in parentheses.
|
|
1963
|
+
* - The author's name and email in angle brackets.
|
|
1964
|
+
*/
|
|
1965
|
+
const formatCommitLog = (commitLog) => {
|
|
1966
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
const getChangesSinceLastTag = async ({ git }) => {
|
|
1970
|
+
const tags = await git.tags();
|
|
1971
|
+
if (tags.all.length > 0) {
|
|
1972
|
+
const lastTag = tags.latest;
|
|
1973
|
+
const commitLog = await git.log({ from: lastTag });
|
|
1974
|
+
return formatCommitLog(commitLog);
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
return ['No tags found in the repository.'];
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1931
1981
|
/**
|
|
1932
1982
|
* Retrieves the commit log range between two specified commits.
|
|
1933
1983
|
*
|
|
@@ -2061,8 +2111,17 @@ const getRepo = () => {
|
|
|
2061
2111
|
return git;
|
|
2062
2112
|
};
|
|
2063
2113
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2114
|
+
/**
|
|
2115
|
+
* Template for generating git commit messages based on code changes
|
|
2116
|
+
*
|
|
2117
|
+
* Variables:
|
|
2118
|
+
* - summary: Contains the diff summary of staged changes
|
|
2119
|
+
* - format_instructions: Instructions for the output format (JSON with title and body)
|
|
2120
|
+
* - additional_context: Optional user-provided context to guide the commit message generation
|
|
2121
|
+
* - commit_history: Optional history of previous commits for context
|
|
2122
|
+
*/
|
|
2123
|
+
const template$4 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
2124
|
+
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary around 300 characters, the shorter and more concise the better.
|
|
2066
2125
|
|
|
2067
2126
|
Please follow the guidelines below when writing your commit message:
|
|
2068
2127
|
|
|
@@ -2074,13 +2133,16 @@ Please follow the guidelines below when writing your commit message:
|
|
|
2074
2133
|
|
|
2075
2134
|
{format_instructions}
|
|
2076
2135
|
|
|
2136
|
+
{commit_history}
|
|
2137
|
+
|
|
2077
2138
|
""""""
|
|
2078
2139
|
{summary}
|
|
2079
2140
|
""""""
|
|
2080
2141
|
|
|
2081
|
-
{
|
|
2142
|
+
{additional_context}
|
|
2082
2143
|
`;
|
|
2083
|
-
|
|
2144
|
+
// Define the variables that will be passed to the prompt template
|
|
2145
|
+
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history'];
|
|
2084
2146
|
const COMMIT_PROMPT = new PromptTemplate({
|
|
2085
2147
|
template: template$4,
|
|
2086
2148
|
inputVariables: inputVariables$3,
|
|
@@ -2246,9 +2308,16 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
2246
2308
|
result = '';
|
|
2247
2309
|
continue;
|
|
2248
2310
|
}
|
|
2311
|
+
// Only edit the result in interactive mode if approved
|
|
2312
|
+
result = await editResult(result, options);
|
|
2313
|
+
}
|
|
2314
|
+
else {
|
|
2315
|
+
// In non-interactive mode, we return the result as is to be output to stdout by the caller.
|
|
2316
|
+
const displayResult = reviewParser ? reviewParser(result, options) : result;
|
|
2317
|
+
// In non-interactive mode, ensure we return the properly formatted result
|
|
2318
|
+
result = displayResult;
|
|
2249
2319
|
}
|
|
2250
2320
|
// if we're here, we're done.
|
|
2251
|
-
result = await editResult(result, options);
|
|
2252
2321
|
continueLoop = false;
|
|
2253
2322
|
}
|
|
2254
2323
|
return result;
|
|
@@ -2271,7 +2340,8 @@ async function handleResult({ result, mode, interactiveModeCallback }) {
|
|
|
2271
2340
|
break;
|
|
2272
2341
|
case 'stdout':
|
|
2273
2342
|
default:
|
|
2274
|
-
|
|
2343
|
+
// Ensure we write the result to stdout in non-interactive mode
|
|
2344
|
+
process.stdout.write(result + '\n', 'utf8');
|
|
2275
2345
|
break;
|
|
2276
2346
|
}
|
|
2277
2347
|
process.exit(0);
|
|
@@ -2310,6 +2380,13 @@ const handler$4 = async (argv, logger) => {
|
|
|
2310
2380
|
}
|
|
2311
2381
|
async function factory() {
|
|
2312
2382
|
const branchName = await getCurrentBranchName({ git });
|
|
2383
|
+
if (config.sinceLastTag) {
|
|
2384
|
+
logger.verbose(`Generating commit log since the last tag`, { color: 'yellow' });
|
|
2385
|
+
return {
|
|
2386
|
+
branch: branchName,
|
|
2387
|
+
commits: await getChangesSinceLastTag({ git, logger }),
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2313
2390
|
if (config.range && config.range.includes(':')) {
|
|
2314
2391
|
const [from, to] = config.range.split(':');
|
|
2315
2392
|
if (!from || !to) {
|
|
@@ -2328,7 +2405,7 @@ const handler$4 = async (argv, logger) => {
|
|
|
2328
2405
|
commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
|
|
2329
2406
|
};
|
|
2330
2407
|
}
|
|
2331
|
-
logger.verbose(`No range or
|
|
2408
|
+
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, { color: 'yellow' });
|
|
2332
2409
|
return {
|
|
2333
2410
|
branch: branchName,
|
|
2334
2411
|
commits: await getCommitLogCurrentBranch({ git, logger }),
|
|
@@ -2400,7 +2477,7 @@ const handler$4 = async (argv, logger) => {
|
|
|
2400
2477
|
|
|
2401
2478
|
var changelog = {
|
|
2402
2479
|
command: command$4,
|
|
2403
|
-
desc: 'Generate a changelog from current or target branch
|
|
2480
|
+
desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
|
|
2404
2481
|
builder: builder$4,
|
|
2405
2482
|
handler: commandExecutor(handler$4),
|
|
2406
2483
|
options: options$4,
|
|
@@ -2438,6 +2515,12 @@ const options$3 = {
|
|
|
2438
2515
|
type: 'string',
|
|
2439
2516
|
alias: 'a',
|
|
2440
2517
|
},
|
|
2518
|
+
withPreviousCommits: {
|
|
2519
|
+
description: 'Include previous commits as context (specify number of commits, 0 for none)',
|
|
2520
|
+
type: 'number',
|
|
2521
|
+
default: 0,
|
|
2522
|
+
alias: 'p',
|
|
2523
|
+
},
|
|
2441
2524
|
};
|
|
2442
2525
|
const builder$3 = (yargs) => {
|
|
2443
2526
|
return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
|
|
@@ -6175,6 +6258,48 @@ async function getChanges({ git, options }) {
|
|
|
6175
6258
|
};
|
|
6176
6259
|
}
|
|
6177
6260
|
|
|
6261
|
+
/**
|
|
6262
|
+
* Format a single commit log entry into a readable string
|
|
6263
|
+
* @param commit - The commit log entry
|
|
6264
|
+
* @returns Formatted commit log string
|
|
6265
|
+
*/
|
|
6266
|
+
function formatSingleCommit(commit) {
|
|
6267
|
+
const { hash, date, message, body, author_name } = commit;
|
|
6268
|
+
const shortHash = hash.substring(0, 7);
|
|
6269
|
+
return `Commit: ${shortHash}
|
|
6270
|
+
Author: ${author_name}
|
|
6271
|
+
Date: ${date}
|
|
6272
|
+
Message: ${message}
|
|
6273
|
+
${body ? `\nDetails: ${body}` : ''}`;
|
|
6274
|
+
}
|
|
6275
|
+
|
|
6276
|
+
/**
|
|
6277
|
+
* Get the specified number of previous commits
|
|
6278
|
+
* @param options - Options for getting previous commits
|
|
6279
|
+
* @returns Formatted commit logs
|
|
6280
|
+
*/
|
|
6281
|
+
async function getPreviousCommits(options) {
|
|
6282
|
+
const { git, count = 1 } = options;
|
|
6283
|
+
if (count <= 0) {
|
|
6284
|
+
return '';
|
|
6285
|
+
}
|
|
6286
|
+
try {
|
|
6287
|
+
const logs = await git.log({ maxCount: count });
|
|
6288
|
+
if (!logs || logs.total === 0) {
|
|
6289
|
+
return '';
|
|
6290
|
+
}
|
|
6291
|
+
// Format the commit logs
|
|
6292
|
+
const formattedLogs = logs.all.map((commit) => {
|
|
6293
|
+
return formatSingleCommit(commit);
|
|
6294
|
+
}).join('\n\n');
|
|
6295
|
+
return formattedLogs;
|
|
6296
|
+
}
|
|
6297
|
+
catch (error) {
|
|
6298
|
+
console.error(`Error getting previous commits: ${error.message}`);
|
|
6299
|
+
return '';
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6178
6303
|
/**
|
|
6179
6304
|
* Retrieves a TikToken for the specified model.
|
|
6180
6305
|
*
|
|
@@ -6237,10 +6362,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
6237
6362
|
}
|
|
6238
6363
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
6239
6364
|
const llm = getLlm(provider, model, config);
|
|
6240
|
-
const INTERACTIVE = isInteractive(config);
|
|
6365
|
+
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
6241
6366
|
if (INTERACTIVE) {
|
|
6242
6367
|
logger.log(LOGO);
|
|
6243
6368
|
}
|
|
6369
|
+
else {
|
|
6370
|
+
logger.setConfig({ silent: true });
|
|
6371
|
+
}
|
|
6244
6372
|
async function factory() {
|
|
6245
6373
|
const changes = await getChanges({
|
|
6246
6374
|
git,
|
|
@@ -6285,14 +6413,30 @@ const handler$3 = async (argv, logger) => {
|
|
|
6285
6413
|
fallback: COMMIT_PROMPT,
|
|
6286
6414
|
});
|
|
6287
6415
|
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body', both strings.";
|
|
6288
|
-
|
|
6416
|
+
// Get additional context if provided
|
|
6417
|
+
let additional_context = '';
|
|
6418
|
+
if (argv.additional) {
|
|
6419
|
+
additional_context = `## Additional Context\n${argv.additional}`;
|
|
6420
|
+
}
|
|
6421
|
+
// Get commit history if requested
|
|
6422
|
+
let commit_history = '';
|
|
6423
|
+
if (argv.withPreviousCommits > 0) {
|
|
6424
|
+
const commitHistoryData = await getPreviousCommits({
|
|
6425
|
+
git,
|
|
6426
|
+
count: argv.withPreviousCommits
|
|
6427
|
+
});
|
|
6428
|
+
if (commitHistoryData) {
|
|
6429
|
+
commit_history = `## Commit History\n${commitHistoryData}`;
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6289
6432
|
const commitMsg = await executeChain({
|
|
6290
6433
|
llm,
|
|
6291
6434
|
prompt,
|
|
6292
6435
|
variables: {
|
|
6293
6436
|
summary: context,
|
|
6294
6437
|
format_instructions: formatInstructions,
|
|
6295
|
-
|
|
6438
|
+
additional_context: additional_context,
|
|
6439
|
+
commit_history: commit_history,
|
|
6296
6440
|
},
|
|
6297
6441
|
parser,
|
|
6298
6442
|
});
|
|
@@ -6816,40 +6960,11 @@ const builder$1 = (yargs) => {
|
|
|
6816
6960
|
return yargs.options(options$1).usage(getCommandUsageHeader(command$1));
|
|
6817
6961
|
};
|
|
6818
6962
|
|
|
6819
|
-
/**
|
|
6820
|
-
* Formats a commit log into a readable string format.
|
|
6821
|
-
*
|
|
6822
|
-
* @param commitLog - The commit log result containing an array of commit details.
|
|
6823
|
-
* @returns An array of formatted commit log strings.
|
|
6824
|
-
*
|
|
6825
|
-
* Each formatted string includes:
|
|
6826
|
-
* - The date of the commit in square brackets.
|
|
6827
|
-
* - The commit message.
|
|
6828
|
-
* - The commit body.
|
|
6829
|
-
* - The commit hash in parentheses.
|
|
6830
|
-
* - The author's name and email in angle brackets.
|
|
6831
|
-
*/
|
|
6832
|
-
const formatCommitLog = (commitLog) => {
|
|
6833
|
-
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6834
|
-
};
|
|
6835
|
-
|
|
6836
6963
|
const getChangesByTimestamp = async ({ since, git }) => {
|
|
6837
6964
|
const commitLog = await git.log({ '--since': since });
|
|
6838
6965
|
return formatCommitLog(commitLog);
|
|
6839
6966
|
};
|
|
6840
6967
|
|
|
6841
|
-
const getChangesSinceLastTag = async ({ git }) => {
|
|
6842
|
-
const tags = await git.tags();
|
|
6843
|
-
if (tags.all.length > 0) {
|
|
6844
|
-
const lastTag = tags.latest;
|
|
6845
|
-
const commitLog = await git.log({ from: lastTag });
|
|
6846
|
-
return formatCommitLog(commitLog);
|
|
6847
|
-
}
|
|
6848
|
-
else {
|
|
6849
|
-
return ['No tags found in the repository.'];
|
|
6850
|
-
}
|
|
6851
|
-
};
|
|
6852
|
-
|
|
6853
6968
|
async function noResult$1({ logger }) {
|
|
6854
6969
|
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
6855
6970
|
}
|
|
@@ -6859,12 +6974,12 @@ The summarization should descibe in a general sense what has changed in the repo
|
|
|
6859
6974
|
|
|
6860
6975
|
Breaking down the changes into categories (e.g. bug fixes, new features, etc.) with markdown headings is encouraged.
|
|
6861
6976
|
|
|
6862
|
-
{timeframe}
|
|
6977
|
+
{{timeframe}}
|
|
6863
6978
|
|
|
6864
|
-
{format_instructions}
|
|
6979
|
+
{{format_instructions}}
|
|
6865
6980
|
|
|
6866
|
-
"""{changes}"""`;
|
|
6867
|
-
const inputVariables$1 = ['
|
|
6981
|
+
"""{{changes}}"""`;
|
|
6982
|
+
const inputVariables$1 = ['timeframe', 'format_instructions', 'changes'];
|
|
6868
6983
|
const RECAP_PROMPT = new PromptTemplate({
|
|
6869
6984
|
template: template$1,
|
|
6870
6985
|
inputVariables: inputVariables$1,
|
|
@@ -6881,10 +6996,13 @@ const handler$1 = async (argv, logger) => {
|
|
|
6881
6996
|
}
|
|
6882
6997
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
6883
6998
|
const llm = getLlm(provider, model, config);
|
|
6884
|
-
const INTERACTIVE = isInteractive(config);
|
|
6999
|
+
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
6885
7000
|
if (INTERACTIVE) {
|
|
6886
7001
|
logger.log(LOGO);
|
|
6887
7002
|
}
|
|
7003
|
+
else {
|
|
7004
|
+
logger.setConfig({ silent: true });
|
|
7005
|
+
}
|
|
6888
7006
|
const { 'last-month': lastMonth, 'last-tag': lastTag, yesterday, 'last-week': lastWeek } = argv;
|
|
6889
7007
|
const timeframe = lastMonth
|
|
6890
7008
|
? 'last-month'
|
|
@@ -6943,7 +7061,7 @@ const handler$1 = async (argv, logger) => {
|
|
|
6943
7061
|
async function parser(changes) {
|
|
6944
7062
|
return changes.join('\n');
|
|
6945
7063
|
}
|
|
6946
|
-
await generateAndReviewLoop({
|
|
7064
|
+
const recapResult = await generateAndReviewLoop({
|
|
6947
7065
|
label: 'recap',
|
|
6948
7066
|
options: {
|
|
6949
7067
|
...config,
|
|
@@ -6968,30 +7086,61 @@ const handler$1 = async (argv, logger) => {
|
|
|
6968
7086
|
factory,
|
|
6969
7087
|
parser,
|
|
6970
7088
|
agent: async (context, options) => {
|
|
6971
|
-
const
|
|
6972
|
-
const formatInstructions = "Respond with a valid JSON object, containing one field: 'summary', a string.";
|
|
7089
|
+
const formatInstructions = 'Respond in a readable format. Include both high level and detailed information. Use markdown to format the response.';
|
|
6973
7090
|
const prompt = getPrompt({
|
|
6974
7091
|
template: options.prompt,
|
|
6975
7092
|
variables: RECAP_PROMPT.inputVariables,
|
|
6976
7093
|
fallback: RECAP_PROMPT,
|
|
6977
7094
|
});
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
7095
|
+
try {
|
|
7096
|
+
const stringParser = new StringOutputParser();
|
|
7097
|
+
const response = (await executeChain({
|
|
7098
|
+
llm,
|
|
7099
|
+
prompt,
|
|
7100
|
+
variables: {
|
|
7101
|
+
changes: context,
|
|
7102
|
+
format_instructions: formatInstructions,
|
|
7103
|
+
timeframe,
|
|
7104
|
+
},
|
|
7105
|
+
// NOTE: parser is not optional and JSONOutputParser is expected, however making a union type for `executeChain` breaks type generation downstream.
|
|
7106
|
+
// In the future, we should consider making the parser optional in `executeChain` and better handle parser types.
|
|
7107
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7108
|
+
// @ts-expect-error - parser is not optional and JSONOutputParser is expected
|
|
7109
|
+
parser: stringParser,
|
|
7110
|
+
}));
|
|
7111
|
+
return `${response || 'no response'}`;
|
|
7112
|
+
}
|
|
7113
|
+
catch (error) {
|
|
7114
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7115
|
+
// Log the error but don't exit
|
|
7116
|
+
logger.log(`Error parsing LLM response: ${errorMessage}`, { color: 'red' });
|
|
7117
|
+
// Always return a fallback message instead of exiting
|
|
7118
|
+
const fallbackMessage = `
|
|
7119
|
+
## Failed to parse the response [timeframe: ${timeframe}]
|
|
7120
|
+
- There are changes in the codebase that couldn't be properly summarized due to a technical issue.
|
|
7121
|
+
- LLM encountered issues when parsing the changes.
|
|
7122
|
+
|
|
7123
|
+
### Error encountered
|
|
7124
|
+
|
|
7125
|
+
${errorMessage}
|
|
7126
|
+
`;
|
|
7127
|
+
return fallbackMessage;
|
|
7128
|
+
}
|
|
6989
7129
|
},
|
|
6990
7130
|
noResult: async () => {
|
|
6991
7131
|
await noResult$1({ git, logger });
|
|
6992
7132
|
process.exit(0);
|
|
6993
7133
|
},
|
|
6994
7134
|
});
|
|
7135
|
+
// Handle the result based on the mode (interactive or stdout)
|
|
7136
|
+
const MODE = (INTERACTIVE && 'interactive') || (config.recap && 'interactive') || config?.mode || 'stdout'; // Default to stdout
|
|
7137
|
+
handleResult({
|
|
7138
|
+
result: recapResult,
|
|
7139
|
+
interactiveModeCallback: async () => {
|
|
7140
|
+
logSuccess();
|
|
7141
|
+
},
|
|
7142
|
+
mode: MODE,
|
|
7143
|
+
});
|
|
6995
7144
|
};
|
|
6996
7145
|
|
|
6997
7146
|
var recap = {
|
package/dist/index.js
CHANGED
|
@@ -77,7 +77,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline$1);
|
|
|
77
77
|
/**
|
|
78
78
|
* Current build version from package.json
|
|
79
79
|
*/
|
|
80
|
-
const BUILD_VERSION = "0.14.
|
|
80
|
+
const BUILD_VERSION = "0.14.9";
|
|
81
81
|
|
|
82
82
|
const isInteractive = (config) => {
|
|
83
83
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -1791,7 +1791,17 @@ class Logger {
|
|
|
1791
1791
|
this.config = config;
|
|
1792
1792
|
this.spinner = null;
|
|
1793
1793
|
}
|
|
1794
|
+
setConfig(config) {
|
|
1795
|
+
this.config = {
|
|
1796
|
+
...this.config,
|
|
1797
|
+
...config,
|
|
1798
|
+
};
|
|
1799
|
+
return this;
|
|
1800
|
+
}
|
|
1794
1801
|
log(message, options = { color: 'blue' }) {
|
|
1802
|
+
if (this.config?.silent) {
|
|
1803
|
+
return this;
|
|
1804
|
+
}
|
|
1795
1805
|
let outputMessage = message;
|
|
1796
1806
|
if (options.color) {
|
|
1797
1807
|
outputMessage = chalk[options.color](outputMessage);
|
|
@@ -1800,7 +1810,7 @@ class Logger {
|
|
|
1800
1810
|
return this;
|
|
1801
1811
|
}
|
|
1802
1812
|
verbose(message, options = {}) {
|
|
1803
|
-
if (!this.config?.verbose) {
|
|
1813
|
+
if (!this.config?.verbose || this.config?.silent) {
|
|
1804
1814
|
return this;
|
|
1805
1815
|
}
|
|
1806
1816
|
this.log(message, options);
|
|
@@ -1811,13 +1821,11 @@ class Logger {
|
|
|
1811
1821
|
return this;
|
|
1812
1822
|
}
|
|
1813
1823
|
stopTimer(message, options = { color: 'yellow' }) {
|
|
1814
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
1824
|
+
if (!this.config?.verbose || !this.timerStart || this.config?.silent) {
|
|
1815
1825
|
return this;
|
|
1816
1826
|
}
|
|
1817
1827
|
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
1818
|
-
let outputMessage = message
|
|
1819
|
-
? `${message} (⏲ ${elapsedTime})`
|
|
1820
|
-
: `⏲ ${elapsedTime}`;
|
|
1828
|
+
let outputMessage = message ? `${message} (⏲ ${elapsedTime})` : `⏲ ${elapsedTime}`;
|
|
1821
1829
|
if (options.color) {
|
|
1822
1830
|
outputMessage = chalk[options.color](outputMessage);
|
|
1823
1831
|
}
|
|
@@ -1825,11 +1833,17 @@ class Logger {
|
|
|
1825
1833
|
return this;
|
|
1826
1834
|
}
|
|
1827
1835
|
startSpinner(message, options = { color: 'green' }) {
|
|
1836
|
+
if (this.config?.silent) {
|
|
1837
|
+
return this;
|
|
1838
|
+
}
|
|
1828
1839
|
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
1829
1840
|
this.spinner = ora(spinnerMessage).start();
|
|
1830
1841
|
return this;
|
|
1831
1842
|
}
|
|
1832
1843
|
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
1844
|
+
if (this.config?.silent) {
|
|
1845
|
+
return this;
|
|
1846
|
+
}
|
|
1833
1847
|
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
1834
1848
|
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
1835
1849
|
this.spinner = null;
|
|
@@ -1868,6 +1882,12 @@ const options$4 = {
|
|
|
1868
1882
|
alias: 'b',
|
|
1869
1883
|
description: 'Target branch to compare against',
|
|
1870
1884
|
},
|
|
1885
|
+
sinceLastTag: {
|
|
1886
|
+
type: 'boolean',
|
|
1887
|
+
alias: 't',
|
|
1888
|
+
description: 'Generate changelog for all commits since the last tag',
|
|
1889
|
+
default: false,
|
|
1890
|
+
},
|
|
1871
1891
|
i: {
|
|
1872
1892
|
type: 'boolean',
|
|
1873
1893
|
alias: 'interactive',
|
|
@@ -1920,6 +1940,7 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
1920
1940
|
? new prompts$1.PromptTemplate({
|
|
1921
1941
|
template,
|
|
1922
1942
|
inputVariables: variables,
|
|
1943
|
+
templateFormat: 'mustache',
|
|
1923
1944
|
})
|
|
1924
1945
|
: fallback);
|
|
1925
1946
|
}
|
|
@@ -1950,6 +1971,35 @@ function extractTicketIdFromBranchName(branchName) {
|
|
|
1950
1971
|
return match ? match[0] : null;
|
|
1951
1972
|
}
|
|
1952
1973
|
|
|
1974
|
+
/**
|
|
1975
|
+
* Formats a commit log into a readable string format.
|
|
1976
|
+
*
|
|
1977
|
+
* @param commitLog - The commit log result containing an array of commit details.
|
|
1978
|
+
* @returns An array of formatted commit log strings.
|
|
1979
|
+
*
|
|
1980
|
+
* Each formatted string includes:
|
|
1981
|
+
* - The date of the commit in square brackets.
|
|
1982
|
+
* - The commit message.
|
|
1983
|
+
* - The commit body.
|
|
1984
|
+
* - The commit hash in parentheses.
|
|
1985
|
+
* - The author's name and email in angle brackets.
|
|
1986
|
+
*/
|
|
1987
|
+
const formatCommitLog = (commitLog) => {
|
|
1988
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
const getChangesSinceLastTag = async ({ git }) => {
|
|
1992
|
+
const tags = await git.tags();
|
|
1993
|
+
if (tags.all.length > 0) {
|
|
1994
|
+
const lastTag = tags.latest;
|
|
1995
|
+
const commitLog = await git.log({ from: lastTag });
|
|
1996
|
+
return formatCommitLog(commitLog);
|
|
1997
|
+
}
|
|
1998
|
+
else {
|
|
1999
|
+
return ['No tags found in the repository.'];
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
|
|
1953
2003
|
/**
|
|
1954
2004
|
* Retrieves the commit log range between two specified commits.
|
|
1955
2005
|
*
|
|
@@ -2083,8 +2133,17 @@ const getRepo = () => {
|
|
|
2083
2133
|
return git;
|
|
2084
2134
|
};
|
|
2085
2135
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2136
|
+
/**
|
|
2137
|
+
* Template for generating git commit messages based on code changes
|
|
2138
|
+
*
|
|
2139
|
+
* Variables:
|
|
2140
|
+
* - summary: Contains the diff summary of staged changes
|
|
2141
|
+
* - format_instructions: Instructions for the output format (JSON with title and body)
|
|
2142
|
+
* - additional_context: Optional user-provided context to guide the commit message generation
|
|
2143
|
+
* - commit_history: Optional history of previous commits for context
|
|
2144
|
+
*/
|
|
2145
|
+
const template$4 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
2146
|
+
Commit Messages must have a short description that is less than 50 characters and a longer detailed summary around 300 characters, the shorter and more concise the better.
|
|
2088
2147
|
|
|
2089
2148
|
Please follow the guidelines below when writing your commit message:
|
|
2090
2149
|
|
|
@@ -2096,13 +2155,16 @@ Please follow the guidelines below when writing your commit message:
|
|
|
2096
2155
|
|
|
2097
2156
|
{format_instructions}
|
|
2098
2157
|
|
|
2158
|
+
{commit_history}
|
|
2159
|
+
|
|
2099
2160
|
""""""
|
|
2100
2161
|
{summary}
|
|
2101
2162
|
""""""
|
|
2102
2163
|
|
|
2103
|
-
{
|
|
2164
|
+
{additional_context}
|
|
2104
2165
|
`;
|
|
2105
|
-
|
|
2166
|
+
// Define the variables that will be passed to the prompt template
|
|
2167
|
+
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history'];
|
|
2106
2168
|
const COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
2107
2169
|
template: template$4,
|
|
2108
2170
|
inputVariables: inputVariables$3,
|
|
@@ -2268,9 +2330,16 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
2268
2330
|
result = '';
|
|
2269
2331
|
continue;
|
|
2270
2332
|
}
|
|
2333
|
+
// Only edit the result in interactive mode if approved
|
|
2334
|
+
result = await editResult(result, options);
|
|
2335
|
+
}
|
|
2336
|
+
else {
|
|
2337
|
+
// In non-interactive mode, we return the result as is to be output to stdout by the caller.
|
|
2338
|
+
const displayResult = reviewParser ? reviewParser(result, options) : result;
|
|
2339
|
+
// In non-interactive mode, ensure we return the properly formatted result
|
|
2340
|
+
result = displayResult;
|
|
2271
2341
|
}
|
|
2272
2342
|
// if we're here, we're done.
|
|
2273
|
-
result = await editResult(result, options);
|
|
2274
2343
|
continueLoop = false;
|
|
2275
2344
|
}
|
|
2276
2345
|
return result;
|
|
@@ -2293,7 +2362,8 @@ async function handleResult({ result, mode, interactiveModeCallback }) {
|
|
|
2293
2362
|
break;
|
|
2294
2363
|
case 'stdout':
|
|
2295
2364
|
default:
|
|
2296
|
-
|
|
2365
|
+
// Ensure we write the result to stdout in non-interactive mode
|
|
2366
|
+
process.stdout.write(result + '\n', 'utf8');
|
|
2297
2367
|
break;
|
|
2298
2368
|
}
|
|
2299
2369
|
process.exit(0);
|
|
@@ -2332,6 +2402,13 @@ const handler$4 = async (argv, logger) => {
|
|
|
2332
2402
|
}
|
|
2333
2403
|
async function factory() {
|
|
2334
2404
|
const branchName = await getCurrentBranchName({ git });
|
|
2405
|
+
if (config.sinceLastTag) {
|
|
2406
|
+
logger.verbose(`Generating commit log since the last tag`, { color: 'yellow' });
|
|
2407
|
+
return {
|
|
2408
|
+
branch: branchName,
|
|
2409
|
+
commits: await getChangesSinceLastTag({ git, logger }),
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2335
2412
|
if (config.range && config.range.includes(':')) {
|
|
2336
2413
|
const [from, to] = config.range.split(':');
|
|
2337
2414
|
if (!from || !to) {
|
|
@@ -2350,7 +2427,7 @@ const handler$4 = async (argv, logger) => {
|
|
|
2350
2427
|
commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
|
|
2351
2428
|
};
|
|
2352
2429
|
}
|
|
2353
|
-
logger.verbose(`No range or
|
|
2430
|
+
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, { color: 'yellow' });
|
|
2354
2431
|
return {
|
|
2355
2432
|
branch: branchName,
|
|
2356
2433
|
commits: await getCommitLogCurrentBranch({ git, logger }),
|
|
@@ -2422,7 +2499,7 @@ const handler$4 = async (argv, logger) => {
|
|
|
2422
2499
|
|
|
2423
2500
|
var changelog = {
|
|
2424
2501
|
command: command$4,
|
|
2425
|
-
desc: 'Generate a changelog from current or target branch
|
|
2502
|
+
desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
|
|
2426
2503
|
builder: builder$4,
|
|
2427
2504
|
handler: commandExecutor(handler$4),
|
|
2428
2505
|
options: options$4,
|
|
@@ -2460,6 +2537,12 @@ const options$3 = {
|
|
|
2460
2537
|
type: 'string',
|
|
2461
2538
|
alias: 'a',
|
|
2462
2539
|
},
|
|
2540
|
+
withPreviousCommits: {
|
|
2541
|
+
description: 'Include previous commits as context (specify number of commits, 0 for none)',
|
|
2542
|
+
type: 'number',
|
|
2543
|
+
default: 0,
|
|
2544
|
+
alias: 'p',
|
|
2545
|
+
},
|
|
2463
2546
|
};
|
|
2464
2547
|
const builder$3 = (yargs) => {
|
|
2465
2548
|
return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
|
|
@@ -6197,6 +6280,48 @@ async function getChanges({ git, options }) {
|
|
|
6197
6280
|
};
|
|
6198
6281
|
}
|
|
6199
6282
|
|
|
6283
|
+
/**
|
|
6284
|
+
* Format a single commit log entry into a readable string
|
|
6285
|
+
* @param commit - The commit log entry
|
|
6286
|
+
* @returns Formatted commit log string
|
|
6287
|
+
*/
|
|
6288
|
+
function formatSingleCommit(commit) {
|
|
6289
|
+
const { hash, date, message, body, author_name } = commit;
|
|
6290
|
+
const shortHash = hash.substring(0, 7);
|
|
6291
|
+
return `Commit: ${shortHash}
|
|
6292
|
+
Author: ${author_name}
|
|
6293
|
+
Date: ${date}
|
|
6294
|
+
Message: ${message}
|
|
6295
|
+
${body ? `\nDetails: ${body}` : ''}`;
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
/**
|
|
6299
|
+
* Get the specified number of previous commits
|
|
6300
|
+
* @param options - Options for getting previous commits
|
|
6301
|
+
* @returns Formatted commit logs
|
|
6302
|
+
*/
|
|
6303
|
+
async function getPreviousCommits(options) {
|
|
6304
|
+
const { git, count = 1 } = options;
|
|
6305
|
+
if (count <= 0) {
|
|
6306
|
+
return '';
|
|
6307
|
+
}
|
|
6308
|
+
try {
|
|
6309
|
+
const logs = await git.log({ maxCount: count });
|
|
6310
|
+
if (!logs || logs.total === 0) {
|
|
6311
|
+
return '';
|
|
6312
|
+
}
|
|
6313
|
+
// Format the commit logs
|
|
6314
|
+
const formattedLogs = logs.all.map((commit) => {
|
|
6315
|
+
return formatSingleCommit(commit);
|
|
6316
|
+
}).join('\n\n');
|
|
6317
|
+
return formattedLogs;
|
|
6318
|
+
}
|
|
6319
|
+
catch (error) {
|
|
6320
|
+
console.error(`Error getting previous commits: ${error.message}`);
|
|
6321
|
+
return '';
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
|
|
6200
6325
|
/**
|
|
6201
6326
|
* Retrieves a TikToken for the specified model.
|
|
6202
6327
|
*
|
|
@@ -6259,10 +6384,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
6259
6384
|
}
|
|
6260
6385
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
6261
6386
|
const llm = getLlm(provider, model, config);
|
|
6262
|
-
const INTERACTIVE = isInteractive(config);
|
|
6387
|
+
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
6263
6388
|
if (INTERACTIVE) {
|
|
6264
6389
|
logger.log(LOGO);
|
|
6265
6390
|
}
|
|
6391
|
+
else {
|
|
6392
|
+
logger.setConfig({ silent: true });
|
|
6393
|
+
}
|
|
6266
6394
|
async function factory() {
|
|
6267
6395
|
const changes = await getChanges({
|
|
6268
6396
|
git,
|
|
@@ -6307,14 +6435,30 @@ const handler$3 = async (argv, logger) => {
|
|
|
6307
6435
|
fallback: COMMIT_PROMPT,
|
|
6308
6436
|
});
|
|
6309
6437
|
const formatInstructions = "Respond with a valid JSON object, containing two fields: 'title' and 'body', both strings.";
|
|
6310
|
-
|
|
6438
|
+
// Get additional context if provided
|
|
6439
|
+
let additional_context = '';
|
|
6440
|
+
if (argv.additional) {
|
|
6441
|
+
additional_context = `## Additional Context\n${argv.additional}`;
|
|
6442
|
+
}
|
|
6443
|
+
// Get commit history if requested
|
|
6444
|
+
let commit_history = '';
|
|
6445
|
+
if (argv.withPreviousCommits > 0) {
|
|
6446
|
+
const commitHistoryData = await getPreviousCommits({
|
|
6447
|
+
git,
|
|
6448
|
+
count: argv.withPreviousCommits
|
|
6449
|
+
});
|
|
6450
|
+
if (commitHistoryData) {
|
|
6451
|
+
commit_history = `## Commit History\n${commitHistoryData}`;
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6311
6454
|
const commitMsg = await executeChain({
|
|
6312
6455
|
llm,
|
|
6313
6456
|
prompt,
|
|
6314
6457
|
variables: {
|
|
6315
6458
|
summary: context,
|
|
6316
6459
|
format_instructions: formatInstructions,
|
|
6317
|
-
|
|
6460
|
+
additional_context: additional_context,
|
|
6461
|
+
commit_history: commit_history,
|
|
6318
6462
|
},
|
|
6319
6463
|
parser,
|
|
6320
6464
|
});
|
|
@@ -6838,40 +6982,11 @@ const builder$1 = (yargs) => {
|
|
|
6838
6982
|
return yargs.options(options$1).usage(getCommandUsageHeader(command$1));
|
|
6839
6983
|
};
|
|
6840
6984
|
|
|
6841
|
-
/**
|
|
6842
|
-
* Formats a commit log into a readable string format.
|
|
6843
|
-
*
|
|
6844
|
-
* @param commitLog - The commit log result containing an array of commit details.
|
|
6845
|
-
* @returns An array of formatted commit log strings.
|
|
6846
|
-
*
|
|
6847
|
-
* Each formatted string includes:
|
|
6848
|
-
* - The date of the commit in square brackets.
|
|
6849
|
-
* - The commit message.
|
|
6850
|
-
* - The commit body.
|
|
6851
|
-
* - The commit hash in parentheses.
|
|
6852
|
-
* - The author's name and email in angle brackets.
|
|
6853
|
-
*/
|
|
6854
|
-
const formatCommitLog = (commitLog) => {
|
|
6855
|
-
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6856
|
-
};
|
|
6857
|
-
|
|
6858
6985
|
const getChangesByTimestamp = async ({ since, git }) => {
|
|
6859
6986
|
const commitLog = await git.log({ '--since': since });
|
|
6860
6987
|
return formatCommitLog(commitLog);
|
|
6861
6988
|
};
|
|
6862
6989
|
|
|
6863
|
-
const getChangesSinceLastTag = async ({ git }) => {
|
|
6864
|
-
const tags = await git.tags();
|
|
6865
|
-
if (tags.all.length > 0) {
|
|
6866
|
-
const lastTag = tags.latest;
|
|
6867
|
-
const commitLog = await git.log({ from: lastTag });
|
|
6868
|
-
return formatCommitLog(commitLog);
|
|
6869
|
-
}
|
|
6870
|
-
else {
|
|
6871
|
-
return ['No tags found in the repository.'];
|
|
6872
|
-
}
|
|
6873
|
-
};
|
|
6874
|
-
|
|
6875
6990
|
async function noResult$1({ logger }) {
|
|
6876
6991
|
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
6877
6992
|
}
|
|
@@ -6881,12 +6996,12 @@ The summarization should descibe in a general sense what has changed in the repo
|
|
|
6881
6996
|
|
|
6882
6997
|
Breaking down the changes into categories (e.g. bug fixes, new features, etc.) with markdown headings is encouraged.
|
|
6883
6998
|
|
|
6884
|
-
{timeframe}
|
|
6999
|
+
{{timeframe}}
|
|
6885
7000
|
|
|
6886
|
-
{format_instructions}
|
|
7001
|
+
{{format_instructions}}
|
|
6887
7002
|
|
|
6888
|
-
"""{changes}"""`;
|
|
6889
|
-
const inputVariables$1 = ['
|
|
7003
|
+
"""{{changes}}"""`;
|
|
7004
|
+
const inputVariables$1 = ['timeframe', 'format_instructions', 'changes'];
|
|
6890
7005
|
const RECAP_PROMPT = new prompts$1.PromptTemplate({
|
|
6891
7006
|
template: template$1,
|
|
6892
7007
|
inputVariables: inputVariables$1,
|
|
@@ -6903,10 +7018,13 @@ const handler$1 = async (argv, logger) => {
|
|
|
6903
7018
|
}
|
|
6904
7019
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
6905
7020
|
const llm = getLlm(provider, model, config);
|
|
6906
|
-
const INTERACTIVE = isInteractive(config);
|
|
7021
|
+
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
6907
7022
|
if (INTERACTIVE) {
|
|
6908
7023
|
logger.log(LOGO);
|
|
6909
7024
|
}
|
|
7025
|
+
else {
|
|
7026
|
+
logger.setConfig({ silent: true });
|
|
7027
|
+
}
|
|
6910
7028
|
const { 'last-month': lastMonth, 'last-tag': lastTag, yesterday, 'last-week': lastWeek } = argv;
|
|
6911
7029
|
const timeframe = lastMonth
|
|
6912
7030
|
? 'last-month'
|
|
@@ -6965,7 +7083,7 @@ const handler$1 = async (argv, logger) => {
|
|
|
6965
7083
|
async function parser(changes) {
|
|
6966
7084
|
return changes.join('\n');
|
|
6967
7085
|
}
|
|
6968
|
-
await generateAndReviewLoop({
|
|
7086
|
+
const recapResult = await generateAndReviewLoop({
|
|
6969
7087
|
label: 'recap',
|
|
6970
7088
|
options: {
|
|
6971
7089
|
...config,
|
|
@@ -6990,30 +7108,61 @@ const handler$1 = async (argv, logger) => {
|
|
|
6990
7108
|
factory,
|
|
6991
7109
|
parser,
|
|
6992
7110
|
agent: async (context, options) => {
|
|
6993
|
-
const
|
|
6994
|
-
const formatInstructions = "Respond with a valid JSON object, containing one field: 'summary', a string.";
|
|
7111
|
+
const formatInstructions = 'Respond in a readable format. Include both high level and detailed information. Use markdown to format the response.';
|
|
6995
7112
|
const prompt = getPrompt({
|
|
6996
7113
|
template: options.prompt,
|
|
6997
7114
|
variables: RECAP_PROMPT.inputVariables,
|
|
6998
7115
|
fallback: RECAP_PROMPT,
|
|
6999
7116
|
});
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7117
|
+
try {
|
|
7118
|
+
const stringParser = new output_parsers.StringOutputParser();
|
|
7119
|
+
const response = (await executeChain({
|
|
7120
|
+
llm,
|
|
7121
|
+
prompt,
|
|
7122
|
+
variables: {
|
|
7123
|
+
changes: context,
|
|
7124
|
+
format_instructions: formatInstructions,
|
|
7125
|
+
timeframe,
|
|
7126
|
+
},
|
|
7127
|
+
// NOTE: parser is not optional and JSONOutputParser is expected, however making a union type for `executeChain` breaks type generation downstream.
|
|
7128
|
+
// In the future, we should consider making the parser optional in `executeChain` and better handle parser types.
|
|
7129
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7130
|
+
// @ts-expect-error - parser is not optional and JSONOutputParser is expected
|
|
7131
|
+
parser: stringParser,
|
|
7132
|
+
}));
|
|
7133
|
+
return `${response || 'no response'}`;
|
|
7134
|
+
}
|
|
7135
|
+
catch (error) {
|
|
7136
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7137
|
+
// Log the error but don't exit
|
|
7138
|
+
logger.log(`Error parsing LLM response: ${errorMessage}`, { color: 'red' });
|
|
7139
|
+
// Always return a fallback message instead of exiting
|
|
7140
|
+
const fallbackMessage = `
|
|
7141
|
+
## Failed to parse the response [timeframe: ${timeframe}]
|
|
7142
|
+
- There are changes in the codebase that couldn't be properly summarized due to a technical issue.
|
|
7143
|
+
- LLM encountered issues when parsing the changes.
|
|
7144
|
+
|
|
7145
|
+
### Error encountered
|
|
7146
|
+
|
|
7147
|
+
${errorMessage}
|
|
7148
|
+
`;
|
|
7149
|
+
return fallbackMessage;
|
|
7150
|
+
}
|
|
7011
7151
|
},
|
|
7012
7152
|
noResult: async () => {
|
|
7013
7153
|
await noResult$1({ git, logger });
|
|
7014
7154
|
process.exit(0);
|
|
7015
7155
|
},
|
|
7016
7156
|
});
|
|
7157
|
+
// Handle the result based on the mode (interactive or stdout)
|
|
7158
|
+
const MODE = (INTERACTIVE && 'interactive') || (config.recap && 'interactive') || config?.mode || 'stdout'; // Default to stdout
|
|
7159
|
+
handleResult({
|
|
7160
|
+
result: recapResult,
|
|
7161
|
+
interactiveModeCallback: async () => {
|
|
7162
|
+
logSuccess();
|
|
7163
|
+
},
|
|
7164
|
+
mode: MODE,
|
|
7165
|
+
});
|
|
7017
7166
|
};
|
|
7018
7167
|
|
|
7019
7168
|
var recap = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-coco",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.9",
|
|
4
4
|
"description": "zero-effort git commits with coco.",
|
|
5
5
|
"author": "gfargo <ghfargo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@types/async": "^3.2.20",
|
|
53
53
|
"@types/chunk-text": "^1.0.0",
|
|
54
54
|
"@types/common-tags": "^1.8.1",
|
|
55
|
-
"@types/diff": "^
|
|
56
|
-
"@types/ini": "^1.
|
|
55
|
+
"@types/diff": "^7.0.1",
|
|
56
|
+
"@types/ini": "^4.1.1",
|
|
57
57
|
"@types/inquirer": "^9.0.3",
|
|
58
58
|
"@types/jest": "^29.5.10",
|
|
59
59
|
"@types/node": "^22.7.5",
|
|
@@ -93,14 +93,14 @@
|
|
|
93
93
|
"ajv": "^8.16.0",
|
|
94
94
|
"ajv-formats": "^3.0.1",
|
|
95
95
|
"chalk": "4.1.2",
|
|
96
|
-
"diff": "
|
|
97
|
-
"ini": "
|
|
96
|
+
"diff": "7.0.0",
|
|
97
|
+
"ini": "5.0.0",
|
|
98
98
|
"minimatch": "10.0.1",
|
|
99
99
|
"ora": "5.4.1",
|
|
100
100
|
"p-queue": "5.0.0",
|
|
101
101
|
"performance-now": "2.1.0",
|
|
102
102
|
"pretty-ms": "7.0.1",
|
|
103
|
-
"simple-git": "3.
|
|
103
|
+
"simple-git": "3.27.0",
|
|
104
104
|
"tiktoken": "^1.0.17",
|
|
105
105
|
"yargs": "17.7.2"
|
|
106
106
|
}
|