git-coco 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -4
- package/dist/index.esm.mjs +1449 -1338
- package/dist/index.js +1448 -1337
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,12 +18,12 @@ var anthropic = require('@langchain/anthropic');
|
|
|
18
18
|
var ollama = require('@langchain/ollama');
|
|
19
19
|
var openai = require('@langchain/openai');
|
|
20
20
|
var output_parsers = require('@langchain/core/output_parsers');
|
|
21
|
-
var simpleGit = require('simple-git');
|
|
22
21
|
var base = require('@langchain/core/language_models/base');
|
|
23
22
|
var runnables = require('@langchain/core/runnables');
|
|
24
23
|
var outputs = require('@langchain/core/outputs');
|
|
25
24
|
var manager = require('@langchain/core/callbacks/manager');
|
|
26
25
|
require('@langchain/core/utils/json_patch');
|
|
26
|
+
var simpleGit = require('simple-git');
|
|
27
27
|
var pQueue = require('p-queue');
|
|
28
28
|
var documents = require('@langchain/core/documents');
|
|
29
29
|
var diff = require('diff');
|
|
@@ -68,7 +68,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline$1);
|
|
|
68
68
|
/**
|
|
69
69
|
* Current build version from package.json
|
|
70
70
|
*/
|
|
71
|
-
const BUILD_VERSION = "0.
|
|
71
|
+
const BUILD_VERSION = "0.20.0";
|
|
72
72
|
|
|
73
73
|
const isInteractive = (config) => {
|
|
74
74
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -6223,1421 +6223,1424 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
6223
6223
|
throw new LangChainExecutionError('getPrompt: Unexpected execution path - neither template nor fallback available', { template, fallback, variables });
|
|
6224
6224
|
}
|
|
6225
6225
|
|
|
6226
|
+
new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
|
|
6227
|
+
|
|
6226
6228
|
/**
|
|
6227
|
-
*
|
|
6228
|
-
* @param params - The execution parameters
|
|
6229
|
-
* @returns The parsed result from the LLM chain
|
|
6230
|
-
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
6229
|
+
* Base interface that all chains must implement.
|
|
6231
6230
|
*/
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
validateRequired(variables, 'variables', 'executeChain');
|
|
6236
|
-
validateRequired(parser, 'parser', 'executeChain');
|
|
6237
|
-
// Validate that variables is an object
|
|
6238
|
-
if (typeof variables !== 'object' || Array.isArray(variables)) {
|
|
6239
|
-
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', { variables, type: typeof variables, isArray: Array.isArray(variables) });
|
|
6231
|
+
class BaseChain extends base.BaseLangChain {
|
|
6232
|
+
get lc_namespace() {
|
|
6233
|
+
return ["langchain", "chains", this._chainType()];
|
|
6240
6234
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6235
|
+
constructor(fields,
|
|
6236
|
+
/** @deprecated */
|
|
6237
|
+
verbose,
|
|
6238
|
+
/** @deprecated */
|
|
6239
|
+
callbacks) {
|
|
6240
|
+
if (arguments.length === 1 &&
|
|
6241
|
+
typeof fields === "object" &&
|
|
6242
|
+
!("saveContext" in fields)) {
|
|
6243
|
+
// fields is not a BaseMemory
|
|
6244
|
+
const { memory, callbackManager, ...rest } = fields;
|
|
6245
|
+
super({ ...rest, callbacks: callbackManager ?? rest.callbacks });
|
|
6246
|
+
this.memory = memory;
|
|
6247
|
+
}
|
|
6248
|
+
else {
|
|
6249
|
+
// fields is a BaseMemory
|
|
6250
|
+
super({ verbose, callbacks });
|
|
6251
|
+
this.memory = fields;
|
|
6246
6252
|
}
|
|
6247
|
-
return result;
|
|
6248
6253
|
}
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6254
|
+
/** @ignore */
|
|
6255
|
+
_selectMemoryInputs(values) {
|
|
6256
|
+
const valuesForMemory = { ...values };
|
|
6257
|
+
if ("signal" in valuesForMemory) {
|
|
6258
|
+
delete valuesForMemory.signal;
|
|
6253
6259
|
}
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6260
|
+
if ("timeout" in valuesForMemory) {
|
|
6261
|
+
delete valuesForMemory.timeout;
|
|
6262
|
+
}
|
|
6263
|
+
return valuesForMemory;
|
|
6264
|
+
}
|
|
6265
|
+
/**
|
|
6266
|
+
* Invoke the chain with the provided input and returns the output.
|
|
6267
|
+
* @param input Input values for the chain run.
|
|
6268
|
+
* @param config Optional configuration for the Runnable.
|
|
6269
|
+
* @returns Promise that resolves with the output of the chain run.
|
|
6270
|
+
*/
|
|
6271
|
+
async invoke(input, options) {
|
|
6272
|
+
const config = runnables.ensureConfig(options);
|
|
6273
|
+
const fullValues = await this._formatValues(input);
|
|
6274
|
+
const callbackManager_ = await manager.CallbackManager.configure(config?.callbacks, this.callbacks, config?.tags, this.tags, config?.metadata, this.metadata, { verbose: this.verbose });
|
|
6275
|
+
const runManager = await callbackManager_?.handleChainStart(this.toJSON(), fullValues, undefined, undefined, undefined, undefined, config?.runName);
|
|
6276
|
+
let outputValues;
|
|
6277
|
+
try {
|
|
6278
|
+
outputValues = await (fullValues.signal
|
|
6279
|
+
? Promise.race([
|
|
6280
|
+
this._call(fullValues, runManager, config),
|
|
6281
|
+
new Promise((_, reject) => {
|
|
6282
|
+
fullValues.signal?.addEventListener("abort", () => {
|
|
6283
|
+
reject(new Error("AbortError"));
|
|
6284
|
+
});
|
|
6285
|
+
}),
|
|
6286
|
+
])
|
|
6287
|
+
: this._call(fullValues, runManager, config));
|
|
6288
|
+
}
|
|
6289
|
+
catch (e) {
|
|
6290
|
+
await runManager?.handleChainError(e);
|
|
6291
|
+
throw e;
|
|
6292
|
+
}
|
|
6293
|
+
if (!(this.memory == null)) {
|
|
6294
|
+
await this.memory.saveContext(this._selectMemoryInputs(input), outputValues);
|
|
6295
|
+
}
|
|
6296
|
+
await runManager?.handleChainEnd(outputValues);
|
|
6297
|
+
// add the runManager's currentRunId to the outputValues
|
|
6298
|
+
Object.defineProperty(outputValues, outputs.RUN_KEY, {
|
|
6299
|
+
value: runManager ? { runId: runManager?.runId } : undefined,
|
|
6300
|
+
configurable: true,
|
|
6259
6301
|
});
|
|
6302
|
+
return outputValues;
|
|
6260
6303
|
}
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
return match ? match[0] : null;
|
|
6267
|
-
}
|
|
6268
|
-
|
|
6269
|
-
/**
|
|
6270
|
-
* Formats a commit log into a readable string format.
|
|
6271
|
-
*
|
|
6272
|
-
* @param commitLog - The commit log result containing an array of commit details.
|
|
6273
|
-
* @returns An array of formatted commit log strings.
|
|
6274
|
-
*
|
|
6275
|
-
* Each formatted string includes:
|
|
6276
|
-
* - The date of the commit in square brackets.
|
|
6277
|
-
* - The commit message.
|
|
6278
|
-
* - The commit body.
|
|
6279
|
-
* - The commit hash in parentheses.
|
|
6280
|
-
* - The author's name and email in angle brackets.
|
|
6281
|
-
*/
|
|
6282
|
-
const formatCommitLog = (commitLog) => {
|
|
6283
|
-
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6284
|
-
};
|
|
6285
|
-
|
|
6286
|
-
const getChangesSinceLastTag = async ({ git }) => {
|
|
6287
|
-
const tags = await git.tags();
|
|
6288
|
-
if (tags.all.length > 0) {
|
|
6289
|
-
const lastTag = tags.latest;
|
|
6290
|
-
const commitLog = await git.log({ from: lastTag });
|
|
6291
|
-
return formatCommitLog(commitLog);
|
|
6304
|
+
_validateOutputs(outputs) {
|
|
6305
|
+
const missingKeys = this.outputKeys.filter((k) => !(k in outputs));
|
|
6306
|
+
if (missingKeys.length) {
|
|
6307
|
+
throw new Error(`Missing output keys: ${missingKeys.join(", ")} from chain ${this._chainType()}`);
|
|
6308
|
+
}
|
|
6292
6309
|
}
|
|
6293
|
-
|
|
6294
|
-
|
|
6310
|
+
async prepOutputs(inputs, outputs, returnOnlyOutputs = false) {
|
|
6311
|
+
this._validateOutputs(outputs);
|
|
6312
|
+
if (this.memory) {
|
|
6313
|
+
await this.memory.saveContext(inputs, outputs);
|
|
6314
|
+
}
|
|
6315
|
+
if (returnOnlyOutputs) {
|
|
6316
|
+
return outputs;
|
|
6317
|
+
}
|
|
6318
|
+
return { ...inputs, ...outputs };
|
|
6295
6319
|
}
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
* @param from - The starting commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
|
|
6302
|
-
* @param to - The ending commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
|
|
6303
|
-
* @param options - Additional options for retrieving the commit log range.
|
|
6304
|
-
* @returns A promise that resolves to an array of commit log messages, including both the 'from' and 'to' commits.
|
|
6305
|
-
* @throws If there is an error retrieving the commit log range.
|
|
6306
|
-
*/
|
|
6307
|
-
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
6308
|
-
try {
|
|
6309
|
-
// Use from^..to to include the 'from' commit in the range
|
|
6310
|
-
// This works because from^..to means "commits reachable from 'to' but not from the parent of 'from'"
|
|
6311
|
-
const logOptions = {
|
|
6312
|
-
from: `${from}^`,
|
|
6313
|
-
to,
|
|
6314
|
-
'--no-merges': noMerges
|
|
6315
|
-
};
|
|
6316
|
-
const commitLog = await git.log(logOptions);
|
|
6317
|
-
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6320
|
+
/**
|
|
6321
|
+
* Return a json-like object representing this chain.
|
|
6322
|
+
*/
|
|
6323
|
+
serialize() {
|
|
6324
|
+
throw new Error("Method not implemented.");
|
|
6318
6325
|
}
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
const rangeLogOptions = {
|
|
6328
|
-
from,
|
|
6329
|
-
to,
|
|
6330
|
-
'--no-merges': noMerges
|
|
6331
|
-
};
|
|
6332
|
-
const rangeCommitLog = await git.log(rangeLogOptions);
|
|
6333
|
-
// Combine the 'from' commit with the range commits
|
|
6334
|
-
const allCommits = fromCommit
|
|
6335
|
-
? [fromCommit, ...rangeCommitLog.all]
|
|
6336
|
-
: rangeCommitLog.all;
|
|
6337
|
-
return allCommits.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6338
|
-
}
|
|
6339
|
-
catch (fallbackError) {
|
|
6340
|
-
throw fallbackError;
|
|
6341
|
-
}
|
|
6326
|
+
/** @deprecated Use .invoke() instead. Will be removed in 0.2.0. */
|
|
6327
|
+
async run(
|
|
6328
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6329
|
+
input, config) {
|
|
6330
|
+
const inputKeys = this.inputKeys.filter((k) => !this.memory?.memoryKeys.includes(k) ?? true);
|
|
6331
|
+
const isKeylessInput = inputKeys.length <= 1;
|
|
6332
|
+
if (!isKeylessInput) {
|
|
6333
|
+
throw new Error(`Chain ${this._chainType()} expects multiple inputs, cannot use 'run' `);
|
|
6342
6334
|
}
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
*
|
|
6350
|
-
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
6351
|
-
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
6352
|
-
*/
|
|
6353
|
-
async function getCurrentBranchName({ git }) {
|
|
6354
|
-
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
6355
|
-
}
|
|
6356
|
-
|
|
6357
|
-
/**
|
|
6358
|
-
* Retrieves the commit log between the current branch and a specified target branch.
|
|
6359
|
-
*
|
|
6360
|
-
* @param {Object} options - The options for retrieving the commit log.
|
|
6361
|
-
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
6362
|
-
* @param {Logger} options.logger - The logger for logging messages.
|
|
6363
|
-
* @param {string} options.targetBranch - The target branch to compare against.
|
|
6364
|
-
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
6365
|
-
*/
|
|
6366
|
-
async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
|
|
6367
|
-
try {
|
|
6368
|
-
// Get the current branch name
|
|
6369
|
-
const currentBranch = await getCurrentBranchName({ git });
|
|
6370
|
-
// Get the list of commits that are unique to the current branch compared to the target branch
|
|
6371
|
-
const uniqueCommits = (await git.raw(['rev-list', `${targetBranch}..${currentBranch}`]))
|
|
6372
|
-
.split('\n')
|
|
6373
|
-
.filter(Boolean)
|
|
6374
|
-
.reverse();
|
|
6375
|
-
logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and "${targetBranch}"`, { color: 'blue' });
|
|
6376
|
-
const firstCommit = uniqueCommits[0];
|
|
6377
|
-
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
6378
|
-
if (!firstCommit || !lastCommit) {
|
|
6379
|
-
logger?.log('Unable to determine first and last commit between branches', { color: 'yellow' });
|
|
6380
|
-
return [];
|
|
6335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6336
|
+
const values = inputKeys.length ? { [inputKeys[0]]: input } : {};
|
|
6337
|
+
const returnValues = await this.call(values, config);
|
|
6338
|
+
const keys = Object.keys(returnValues);
|
|
6339
|
+
if (keys.length === 1) {
|
|
6340
|
+
return returnValues[keys[0]];
|
|
6381
6341
|
}
|
|
6382
|
-
|
|
6383
|
-
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
6384
|
-
}
|
|
6385
|
-
catch (error) {
|
|
6386
|
-
logger?.log('Encountered an error getting commit log between branches', { color: 'red' });
|
|
6342
|
+
throw new Error("return values have multiple keys, `run` only supported when one key currently");
|
|
6387
6343
|
}
|
|
6388
|
-
|
|
6389
|
-
}
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
*
|
|
6394
|
-
* @param {Object} options - The options for retrieving the commit log.
|
|
6395
|
-
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
6396
|
-
* @param {Logger} options.logger - The logger for logging messages.
|
|
6397
|
-
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
6398
|
-
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
6399
|
-
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
6400
|
-
*/
|
|
6401
|
-
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
6402
|
-
try {
|
|
6403
|
-
const branchName = await getCurrentBranchName({ git });
|
|
6404
|
-
const hasCommits = (await git.raw(['rev-list', '--count', branchName])) !== '0';
|
|
6405
|
-
if (!hasCommits) {
|
|
6406
|
-
logger?.log('No commits on the current branch.');
|
|
6407
|
-
return [];
|
|
6408
|
-
}
|
|
6409
|
-
let uniqueCommits;
|
|
6410
|
-
if (comparisonBranch === branchName) {
|
|
6411
|
-
// If the comparison branch is the same as the current branch, we compare against the remote.
|
|
6412
|
-
uniqueCommits = (await git.raw(['rev-list', `${comparisonRemote}/${comparisonBranch}..${branchName}`]))
|
|
6413
|
-
.split('\n')
|
|
6414
|
-
.filter(Boolean)
|
|
6415
|
-
.reverse();
|
|
6416
|
-
}
|
|
6417
|
-
else {
|
|
6418
|
-
// Your existing code for different branches
|
|
6419
|
-
uniqueCommits = (await git.raw(['rev-list', `${comparisonBranch}..${branchName}`]))
|
|
6420
|
-
.split('\n')
|
|
6421
|
-
.filter(Boolean)
|
|
6422
|
-
.reverse();
|
|
6344
|
+
async _formatValues(values) {
|
|
6345
|
+
const fullValues = { ...values };
|
|
6346
|
+
if (fullValues.timeout && !fullValues.signal) {
|
|
6347
|
+
fullValues.signal = AbortSignal.timeout(fullValues.timeout);
|
|
6348
|
+
delete fullValues.timeout;
|
|
6423
6349
|
}
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
if (!firstCommit || !lastCommit) {
|
|
6430
|
-
logger?.log('Unable to determine first and last commit on the current branch', {
|
|
6431
|
-
color: 'yellow',
|
|
6432
|
-
});
|
|
6433
|
-
return [];
|
|
6350
|
+
if (!(this.memory == null)) {
|
|
6351
|
+
const newValues = await this.memory.loadMemoryVariables(this._selectMemoryInputs(values));
|
|
6352
|
+
for (const [key, value] of Object.entries(newValues)) {
|
|
6353
|
+
fullValues[key] = value;
|
|
6354
|
+
}
|
|
6434
6355
|
}
|
|
6435
|
-
return
|
|
6356
|
+
return fullValues;
|
|
6436
6357
|
}
|
|
6437
|
-
|
|
6438
|
-
|
|
6358
|
+
/**
|
|
6359
|
+
* @deprecated Use .invoke() instead. Will be removed in 0.2.0.
|
|
6360
|
+
*
|
|
6361
|
+
* Run the core logic of this chain and add to output if desired.
|
|
6362
|
+
*
|
|
6363
|
+
* Wraps _call and handles memory.
|
|
6364
|
+
*/
|
|
6365
|
+
async call(values, config,
|
|
6366
|
+
/** @deprecated */
|
|
6367
|
+
tags) {
|
|
6368
|
+
const parsedConfig = { tags, ...manager.parseCallbackConfigArg(config) };
|
|
6369
|
+
return this.invoke(values, parsedConfig);
|
|
6370
|
+
}
|
|
6371
|
+
/**
|
|
6372
|
+
* @deprecated Use .batch() instead. Will be removed in 0.2.0.
|
|
6373
|
+
*
|
|
6374
|
+
* Call the chain on all inputs in the list
|
|
6375
|
+
*/
|
|
6376
|
+
async apply(inputs, config) {
|
|
6377
|
+
return Promise.all(inputs.map(async (i, idx) => this.call(i, config?.[idx])));
|
|
6378
|
+
}
|
|
6379
|
+
/**
|
|
6380
|
+
* Load a chain from a json-like object describing it.
|
|
6381
|
+
*/
|
|
6382
|
+
static async deserialize(data, values = {}) {
|
|
6383
|
+
switch (data._type) {
|
|
6384
|
+
case "llm_chain": {
|
|
6385
|
+
const { LLMChain } = await Promise.resolve().then(function () { return llm_chain; });
|
|
6386
|
+
return LLMChain.deserialize(data);
|
|
6387
|
+
}
|
|
6388
|
+
case "sequential_chain": {
|
|
6389
|
+
const { SequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
|
|
6390
|
+
return SequentialChain.deserialize(data);
|
|
6391
|
+
}
|
|
6392
|
+
case "simple_sequential_chain": {
|
|
6393
|
+
const { SimpleSequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
|
|
6394
|
+
return SimpleSequentialChain.deserialize(data);
|
|
6395
|
+
}
|
|
6396
|
+
case "stuff_documents_chain": {
|
|
6397
|
+
const { StuffDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
6398
|
+
return StuffDocumentsChain.deserialize(data);
|
|
6399
|
+
}
|
|
6400
|
+
case "map_reduce_documents_chain": {
|
|
6401
|
+
const { MapReduceDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
6402
|
+
return MapReduceDocumentsChain.deserialize(data);
|
|
6403
|
+
}
|
|
6404
|
+
case "refine_documents_chain": {
|
|
6405
|
+
const { RefineDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
6406
|
+
return RefineDocumentsChain.deserialize(data);
|
|
6407
|
+
}
|
|
6408
|
+
case "vector_db_qa": {
|
|
6409
|
+
const { VectorDBQAChain } = await Promise.resolve().then(function () { return vector_db_qa; });
|
|
6410
|
+
return VectorDBQAChain.deserialize(data, values);
|
|
6411
|
+
}
|
|
6412
|
+
case "api_chain": {
|
|
6413
|
+
const { APIChain } = await Promise.resolve().then(function () { return api_chain; });
|
|
6414
|
+
return APIChain.deserialize(data);
|
|
6415
|
+
}
|
|
6416
|
+
default:
|
|
6417
|
+
throw new Error(`Invalid prompt type in config: ${data._type}`);
|
|
6418
|
+
}
|
|
6439
6419
|
}
|
|
6440
|
-
return [];
|
|
6441
6420
|
}
|
|
6442
6421
|
|
|
6443
6422
|
/**
|
|
6444
|
-
*
|
|
6445
|
-
*
|
|
6423
|
+
* The NoOpOutputParser class is a type of output parser that does not
|
|
6424
|
+
* perform any operations on the output. It extends the BaseOutputParser
|
|
6425
|
+
* class and is part of the LangChain's output parsers module. This class
|
|
6426
|
+
* is useful in scenarios where the raw output of the Large Language
|
|
6427
|
+
* Models (LLMs) is required.
|
|
6446
6428
|
*/
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6429
|
+
class NoOpOutputParser extends output_parsers.BaseOutputParser {
|
|
6430
|
+
constructor() {
|
|
6431
|
+
super(...arguments);
|
|
6432
|
+
Object.defineProperty(this, "lc_namespace", {
|
|
6433
|
+
enumerable: true,
|
|
6434
|
+
configurable: true,
|
|
6435
|
+
writable: true,
|
|
6436
|
+
value: ["langchain", "output_parsers", "default"]
|
|
6437
|
+
});
|
|
6438
|
+
Object.defineProperty(this, "lc_serializable", {
|
|
6439
|
+
enumerable: true,
|
|
6440
|
+
configurable: true,
|
|
6441
|
+
writable: true,
|
|
6442
|
+
value: true
|
|
6443
|
+
});
|
|
6451
6444
|
}
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
process.exit(1);
|
|
6445
|
+
static lc_name() {
|
|
6446
|
+
return "NoOpOutputParser";
|
|
6455
6447
|
}
|
|
6456
|
-
|
|
6457
|
-
|
|
6448
|
+
/**
|
|
6449
|
+
* This method takes a string as input and returns the same string as
|
|
6450
|
+
* output. It does not perform any operations on the input string.
|
|
6451
|
+
* @param text The input string to be parsed.
|
|
6452
|
+
* @returns The same input string without any operations performed on it.
|
|
6453
|
+
*/
|
|
6454
|
+
parse(text) {
|
|
6455
|
+
return Promise.resolve(text);
|
|
6456
|
+
}
|
|
6457
|
+
/**
|
|
6458
|
+
* This method returns an empty string. It does not provide any formatting
|
|
6459
|
+
* instructions.
|
|
6460
|
+
* @returns An empty string, indicating no formatting instructions.
|
|
6461
|
+
*/
|
|
6462
|
+
getFormatInstructions() {
|
|
6463
|
+
return "";
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6458
6466
|
|
|
6467
|
+
function isBaseLanguageModel(llmLike) {
|
|
6468
|
+
return typeof llmLike._llmType === "function";
|
|
6469
|
+
}
|
|
6470
|
+
function _getLanguageModel(llmLike) {
|
|
6471
|
+
if (isBaseLanguageModel(llmLike)) {
|
|
6472
|
+
return llmLike;
|
|
6473
|
+
}
|
|
6474
|
+
else if ("bound" in llmLike && runnables.Runnable.isRunnable(llmLike.bound)) {
|
|
6475
|
+
return _getLanguageModel(llmLike.bound);
|
|
6476
|
+
}
|
|
6477
|
+
else if ("runnable" in llmLike &&
|
|
6478
|
+
"fallbacks" in llmLike &&
|
|
6479
|
+
runnables.Runnable.isRunnable(llmLike.runnable)) {
|
|
6480
|
+
return _getLanguageModel(llmLike.runnable);
|
|
6481
|
+
}
|
|
6482
|
+
else if ("default" in llmLike && runnables.Runnable.isRunnable(llmLike.default)) {
|
|
6483
|
+
return _getLanguageModel(llmLike.default);
|
|
6484
|
+
}
|
|
6485
|
+
else {
|
|
6486
|
+
throw new Error("Unable to extract BaseLanguageModel from llmLike object.");
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6459
6489
|
/**
|
|
6460
|
-
*
|
|
6490
|
+
* @deprecated This class will be removed in 1.0.0. Use the LangChain Expression Language (LCEL) instead.
|
|
6491
|
+
* See the example below for how to use LCEL with the LLMChain class:
|
|
6461
6492
|
*
|
|
6462
|
-
*
|
|
6463
|
-
*
|
|
6464
|
-
*
|
|
6465
|
-
*
|
|
6466
|
-
*
|
|
6467
|
-
*
|
|
6493
|
+
* Chain to run queries against LLMs.
|
|
6494
|
+
*
|
|
6495
|
+
* @example
|
|
6496
|
+
* ```ts
|
|
6497
|
+
* import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
6498
|
+
* import { ChatOpenAI } from "@langchain/openai";
|
|
6499
|
+
*
|
|
6500
|
+
* const prompt = ChatPromptTemplate.fromTemplate("Tell me a {adjective} joke");
|
|
6501
|
+
* const llm = new ChatOpenAI();
|
|
6502
|
+
* const chain = prompt.pipe(llm);
|
|
6503
|
+
*
|
|
6504
|
+
* const response = await chain.invoke({ adjective: "funny" });
|
|
6505
|
+
* ```
|
|
6468
6506
|
*/
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
Please follow the guidelines below when writing your commit message:
|
|
6473
|
-
|
|
6474
|
-
- Write concisely using an informal tone
|
|
6475
|
-
- Avoid phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
6476
|
-
- Avoid referencing specific files names or long paths in the commit message
|
|
6477
|
-
- DO NOT include any diffs or file changes in the commit message
|
|
6478
|
-
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
6479
|
-
|
|
6480
|
-
""""""
|
|
6481
|
-
{{summary}}
|
|
6482
|
-
""""""
|
|
6483
|
-
|
|
6484
|
-
{{branch_name_context}}
|
|
6485
|
-
|
|
6486
|
-
{{format_instructions}}
|
|
6487
|
-
|
|
6488
|
-
{{commit_history}}
|
|
6489
|
-
|
|
6490
|
-
{{additional_context}}
|
|
6491
|
-
`;
|
|
6492
|
-
// Define the variables that will be passed to the prompt template
|
|
6493
|
-
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history', 'branch_name_context'];
|
|
6494
|
-
const COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
6495
|
-
template: template$4,
|
|
6496
|
-
inputVariables: inputVariables$3,
|
|
6497
|
-
});
|
|
6498
|
-
const CONVENTIONAL_TEMPLATE = `Generate a commit message that strictly adheres to the Conventional Commits specification. Follow these rules precisely:
|
|
6499
|
-
|
|
6500
|
-
1. Type Selection:
|
|
6501
|
-
- Choose ONE of these types based on the changes:
|
|
6502
|
-
* feat: A new feature
|
|
6503
|
-
* fix: A bug fix
|
|
6504
|
-
* docs: Documentation only changes
|
|
6505
|
-
* style: Changes that don't affect the code's meaning (white-space, formatting, etc)
|
|
6506
|
-
* refactor: Code changes that neither fix a bug nor add a feature
|
|
6507
|
-
* perf: Code changes that improve performance
|
|
6508
|
-
* test: Adding missing tests or correcting existing tests
|
|
6509
|
-
* build: Changes that affect the build system or external dependencies
|
|
6510
|
-
* ci: Changes to CI configuration files and scripts
|
|
6511
|
-
* chore: Other changes that don't modify src or test files
|
|
6512
|
-
* revert: Reverts a previous commit
|
|
6513
|
-
|
|
6514
|
-
2. Format Requirements:
|
|
6515
|
-
- Title format: <type>(<optional-scope>): <description>
|
|
6516
|
-
- Title must be 50 characters or less
|
|
6517
|
-
- Description should be in imperative mood (e.g., "add" not "adds/added")
|
|
6518
|
-
- Body MUST be 280 characters or less
|
|
6519
|
-
- Separate body from title with a blank line
|
|
6520
|
-
- Body should explain the motivation for the change and contrast it with previous behavior
|
|
6521
|
-
|
|
6522
|
-
3. Scope Guidelines:
|
|
6523
|
-
- If the change affects a specific component/area, include it as a scope
|
|
6524
|
-
- Scope should be a noun in parentheses (e.g., (parser), (ui), (config))
|
|
6525
|
-
- Omit scope if the change is broad or affects multiple areas
|
|
6526
|
-
|
|
6527
|
-
Based on the following diff summary, generate a conventional commit message that follows these rules exactly:
|
|
6528
|
-
|
|
6529
|
-
""""""
|
|
6530
|
-
{{summary}}
|
|
6531
|
-
""""""
|
|
6532
|
-
|
|
6533
|
-
{{branch_name_context}}
|
|
6534
|
-
|
|
6535
|
-
{{format_instructions}}
|
|
6536
|
-
|
|
6537
|
-
{{commit_history}}
|
|
6538
|
-
|
|
6539
|
-
{{additional_context}}
|
|
6540
|
-
|
|
6541
|
-
Remember:
|
|
6542
|
-
- Be concise and precise
|
|
6543
|
-
- Focus on WHAT and WHY, not HOW
|
|
6544
|
-
- Use imperative mood in both title and body
|
|
6545
|
-
- Ensure the title alone provides enough context to understand the change`;
|
|
6546
|
-
const conventionalInputVariables = [
|
|
6547
|
-
'summary',
|
|
6548
|
-
'additional_context',
|
|
6549
|
-
'commit_history',
|
|
6550
|
-
'format_instructions',
|
|
6551
|
-
'branch_name_context',
|
|
6552
|
-
];
|
|
6553
|
-
const CONVENTIONAL_COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
6554
|
-
template: CONVENTIONAL_TEMPLATE,
|
|
6555
|
-
inputVariables: conventionalInputVariables,
|
|
6556
|
-
});
|
|
6557
|
-
|
|
6558
|
-
/**
|
|
6559
|
-
* Verify template string contains all required input variables
|
|
6560
|
-
*
|
|
6561
|
-
* @param text template string
|
|
6562
|
-
* @param inputVariables template variables
|
|
6563
|
-
* @throws Error if validation fails
|
|
6564
|
-
*/
|
|
6565
|
-
function validatePromptTemplate(text, inputVariables) {
|
|
6566
|
-
if (!text || text.trim() === '') {
|
|
6567
|
-
throw new Error('Prompt template cannot be empty');
|
|
6568
|
-
}
|
|
6569
|
-
if (!inputVariables || inputVariables.length === 0) {
|
|
6570
|
-
return; // No variables to validate
|
|
6571
|
-
}
|
|
6572
|
-
// Extract variables from template using regex to find {variable} patterns
|
|
6573
|
-
// This regex matches {variable_name} with no whitespace inside braces
|
|
6574
|
-
// Excludes JSON-like patterns with quotes, colons, or whitespace
|
|
6575
|
-
const templateVariableRegex = /\{([^}\s:"']+)\}/g;
|
|
6576
|
-
const foundVariables = new Set();
|
|
6577
|
-
let match;
|
|
6578
|
-
while ((match = templateVariableRegex.exec(text)) !== null) {
|
|
6579
|
-
foundVariables.add(match[1]);
|
|
6507
|
+
class LLMChain extends BaseChain {
|
|
6508
|
+
static lc_name() {
|
|
6509
|
+
return "LLMChain";
|
|
6580
6510
|
}
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
if (missingVariables.length > 0) {
|
|
6584
|
-
throw new Error(`Prompt template is missing required variables: ${missingVariables.map(v => `{${v}}`).join(', ')}. ` +
|
|
6585
|
-
`Found variables: ${Array.from(foundVariables).map(v => `{${v}}`).join(', ') || 'none'}`);
|
|
6511
|
+
get inputKeys() {
|
|
6512
|
+
return this.prompt.inputVariables;
|
|
6586
6513
|
}
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
if (unusedVariables.length > 0) {
|
|
6590
|
-
console.warn(`Prompt template contains undefined variables: ${unusedVariables.map(v => `{${v}}`).join(', ')}`);
|
|
6514
|
+
get outputKeys() {
|
|
6515
|
+
return [this.outputKey];
|
|
6591
6516
|
}
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
postfix: 'Press ENTER to continue',
|
|
6600
|
-
validate: (text) => {
|
|
6601
|
-
try {
|
|
6602
|
-
validatePromptTemplate(text, COMMIT_PROMPT.inputVariables);
|
|
6603
|
-
return true;
|
|
6604
|
-
}
|
|
6605
|
-
catch (error) {
|
|
6606
|
-
return error instanceof Error ? error.message : 'Invalid prompt template';
|
|
6607
|
-
}
|
|
6608
|
-
},
|
|
6609
|
-
});
|
|
6610
|
-
}
|
|
6611
|
-
|
|
6612
|
-
async function editResult(result, options) {
|
|
6613
|
-
if (options.openInEditor) {
|
|
6614
|
-
return await prompts.editor({
|
|
6615
|
-
message: 'Edit the commit message',
|
|
6616
|
-
default: result,
|
|
6617
|
-
waitForUseInput: false,
|
|
6618
|
-
validate: (text) => (text ? true : 'Commit message cannot be empty'),
|
|
6517
|
+
constructor(fields) {
|
|
6518
|
+
super(fields);
|
|
6519
|
+
Object.defineProperty(this, "lc_serializable", {
|
|
6520
|
+
enumerable: true,
|
|
6521
|
+
configurable: true,
|
|
6522
|
+
writable: true,
|
|
6523
|
+
value: true
|
|
6619
6524
|
});
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
const choices = [
|
|
6626
|
-
{
|
|
6627
|
-
name: labels?.approve || '✨ Looks good!',
|
|
6628
|
-
value: 'approve',
|
|
6629
|
-
description: descriptions?.approve || `Continue with the generated ${label}`,
|
|
6630
|
-
},
|
|
6631
|
-
];
|
|
6632
|
-
if (enableEdit) {
|
|
6633
|
-
choices.push({
|
|
6634
|
-
name: '📝 Edit',
|
|
6635
|
-
value: 'edit',
|
|
6636
|
-
description: descriptions?.edit || `Edit the generated ${label} before proceeding`,
|
|
6525
|
+
Object.defineProperty(this, "prompt", {
|
|
6526
|
+
enumerable: true,
|
|
6527
|
+
configurable: true,
|
|
6528
|
+
writable: true,
|
|
6529
|
+
value: void 0
|
|
6637
6530
|
});
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
value:
|
|
6643
|
-
description: descriptions?.modifyPrompt || `Modify the prompt template and regenerate the ${label}`,
|
|
6531
|
+
Object.defineProperty(this, "llm", {
|
|
6532
|
+
enumerable: true,
|
|
6533
|
+
configurable: true,
|
|
6534
|
+
writable: true,
|
|
6535
|
+
value: void 0
|
|
6644
6536
|
});
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
value:
|
|
6650
|
-
description: descriptions?.retryMessageOnly ||
|
|
6651
|
-
`Restart the function execution from generating the ${label}`,
|
|
6537
|
+
Object.defineProperty(this, "llmKwargs", {
|
|
6538
|
+
enumerable: true,
|
|
6539
|
+
configurable: true,
|
|
6540
|
+
writable: true,
|
|
6541
|
+
value: void 0
|
|
6652
6542
|
});
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
value:
|
|
6658
|
-
description: descriptions?.retryFull ||
|
|
6659
|
-
`Restart the function execution from the beginning, regenerating both the summary and ${label}`,
|
|
6543
|
+
Object.defineProperty(this, "outputKey", {
|
|
6544
|
+
enumerable: true,
|
|
6545
|
+
configurable: true,
|
|
6546
|
+
writable: true,
|
|
6547
|
+
value: "text"
|
|
6660
6548
|
});
|
|
6549
|
+
Object.defineProperty(this, "outputParser", {
|
|
6550
|
+
enumerable: true,
|
|
6551
|
+
configurable: true,
|
|
6552
|
+
writable: true,
|
|
6553
|
+
value: void 0
|
|
6554
|
+
});
|
|
6555
|
+
this.prompt = fields.prompt;
|
|
6556
|
+
this.llm = fields.llm;
|
|
6557
|
+
this.llmKwargs = fields.llmKwargs;
|
|
6558
|
+
this.outputKey = fields.outputKey ?? this.outputKey;
|
|
6559
|
+
this.outputParser =
|
|
6560
|
+
fields.outputParser ?? new NoOpOutputParser();
|
|
6561
|
+
if (this.prompt.outputParser) {
|
|
6562
|
+
if (fields.outputParser) {
|
|
6563
|
+
throw new Error("Cannot set both outputParser and prompt.outputParser");
|
|
6564
|
+
}
|
|
6565
|
+
this.outputParser = this.prompt.outputParser;
|
|
6566
|
+
}
|
|
6661
6567
|
}
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
description: descriptions?.cancel || `Cancel the ${label}`,
|
|
6666
|
-
});
|
|
6667
|
-
return (await prompts.select({
|
|
6668
|
-
message: selectLabel || `Would you like to make any changes to the ${label}?`,
|
|
6669
|
-
choices,
|
|
6670
|
-
}));
|
|
6671
|
-
}
|
|
6672
|
-
|
|
6673
|
-
function logResult(label, result) {
|
|
6674
|
-
console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
|
|
6675
|
-
}
|
|
6676
|
-
|
|
6677
|
-
async function generateAndReviewLoop({ label, factory, parser, noResult, agent, reviewParser, options, }) {
|
|
6678
|
-
const { logger } = options;
|
|
6679
|
-
let continueLoop = true;
|
|
6680
|
-
let modifyPrompt = false;
|
|
6681
|
-
let context = '';
|
|
6682
|
-
let result = '';
|
|
6683
|
-
const changes = await factory();
|
|
6684
|
-
// if we don't have any changes, bail.
|
|
6685
|
-
if (!changes || !Object.keys(changes).length) {
|
|
6686
|
-
await noResult(options);
|
|
6568
|
+
getCallKeys() {
|
|
6569
|
+
const callKeys = "callKeys" in this.llm ? this.llm.callKeys : [];
|
|
6570
|
+
return callKeys;
|
|
6687
6571
|
}
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6572
|
+
/** @ignore */
|
|
6573
|
+
_selectMemoryInputs(values) {
|
|
6574
|
+
const valuesForMemory = super._selectMemoryInputs(values);
|
|
6575
|
+
const callKeys = this.getCallKeys();
|
|
6576
|
+
for (const key of callKeys) {
|
|
6577
|
+
if (key in values) {
|
|
6578
|
+
delete valuesForMemory[key];
|
|
6579
|
+
}
|
|
6691
6580
|
}
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6581
|
+
return valuesForMemory;
|
|
6582
|
+
}
|
|
6583
|
+
/** @ignore */
|
|
6584
|
+
async _getFinalOutput(generations, promptValue, runManager) {
|
|
6585
|
+
let finalCompletion;
|
|
6586
|
+
if (this.outputParser) {
|
|
6587
|
+
finalCompletion = await this.outputParser.parseResultWithPrompt(generations, promptValue, runManager?.getChild());
|
|
6695
6588
|
}
|
|
6696
|
-
|
|
6697
|
-
|
|
6589
|
+
else {
|
|
6590
|
+
finalCompletion = generations[0].text;
|
|
6698
6591
|
}
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6592
|
+
return finalCompletion;
|
|
6593
|
+
}
|
|
6594
|
+
/**
|
|
6595
|
+
* Run the core logic of this chain and add to output if desired.
|
|
6596
|
+
*
|
|
6597
|
+
* Wraps _call and handles memory.
|
|
6598
|
+
*/
|
|
6599
|
+
call(values, config) {
|
|
6600
|
+
return super.call(values, config);
|
|
6601
|
+
}
|
|
6602
|
+
/** @ignore */
|
|
6603
|
+
async _call(values, runManager) {
|
|
6604
|
+
const valuesForPrompt = { ...values };
|
|
6605
|
+
const valuesForLLM = {
|
|
6606
|
+
...this.llmKwargs,
|
|
6607
|
+
};
|
|
6608
|
+
const callKeys = this.getCallKeys();
|
|
6609
|
+
for (const key of callKeys) {
|
|
6610
|
+
if (key in values) {
|
|
6611
|
+
if (valuesForLLM) {
|
|
6612
|
+
valuesForLLM[key] =
|
|
6613
|
+
values[key];
|
|
6614
|
+
delete valuesForPrompt[key];
|
|
6615
|
+
}
|
|
6710
6616
|
}
|
|
6711
6617
|
}
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
});
|
|
6719
|
-
result = '';
|
|
6720
|
-
continue;
|
|
6721
|
-
}
|
|
6722
|
-
// Re-throw other errors
|
|
6723
|
-
throw error;
|
|
6618
|
+
const promptValue = await this.prompt.formatPromptValue(valuesForPrompt);
|
|
6619
|
+
if ("generatePrompt" in this.llm) {
|
|
6620
|
+
const { generations } = await this.llm.generatePrompt([promptValue], valuesForLLM, runManager?.getChild());
|
|
6621
|
+
return {
|
|
6622
|
+
[this.outputKey]: await this._getFinalOutput(generations[0], promptValue, runManager),
|
|
6623
|
+
};
|
|
6724
6624
|
}
|
|
6725
|
-
|
|
6726
|
-
.
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
.
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
result = '';
|
|
6757
|
-
continue;
|
|
6758
|
-
}
|
|
6759
|
-
// Only edit the result in interactive mode if approved
|
|
6760
|
-
result = await editResult(result, options);
|
|
6625
|
+
const modelWithParser = this.outputParser
|
|
6626
|
+
? this.llm.pipe(this.outputParser)
|
|
6627
|
+
: this.llm;
|
|
6628
|
+
const response = await modelWithParser.invoke(promptValue, runManager?.getChild());
|
|
6629
|
+
return {
|
|
6630
|
+
[this.outputKey]: response,
|
|
6631
|
+
};
|
|
6632
|
+
}
|
|
6633
|
+
/**
|
|
6634
|
+
* Format prompt with values and pass to LLM
|
|
6635
|
+
*
|
|
6636
|
+
* @param values - keys to pass to prompt template
|
|
6637
|
+
* @param callbackManager - CallbackManager to use
|
|
6638
|
+
* @returns Completion from LLM.
|
|
6639
|
+
*
|
|
6640
|
+
* @example
|
|
6641
|
+
* ```ts
|
|
6642
|
+
* llm.predict({ adjective: "funny" })
|
|
6643
|
+
* ```
|
|
6644
|
+
*/
|
|
6645
|
+
async predict(values, callbackManager) {
|
|
6646
|
+
const output = await this.call(values, callbackManager);
|
|
6647
|
+
return output[this.outputKey];
|
|
6648
|
+
}
|
|
6649
|
+
_chainType() {
|
|
6650
|
+
return "llm";
|
|
6651
|
+
}
|
|
6652
|
+
static async deserialize(data) {
|
|
6653
|
+
const { llm, prompt } = data;
|
|
6654
|
+
if (!llm) {
|
|
6655
|
+
throw new Error("LLMChain must have llm");
|
|
6761
6656
|
}
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
const displayResult = reviewParser ? reviewParser(result, options) : result;
|
|
6765
|
-
// In non-interactive mode, ensure we return the properly formatted result
|
|
6766
|
-
result = displayResult;
|
|
6657
|
+
if (!prompt) {
|
|
6658
|
+
throw new Error("LLMChain must have prompt");
|
|
6767
6659
|
}
|
|
6768
|
-
|
|
6769
|
-
|
|
6660
|
+
return new LLMChain({
|
|
6661
|
+
llm: await base.BaseLanguageModel.deserialize(llm),
|
|
6662
|
+
prompt: await prompts$1.BasePromptTemplate.deserialize(prompt),
|
|
6663
|
+
});
|
|
6770
6664
|
}
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
await interactiveModeCallback(result);
|
|
6783
|
-
}
|
|
6784
|
-
else {
|
|
6785
|
-
console.warn('No result handler provided for interactive mode.');
|
|
6786
|
-
logSuccess();
|
|
6787
|
-
}
|
|
6788
|
-
break;
|
|
6789
|
-
case 'stdout':
|
|
6790
|
-
default:
|
|
6791
|
-
// Ensure we write the result to stdout in non-interactive mode
|
|
6792
|
-
process.stdout.write(result + '\n', 'utf8');
|
|
6793
|
-
break;
|
|
6665
|
+
/** @deprecated */
|
|
6666
|
+
serialize() {
|
|
6667
|
+
const serialize = "serialize" in this.llm ? this.llm.serialize() : undefined;
|
|
6668
|
+
return {
|
|
6669
|
+
_type: `${this._chainType()}_chain`,
|
|
6670
|
+
llm: serialize,
|
|
6671
|
+
prompt: this.prompt.serialize(),
|
|
6672
|
+
};
|
|
6673
|
+
}
|
|
6674
|
+
_getNumTokens(text) {
|
|
6675
|
+
return _getLanguageModel(this.llm).getNumTokens(text);
|
|
6794
6676
|
}
|
|
6795
|
-
process.exit(0);
|
|
6796
6677
|
}
|
|
6797
6678
|
|
|
6798
|
-
|
|
6679
|
+
var llm_chain = /*#__PURE__*/Object.freeze({
|
|
6680
|
+
__proto__: null,
|
|
6681
|
+
LLMChain: LLMChain
|
|
6682
|
+
});
|
|
6799
6683
|
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6684
|
+
const NAIVE_FIX_TEMPLATE = `Instructions:
|
|
6685
|
+
--------------
|
|
6686
|
+
{instructions}
|
|
6687
|
+
--------------
|
|
6688
|
+
Completion:
|
|
6689
|
+
--------------
|
|
6690
|
+
{completion}
|
|
6691
|
+
--------------
|
|
6805
6692
|
|
|
6806
|
-
|
|
6693
|
+
Above, the Completion did not satisfy the constraints given in the Instructions.
|
|
6694
|
+
Error:
|
|
6695
|
+
--------------
|
|
6696
|
+
{error}
|
|
6697
|
+
--------------
|
|
6807
6698
|
|
|
6808
|
-
|
|
6809
|
-
const
|
|
6810
|
-
|
|
6811
|
-
template: template$3,
|
|
6812
|
-
inputVariables: inputVariables$2,
|
|
6813
|
-
});
|
|
6699
|
+
Please try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:`;
|
|
6700
|
+
const NAIVE_FIX_PROMPT =
|
|
6701
|
+
/* #__PURE__ */ prompts$1.PromptTemplate.fromTemplate(NAIVE_FIX_TEMPLATE);
|
|
6814
6702
|
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6703
|
+
function isLLMChain(x) {
|
|
6704
|
+
return (x.prompt !== undefined && x.llm !== undefined);
|
|
6705
|
+
}
|
|
6706
|
+
/**
|
|
6707
|
+
* Class that extends the BaseOutputParser to handle situations where the
|
|
6708
|
+
* initial parsing attempt fails. It contains a retryChain for retrying
|
|
6709
|
+
* the parsing process in case of a failure.
|
|
6710
|
+
*/
|
|
6711
|
+
class OutputFixingParser extends output_parsers.BaseOutputParser {
|
|
6712
|
+
static lc_name() {
|
|
6713
|
+
return "OutputFixingParser";
|
|
6823
6714
|
}
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6715
|
+
/**
|
|
6716
|
+
* Static method to create a new instance of OutputFixingParser using a
|
|
6717
|
+
* given language model, parser, and optional fields.
|
|
6718
|
+
* @param llm The language model to be used.
|
|
6719
|
+
* @param parser The parser to be used.
|
|
6720
|
+
* @param fields Optional fields which may contain a prompt.
|
|
6721
|
+
* @returns A new instance of OutputFixingParser.
|
|
6722
|
+
*/
|
|
6723
|
+
static fromLLM(llm, parser, fields) {
|
|
6724
|
+
const prompt = fields?.prompt ?? NAIVE_FIX_PROMPT;
|
|
6725
|
+
const chain = new LLMChain({ llm, prompt });
|
|
6726
|
+
return new OutputFixingParser({ parser, retryChain: chain });
|
|
6830
6727
|
}
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
};
|
|
6839
|
-
}
|
|
6840
|
-
if (config.range && config.range.includes(':')) {
|
|
6841
|
-
const [from, to] = config.range.split(':');
|
|
6842
|
-
if (!from || !to) {
|
|
6843
|
-
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
6844
|
-
process.exit(1);
|
|
6845
|
-
}
|
|
6846
|
-
return {
|
|
6847
|
-
branch: branchName,
|
|
6848
|
-
commits: await getCommitLogRange(from, to, { git, noMerges: true }),
|
|
6849
|
-
};
|
|
6850
|
-
}
|
|
6851
|
-
if (argv.branch) {
|
|
6852
|
-
logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
|
|
6853
|
-
return {
|
|
6854
|
-
branch: branchName,
|
|
6855
|
-
commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
|
|
6856
|
-
};
|
|
6857
|
-
}
|
|
6858
|
-
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, {
|
|
6859
|
-
color: 'yellow',
|
|
6728
|
+
constructor({ parser, retryChain, }) {
|
|
6729
|
+
super(...arguments);
|
|
6730
|
+
Object.defineProperty(this, "lc_namespace", {
|
|
6731
|
+
enumerable: true,
|
|
6732
|
+
configurable: true,
|
|
6733
|
+
writable: true,
|
|
6734
|
+
value: ["langchain", "output_parsers", "fix"]
|
|
6860
6735
|
});
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6736
|
+
Object.defineProperty(this, "lc_serializable", {
|
|
6737
|
+
enumerable: true,
|
|
6738
|
+
configurable: true,
|
|
6739
|
+
writable: true,
|
|
6740
|
+
value: true
|
|
6741
|
+
});
|
|
6742
|
+
Object.defineProperty(this, "parser", {
|
|
6743
|
+
enumerable: true,
|
|
6744
|
+
configurable: true,
|
|
6745
|
+
writable: true,
|
|
6746
|
+
value: void 0
|
|
6747
|
+
});
|
|
6748
|
+
Object.defineProperty(this, "retryChain", {
|
|
6749
|
+
enumerable: true,
|
|
6750
|
+
configurable: true,
|
|
6751
|
+
writable: true,
|
|
6752
|
+
value: void 0
|
|
6753
|
+
});
|
|
6754
|
+
this.parser = parser;
|
|
6755
|
+
this.retryChain = retryChain;
|
|
6866
6756
|
}
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6757
|
+
/**
|
|
6758
|
+
* Method to parse the completion using the parser. If the initial parsing
|
|
6759
|
+
* fails, it uses the retryChain to attempt to fix the output and retry
|
|
6760
|
+
* the parsing process.
|
|
6761
|
+
* @param completion The completion to be parsed.
|
|
6762
|
+
* @param callbacks Optional callbacks to be used during parsing.
|
|
6763
|
+
* @returns The parsed output.
|
|
6764
|
+
*/
|
|
6765
|
+
async parse(completion, callbacks) {
|
|
6766
|
+
try {
|
|
6767
|
+
return await this.parser.parse(completion, callbacks);
|
|
6871
6768
|
}
|
|
6872
|
-
|
|
6873
|
-
|
|
6769
|
+
catch (e) {
|
|
6770
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
6771
|
+
if (e instanceof output_parsers.OutputParserException) {
|
|
6772
|
+
const retryInput = {
|
|
6773
|
+
instructions: this.parser.getFormatInstructions(),
|
|
6774
|
+
completion,
|
|
6775
|
+
error: e,
|
|
6776
|
+
};
|
|
6777
|
+
if (isLLMChain(this.retryChain)) {
|
|
6778
|
+
const result = await this.retryChain.call(retryInput, callbacks);
|
|
6779
|
+
const newCompletion = result[this.retryChain.outputKey];
|
|
6780
|
+
return this.parser.parse(newCompletion, callbacks);
|
|
6781
|
+
}
|
|
6782
|
+
else {
|
|
6783
|
+
const result = await this.retryChain.invoke(retryInput, {
|
|
6784
|
+
callbacks,
|
|
6785
|
+
});
|
|
6786
|
+
return result;
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
throw e;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
/**
|
|
6793
|
+
* Method to get the format instructions for the parser.
|
|
6794
|
+
* @returns The format instructions for the parser.
|
|
6795
|
+
*/
|
|
6796
|
+
getFormatInstructions() {
|
|
6797
|
+
return this.parser.getFormatInstructions();
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
|
|
6801
|
+
/**
|
|
6802
|
+
* Creates a parser with built-in retry logic for schema-based generation
|
|
6803
|
+
* @param schema - Zod schema for the expected output structure
|
|
6804
|
+
* @param llm - LLM instance for retry attempts
|
|
6805
|
+
* @param options - Configuration options for retry behavior
|
|
6806
|
+
* @returns OutputFixingParser configured with retry logic
|
|
6807
|
+
* @throws LangChainExecutionError if parser creation fails
|
|
6808
|
+
*/
|
|
6809
|
+
function createSchemaParser(schema, llm, options = {}) {
|
|
6810
|
+
validateRequired(schema, 'schema', 'createSchemaParser');
|
|
6811
|
+
validateRequired(llm, 'llm', 'createSchemaParser');
|
|
6812
|
+
validateRequired(options, 'options', 'createSchemaParser');
|
|
6813
|
+
// Validate schema is actually a Zod schema
|
|
6814
|
+
if (typeof schema.parse !== 'function') {
|
|
6815
|
+
throw new LangChainExecutionError('createSchemaParser: Schema must be a valid Zod schema with a parse method', { schemaType: typeof schema, hasParseMethod: typeof schema.parse });
|
|
6816
|
+
}
|
|
6817
|
+
// Validate options structure
|
|
6818
|
+
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
6819
|
+
throw new LangChainExecutionError('createSchemaParser: Options must be a non-array object', { options, type: typeof options, isArray: Array.isArray(options) });
|
|
6820
|
+
}
|
|
6821
|
+
const { retryTemplate } = options;
|
|
6822
|
+
// Validate retryTemplate if provided
|
|
6823
|
+
if (retryTemplate !== undefined && typeof retryTemplate !== 'string') {
|
|
6824
|
+
throw new LangChainExecutionError('createSchemaParser: retryTemplate must be a string when provided', { retryTemplate, type: typeof retryTemplate });
|
|
6825
|
+
}
|
|
6826
|
+
try {
|
|
6827
|
+
// @ts-expect-error - StructuredOutputParser constructor type issue with Zod schema
|
|
6828
|
+
const baseParser = new output_parsers.StructuredOutputParser(schema);
|
|
6829
|
+
const defaultRetryTemplate = `The following text failed to parse as valid JSON. Please convert it into a valid JSON object that matches the required schema.
|
|
6830
|
+
|
|
6831
|
+
## Text to fix:
|
|
6832
|
+
{completion}
|
|
6833
|
+
|
|
6834
|
+
## Instructions:
|
|
6835
|
+
{instructions}
|
|
6836
|
+
|
|
6837
|
+
You must return ONLY valid JSON that matches the schema exactly. Do not include any additional text, explanations, or markdown formatting:`;
|
|
6838
|
+
const retryPromptTemplate = new prompts$1.PromptTemplate({
|
|
6839
|
+
template: retryTemplate || defaultRetryTemplate,
|
|
6840
|
+
inputVariables: ['completion', 'instructions'],
|
|
6841
|
+
});
|
|
6842
|
+
const retryChain = retryPromptTemplate.pipe(llm).pipe(baseParser);
|
|
6843
|
+
return new OutputFixingParser({
|
|
6844
|
+
parser: baseParser,
|
|
6845
|
+
retryChain: retryChain,
|
|
6846
|
+
});
|
|
6847
|
+
}
|
|
6848
|
+
catch (error) {
|
|
6849
|
+
handleLangChainError(error, 'createSchemaParser: Failed to create schema parser', {
|
|
6850
|
+
schemaName: schema.constructor.name,
|
|
6851
|
+
llmType: llm.constructor.name,
|
|
6852
|
+
hasRetryTemplate: !!retryTemplate
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
|
|
6857
|
+
/**
|
|
6858
|
+
* Executes a LangChain pipeline with the provided LLM, prompt, variables, and parser.
|
|
6859
|
+
* @param params - The execution parameters
|
|
6860
|
+
* @returns The parsed result from the LLM chain
|
|
6861
|
+
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
6862
|
+
*/
|
|
6863
|
+
const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
6864
|
+
validateRequired(llm, 'llm', 'executeChain');
|
|
6865
|
+
validateRequired(prompt, 'prompt', 'executeChain');
|
|
6866
|
+
validateRequired(variables, 'variables', 'executeChain');
|
|
6867
|
+
validateRequired(parser, 'parser', 'executeChain');
|
|
6868
|
+
// Validate that variables is an object
|
|
6869
|
+
if (typeof variables !== 'object' || Array.isArray(variables)) {
|
|
6870
|
+
throw new LangChainExecutionError('executeChain: Variables must be a non-array object', { variables, type: typeof variables, isArray: Array.isArray(variables) });
|
|
6871
|
+
}
|
|
6872
|
+
try {
|
|
6873
|
+
const chain = prompt.pipe(llm).pipe(parser);
|
|
6874
|
+
const result = await chain.invoke(variables);
|
|
6875
|
+
if (result === null || result === undefined) {
|
|
6876
|
+
throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
|
|
6874
6877
|
}
|
|
6875
6878
|
return result;
|
|
6876
6879
|
}
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
}
|
|
6888
|
-
|
|
6889
|
-
parser,
|
|
6890
|
-
agent: async (context, options) => {
|
|
6891
|
-
const parser = new output_parsers.StructuredOutputParser(ChangelogResponseSchema);
|
|
6892
|
-
const prompt = getPrompt({
|
|
6893
|
-
template: options.prompt,
|
|
6894
|
-
variables: CHANGELOG_PROMPT.inputVariables,
|
|
6895
|
-
fallback: CHANGELOG_PROMPT,
|
|
6896
|
-
});
|
|
6897
|
-
const formatInstructions = "Only respond with a valid JSON object, containing two fields: 'title' an escaped string, no more than 65 characters, and 'content' also an escaped string.";
|
|
6898
|
-
const changelog = await executeChain({
|
|
6899
|
-
llm,
|
|
6900
|
-
prompt,
|
|
6901
|
-
variables: {
|
|
6902
|
-
summary: context,
|
|
6903
|
-
format_instructions: formatInstructions,
|
|
6904
|
-
},
|
|
6905
|
-
parser,
|
|
6906
|
-
});
|
|
6907
|
-
const branchName = await getCurrentBranchName({ git });
|
|
6908
|
-
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
6909
|
-
const footer = ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
6910
|
-
return `${changelog.title}\n\n${changelog.content}${footer}`;
|
|
6911
|
-
},
|
|
6912
|
-
noResult: async () => {
|
|
6913
|
-
if (config.range) {
|
|
6914
|
-
logger.log(`No commits found in the provided range.`, { color: 'red' });
|
|
6915
|
-
process.exit(0);
|
|
6916
|
-
}
|
|
6917
|
-
logger.log(`No commits found in the current branch.`, { color: 'red' });
|
|
6918
|
-
process.exit(0);
|
|
6919
|
-
},
|
|
6920
|
-
});
|
|
6921
|
-
const MODE = (INTERACTIVE && 'interactive') || (config.commit && 'interactive') || config?.mode || 'stdout';
|
|
6922
|
-
handleResult({
|
|
6923
|
-
result: changelogMsg,
|
|
6924
|
-
interactiveModeCallback: async () => {
|
|
6925
|
-
logSuccess();
|
|
6926
|
-
},
|
|
6927
|
-
mode: MODE,
|
|
6928
|
-
});
|
|
6880
|
+
catch (error) {
|
|
6881
|
+
// Re-throw LangChain errors as-is
|
|
6882
|
+
if (error instanceof LangChainExecutionError) {
|
|
6883
|
+
throw error;
|
|
6884
|
+
}
|
|
6885
|
+
// Wrap other errors with context
|
|
6886
|
+
handleLangChainError(error, 'executeChain: Chain execution failed', {
|
|
6887
|
+
promptInputVariables: prompt.inputVariables,
|
|
6888
|
+
variableKeys: Object.keys(variables),
|
|
6889
|
+
parserType: parser.constructor.name
|
|
6890
|
+
});
|
|
6891
|
+
}
|
|
6929
6892
|
};
|
|
6930
6893
|
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
options: options$4,
|
|
6937
|
-
};
|
|
6894
|
+
function extractTicketIdFromBranchName(branchName) {
|
|
6895
|
+
const regex = /((?<!([A-Z]+)-?)[A-Z]+-\d+)/;
|
|
6896
|
+
const match = branchName.match(regex);
|
|
6897
|
+
return match ? match[0] : null;
|
|
6898
|
+
}
|
|
6938
6899
|
|
|
6939
|
-
const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:/;
|
|
6940
|
-
// Regular commit message schema with basic validation
|
|
6941
|
-
const CommitMessageResponseSchema = objectType({
|
|
6942
|
-
title: stringType(),
|
|
6943
|
-
body: stringType(),
|
|
6944
|
-
});
|
|
6945
|
-
// Conventional commit message schema with strict formatting rules
|
|
6946
|
-
const ConventionalCommitMessageResponseSchema = objectType({
|
|
6947
|
-
title: stringType()
|
|
6948
|
-
.max(50, "Title must be 50 characters or less")
|
|
6949
|
-
.refine((title) => conventionalTypeRegex.test(title), "Title must follow Conventional Commits format (e.g., 'feat: add new feature' or 'fix(scope): fix bug')"),
|
|
6950
|
-
body: stringType()
|
|
6951
|
-
// .max(280, "Body must be 280 characters or less"),
|
|
6952
|
-
});
|
|
6953
|
-
const command$3 = 'commit';
|
|
6954
6900
|
/**
|
|
6955
|
-
*
|
|
6901
|
+
* Formats a commit log into a readable string format.
|
|
6902
|
+
*
|
|
6903
|
+
* @param commitLog - The commit log result containing an array of commit details.
|
|
6904
|
+
* @returns An array of formatted commit log strings.
|
|
6905
|
+
*
|
|
6906
|
+
* Each formatted string includes:
|
|
6907
|
+
* - The date of the commit in square brackets.
|
|
6908
|
+
* - The commit message.
|
|
6909
|
+
* - The commit body.
|
|
6910
|
+
* - The commit hash in parentheses.
|
|
6911
|
+
* - The author's name and email in angle brackets.
|
|
6956
6912
|
*/
|
|
6957
|
-
const
|
|
6958
|
-
|
|
6959
|
-
alias: 'interactive',
|
|
6960
|
-
description: 'Toggle interactive mode',
|
|
6961
|
-
type: 'boolean',
|
|
6962
|
-
},
|
|
6963
|
-
ignoredFiles: {
|
|
6964
|
-
description: 'Ignored files',
|
|
6965
|
-
type: 'array',
|
|
6966
|
-
},
|
|
6967
|
-
ignoredExtensions: {
|
|
6968
|
-
description: 'Ignored extensions',
|
|
6969
|
-
type: 'array',
|
|
6970
|
-
},
|
|
6971
|
-
append: {
|
|
6972
|
-
description: 'Add content to the end of the generated commit message',
|
|
6973
|
-
type: 'string',
|
|
6974
|
-
},
|
|
6975
|
-
appendTicket: {
|
|
6976
|
-
description: 'Append ticket ID from branch name to the commit message',
|
|
6977
|
-
type: 'boolean',
|
|
6978
|
-
alias: 't',
|
|
6979
|
-
},
|
|
6980
|
-
additional: {
|
|
6981
|
-
description: 'Add extra contextual information to the prompt',
|
|
6982
|
-
type: 'string',
|
|
6983
|
-
alias: 'a',
|
|
6984
|
-
},
|
|
6985
|
-
withPreviousCommits: {
|
|
6986
|
-
description: 'Include previous commits as context (specify number of commits, 0 for none)',
|
|
6987
|
-
type: 'number',
|
|
6988
|
-
default: 0,
|
|
6989
|
-
alias: 'p',
|
|
6990
|
-
},
|
|
6991
|
-
conventional: {
|
|
6992
|
-
description: 'Generate commit message in Conventional Commits format',
|
|
6993
|
-
type: 'boolean',
|
|
6994
|
-
default: false,
|
|
6995
|
-
alias: 'c',
|
|
6996
|
-
},
|
|
6997
|
-
includeBranchName: {
|
|
6998
|
-
description: 'Include the current branch name in the commit prompt for context',
|
|
6999
|
-
type: 'boolean',
|
|
7000
|
-
default: true,
|
|
7001
|
-
},
|
|
7002
|
-
noDiff: {
|
|
7003
|
-
description: 'Only pass basic "git status" result instead of providing entire diff',
|
|
7004
|
-
type: 'boolean',
|
|
7005
|
-
default: false,
|
|
7006
|
-
},
|
|
6913
|
+
const formatCommitLog = (commitLog) => {
|
|
6914
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
7007
6915
|
};
|
|
7008
|
-
|
|
7009
|
-
|
|
6916
|
+
|
|
6917
|
+
const getChangesSinceLastTag = async ({ git }) => {
|
|
6918
|
+
const tags = await git.tags();
|
|
6919
|
+
if (tags.all.length > 0) {
|
|
6920
|
+
const lastTag = tags.latest;
|
|
6921
|
+
const commitLog = await git.log({ from: lastTag });
|
|
6922
|
+
return formatCommitLog(commitLog);
|
|
6923
|
+
}
|
|
6924
|
+
else {
|
|
6925
|
+
return ['No tags found in the repository.'];
|
|
6926
|
+
}
|
|
7010
6927
|
};
|
|
7011
6928
|
|
|
7012
|
-
|
|
6929
|
+
/**
|
|
6930
|
+
* Retrieves the commit log range between two specified commits (inclusive of both commits).
|
|
6931
|
+
*
|
|
6932
|
+
* @param from - The starting commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
|
|
6933
|
+
* @param to - The ending commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
|
|
6934
|
+
* @param options - Additional options for retrieving the commit log range.
|
|
6935
|
+
* @returns A promise that resolves to an array of commit log messages, including both the 'from' and 'to' commits.
|
|
6936
|
+
* @throws If there is an error retrieving the commit log range.
|
|
6937
|
+
*/
|
|
6938
|
+
async function getCommitLogRange(from, to, { noMerges, git }) {
|
|
6939
|
+
try {
|
|
6940
|
+
// Use from^..to to include the 'from' commit in the range
|
|
6941
|
+
// This works because from^..to means "commits reachable from 'to' but not from the parent of 'from'"
|
|
6942
|
+
const logOptions = {
|
|
6943
|
+
from: `${from}^`,
|
|
6944
|
+
to,
|
|
6945
|
+
'--no-merges': noMerges
|
|
6946
|
+
};
|
|
6947
|
+
const commitLog = await git.log(logOptions);
|
|
6948
|
+
return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6949
|
+
}
|
|
6950
|
+
catch (error) {
|
|
6951
|
+
// If from^ fails (e.g., 'from' is the first commit), fall back to using from..to and manually adding the 'from' commit
|
|
6952
|
+
if (error instanceof Error && error.message.includes('unknown revision')) {
|
|
6953
|
+
try {
|
|
6954
|
+
// Get the 'from' commit separately
|
|
6955
|
+
const fromCommitLog = await git.log({ from: from, maxCount: 1 });
|
|
6956
|
+
const fromCommit = fromCommitLog.latest;
|
|
6957
|
+
// Get the range from..to (excluding 'from')
|
|
6958
|
+
const rangeLogOptions = {
|
|
6959
|
+
from,
|
|
6960
|
+
to,
|
|
6961
|
+
'--no-merges': noMerges
|
|
6962
|
+
};
|
|
6963
|
+
const rangeCommitLog = await git.log(rangeLogOptions);
|
|
6964
|
+
// Combine the 'from' commit with the range commits
|
|
6965
|
+
const allCommits = fromCommit
|
|
6966
|
+
? [fromCommit, ...rangeCommitLog.all]
|
|
6967
|
+
: rangeCommitLog.all;
|
|
6968
|
+
return allCommits.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
|
|
6969
|
+
}
|
|
6970
|
+
catch (fallbackError) {
|
|
6971
|
+
throw fallbackError;
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
throw error;
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
|
|
6978
|
+
/**
|
|
6979
|
+
* Retrieves the name of the current branch.
|
|
6980
|
+
*
|
|
6981
|
+
* @param {GetCurrentBranchName} options - The options for retrieving the branch name.
|
|
6982
|
+
* @returns {Promise<string>} - A promise that resolves to the name of the current branch.
|
|
6983
|
+
*/
|
|
6984
|
+
async function getCurrentBranchName({ git }) {
|
|
6985
|
+
return await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
6986
|
+
}
|
|
7013
6987
|
|
|
7014
6988
|
/**
|
|
7015
|
-
*
|
|
6989
|
+
* Retrieves the commit log between the current branch and a specified target branch.
|
|
6990
|
+
*
|
|
6991
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
6992
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
6993
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
6994
|
+
* @param {string} options.targetBranch - The target branch to compare against.
|
|
6995
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
7016
6996
|
*/
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
this.memory = memory;
|
|
7033
|
-
}
|
|
7034
|
-
else {
|
|
7035
|
-
// fields is a BaseMemory
|
|
7036
|
-
super({ verbose, callbacks });
|
|
7037
|
-
this.memory = fields;
|
|
6997
|
+
async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
|
|
6998
|
+
try {
|
|
6999
|
+
// Get the current branch name
|
|
7000
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
7001
|
+
// Get the list of commits that are unique to the current branch compared to the target branch
|
|
7002
|
+
const uniqueCommits = (await git.raw(['rev-list', `${targetBranch}..${currentBranch}`]))
|
|
7003
|
+
.split('\n')
|
|
7004
|
+
.filter(Boolean)
|
|
7005
|
+
.reverse();
|
|
7006
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and "${targetBranch}"`, { color: 'blue' });
|
|
7007
|
+
const firstCommit = uniqueCommits[0];
|
|
7008
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
7009
|
+
if (!firstCommit || !lastCommit) {
|
|
7010
|
+
logger?.log('Unable to determine first and last commit between branches', { color: 'yellow' });
|
|
7011
|
+
return [];
|
|
7038
7012
|
}
|
|
7013
|
+
// Retrieve commit log with messages
|
|
7014
|
+
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
7039
7015
|
}
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
const valuesForMemory = { ...values };
|
|
7043
|
-
if ("signal" in valuesForMemory) {
|
|
7044
|
-
delete valuesForMemory.signal;
|
|
7045
|
-
}
|
|
7046
|
-
if ("timeout" in valuesForMemory) {
|
|
7047
|
-
delete valuesForMemory.timeout;
|
|
7048
|
-
}
|
|
7049
|
-
return valuesForMemory;
|
|
7016
|
+
catch (error) {
|
|
7017
|
+
logger?.log('Encountered an error getting commit log between branches', { color: 'red' });
|
|
7050
7018
|
}
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
}),
|
|
7072
|
-
])
|
|
7073
|
-
: this._call(fullValues, runManager, config));
|
|
7019
|
+
return [];
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
/**
|
|
7023
|
+
* Retrieves the commit log for the current branch.
|
|
7024
|
+
*
|
|
7025
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
7026
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
7027
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
7028
|
+
* @param {string} [options.comparisonBranch='main'] - The branch to compare against.
|
|
7029
|
+
* @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
|
|
7030
|
+
* @returns {Promise<string[]>} The array of commit messages in the commit log.
|
|
7031
|
+
*/
|
|
7032
|
+
async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
|
|
7033
|
+
try {
|
|
7034
|
+
const branchName = await getCurrentBranchName({ git });
|
|
7035
|
+
const hasCommits = (await git.raw(['rev-list', '--count', branchName])) !== '0';
|
|
7036
|
+
if (!hasCommits) {
|
|
7037
|
+
logger?.log('No commits on the current branch.');
|
|
7038
|
+
return [];
|
|
7074
7039
|
}
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7040
|
+
let uniqueCommits;
|
|
7041
|
+
if (comparisonBranch === branchName) {
|
|
7042
|
+
// If the comparison branch is the same as the current branch, we compare against the remote.
|
|
7043
|
+
uniqueCommits = (await git.raw(['rev-list', `${comparisonRemote}/${comparisonBranch}..${branchName}`]))
|
|
7044
|
+
.split('\n')
|
|
7045
|
+
.filter(Boolean)
|
|
7046
|
+
.reverse();
|
|
7078
7047
|
}
|
|
7079
|
-
|
|
7080
|
-
|
|
7048
|
+
else {
|
|
7049
|
+
// Your existing code for different branches
|
|
7050
|
+
uniqueCommits = (await git.raw(['rev-list', `${comparisonBranch}..${branchName}`]))
|
|
7051
|
+
.split('\n')
|
|
7052
|
+
.filter(Boolean)
|
|
7053
|
+
.reverse();
|
|
7081
7054
|
}
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
Object.defineProperty(outputValues, outputs.RUN_KEY, {
|
|
7085
|
-
value: runManager ? { runId: runManager?.runId } : undefined,
|
|
7086
|
-
configurable: true,
|
|
7055
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits on "${branchName}"`, {
|
|
7056
|
+
color: 'blue',
|
|
7087
7057
|
});
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
}
|
|
7096
|
-
async prepOutputs(inputs, outputs, returnOnlyOutputs = false) {
|
|
7097
|
-
this._validateOutputs(outputs);
|
|
7098
|
-
if (this.memory) {
|
|
7099
|
-
await this.memory.saveContext(inputs, outputs);
|
|
7100
|
-
}
|
|
7101
|
-
if (returnOnlyOutputs) {
|
|
7102
|
-
return outputs;
|
|
7103
|
-
}
|
|
7104
|
-
return { ...inputs, ...outputs };
|
|
7105
|
-
}
|
|
7106
|
-
/**
|
|
7107
|
-
* Return a json-like object representing this chain.
|
|
7108
|
-
*/
|
|
7109
|
-
serialize() {
|
|
7110
|
-
throw new Error("Method not implemented.");
|
|
7111
|
-
}
|
|
7112
|
-
/** @deprecated Use .invoke() instead. Will be removed in 0.2.0. */
|
|
7113
|
-
async run(
|
|
7114
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7115
|
-
input, config) {
|
|
7116
|
-
const inputKeys = this.inputKeys.filter((k) => !this.memory?.memoryKeys.includes(k) ?? true);
|
|
7117
|
-
const isKeylessInput = inputKeys.length <= 1;
|
|
7118
|
-
if (!isKeylessInput) {
|
|
7119
|
-
throw new Error(`Chain ${this._chainType()} expects multiple inputs, cannot use 'run' `);
|
|
7120
|
-
}
|
|
7121
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7122
|
-
const values = inputKeys.length ? { [inputKeys[0]]: input } : {};
|
|
7123
|
-
const returnValues = await this.call(values, config);
|
|
7124
|
-
const keys = Object.keys(returnValues);
|
|
7125
|
-
if (keys.length === 1) {
|
|
7126
|
-
return returnValues[keys[0]];
|
|
7127
|
-
}
|
|
7128
|
-
throw new Error("return values have multiple keys, `run` only supported when one key currently");
|
|
7129
|
-
}
|
|
7130
|
-
async _formatValues(values) {
|
|
7131
|
-
const fullValues = { ...values };
|
|
7132
|
-
if (fullValues.timeout && !fullValues.signal) {
|
|
7133
|
-
fullValues.signal = AbortSignal.timeout(fullValues.timeout);
|
|
7134
|
-
delete fullValues.timeout;
|
|
7135
|
-
}
|
|
7136
|
-
if (!(this.memory == null)) {
|
|
7137
|
-
const newValues = await this.memory.loadMemoryVariables(this._selectMemoryInputs(values));
|
|
7138
|
-
for (const [key, value] of Object.entries(newValues)) {
|
|
7139
|
-
fullValues[key] = value;
|
|
7140
|
-
}
|
|
7141
|
-
}
|
|
7142
|
-
return fullValues;
|
|
7143
|
-
}
|
|
7144
|
-
/**
|
|
7145
|
-
* @deprecated Use .invoke() instead. Will be removed in 0.2.0.
|
|
7146
|
-
*
|
|
7147
|
-
* Run the core logic of this chain and add to output if desired.
|
|
7148
|
-
*
|
|
7149
|
-
* Wraps _call and handles memory.
|
|
7150
|
-
*/
|
|
7151
|
-
async call(values, config,
|
|
7152
|
-
/** @deprecated */
|
|
7153
|
-
tags) {
|
|
7154
|
-
const parsedConfig = { tags, ...manager.parseCallbackConfigArg(config) };
|
|
7155
|
-
return this.invoke(values, parsedConfig);
|
|
7156
|
-
}
|
|
7157
|
-
/**
|
|
7158
|
-
* @deprecated Use .batch() instead. Will be removed in 0.2.0.
|
|
7159
|
-
*
|
|
7160
|
-
* Call the chain on all inputs in the list
|
|
7161
|
-
*/
|
|
7162
|
-
async apply(inputs, config) {
|
|
7163
|
-
return Promise.all(inputs.map(async (i, idx) => this.call(i, config?.[idx])));
|
|
7164
|
-
}
|
|
7165
|
-
/**
|
|
7166
|
-
* Load a chain from a json-like object describing it.
|
|
7167
|
-
*/
|
|
7168
|
-
static async deserialize(data, values = {}) {
|
|
7169
|
-
switch (data._type) {
|
|
7170
|
-
case "llm_chain": {
|
|
7171
|
-
const { LLMChain } = await Promise.resolve().then(function () { return llm_chain; });
|
|
7172
|
-
return LLMChain.deserialize(data);
|
|
7173
|
-
}
|
|
7174
|
-
case "sequential_chain": {
|
|
7175
|
-
const { SequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
|
|
7176
|
-
return SequentialChain.deserialize(data);
|
|
7177
|
-
}
|
|
7178
|
-
case "simple_sequential_chain": {
|
|
7179
|
-
const { SimpleSequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
|
|
7180
|
-
return SimpleSequentialChain.deserialize(data);
|
|
7181
|
-
}
|
|
7182
|
-
case "stuff_documents_chain": {
|
|
7183
|
-
const { StuffDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
7184
|
-
return StuffDocumentsChain.deserialize(data);
|
|
7185
|
-
}
|
|
7186
|
-
case "map_reduce_documents_chain": {
|
|
7187
|
-
const { MapReduceDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
7188
|
-
return MapReduceDocumentsChain.deserialize(data);
|
|
7189
|
-
}
|
|
7190
|
-
case "refine_documents_chain": {
|
|
7191
|
-
const { RefineDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
|
|
7192
|
-
return RefineDocumentsChain.deserialize(data);
|
|
7193
|
-
}
|
|
7194
|
-
case "vector_db_qa": {
|
|
7195
|
-
const { VectorDBQAChain } = await Promise.resolve().then(function () { return vector_db_qa; });
|
|
7196
|
-
return VectorDBQAChain.deserialize(data, values);
|
|
7197
|
-
}
|
|
7198
|
-
case "api_chain": {
|
|
7199
|
-
const { APIChain } = await Promise.resolve().then(function () { return api_chain; });
|
|
7200
|
-
return APIChain.deserialize(data);
|
|
7201
|
-
}
|
|
7202
|
-
default:
|
|
7203
|
-
throw new Error(`Invalid prompt type in config: ${data._type}`);
|
|
7058
|
+
const firstCommit = uniqueCommits[0];
|
|
7059
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
7060
|
+
if (!firstCommit || !lastCommit) {
|
|
7061
|
+
logger?.log('Unable to determine first and last commit on the current branch', {
|
|
7062
|
+
color: 'yellow',
|
|
7063
|
+
});
|
|
7064
|
+
return [];
|
|
7204
7065
|
}
|
|
7066
|
+
return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
|
|
7067
|
+
}
|
|
7068
|
+
catch (error) {
|
|
7069
|
+
logger?.log('Encountered an error getting commit log from current branch', { color: 'red' });
|
|
7205
7070
|
}
|
|
7071
|
+
return [];
|
|
7206
7072
|
}
|
|
7207
7073
|
|
|
7208
7074
|
/**
|
|
7209
|
-
*
|
|
7210
|
-
*
|
|
7211
|
-
* class and is part of the LangChain's output parsers module. This class
|
|
7212
|
-
* is useful in scenarios where the raw output of the Large Language
|
|
7213
|
-
* Models (LLMs) is required.
|
|
7075
|
+
* Retrieves the SimpleGit instance for the repository.
|
|
7076
|
+
* @returns {SimpleGit} The SimpleGit instance.
|
|
7214
7077
|
*/
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
enumerable: true,
|
|
7220
|
-
configurable: true,
|
|
7221
|
-
writable: true,
|
|
7222
|
-
value: ["langchain", "output_parsers", "default"]
|
|
7223
|
-
});
|
|
7224
|
-
Object.defineProperty(this, "lc_serializable", {
|
|
7225
|
-
enumerable: true,
|
|
7226
|
-
configurable: true,
|
|
7227
|
-
writable: true,
|
|
7228
|
-
value: true
|
|
7229
|
-
});
|
|
7230
|
-
}
|
|
7231
|
-
static lc_name() {
|
|
7232
|
-
return "NoOpOutputParser";
|
|
7233
|
-
}
|
|
7234
|
-
/**
|
|
7235
|
-
* This method takes a string as input and returns the same string as
|
|
7236
|
-
* output. It does not perform any operations on the input string.
|
|
7237
|
-
* @param text The input string to be parsed.
|
|
7238
|
-
* @returns The same input string without any operations performed on it.
|
|
7239
|
-
*/
|
|
7240
|
-
parse(text) {
|
|
7241
|
-
return Promise.resolve(text);
|
|
7078
|
+
const getRepo = () => {
|
|
7079
|
+
let git;
|
|
7080
|
+
try {
|
|
7081
|
+
git = simpleGit.simpleGit();
|
|
7242
7082
|
}
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
* @returns An empty string, indicating no formatting instructions.
|
|
7247
|
-
*/
|
|
7248
|
-
getFormatInstructions() {
|
|
7249
|
-
return "";
|
|
7083
|
+
catch (e) {
|
|
7084
|
+
console.log('Error initializing git repo', e);
|
|
7085
|
+
process.exit(1);
|
|
7250
7086
|
}
|
|
7251
|
-
|
|
7087
|
+
return git;
|
|
7088
|
+
};
|
|
7252
7089
|
|
|
7253
|
-
function isBaseLanguageModel(llmLike) {
|
|
7254
|
-
return typeof llmLike._llmType === "function";
|
|
7255
|
-
}
|
|
7256
|
-
function _getLanguageModel(llmLike) {
|
|
7257
|
-
if (isBaseLanguageModel(llmLike)) {
|
|
7258
|
-
return llmLike;
|
|
7259
|
-
}
|
|
7260
|
-
else if ("bound" in llmLike && runnables.Runnable.isRunnable(llmLike.bound)) {
|
|
7261
|
-
return _getLanguageModel(llmLike.bound);
|
|
7262
|
-
}
|
|
7263
|
-
else if ("runnable" in llmLike &&
|
|
7264
|
-
"fallbacks" in llmLike &&
|
|
7265
|
-
runnables.Runnable.isRunnable(llmLike.runnable)) {
|
|
7266
|
-
return _getLanguageModel(llmLike.runnable);
|
|
7267
|
-
}
|
|
7268
|
-
else if ("default" in llmLike && runnables.Runnable.isRunnable(llmLike.default)) {
|
|
7269
|
-
return _getLanguageModel(llmLike.default);
|
|
7270
|
-
}
|
|
7271
|
-
else {
|
|
7272
|
-
throw new Error("Unable to extract BaseLanguageModel from llmLike object.");
|
|
7273
|
-
}
|
|
7274
|
-
}
|
|
7275
7090
|
/**
|
|
7276
|
-
*
|
|
7277
|
-
* See the example below for how to use LCEL with the LLMChain class:
|
|
7278
|
-
*
|
|
7279
|
-
* Chain to run queries against LLMs.
|
|
7280
|
-
*
|
|
7281
|
-
* @example
|
|
7282
|
-
* ```ts
|
|
7283
|
-
* import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
7284
|
-
* import { ChatOpenAI } from "@langchain/openai";
|
|
7091
|
+
* Template for generating git commit messages based on code changes
|
|
7285
7092
|
*
|
|
7286
|
-
*
|
|
7287
|
-
*
|
|
7288
|
-
*
|
|
7093
|
+
* Variables:
|
|
7094
|
+
* - summary: Contains the diff summary of staged changes
|
|
7095
|
+
* - format_instructions: Instructions for the output format (JSON with title and body)
|
|
7096
|
+
* - additional_context: Optional user-provided context to guide the commit message generation
|
|
7097
|
+
* - commit_history: Optional history of previous commits for context
|
|
7098
|
+
* - branch_name_context: String containing formatted branch name (or empty if disabled)
|
|
7099
|
+
*/
|
|
7100
|
+
const template$4 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
|
|
7101
|
+
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.
|
|
7102
|
+
|
|
7103
|
+
Please follow the guidelines below when writing your commit message:
|
|
7104
|
+
|
|
7105
|
+
- Write concisely using an informal tone
|
|
7106
|
+
- Avoid phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
|
|
7107
|
+
- Avoid referencing specific files names or long paths in the commit message
|
|
7108
|
+
- DO NOT include any diffs or file changes in the commit message
|
|
7109
|
+
- Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
|
|
7110
|
+
|
|
7111
|
+
""""""
|
|
7112
|
+
{{summary}}
|
|
7113
|
+
""""""
|
|
7114
|
+
|
|
7115
|
+
{{branch_name_context}}
|
|
7116
|
+
|
|
7117
|
+
{{format_instructions}}
|
|
7118
|
+
|
|
7119
|
+
{{commit_history}}
|
|
7120
|
+
|
|
7121
|
+
{{additional_context}}
|
|
7122
|
+
`;
|
|
7123
|
+
// Define the variables that will be passed to the prompt template
|
|
7124
|
+
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history', 'branch_name_context'];
|
|
7125
|
+
const COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
7126
|
+
template: template$4,
|
|
7127
|
+
inputVariables: inputVariables$3,
|
|
7128
|
+
});
|
|
7129
|
+
const CONVENTIONAL_TEMPLATE = `Generate a commit message that strictly adheres to the Conventional Commits specification. Follow these rules precisely:
|
|
7130
|
+
|
|
7131
|
+
1. Type Selection:
|
|
7132
|
+
- Choose ONE of these types based on the changes:
|
|
7133
|
+
* feat: A new feature
|
|
7134
|
+
* fix: A bug fix
|
|
7135
|
+
* docs: Documentation only changes
|
|
7136
|
+
* style: Changes that don't affect the code's meaning (white-space, formatting, etc)
|
|
7137
|
+
* refactor: Code changes that neither fix a bug nor add a feature
|
|
7138
|
+
* perf: Code changes that improve performance
|
|
7139
|
+
* test: Adding missing tests or correcting existing tests
|
|
7140
|
+
* build: Changes that affect the build system or external dependencies
|
|
7141
|
+
* ci: Changes to CI configuration files and scripts
|
|
7142
|
+
* chore: Other changes that don't modify src or test files
|
|
7143
|
+
* revert: Reverts a previous commit
|
|
7144
|
+
|
|
7145
|
+
2. Format Requirements:
|
|
7146
|
+
- Title format: <type>(<optional-scope>): <description>
|
|
7147
|
+
- Title must be 50 characters or less
|
|
7148
|
+
- Description should be in imperative mood (e.g., "add" not "adds/added")
|
|
7149
|
+
- Body MUST be 280 characters or less
|
|
7150
|
+
- Separate body from title with a blank line
|
|
7151
|
+
- Body should explain the motivation for the change and contrast it with previous behavior
|
|
7152
|
+
|
|
7153
|
+
3. Scope Guidelines:
|
|
7154
|
+
- If the change affects a specific component/area, include it as a scope
|
|
7155
|
+
- Scope should be a noun in parentheses (e.g., (parser), (ui), (config))
|
|
7156
|
+
- Omit scope if the change is broad or affects multiple areas
|
|
7157
|
+
|
|
7158
|
+
Based on the following diff summary, generate a conventional commit message that follows these rules exactly:
|
|
7159
|
+
|
|
7160
|
+
""""""
|
|
7161
|
+
{{summary}}
|
|
7162
|
+
""""""
|
|
7163
|
+
|
|
7164
|
+
{{branch_name_context}}
|
|
7165
|
+
|
|
7166
|
+
{{format_instructions}}
|
|
7167
|
+
|
|
7168
|
+
{{commit_history}}
|
|
7169
|
+
|
|
7170
|
+
{{additional_context}}
|
|
7171
|
+
|
|
7172
|
+
Remember:
|
|
7173
|
+
- Be concise and precise
|
|
7174
|
+
- Focus on WHAT and WHY, not HOW
|
|
7175
|
+
- Use imperative mood in both title and body
|
|
7176
|
+
- Ensure the title alone provides enough context to understand the change`;
|
|
7177
|
+
const conventionalInputVariables = [
|
|
7178
|
+
'summary',
|
|
7179
|
+
'additional_context',
|
|
7180
|
+
'commit_history',
|
|
7181
|
+
'format_instructions',
|
|
7182
|
+
'branch_name_context',
|
|
7183
|
+
];
|
|
7184
|
+
const CONVENTIONAL_COMMIT_PROMPT = new prompts$1.PromptTemplate({
|
|
7185
|
+
template: CONVENTIONAL_TEMPLATE,
|
|
7186
|
+
inputVariables: conventionalInputVariables,
|
|
7187
|
+
});
|
|
7188
|
+
|
|
7189
|
+
/**
|
|
7190
|
+
* Verify template string contains all required input variables
|
|
7289
7191
|
*
|
|
7290
|
-
*
|
|
7291
|
-
*
|
|
7192
|
+
* @param text template string
|
|
7193
|
+
* @param inputVariables template variables
|
|
7194
|
+
* @throws Error if validation fails
|
|
7292
7195
|
*/
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7196
|
+
function validatePromptTemplate(text, inputVariables) {
|
|
7197
|
+
if (!text || text.trim() === '') {
|
|
7198
|
+
throw new Error('Prompt template cannot be empty');
|
|
7296
7199
|
}
|
|
7297
|
-
|
|
7298
|
-
return
|
|
7200
|
+
if (!inputVariables || inputVariables.length === 0) {
|
|
7201
|
+
return; // No variables to validate
|
|
7299
7202
|
}
|
|
7300
|
-
|
|
7301
|
-
|
|
7203
|
+
// Extract variables from template using regex to find {variable} patterns
|
|
7204
|
+
// This regex matches {variable_name} with no whitespace inside braces
|
|
7205
|
+
// Excludes JSON-like patterns with quotes, colons, or whitespace
|
|
7206
|
+
const templateVariableRegex = /\{([^}\s:"']+)\}/g;
|
|
7207
|
+
const foundVariables = new Set();
|
|
7208
|
+
let match;
|
|
7209
|
+
while ((match = templateVariableRegex.exec(text)) !== null) {
|
|
7210
|
+
foundVariables.add(match[1]);
|
|
7302
7211
|
}
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7212
|
+
// Check if all required variables are present in template
|
|
7213
|
+
const missingVariables = inputVariables.filter(variable => !foundVariables.has(variable));
|
|
7214
|
+
if (missingVariables.length > 0) {
|
|
7215
|
+
throw new Error(`Prompt template is missing required variables: ${missingVariables.map(v => `{${v}}`).join(', ')}. ` +
|
|
7216
|
+
`Found variables: ${Array.from(foundVariables).map(v => `{${v}}`).join(', ') || 'none'}`);
|
|
7217
|
+
}
|
|
7218
|
+
// Warn about unused variables in template (optional check)
|
|
7219
|
+
const unusedVariables = Array.from(foundVariables).filter(variable => !inputVariables.includes(variable));
|
|
7220
|
+
if (unusedVariables.length > 0) {
|
|
7221
|
+
console.warn(`Prompt template contains undefined variables: ${unusedVariables.map(v => `{${v}}`).join(', ')}`);
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
async function editPrompt(options) {
|
|
7226
|
+
return await prompts.editor({
|
|
7227
|
+
message: 'Edit the prompt',
|
|
7228
|
+
default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
|
|
7229
|
+
waitForUseInput: false,
|
|
7230
|
+
postfix: 'Press ENTER to continue',
|
|
7231
|
+
validate: (text) => {
|
|
7232
|
+
try {
|
|
7233
|
+
validatePromptTemplate(text, COMMIT_PROMPT.inputVariables);
|
|
7234
|
+
return true;
|
|
7235
|
+
}
|
|
7236
|
+
catch (error) {
|
|
7237
|
+
return error instanceof Error ? error.message : 'Invalid prompt template';
|
|
7238
|
+
}
|
|
7239
|
+
},
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
7242
|
+
|
|
7243
|
+
async function editResult(result, options) {
|
|
7244
|
+
if (options.openInEditor) {
|
|
7245
|
+
return await prompts.editor({
|
|
7246
|
+
message: 'Edit the commit message',
|
|
7247
|
+
default: result,
|
|
7248
|
+
waitForUseInput: false,
|
|
7249
|
+
validate: (text) => (text ? true : 'Commit message cannot be empty'),
|
|
7316
7250
|
});
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7251
|
+
}
|
|
7252
|
+
return result;
|
|
7253
|
+
}
|
|
7254
|
+
|
|
7255
|
+
async function getUserReviewDecision({ label, descriptions, labels, enableEdit = true, enableRetry = true, enableFullRetry = true, enableModifyPrompt = true, selectLabel, }) {
|
|
7256
|
+
const choices = [
|
|
7257
|
+
{
|
|
7258
|
+
name: labels?.approve || '✨ Looks good!',
|
|
7259
|
+
value: 'approve',
|
|
7260
|
+
description: descriptions?.approve || `Continue with the generated ${label}`,
|
|
7261
|
+
},
|
|
7262
|
+
];
|
|
7263
|
+
if (enableEdit) {
|
|
7264
|
+
choices.push({
|
|
7265
|
+
name: '📝 Edit',
|
|
7266
|
+
value: 'edit',
|
|
7267
|
+
description: descriptions?.edit || `Edit the generated ${label} before proceeding`,
|
|
7322
7268
|
});
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
value:
|
|
7269
|
+
}
|
|
7270
|
+
if (enableModifyPrompt) {
|
|
7271
|
+
choices.push({
|
|
7272
|
+
name: '🪶 Modify Prompt',
|
|
7273
|
+
value: 'modifyPrompt',
|
|
7274
|
+
description: descriptions?.modifyPrompt || `Modify the prompt template and regenerate the ${label}`,
|
|
7328
7275
|
});
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
value:
|
|
7276
|
+
}
|
|
7277
|
+
if (enableRetry) {
|
|
7278
|
+
choices.push({
|
|
7279
|
+
name: labels?.retryMessageOnly || '🔄 Retry',
|
|
7280
|
+
value: 'retryMessageOnly',
|
|
7281
|
+
description: descriptions?.retryMessageOnly ||
|
|
7282
|
+
`Restart the function execution from generating the ${label}`,
|
|
7334
7283
|
});
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
value:
|
|
7284
|
+
}
|
|
7285
|
+
if (enableFullRetry) {
|
|
7286
|
+
choices.push({
|
|
7287
|
+
name: labels?.retryFull || '🔄 Retry Full',
|
|
7288
|
+
value: 'retryFull',
|
|
7289
|
+
description: descriptions?.retryFull ||
|
|
7290
|
+
`Restart the function execution from the beginning, regenerating both the summary and ${label}`,
|
|
7340
7291
|
});
|
|
7341
|
-
this.prompt = fields.prompt;
|
|
7342
|
-
this.llm = fields.llm;
|
|
7343
|
-
this.llmKwargs = fields.llmKwargs;
|
|
7344
|
-
this.outputKey = fields.outputKey ?? this.outputKey;
|
|
7345
|
-
this.outputParser =
|
|
7346
|
-
fields.outputParser ?? new NoOpOutputParser();
|
|
7347
|
-
if (this.prompt.outputParser) {
|
|
7348
|
-
if (fields.outputParser) {
|
|
7349
|
-
throw new Error("Cannot set both outputParser and prompt.outputParser");
|
|
7350
|
-
}
|
|
7351
|
-
this.outputParser = this.prompt.outputParser;
|
|
7352
|
-
}
|
|
7353
7292
|
}
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7293
|
+
choices.push({
|
|
7294
|
+
name: labels?.cancel || '💣 Cancel',
|
|
7295
|
+
value: 'cancel',
|
|
7296
|
+
description: descriptions?.cancel || `Cancel the ${label}`,
|
|
7297
|
+
});
|
|
7298
|
+
return (await prompts.select({
|
|
7299
|
+
message: selectLabel || `Would you like to make any changes to the ${label}?`,
|
|
7300
|
+
choices,
|
|
7301
|
+
}));
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
function logResult(label, result) {
|
|
7305
|
+
console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
|
|
7306
|
+
}
|
|
7307
|
+
|
|
7308
|
+
async function generateAndReviewLoop({ label, factory, parser, noResult, agent, reviewParser, options, }) {
|
|
7309
|
+
const { logger } = options;
|
|
7310
|
+
let continueLoop = true;
|
|
7311
|
+
let modifyPrompt = false;
|
|
7312
|
+
let context = '';
|
|
7313
|
+
let result = '';
|
|
7314
|
+
const changes = await factory();
|
|
7315
|
+
// if we don't have any changes, bail.
|
|
7316
|
+
if (!changes || !Object.keys(changes).length) {
|
|
7317
|
+
await noResult(options);
|
|
7357
7318
|
}
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
const callKeys = this.getCallKeys();
|
|
7362
|
-
for (const key of callKeys) {
|
|
7363
|
-
if (key in values) {
|
|
7364
|
-
delete valuesForMemory[key];
|
|
7365
|
-
}
|
|
7319
|
+
while (continueLoop) {
|
|
7320
|
+
if (!context.length) {
|
|
7321
|
+
context = await parser(changes, result, options);
|
|
7366
7322
|
}
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
async _getFinalOutput(generations, promptValue, runManager) {
|
|
7371
|
-
let finalCompletion;
|
|
7372
|
-
if (this.outputParser) {
|
|
7373
|
-
finalCompletion = await this.outputParser.parseResultWithPrompt(generations, promptValue, runManager?.getChild());
|
|
7323
|
+
// if we still don't have a context, bail.
|
|
7324
|
+
if (!context.length) {
|
|
7325
|
+
await noResult(options);
|
|
7374
7326
|
}
|
|
7375
|
-
|
|
7376
|
-
|
|
7327
|
+
if (modifyPrompt) {
|
|
7328
|
+
options.prompt = await editPrompt(options);
|
|
7377
7329
|
}
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
async _call(values, runManager) {
|
|
7390
|
-
const valuesForPrompt = { ...values };
|
|
7391
|
-
const valuesForLLM = {
|
|
7392
|
-
...this.llmKwargs,
|
|
7393
|
-
};
|
|
7394
|
-
const callKeys = this.getCallKeys();
|
|
7395
|
-
for (const key of callKeys) {
|
|
7396
|
-
if (key in values) {
|
|
7397
|
-
if (valuesForLLM) {
|
|
7398
|
-
valuesForLLM[key] =
|
|
7399
|
-
values[key];
|
|
7400
|
-
delete valuesForPrompt[key];
|
|
7401
|
-
}
|
|
7330
|
+
logger.startTimer().startSpinner(`Generating ${label}\n`, {
|
|
7331
|
+
color: 'blue',
|
|
7332
|
+
});
|
|
7333
|
+
try {
|
|
7334
|
+
result = await agent(context, options);
|
|
7335
|
+
if (!result) {
|
|
7336
|
+
logger.stopSpinner('💀 Agent failed to return content.', {
|
|
7337
|
+
mode: 'fail',
|
|
7338
|
+
color: 'red',
|
|
7339
|
+
});
|
|
7340
|
+
process.exit(0);
|
|
7402
7341
|
}
|
|
7403
7342
|
}
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7343
|
+
catch (error) {
|
|
7344
|
+
// Handle special regeneration request from validation
|
|
7345
|
+
if (error.message === 'REGENERATE_COMMIT_MESSAGE') {
|
|
7346
|
+
logger.stopSpinner('Regenerating commit message...', {
|
|
7347
|
+
mode: 'stop',
|
|
7348
|
+
color: 'blue',
|
|
7349
|
+
});
|
|
7350
|
+
result = '';
|
|
7351
|
+
continue;
|
|
7352
|
+
}
|
|
7353
|
+
// Re-throw other errors
|
|
7354
|
+
throw error;
|
|
7410
7355
|
}
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
:
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7356
|
+
logger
|
|
7357
|
+
.stopSpinner(`Generated ${label}`, {
|
|
7358
|
+
color: 'green',
|
|
7359
|
+
mode: 'succeed',
|
|
7360
|
+
})
|
|
7361
|
+
.stopTimer();
|
|
7362
|
+
if (options?.interactive) {
|
|
7363
|
+
logResult(label, reviewParser ? reviewParser(result, options) : result);
|
|
7364
|
+
const reviewAnswer = await getUserReviewDecision({
|
|
7365
|
+
label,
|
|
7366
|
+
...(options?.review || {}),
|
|
7367
|
+
});
|
|
7368
|
+
if (reviewAnswer === 'cancel') {
|
|
7369
|
+
process.exit(0);
|
|
7370
|
+
}
|
|
7371
|
+
if (reviewAnswer === 'edit') {
|
|
7372
|
+
options.openInEditor = true;
|
|
7373
|
+
}
|
|
7374
|
+
if (reviewAnswer === 'retryFull') {
|
|
7375
|
+
context = '';
|
|
7376
|
+
result = '';
|
|
7377
|
+
options.prompt = '';
|
|
7378
|
+
continue;
|
|
7379
|
+
}
|
|
7380
|
+
if (reviewAnswer === 'retryMessageOnly') {
|
|
7381
|
+
modifyPrompt = false;
|
|
7382
|
+
result = '';
|
|
7383
|
+
continue;
|
|
7384
|
+
}
|
|
7385
|
+
if (reviewAnswer === 'modifyPrompt') {
|
|
7386
|
+
modifyPrompt = true;
|
|
7387
|
+
result = '';
|
|
7388
|
+
continue;
|
|
7389
|
+
}
|
|
7390
|
+
// Only edit the result in interactive mode if approved
|
|
7391
|
+
result = await editResult(result, options);
|
|
7442
7392
|
}
|
|
7443
|
-
|
|
7444
|
-
|
|
7393
|
+
else {
|
|
7394
|
+
// In non-interactive mode, we return the result as is to be output to stdout by the caller.
|
|
7395
|
+
const displayResult = reviewParser ? reviewParser(result, options) : result;
|
|
7396
|
+
// In non-interactive mode, ensure we return the properly formatted result
|
|
7397
|
+
result = displayResult;
|
|
7445
7398
|
}
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
prompt: await prompts$1.BasePromptTemplate.deserialize(prompt),
|
|
7449
|
-
});
|
|
7399
|
+
// if we're here, we're done.
|
|
7400
|
+
continueLoop = false;
|
|
7450
7401
|
}
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7402
|
+
return result;
|
|
7403
|
+
}
|
|
7404
|
+
|
|
7405
|
+
const logSuccess = () => {
|
|
7406
|
+
console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
|
|
7407
|
+
};
|
|
7408
|
+
|
|
7409
|
+
async function handleResult({ result, mode, interactiveModeCallback }) {
|
|
7410
|
+
switch (mode) {
|
|
7411
|
+
case 'interactive':
|
|
7412
|
+
if (interactiveModeCallback) {
|
|
7413
|
+
await interactiveModeCallback(result);
|
|
7414
|
+
}
|
|
7415
|
+
else {
|
|
7416
|
+
console.warn('No result handler provided for interactive mode.');
|
|
7417
|
+
logSuccess();
|
|
7418
|
+
}
|
|
7419
|
+
break;
|
|
7420
|
+
case 'stdout':
|
|
7421
|
+
default:
|
|
7422
|
+
// Ensure we write the result to stdout in non-interactive mode
|
|
7423
|
+
process.stdout.write(result + '\n', 'utf8');
|
|
7424
|
+
break;
|
|
7459
7425
|
}
|
|
7460
|
-
|
|
7461
|
-
|
|
7426
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
7427
|
+
process.exit(0);
|
|
7462
7428
|
}
|
|
7463
7429
|
}
|
|
7464
7430
|
|
|
7465
|
-
|
|
7466
|
-
__proto__: null,
|
|
7467
|
-
LLMChain: LLMChain
|
|
7468
|
-
});
|
|
7431
|
+
const template$3 = `Write informative git changelog, in the imperative, based on a series of individual messages.
|
|
7469
7432
|
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
--------------
|
|
7476
|
-
{completion}
|
|
7477
|
-
--------------
|
|
7433
|
+
- Annotate each change with the git commit hash as reference, including just the first 7 characters
|
|
7434
|
+
- Logically group changes, and if necessary, summarize dependency updates
|
|
7435
|
+
- Include a descriptive title for the changelog, to give a high-level overview of the changes
|
|
7436
|
+
- Depending on the size of the changes, consider breaking the changelog into sections
|
|
7437
|
+
- Avoid generlizations like "various bug fixes" or "improvements" or "enhancements"
|
|
7478
7438
|
|
|
7479
|
-
|
|
7480
|
-
Error:
|
|
7481
|
-
--------------
|
|
7482
|
-
{error}
|
|
7483
|
-
--------------
|
|
7439
|
+
{{format_instructions}}
|
|
7484
7440
|
|
|
7485
|
-
|
|
7486
|
-
const
|
|
7487
|
-
|
|
7441
|
+
"""{{summary}}"""`;
|
|
7442
|
+
const inputVariables$2 = ['format_instructions', 'summary'];
|
|
7443
|
+
const CHANGELOG_PROMPT = new prompts$1.PromptTemplate({
|
|
7444
|
+
template: template$3,
|
|
7445
|
+
inputVariables: inputVariables$2,
|
|
7446
|
+
});
|
|
7488
7447
|
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
class OutputFixingParser extends output_parsers.BaseOutputParser {
|
|
7498
|
-
static lc_name() {
|
|
7499
|
-
return "OutputFixingParser";
|
|
7500
|
-
}
|
|
7501
|
-
/**
|
|
7502
|
-
* Static method to create a new instance of OutputFixingParser using a
|
|
7503
|
-
* given language model, parser, and optional fields.
|
|
7504
|
-
* @param llm The language model to be used.
|
|
7505
|
-
* @param parser The parser to be used.
|
|
7506
|
-
* @param fields Optional fields which may contain a prompt.
|
|
7507
|
-
* @returns A new instance of OutputFixingParser.
|
|
7508
|
-
*/
|
|
7509
|
-
static fromLLM(llm, parser, fields) {
|
|
7510
|
-
const prompt = fields?.prompt ?? NAIVE_FIX_PROMPT;
|
|
7511
|
-
const chain = new LLMChain({ llm, prompt });
|
|
7512
|
-
return new OutputFixingParser({ parser, retryChain: chain });
|
|
7448
|
+
const handler$4 = async (argv, logger) => {
|
|
7449
|
+
const config = loadConfig(argv);
|
|
7450
|
+
const git = getRepo();
|
|
7451
|
+
const key = getApiKeyForModel(config);
|
|
7452
|
+
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
7453
|
+
if (config.service.authentication.type !== 'None' && !key) {
|
|
7454
|
+
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
7455
|
+
process.exit(1);
|
|
7513
7456
|
}
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
value: ["langchain", "output_parsers", "fix"]
|
|
7521
|
-
});
|
|
7522
|
-
Object.defineProperty(this, "lc_serializable", {
|
|
7523
|
-
enumerable: true,
|
|
7524
|
-
configurable: true,
|
|
7525
|
-
writable: true,
|
|
7526
|
-
value: true
|
|
7527
|
-
});
|
|
7528
|
-
Object.defineProperty(this, "parser", {
|
|
7529
|
-
enumerable: true,
|
|
7530
|
-
configurable: true,
|
|
7531
|
-
writable: true,
|
|
7532
|
-
value: void 0
|
|
7533
|
-
});
|
|
7534
|
-
Object.defineProperty(this, "retryChain", {
|
|
7535
|
-
enumerable: true,
|
|
7536
|
-
configurable: true,
|
|
7537
|
-
writable: true,
|
|
7538
|
-
value: void 0
|
|
7539
|
-
});
|
|
7540
|
-
this.parser = parser;
|
|
7541
|
-
this.retryChain = retryChain;
|
|
7457
|
+
const llm = getLlm(provider, model, config);
|
|
7458
|
+
const INTERACTIVE = isInteractive(config);
|
|
7459
|
+
if (INTERACTIVE) {
|
|
7460
|
+
if (!config.hideCocoBanner) {
|
|
7461
|
+
logger.log(LOGO);
|
|
7462
|
+
}
|
|
7542
7463
|
}
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
async parse(completion, callbacks) {
|
|
7552
|
-
try {
|
|
7553
|
-
return await this.parser.parse(completion, callbacks);
|
|
7464
|
+
async function factory() {
|
|
7465
|
+
const branchName = await getCurrentBranchName({ git });
|
|
7466
|
+
if (config.sinceLastTag) {
|
|
7467
|
+
logger.verbose(`Generating commit log since the last tag`, { color: 'yellow' });
|
|
7468
|
+
return {
|
|
7469
|
+
branch: branchName,
|
|
7470
|
+
commits: await getChangesSinceLastTag({ git, logger }),
|
|
7471
|
+
};
|
|
7554
7472
|
}
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
if (
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
completion,
|
|
7561
|
-
error: e,
|
|
7562
|
-
};
|
|
7563
|
-
if (isLLMChain(this.retryChain)) {
|
|
7564
|
-
const result = await this.retryChain.call(retryInput, callbacks);
|
|
7565
|
-
const newCompletion = result[this.retryChain.outputKey];
|
|
7566
|
-
return this.parser.parse(newCompletion, callbacks);
|
|
7567
|
-
}
|
|
7568
|
-
else {
|
|
7569
|
-
const result = await this.retryChain.invoke(retryInput, {
|
|
7570
|
-
callbacks,
|
|
7571
|
-
});
|
|
7572
|
-
return result;
|
|
7573
|
-
}
|
|
7473
|
+
if (config.range && config.range.includes(':')) {
|
|
7474
|
+
const [from, to] = config.range.split(':');
|
|
7475
|
+
if (!from || !to) {
|
|
7476
|
+
logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
|
|
7477
|
+
process.exit(1);
|
|
7574
7478
|
}
|
|
7575
|
-
|
|
7479
|
+
return {
|
|
7480
|
+
branch: branchName,
|
|
7481
|
+
commits: await getCommitLogRange(from, to, { git, noMerges: true }),
|
|
7482
|
+
};
|
|
7483
|
+
}
|
|
7484
|
+
if (argv.branch) {
|
|
7485
|
+
logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
|
|
7486
|
+
return {
|
|
7487
|
+
branch: branchName,
|
|
7488
|
+
commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
|
|
7489
|
+
};
|
|
7576
7490
|
}
|
|
7491
|
+
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, {
|
|
7492
|
+
color: 'yellow',
|
|
7493
|
+
});
|
|
7494
|
+
const commits = await getCommitLogCurrentBranch({ git, logger });
|
|
7495
|
+
return {
|
|
7496
|
+
branch: branchName,
|
|
7497
|
+
commits,
|
|
7498
|
+
};
|
|
7577
7499
|
}
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7500
|
+
async function parser({ branch, commits }) {
|
|
7501
|
+
let result;
|
|
7502
|
+
if (!commits || commits.length === 0) {
|
|
7503
|
+
result = `## ${branch}\n\nNo commits found.`;
|
|
7504
|
+
}
|
|
7505
|
+
else {
|
|
7506
|
+
result = `## ${branch}\n\n${commits.map((commit) => commit.trim()).join('\n\n')}`;
|
|
7507
|
+
}
|
|
7508
|
+
return result;
|
|
7584
7509
|
}
|
|
7585
|
-
|
|
7510
|
+
const changelogMsg = await generateAndReviewLoop({
|
|
7511
|
+
label: 'changelog',
|
|
7512
|
+
options: {
|
|
7513
|
+
...config,
|
|
7514
|
+
prompt: config.prompt || CHANGELOG_PROMPT.template,
|
|
7515
|
+
logger,
|
|
7516
|
+
interactive: INTERACTIVE,
|
|
7517
|
+
review: {
|
|
7518
|
+
enableFullRetry: false,
|
|
7519
|
+
},
|
|
7520
|
+
},
|
|
7521
|
+
factory,
|
|
7522
|
+
parser,
|
|
7523
|
+
agent: async (context, options) => {
|
|
7524
|
+
const parser = createSchemaParser(ChangelogResponseSchema, llm);
|
|
7525
|
+
const prompt = getPrompt({
|
|
7526
|
+
template: options.prompt,
|
|
7527
|
+
variables: CHANGELOG_PROMPT.inputVariables,
|
|
7528
|
+
fallback: CHANGELOG_PROMPT,
|
|
7529
|
+
});
|
|
7530
|
+
const formatInstructions = "Only respond with a valid JSON object, containing two fields: 'title' an escaped string, no more than 65 characters, and 'content' also an escaped string.";
|
|
7531
|
+
const changelog = await executeChain({
|
|
7532
|
+
llm,
|
|
7533
|
+
prompt,
|
|
7534
|
+
variables: {
|
|
7535
|
+
summary: context,
|
|
7536
|
+
format_instructions: formatInstructions,
|
|
7537
|
+
},
|
|
7538
|
+
parser,
|
|
7539
|
+
});
|
|
7540
|
+
const branchName = await getCurrentBranchName({ git });
|
|
7541
|
+
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
7542
|
+
const footer = ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
7543
|
+
return `${changelog.title}\n\n${changelog.content}${footer}`;
|
|
7544
|
+
},
|
|
7545
|
+
noResult: async () => {
|
|
7546
|
+
if (config.range) {
|
|
7547
|
+
logger.log(`No commits found in the provided range.`, { color: 'red' });
|
|
7548
|
+
process.exit(0);
|
|
7549
|
+
}
|
|
7550
|
+
logger.log(`No commits found in the current branch.`, { color: 'red' });
|
|
7551
|
+
process.exit(0);
|
|
7552
|
+
},
|
|
7553
|
+
});
|
|
7554
|
+
const MODE = (INTERACTIVE && 'interactive') || (config.commit && 'interactive') || config?.mode || 'stdout';
|
|
7555
|
+
handleResult({
|
|
7556
|
+
result: changelogMsg,
|
|
7557
|
+
interactiveModeCallback: async () => {
|
|
7558
|
+
logSuccess();
|
|
7559
|
+
},
|
|
7560
|
+
mode: MODE,
|
|
7561
|
+
});
|
|
7562
|
+
};
|
|
7563
|
+
|
|
7564
|
+
var changelog = {
|
|
7565
|
+
command: command$4,
|
|
7566
|
+
desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
|
|
7567
|
+
builder: builder$4,
|
|
7568
|
+
handler: commandExecutor(handler$4),
|
|
7569
|
+
options: options$4,
|
|
7570
|
+
};
|
|
7586
7571
|
|
|
7572
|
+
const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:/;
|
|
7573
|
+
// Regular commit message schema with basic validation
|
|
7574
|
+
const CommitMessageResponseSchema = objectType({
|
|
7575
|
+
title: stringType(),
|
|
7576
|
+
body: stringType(),
|
|
7577
|
+
});
|
|
7578
|
+
// Conventional commit message schema with strict formatting rules
|
|
7579
|
+
const ConventionalCommitMessageResponseSchema = objectType({
|
|
7580
|
+
title: stringType()
|
|
7581
|
+
.max(50, "Title must be 50 characters or less")
|
|
7582
|
+
.refine((title) => conventionalTypeRegex.test(title), "Title must follow Conventional Commits format (e.g., 'feat: add new feature' or 'fix(scope): fix bug')"),
|
|
7583
|
+
body: stringType()
|
|
7584
|
+
// .max(280, "Body must be 280 characters or less"),
|
|
7585
|
+
});
|
|
7586
|
+
const command$3 = 'commit';
|
|
7587
7587
|
/**
|
|
7588
|
-
*
|
|
7589
|
-
* @param schema - Zod schema for the expected output structure
|
|
7590
|
-
* @param llm - LLM instance for retry attempts
|
|
7591
|
-
* @param options - Configuration options for retry behavior
|
|
7592
|
-
* @returns OutputFixingParser configured with retry logic
|
|
7593
|
-
* @throws LangChainExecutionError if parser creation fails
|
|
7588
|
+
* Command line options via yargs
|
|
7594
7589
|
*/
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
}
|
|
7640
|
-
|
|
7590
|
+
const options$3 = {
|
|
7591
|
+
i: {
|
|
7592
|
+
alias: 'interactive',
|
|
7593
|
+
description: 'Toggle interactive mode',
|
|
7594
|
+
type: 'boolean',
|
|
7595
|
+
},
|
|
7596
|
+
ignoredFiles: {
|
|
7597
|
+
description: 'Ignored files',
|
|
7598
|
+
type: 'array',
|
|
7599
|
+
},
|
|
7600
|
+
ignoredExtensions: {
|
|
7601
|
+
description: 'Ignored extensions',
|
|
7602
|
+
type: 'array',
|
|
7603
|
+
},
|
|
7604
|
+
append: {
|
|
7605
|
+
description: 'Add content to the end of the generated commit message',
|
|
7606
|
+
type: 'string',
|
|
7607
|
+
},
|
|
7608
|
+
appendTicket: {
|
|
7609
|
+
description: 'Append ticket ID from branch name to the commit message',
|
|
7610
|
+
type: 'boolean',
|
|
7611
|
+
alias: 't',
|
|
7612
|
+
},
|
|
7613
|
+
additional: {
|
|
7614
|
+
description: 'Add extra contextual information to the prompt',
|
|
7615
|
+
type: 'string',
|
|
7616
|
+
alias: 'a',
|
|
7617
|
+
},
|
|
7618
|
+
withPreviousCommits: {
|
|
7619
|
+
description: 'Include previous commits as context (specify number of commits, 0 for none)',
|
|
7620
|
+
type: 'number',
|
|
7621
|
+
default: 0,
|
|
7622
|
+
alias: 'p',
|
|
7623
|
+
},
|
|
7624
|
+
conventional: {
|
|
7625
|
+
description: 'Generate commit message in Conventional Commits format',
|
|
7626
|
+
type: 'boolean',
|
|
7627
|
+
default: false,
|
|
7628
|
+
alias: 'c',
|
|
7629
|
+
},
|
|
7630
|
+
includeBranchName: {
|
|
7631
|
+
description: 'Include the current branch name in the commit prompt for context',
|
|
7632
|
+
type: 'boolean',
|
|
7633
|
+
default: true,
|
|
7634
|
+
},
|
|
7635
|
+
noDiff: {
|
|
7636
|
+
description: 'Only pass basic "git status" result instead of providing entire diff',
|
|
7637
|
+
type: 'boolean',
|
|
7638
|
+
default: false,
|
|
7639
|
+
},
|
|
7640
|
+
};
|
|
7641
|
+
const builder$3 = (yargs) => {
|
|
7642
|
+
return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
|
|
7643
|
+
};
|
|
7641
7644
|
|
|
7642
7645
|
/**
|
|
7643
7646
|
* High-level function that combines chain execution with schema-based parsing
|
|
@@ -7923,7 +7926,38 @@ async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
|
|
|
7923
7926
|
throw new Error(`Error reading untracked file: ${error?.message || 'Unknown error'}`);
|
|
7924
7927
|
}
|
|
7925
7928
|
}
|
|
7926
|
-
|
|
7929
|
+
// For branch comparisons, handle files that may not exist in the base branch
|
|
7930
|
+
try {
|
|
7931
|
+
return await git.diff([commit, nodeFile.filePath]);
|
|
7932
|
+
}
|
|
7933
|
+
catch (error) {
|
|
7934
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7935
|
+
// If the error indicates the file doesn't exist in the base branch, handle it gracefully
|
|
7936
|
+
if (errorMessage.includes('unknown revision or path not in the working tree') ||
|
|
7937
|
+
errorMessage.includes('ambiguous argument')) {
|
|
7938
|
+
// This is likely a newly added file - show the entire file content as an addition
|
|
7939
|
+
if (nodeFile.status === 'added') {
|
|
7940
|
+
try {
|
|
7941
|
+
const fileContent = await fs.promises.readFile(nodeFile.filePath, 'utf-8');
|
|
7942
|
+
return `+++ ${nodeFile.filePath}\n${fileContent.split('\n').map(line => `+${line}`).join('\n')}`;
|
|
7943
|
+
}
|
|
7944
|
+
catch (fsError) {
|
|
7945
|
+
return `Error reading added file ${nodeFile.filePath}: ${fsError instanceof Error ? fsError.message : String(fsError)}`;
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7948
|
+
// For other cases, try to get the file content from the current HEAD
|
|
7949
|
+
try {
|
|
7950
|
+
const fileContent = await git.show([`HEAD:${nodeFile.filePath}`]);
|
|
7951
|
+
return `File content from current version:\n${fileContent}`;
|
|
7952
|
+
}
|
|
7953
|
+
catch (showError) {
|
|
7954
|
+
// If all else fails, provide a meaningful error message
|
|
7955
|
+
return `Unable to retrieve diff for ${nodeFile.filePath}. File may be newly added or renamed.`;
|
|
7956
|
+
}
|
|
7957
|
+
}
|
|
7958
|
+
// Re-throw other types of errors
|
|
7959
|
+
throw error;
|
|
7960
|
+
}
|
|
7927
7961
|
}
|
|
7928
7962
|
/**
|
|
7929
7963
|
* Parses the diff for a renamed file.
|
|
@@ -11778,6 +11812,10 @@ const options$1 = {
|
|
|
11778
11812
|
type: 'boolean',
|
|
11779
11813
|
description: 'Recap for last tag',
|
|
11780
11814
|
},
|
|
11815
|
+
currentBranch: {
|
|
11816
|
+
type: 'boolean',
|
|
11817
|
+
description: 'Recap for the current branch',
|
|
11818
|
+
},
|
|
11781
11819
|
i: {
|
|
11782
11820
|
type: 'boolean',
|
|
11783
11821
|
alias: 'interactive',
|
|
@@ -11793,8 +11831,89 @@ const getChangesByTimestamp = async ({ since, git }) => {
|
|
|
11793
11831
|
return formatCommitLog(commitLog);
|
|
11794
11832
|
};
|
|
11795
11833
|
|
|
11834
|
+
/**
|
|
11835
|
+
* Retrieves the diff between the current branch and a specified target branch.
|
|
11836
|
+
*
|
|
11837
|
+
* @param {Object} options - The options for retrieving the diff.
|
|
11838
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
11839
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
11840
|
+
* @param {string} options.baseBranch - The base branch to compare against.
|
|
11841
|
+
* @param {string} options.headBranch - The head branch to compare.
|
|
11842
|
+
* @param {string[]} options.ignoredFiles - Array of specific files to ignore.
|
|
11843
|
+
* @param {string[]} options.ignoredExtensions - Array of file extensions to ignore.
|
|
11844
|
+
* @returns {Promise<GetChangesResult>} The diff between the current branch and the target branch.
|
|
11845
|
+
*/
|
|
11846
|
+
async function getDiffForBranch({ git, logger, baseBranch, headBranch, options, }) {
|
|
11847
|
+
try {
|
|
11848
|
+
logger?.verbose(`Getting diff for branches: baseBranch="${baseBranch}", headBranch="${headBranch}"`, {
|
|
11849
|
+
color: 'blue',
|
|
11850
|
+
});
|
|
11851
|
+
// Validate branch names
|
|
11852
|
+
if (!baseBranch || !headBranch) {
|
|
11853
|
+
throw new Error(`Invalid branch names: baseBranch="${baseBranch}", headBranch="${headBranch}"`);
|
|
11854
|
+
}
|
|
11855
|
+
const { ignoredFiles = [], ignoredExtensions = [] } = options || {};
|
|
11856
|
+
// Prepare ignore patterns
|
|
11857
|
+
const ignorePatterns = [
|
|
11858
|
+
...ignoredFiles.map((file) => `:!${file}`),
|
|
11859
|
+
...ignoredExtensions.map((ext) => `:!*${ext}`),
|
|
11860
|
+
];
|
|
11861
|
+
// Construct the diff command
|
|
11862
|
+
const diffArgs = [`${baseBranch}..${headBranch}`];
|
|
11863
|
+
if (ignorePatterns.length > 0) {
|
|
11864
|
+
diffArgs.push('--');
|
|
11865
|
+
diffArgs.push(...ignorePatterns);
|
|
11866
|
+
}
|
|
11867
|
+
logger?.verbose(`Running git diff with args: ${diffArgs.join(' ')}`, {
|
|
11868
|
+
color: 'blue',
|
|
11869
|
+
});
|
|
11870
|
+
// Get the diff
|
|
11871
|
+
const diff = await git.diff(diffArgs);
|
|
11872
|
+
logger?.verbose(`Generated diff between "${headBranch}" and "${baseBranch}"`, {
|
|
11873
|
+
color: 'blue',
|
|
11874
|
+
});
|
|
11875
|
+
const changes = diff.split('diff --git').slice(1).map((fileDiff) => {
|
|
11876
|
+
const lines = fileDiff.split('\n');
|
|
11877
|
+
const filePathLine = lines[0];
|
|
11878
|
+
const filePath = filePathLine.split('b/')[1]?.split(' ')[0];
|
|
11879
|
+
const oldFilePath = filePathLine.split('a/')[1]?.split(' ')[0];
|
|
11880
|
+
// Determine status based on diff headers
|
|
11881
|
+
let status = 'modified';
|
|
11882
|
+
if (fileDiff.includes('new file mode')) {
|
|
11883
|
+
status = 'added';
|
|
11884
|
+
}
|
|
11885
|
+
else if (fileDiff.includes('deleted file mode')) {
|
|
11886
|
+
status = 'deleted';
|
|
11887
|
+
}
|
|
11888
|
+
else if (fileDiff.includes('rename from')) {
|
|
11889
|
+
status = 'renamed';
|
|
11890
|
+
}
|
|
11891
|
+
return {
|
|
11892
|
+
filePath: filePath || '',
|
|
11893
|
+
oldFilePath: oldFilePath || '',
|
|
11894
|
+
status,
|
|
11895
|
+
summary: getSummaryText({ path: filePath || '', index: '', working_dir: '' }, { filePath: filePath || '', status }),
|
|
11896
|
+
};
|
|
11897
|
+
});
|
|
11898
|
+
return {
|
|
11899
|
+
staged: changes,
|
|
11900
|
+
unstaged: [],
|
|
11901
|
+
untracked: [],
|
|
11902
|
+
};
|
|
11903
|
+
}
|
|
11904
|
+
catch (error) {
|
|
11905
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11906
|
+
console.error('Error in getDiffForBranch:', error);
|
|
11907
|
+
logger?.log(`Encountered an error getting diff between branches: ${errorMessage}`, { color: 'red' });
|
|
11908
|
+
logger?.log(`Branch details: baseBranch="${baseBranch}", headBranch="${headBranch}"`, { color: 'red' });
|
|
11909
|
+
// Re-throw the error so the caller can handle it appropriately
|
|
11910
|
+
throw error;
|
|
11911
|
+
}
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11796
11914
|
async function noResult$1({ logger }) {
|
|
11797
11915
|
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
11916
|
+
throw new Error('NO_CHANGES_DETECTED');
|
|
11798
11917
|
}
|
|
11799
11918
|
|
|
11800
11919
|
const template$1 = `Following the formatting instructions, summarize the following changes in the underlying git repository/branch.
|
|
@@ -11842,7 +11961,9 @@ const handler$1 = async (argv, logger) => {
|
|
|
11842
11961
|
? 'yesterday'
|
|
11843
11962
|
: lastWeek
|
|
11844
11963
|
? 'last-week'
|
|
11845
|
-
:
|
|
11964
|
+
: argv.currentBranch || config.currentBranch
|
|
11965
|
+
? 'currentBranch'
|
|
11966
|
+
: 'current';
|
|
11846
11967
|
logger.log(`Generating recap for timeframe: ${timeframe}`);
|
|
11847
11968
|
async function factory() {
|
|
11848
11969
|
switch (timeframe) {
|
|
@@ -11883,6 +12004,25 @@ const handler$1 = async (argv, logger) => {
|
|
|
11883
12004
|
case 'last-tag':
|
|
11884
12005
|
const tags = await getChangesSinceLastTag({ git });
|
|
11885
12006
|
return tags;
|
|
12007
|
+
case 'currentBranch':
|
|
12008
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
12009
|
+
const baseBranch = config.defaultBranch || 'main';
|
|
12010
|
+
logger.log(`Recapping changes on branch '${currentBranch}' compared to '${baseBranch}'`);
|
|
12011
|
+
const changes = await getDiffForBranch({
|
|
12012
|
+
git,
|
|
12013
|
+
baseBranch,
|
|
12014
|
+
headBranch: currentBranch,
|
|
12015
|
+
options: {
|
|
12016
|
+
ignoredFiles: config.ignoredFiles || undefined,
|
|
12017
|
+
ignoredExtensions: config.ignoredExtensions || undefined,
|
|
12018
|
+
},
|
|
12019
|
+
});
|
|
12020
|
+
const branchChanges = await fileChangeParser({
|
|
12021
|
+
changes: changes.staged,
|
|
12022
|
+
commit: baseBranch,
|
|
12023
|
+
options: { tokenizer, git, llm, logger },
|
|
12024
|
+
});
|
|
12025
|
+
return [branchChanges];
|
|
11886
12026
|
default:
|
|
11887
12027
|
logger.log(`Invalid timeframe: ${timeframe}`, { color: 'red' });
|
|
11888
12028
|
return [];
|
|
@@ -11923,7 +12063,7 @@ const handler$1 = async (argv, logger) => {
|
|
|
11923
12063
|
fallback: RECAP_PROMPT,
|
|
11924
12064
|
});
|
|
11925
12065
|
try {
|
|
11926
|
-
const parser =
|
|
12066
|
+
const parser = createSchemaParser(RecapLlmResponseSchema, llm);
|
|
11927
12067
|
const response = await executeChain({
|
|
11928
12068
|
llm,
|
|
11929
12069
|
prompt,
|
|
@@ -11955,11 +12095,13 @@ ${errorMessage}
|
|
|
11955
12095
|
},
|
|
11956
12096
|
noResult: async () => {
|
|
11957
12097
|
await noResult$1({ git, logger });
|
|
11958
|
-
process.
|
|
12098
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
12099
|
+
process.exit(0);
|
|
12100
|
+
}
|
|
11959
12101
|
},
|
|
11960
12102
|
});
|
|
11961
12103
|
// Handle the result based on the mode (interactive or stdout)
|
|
11962
|
-
const MODE = (INTERACTIVE && 'interactive')
|
|
12104
|
+
const MODE = (INTERACTIVE && 'interactive') ?? (config.recap && 'interactive') ?? config?.mode ?? 'stdout'; // Default to stdout
|
|
11963
12105
|
handleResult({
|
|
11964
12106
|
result: recapResult,
|
|
11965
12107
|
interactiveModeCallback: async () => {
|
|
@@ -12017,46 +12159,6 @@ const builder = (yargs) => {
|
|
|
12017
12159
|
return yargs.options(options).usage(getCommandUsageHeader(command));
|
|
12018
12160
|
};
|
|
12019
12161
|
|
|
12020
|
-
/**
|
|
12021
|
-
* Retrieves the diff between the current branch and a specified target branch.
|
|
12022
|
-
*
|
|
12023
|
-
* @param {Object} options - The options for retrieving the diff.
|
|
12024
|
-
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
12025
|
-
* @param {Logger} options.logger - The logger for logging messages.
|
|
12026
|
-
* @param {string} options.targetBranch - The target branch to compare against.
|
|
12027
|
-
* @param {string[]} options.ignoredFiles - Array of specific files to ignore.
|
|
12028
|
-
* @param {string[]} options.ignoredExtensions - Array of file extensions to ignore.
|
|
12029
|
-
* @returns {Promise<string>} The diff between the current branch and the target branch.
|
|
12030
|
-
*/
|
|
12031
|
-
async function getDiffForBranch({ git, logger, targetBranch, ignoredFiles = [], ignoredExtensions = [], }) {
|
|
12032
|
-
try {
|
|
12033
|
-
// Get the current branch name
|
|
12034
|
-
const currentBranch = await getCurrentBranchName({ git });
|
|
12035
|
-
// Prepare ignore patterns
|
|
12036
|
-
const ignorePatterns = [
|
|
12037
|
-
...ignoredFiles.map((file) => `:!${file}`),
|
|
12038
|
-
...ignoredExtensions.map((ext) => `:!*${ext}`),
|
|
12039
|
-
];
|
|
12040
|
-
// Construct the diff command
|
|
12041
|
-
const diffArgs = [`${targetBranch}..${currentBranch}`];
|
|
12042
|
-
if (ignorePatterns.length > 0) {
|
|
12043
|
-
diffArgs.push('--');
|
|
12044
|
-
diffArgs.push(...ignorePatterns);
|
|
12045
|
-
}
|
|
12046
|
-
// Get the diff
|
|
12047
|
-
const diff = await git.diff(diffArgs);
|
|
12048
|
-
logger?.verbose(`Generated diff between "${currentBranch}" and "${targetBranch}"`, {
|
|
12049
|
-
color: 'blue',
|
|
12050
|
-
});
|
|
12051
|
-
return diff;
|
|
12052
|
-
}
|
|
12053
|
-
catch (error) {
|
|
12054
|
-
console.error('Error in getDiffForBranch:', error);
|
|
12055
|
-
logger?.log('Encountered an error getting diff between branches', { color: 'red' });
|
|
12056
|
-
return '';
|
|
12057
|
-
}
|
|
12058
|
-
}
|
|
12059
|
-
|
|
12060
12162
|
/******************************************************************************
|
|
12061
12163
|
Copyright (c) Microsoft Corporation.
|
|
12062
12164
|
|
|
@@ -14273,14 +14375,23 @@ const handler = async (argv, logger) => {
|
|
|
14273
14375
|
async function factory() {
|
|
14274
14376
|
if (argv.branch) {
|
|
14275
14377
|
logger.verbose(`Generating diff for branch: ${argv.branch}`, { color: 'yellow' });
|
|
14378
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
14276
14379
|
const diff = await getDiffForBranch({
|
|
14277
14380
|
git,
|
|
14278
14381
|
logger,
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14382
|
+
baseBranch: argv.branch,
|
|
14383
|
+
headBranch: currentBranch,
|
|
14384
|
+
options: {
|
|
14385
|
+
ignoredFiles: config.ignoredFiles || [],
|
|
14386
|
+
ignoredExtensions: config.ignoredExtensions || [],
|
|
14387
|
+
},
|
|
14388
|
+
});
|
|
14389
|
+
const branchChanges = await fileChangeParser({
|
|
14390
|
+
changes: diff.staged,
|
|
14391
|
+
commit: `--branch-diff-${argv.branch}`,
|
|
14392
|
+
options: { tokenizer, git, llm, logger },
|
|
14282
14393
|
});
|
|
14283
|
-
return [
|
|
14394
|
+
return [branchChanges];
|
|
14284
14395
|
}
|
|
14285
14396
|
else {
|
|
14286
14397
|
const { staged, unstaged, untracked } = await getChanges({
|
|
@@ -14344,7 +14455,7 @@ const handler = async (argv, logger) => {
|
|
|
14344
14455
|
factory,
|
|
14345
14456
|
parser,
|
|
14346
14457
|
agent: async (context, options) => {
|
|
14347
|
-
const parser =
|
|
14458
|
+
const parser = createSchemaParser(ReviewFeedbackItemArraySchema, llm);
|
|
14348
14459
|
const formatInstructions = "Respond with a valid JSON object, containing four fields:'title' a string, 'summary' a short summary of the problem (include line number if big file), 'severity' a numeric enum up to ten, 'category' an enum string, and 'filePath' a relative filepath to file as string.";
|
|
14349
14460
|
const prompt = getPrompt({
|
|
14350
14461
|
template: options.prompt,
|