git-coco 0.18.2 → 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.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.18.2";
71
+ const BUILD_VERSION = "0.20.0";
72
72
 
73
73
  const isInteractive = (config) => {
74
74
  return config?.mode === 'interactive' || !!config?.interactive;
@@ -1006,7 +1006,7 @@ const schema$1 = {
1006
1006
  "deprecated": "Use \"model\" instead."
1007
1007
  },
1008
1008
  "model": {
1009
- "type": "string",
1009
+ "$ref": "#/definitions/OpenAIChatModelId",
1010
1010
  "description": "Model name to use"
1011
1011
  },
1012
1012
  "modelKwargs": {
@@ -1484,6 +1484,79 @@ const schema$1 = {
1484
1484
  "additionalProperties": false,
1485
1485
  "description": "Base class for all caches. All caches should extend this class."
1486
1486
  },
1487
+ "OpenAIChatModelId": {
1488
+ "anyOf": [
1489
+ {
1490
+ "$ref": "#/definitions/OpenAI.ChatModel"
1491
+ },
1492
+ {
1493
+ "type": "string"
1494
+ }
1495
+ ]
1496
+ },
1497
+ "OpenAI.ChatModel": {
1498
+ "$ref": "#/definitions/ChatModel"
1499
+ },
1500
+ "ChatModel": {
1501
+ "type": "string",
1502
+ "enum": [
1503
+ "gpt-4.1",
1504
+ "gpt-4.1-mini",
1505
+ "gpt-4.1-nano",
1506
+ "gpt-4.1-2025-04-14",
1507
+ "gpt-4.1-mini-2025-04-14",
1508
+ "gpt-4.1-nano-2025-04-14",
1509
+ "o4-mini",
1510
+ "o4-mini-2025-04-16",
1511
+ "o3",
1512
+ "o3-2025-04-16",
1513
+ "o3-mini",
1514
+ "o3-mini-2025-01-31",
1515
+ "o1",
1516
+ "o1-2024-12-17",
1517
+ "o1-preview",
1518
+ "o1-preview-2024-09-12",
1519
+ "o1-mini",
1520
+ "o1-mini-2024-09-12",
1521
+ "gpt-4o",
1522
+ "gpt-4o-2024-11-20",
1523
+ "gpt-4o-2024-08-06",
1524
+ "gpt-4o-2024-05-13",
1525
+ "gpt-4o-audio-preview",
1526
+ "gpt-4o-audio-preview-2024-10-01",
1527
+ "gpt-4o-audio-preview-2024-12-17",
1528
+ "gpt-4o-audio-preview-2025-06-03",
1529
+ "gpt-4o-mini-audio-preview",
1530
+ "gpt-4o-mini-audio-preview-2024-12-17",
1531
+ "gpt-4o-search-preview",
1532
+ "gpt-4o-mini-search-preview",
1533
+ "gpt-4o-search-preview-2025-03-11",
1534
+ "gpt-4o-mini-search-preview-2025-03-11",
1535
+ "chatgpt-4o-latest",
1536
+ "codex-mini-latest",
1537
+ "gpt-4o-mini",
1538
+ "gpt-4o-mini-2024-07-18",
1539
+ "gpt-4-turbo",
1540
+ "gpt-4-turbo-2024-04-09",
1541
+ "gpt-4-0125-preview",
1542
+ "gpt-4-turbo-preview",
1543
+ "gpt-4-1106-preview",
1544
+ "gpt-4-vision-preview",
1545
+ "gpt-4",
1546
+ "gpt-4-0314",
1547
+ "gpt-4-0613",
1548
+ "gpt-4-32k",
1549
+ "gpt-4-32k-0314",
1550
+ "gpt-4-32k-0613",
1551
+ "gpt-3.5-turbo",
1552
+ "gpt-3.5-turbo-16k",
1553
+ "gpt-3.5-turbo-0301",
1554
+ "gpt-3.5-turbo-0613",
1555
+ "gpt-3.5-turbo-1106",
1556
+ "gpt-3.5-turbo-0125",
1557
+ "gpt-3.5-turbo-16k-0613"
1558
+ ]
1559
+ },
1487
1560
  "OllamaLLMService": {
1488
1561
  "type": "object",
1489
1562
  "additionalProperties": false,
@@ -6150,1416 +6223,1424 @@ function getPrompt({ template, variables, fallback }) {
6150
6223
  throw new LangChainExecutionError('getPrompt: Unexpected execution path - neither template nor fallback available', { template, fallback, variables });
6151
6224
  }
6152
6225
 
6226
+ new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
6227
+
6153
6228
  /**
6154
- * Executes a LangChain pipeline with the provided LLM, prompt, variables, and parser.
6155
- * @param params - The execution parameters
6156
- * @returns The parsed result from the LLM chain
6157
- * @throws LangChainExecutionError if the chain execution fails or returns empty results
6229
+ * Base interface that all chains must implement.
6158
6230
  */
6159
- const executeChain = async ({ llm, prompt, variables, parser }) => {
6160
- validateRequired(llm, 'llm', 'executeChain');
6161
- validateRequired(prompt, 'prompt', 'executeChain');
6162
- validateRequired(variables, 'variables', 'executeChain');
6163
- validateRequired(parser, 'parser', 'executeChain');
6164
- // Validate that variables is an object
6165
- if (typeof variables !== 'object' || Array.isArray(variables)) {
6166
- 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()];
6167
6234
  }
6168
- try {
6169
- const chain = prompt.pipe(llm).pipe(parser);
6170
- const result = await chain.invoke(variables);
6171
- if (result === null || result === undefined) {
6172
- throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
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;
6173
6252
  }
6174
- return result;
6175
6253
  }
6176
- catch (error) {
6177
- // Re-throw LangChain errors as-is
6178
- if (error instanceof LangChainExecutionError) {
6179
- throw error;
6254
+ /** @ignore */
6255
+ _selectMemoryInputs(values) {
6256
+ const valuesForMemory = { ...values };
6257
+ if ("signal" in valuesForMemory) {
6258
+ delete valuesForMemory.signal;
6180
6259
  }
6181
- // Wrap other errors with context
6182
- handleLangChainError(error, 'executeChain: Chain execution failed', {
6183
- promptInputVariables: prompt.inputVariables,
6184
- variableKeys: Object.keys(variables),
6185
- parserType: parser.constructor.name
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,
6186
6301
  });
6302
+ return outputValues;
6187
6303
  }
6188
- };
6189
-
6190
- function extractTicketIdFromBranchName(branchName) {
6191
- const regex = /((?<!([A-Z]+)-?)[A-Z]+-\d+)/;
6192
- const match = branchName.match(regex);
6193
- return match ? match[0] : null;
6194
- }
6195
-
6196
- /**
6197
- * Formats a commit log into a readable string format.
6198
- *
6199
- * @param commitLog - The commit log result containing an array of commit details.
6200
- * @returns An array of formatted commit log strings.
6201
- *
6202
- * Each formatted string includes:
6203
- * - The date of the commit in square brackets.
6204
- * - The commit message.
6205
- * - The commit body.
6206
- * - The commit hash in parentheses.
6207
- * - The author's name and email in angle brackets.
6208
- */
6209
- const formatCommitLog = (commitLog) => {
6210
- return commitLog.all.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
6211
- };
6212
-
6213
- const getChangesSinceLastTag = async ({ git }) => {
6214
- const tags = await git.tags();
6215
- if (tags.all.length > 0) {
6216
- const lastTag = tags.latest;
6217
- const commitLog = await git.log({ from: lastTag });
6218
- 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
+ }
6219
6309
  }
6220
- else {
6221
- return ['No tags found in the repository.'];
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 };
6222
6319
  }
6223
- };
6224
-
6225
- /**
6226
- * Retrieves the commit log range between two specified commits (inclusive of both commits).
6227
- *
6228
- * @param from - The starting commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
6229
- * @param to - The ending commit (can be a commit hash, HEAD reference, or branch name). This commit will be included in the results.
6230
- * @param options - Additional options for retrieving the commit log range.
6231
- * @returns A promise that resolves to an array of commit log messages, including both the 'from' and 'to' commits.
6232
- * @throws If there is an error retrieving the commit log range.
6233
- */
6234
- async function getCommitLogRange(from, to, { noMerges, git }) {
6235
- try {
6236
- // Use from^..to to include the 'from' commit in the range
6237
- // This works because from^..to means "commits reachable from 'to' but not from the parent of 'from'"
6238
- const logOptions = {
6239
- from: `${from}^`,
6240
- to,
6241
- '--no-merges': noMerges
6242
- };
6243
- const commitLog = await git.log(logOptions);
6244
- 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.");
6245
6325
  }
6246
- catch (error) {
6247
- // If from^ fails (e.g., 'from' is the first commit), fall back to using from..to and manually adding the 'from' commit
6248
- if (error instanceof Error && error.message.includes('unknown revision')) {
6249
- try {
6250
- // Get the 'from' commit separately
6251
- const fromCommitLog = await git.log({ from: from, maxCount: 1 });
6252
- const fromCommit = fromCommitLog.latest;
6253
- // Get the range from..to (excluding 'from')
6254
- const rangeLogOptions = {
6255
- from,
6256
- to,
6257
- '--no-merges': noMerges
6258
- };
6259
- const rangeCommitLog = await git.log(rangeLogOptions);
6260
- // Combine the 'from' commit with the range commits
6261
- const allCommits = fromCommit
6262
- ? [fromCommit, ...rangeCommitLog.all]
6263
- : rangeCommitLog.all;
6264
- return allCommits.map(({ message, date, body, author_name, hash, author_email }) => `[${date}] ${message}\n${body}\n(${hash}) - ${author_name}<${author_email}>`);
6265
- }
6266
- catch (fallbackError) {
6267
- throw fallbackError;
6268
- }
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' `);
6269
6334
  }
6270
- throw error;
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]];
6341
+ }
6342
+ throw new Error("return values have multiple keys, `run` only supported when one key currently");
6271
6343
  }
6272
- }
6273
-
6274
- /**
6275
- * Retrieves the name of the current branch.
6276
- *
6277
- * @param {GetCurrentBranchName} options - The options for retrieving the branch name.
6278
- * @returns {Promise<string>} - A promise that resolves to the name of the current branch.
6279
- */
6280
- async function getCurrentBranchName({ git }) {
6281
- return await git.revparse(['--abbrev-ref', 'HEAD']);
6282
- }
6283
-
6284
- /**
6285
- * Retrieves the commit log between the current branch and a specified target branch.
6286
- *
6287
- * @param {Object} options - The options for retrieving the commit log.
6288
- * @param {SimpleGit} options.git - The SimpleGit instance.
6289
- * @param {Logger} options.logger - The logger for logging messages.
6290
- * @param {string} options.targetBranch - The target branch to compare against.
6291
- * @returns {Promise<string[]>} The array of commit messages in the commit log.
6292
- */
6293
- async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
6294
- try {
6295
- // Get the current branch name
6296
- const currentBranch = await getCurrentBranchName({ git });
6297
- // Get the list of commits that are unique to the current branch compared to the target branch
6298
- const uniqueCommits = (await git.raw(['rev-list', `${targetBranch}..${currentBranch}`]))
6299
- .split('\n')
6300
- .filter(Boolean)
6301
- .reverse();
6302
- logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and "${targetBranch}"`, { color: 'blue' });
6303
- const firstCommit = uniqueCommits[0];
6304
- const lastCommit = uniqueCommits[uniqueCommits.length - 1];
6305
- if (!firstCommit || !lastCommit) {
6306
- logger?.log('Unable to determine first and last commit between branches', { color: 'yellow' });
6307
- return [];
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;
6308
6349
  }
6309
- // Retrieve commit log with messages
6310
- return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
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
+ }
6355
+ }
6356
+ return fullValues;
6311
6357
  }
6312
- catch (error) {
6313
- logger?.log('Encountered an error getting commit log between branches', { color: 'red' });
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
+ }
6314
6419
  }
6315
- return [];
6316
6420
  }
6317
6421
 
6318
6422
  /**
6319
- * Retrieves the commit log for the current branch.
6320
- *
6321
- * @param {Object} options - The options for retrieving the commit log.
6322
- * @param {SimpleGit} options.git - The SimpleGit instance.
6323
- * @param {Logger} options.logger - The logger for logging messages.
6324
- * @param {string} [options.comparisonBranch='main'] - The branch to compare against.
6325
- * @param {string} [options.comparisonRemote='origin'] - The remote to compare against.
6326
- * @returns {Promise<string[]>} The array of commit messages in the commit log.
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.
6327
6428
  */
6328
- async function getCommitLogCurrentBranch({ git, logger, comparisonBranch = 'main', comparisonRemote = 'origin', }) {
6329
- try {
6330
- const branchName = await getCurrentBranchName({ git });
6331
- const hasCommits = (await git.raw(['rev-list', '--count', branchName])) !== '0';
6332
- if (!hasCommits) {
6333
- logger?.log('No commits on the current branch.');
6334
- return [];
6335
- }
6336
- let uniqueCommits;
6337
- if (comparisonBranch === branchName) {
6338
- // If the comparison branch is the same as the current branch, we compare against the remote.
6339
- uniqueCommits = (await git.raw(['rev-list', `${comparisonRemote}/${comparisonBranch}..${branchName}`]))
6340
- .split('\n')
6341
- .filter(Boolean)
6342
- .reverse();
6343
- }
6344
- else {
6345
- // Your existing code for different branches
6346
- uniqueCommits = (await git.raw(['rev-list', `${comparisonBranch}..${branchName}`]))
6347
- .split('\n')
6348
- .filter(Boolean)
6349
- .reverse();
6350
- }
6351
- logger?.verbose(`Found ${uniqueCommits.length} unique commits on "${branchName}"`, {
6352
- color: 'blue',
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
6353
6443
  });
6354
- const firstCommit = uniqueCommits[0];
6355
- const lastCommit = uniqueCommits[uniqueCommits.length - 1];
6356
- if (!firstCommit || !lastCommit) {
6357
- logger?.log('Unable to determine first and last commit on the current branch', {
6358
- color: 'yellow',
6359
- });
6360
- return [];
6361
- }
6362
- return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
6363
6444
  }
6364
- catch (error) {
6365
- logger?.log('Encountered an error getting commit log from current branch', { color: 'red' });
6445
+ static lc_name() {
6446
+ return "NoOpOutputParser";
6447
+ }
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 "";
6366
6464
  }
6367
- return [];
6368
6465
  }
6369
6466
 
6370
- /**
6371
- * Retrieves the SimpleGit instance for the repository.
6372
- * @returns {SimpleGit} The SimpleGit instance.
6373
- */
6374
- const getRepo = () => {
6375
- let git;
6376
- try {
6377
- git = simpleGit.simpleGit();
6467
+ function isBaseLanguageModel(llmLike) {
6468
+ return typeof llmLike._llmType === "function";
6469
+ }
6470
+ function _getLanguageModel(llmLike) {
6471
+ if (isBaseLanguageModel(llmLike)) {
6472
+ return llmLike;
6378
6473
  }
6379
- catch (e) {
6380
- console.log('Error initializing git repo', e);
6381
- process.exit(1);
6474
+ else if ("bound" in llmLike && runnables.Runnable.isRunnable(llmLike.bound)) {
6475
+ return _getLanguageModel(llmLike.bound);
6382
6476
  }
6383
- return git;
6384
- };
6385
-
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
+ }
6386
6489
  /**
6387
- * Template for generating git commit messages based on code changes
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:
6388
6492
  *
6389
- * Variables:
6390
- * - summary: Contains the diff summary of staged changes
6391
- * - format_instructions: Instructions for the output format (JSON with title and body)
6392
- * - additional_context: Optional user-provided context to guide the commit message generation
6393
- * - commit_history: Optional history of previous commits for context
6394
- * - branch_name_context: String containing formatted branch name (or empty if disabled)
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
+ * ```
6395
6506
  */
6396
- const template$4 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
6397
- 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.
6398
-
6399
- Please follow the guidelines below when writing your commit message:
6400
-
6401
- - Write concisely using an informal tone
6402
- - Avoid phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
6403
- - Avoid referencing specific files names or long paths in the commit message
6404
- - DO NOT include any diffs or file changes in the commit message
6405
- - Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
6406
-
6407
- """"""
6408
- {{summary}}
6409
- """"""
6410
-
6411
- {{branch_name_context}}
6412
-
6413
- {{format_instructions}}
6414
-
6415
- {{commit_history}}
6416
-
6417
- {{additional_context}}
6418
- `;
6419
- // Define the variables that will be passed to the prompt template
6420
- const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history', 'branch_name_context'];
6421
- const COMMIT_PROMPT = new prompts$1.PromptTemplate({
6422
- template: template$4,
6423
- inputVariables: inputVariables$3,
6424
- });
6425
- const CONVENTIONAL_TEMPLATE = `Generate a commit message that strictly adheres to the Conventional Commits specification. Follow these rules precisely:
6426
-
6427
- 1. Type Selection:
6428
- - Choose ONE of these types based on the changes:
6429
- * feat: A new feature
6430
- * fix: A bug fix
6431
- * docs: Documentation only changes
6432
- * style: Changes that don't affect the code's meaning (white-space, formatting, etc)
6433
- * refactor: Code changes that neither fix a bug nor add a feature
6434
- * perf: Code changes that improve performance
6435
- * test: Adding missing tests or correcting existing tests
6436
- * build: Changes that affect the build system or external dependencies
6437
- * ci: Changes to CI configuration files and scripts
6438
- * chore: Other changes that don't modify src or test files
6439
- * revert: Reverts a previous commit
6440
-
6441
- 2. Format Requirements:
6442
- - Title format: <type>(<optional-scope>): <description>
6443
- - Title must be 50 characters or less
6444
- - Description should be in imperative mood (e.g., "add" not "adds/added")
6445
- - Body MUST be 280 characters or less
6446
- - Separate body from title with a blank line
6447
- - Body should explain the motivation for the change and contrast it with previous behavior
6448
-
6449
- 3. Scope Guidelines:
6450
- - If the change affects a specific component/area, include it as a scope
6451
- - Scope should be a noun in parentheses (e.g., (parser), (ui), (config))
6452
- - Omit scope if the change is broad or affects multiple areas
6453
-
6454
- Based on the following diff summary, generate a conventional commit message that follows these rules exactly:
6455
-
6456
- """"""
6457
- {{summary}}
6458
- """"""
6459
-
6460
- {{branch_name_context}}
6461
-
6462
- {{format_instructions}}
6463
-
6464
- {{commit_history}}
6465
-
6466
- {{additional_context}}
6467
-
6468
- Remember:
6469
- - Be concise and precise
6470
- - Focus on WHAT and WHY, not HOW
6471
- - Use imperative mood in both title and body
6472
- - Ensure the title alone provides enough context to understand the change`;
6473
- const conventionalInputVariables = [
6474
- 'summary',
6475
- 'additional_context',
6476
- 'commit_history',
6477
- 'format_instructions',
6478
- 'branch_name_context',
6479
- ];
6480
- const CONVENTIONAL_COMMIT_PROMPT = new prompts$1.PromptTemplate({
6481
- template: CONVENTIONAL_TEMPLATE,
6482
- inputVariables: conventionalInputVariables,
6483
- });
6484
-
6485
- /**
6486
- * Verify template string contains all required input variables
6487
- *
6488
- * @param text template string
6489
- * @param inputVariables template variables
6490
- * @throws Error if validation fails
6491
- */
6492
- function validatePromptTemplate(text, inputVariables) {
6493
- if (!text || text.trim() === '') {
6494
- throw new Error('Prompt template cannot be empty');
6495
- }
6496
- if (!inputVariables || inputVariables.length === 0) {
6497
- return; // No variables to validate
6498
- }
6499
- // Extract variables from template using regex to find {variable} patterns
6500
- // This regex matches {variable_name} with no whitespace inside braces
6501
- // Excludes JSON-like patterns with quotes, colons, or whitespace
6502
- const templateVariableRegex = /\{([^}\s:"']+)\}/g;
6503
- const foundVariables = new Set();
6504
- let match;
6505
- while ((match = templateVariableRegex.exec(text)) !== null) {
6506
- foundVariables.add(match[1]);
6507
+ class LLMChain extends BaseChain {
6508
+ static lc_name() {
6509
+ return "LLMChain";
6507
6510
  }
6508
- // Check if all required variables are present in template
6509
- const missingVariables = inputVariables.filter(variable => !foundVariables.has(variable));
6510
- if (missingVariables.length > 0) {
6511
- throw new Error(`Prompt template is missing required variables: ${missingVariables.map(v => `{${v}}`).join(', ')}. ` +
6512
- `Found variables: ${Array.from(foundVariables).map(v => `{${v}}`).join(', ') || 'none'}`);
6511
+ get inputKeys() {
6512
+ return this.prompt.inputVariables;
6513
6513
  }
6514
- // Warn about unused variables in template (optional check)
6515
- const unusedVariables = Array.from(foundVariables).filter(variable => !inputVariables.includes(variable));
6516
- if (unusedVariables.length > 0) {
6517
- console.warn(`Prompt template contains undefined variables: ${unusedVariables.map(v => `{${v}}`).join(', ')}`);
6514
+ get outputKeys() {
6515
+ return [this.outputKey];
6518
6516
  }
6519
- }
6520
-
6521
- async function editPrompt(options) {
6522
- return await prompts.editor({
6523
- message: 'Edit the prompt',
6524
- default: options.prompt?.length ? options.prompt : COMMIT_PROMPT.template,
6525
- waitForUseInput: false,
6526
- postfix: 'Press ENTER to continue',
6527
- validate: (text) => {
6528
- try {
6529
- validatePromptTemplate(text, COMMIT_PROMPT.inputVariables);
6530
- return true;
6531
- }
6532
- catch (error) {
6533
- return error instanceof Error ? error.message : 'Invalid prompt template';
6534
- }
6535
- },
6536
- });
6537
- }
6538
-
6539
- async function editResult(result, options) {
6540
- if (options.openInEditor) {
6541
- return await prompts.editor({
6542
- message: 'Edit the commit message',
6543
- default: result,
6544
- waitForUseInput: false,
6545
- 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
6546
6524
  });
6547
- }
6548
- return result;
6549
- }
6550
-
6551
- async function getUserReviewDecision({ label, descriptions, labels, enableEdit = true, enableRetry = true, enableFullRetry = true, enableModifyPrompt = true, selectLabel, }) {
6552
- const choices = [
6553
- {
6554
- name: labels?.approve || '✨ Looks good!',
6555
- value: 'approve',
6556
- description: descriptions?.approve || `Continue with the generated ${label}`,
6557
- },
6558
- ];
6559
- if (enableEdit) {
6560
- choices.push({
6561
- name: '📝 Edit',
6562
- value: 'edit',
6563
- 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
6564
6530
  });
6565
- }
6566
- if (enableModifyPrompt) {
6567
- choices.push({
6568
- name: '🪶 Modify Prompt',
6569
- value: 'modifyPrompt',
6570
- 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
6571
6536
  });
6572
- }
6573
- if (enableRetry) {
6574
- choices.push({
6575
- name: labels?.retryMessageOnly || '🔄 Retry',
6576
- value: 'retryMessageOnly',
6577
- description: descriptions?.retryMessageOnly ||
6578
- `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
6579
6542
  });
6580
- }
6581
- if (enableFullRetry) {
6582
- choices.push({
6583
- name: labels?.retryFull || '🔄 Retry Full',
6584
- value: 'retryFull',
6585
- description: descriptions?.retryFull ||
6586
- `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"
6587
6548
  });
6588
- }
6589
- choices.push({
6590
- name: labels?.cancel || '💣 Cancel',
6591
- value: 'cancel',
6592
- description: descriptions?.cancel || `Cancel the ${label}`,
6593
- });
6594
- return (await prompts.select({
6595
- message: selectLabel || `Would you like to make any changes to the ${label}?`,
6596
- choices,
6597
- }));
6598
- }
6599
-
6600
- function logResult(label, result) {
6601
- console.log(`\n${chalk.bgBlue(chalk.bold(`Proposed ${label}:`))}\n${SEPERATOR}\n${result}\n${SEPERATOR}\n`);
6602
- }
6603
-
6604
- async function generateAndReviewLoop({ label, factory, parser, noResult, agent, reviewParser, options, }) {
6605
- const { logger } = options;
6606
- let continueLoop = true;
6607
- let modifyPrompt = false;
6608
- let context = '';
6609
- let result = '';
6610
- const changes = await factory();
6611
- // if we don't have any changes, bail.
6612
- if (!changes || !Object.keys(changes).length) {
6613
- await noResult(options);
6614
- }
6615
- while (continueLoop) {
6616
- if (!context.length) {
6617
- context = await parser(changes, result, options);
6618
- }
6619
- // if we still don't have a context, bail.
6620
- if (!context.length) {
6621
- await noResult(options);
6622
- }
6623
- if (modifyPrompt) {
6624
- options.prompt = await editPrompt(options);
6625
- }
6626
- logger.startTimer().startSpinner(`Generating ${label}\n`, {
6627
- color: 'blue',
6549
+ Object.defineProperty(this, "outputParser", {
6550
+ enumerable: true,
6551
+ configurable: true,
6552
+ writable: true,
6553
+ value: void 0
6628
6554
  });
6629
- try {
6630
- result = await agent(context, options);
6631
- if (!result) {
6632
- logger.stopSpinner('💀 Agent failed to return content.', {
6633
- mode: 'fail',
6634
- color: 'red',
6635
- });
6636
- process.exit(0);
6637
- }
6638
- }
6639
- catch (error) {
6640
- // Handle special regeneration request from validation
6641
- if (error.message === 'REGENERATE_COMMIT_MESSAGE') {
6642
- logger.stopSpinner('Regenerating commit message...', {
6643
- mode: 'stop',
6644
- color: 'blue',
6645
- });
6646
- result = '';
6647
- continue;
6648
- }
6649
- // Re-throw other errors
6650
- throw error;
6651
- }
6652
- logger
6653
- .stopSpinner(`Generated ${label}`, {
6654
- color: 'green',
6655
- mode: 'succeed',
6656
- })
6657
- .stopTimer();
6658
- if (options?.interactive) {
6659
- logResult(label, reviewParser ? reviewParser(result, options) : result);
6660
- const reviewAnswer = await getUserReviewDecision({
6661
- label,
6662
- ...(options?.review || {}),
6663
- });
6664
- if (reviewAnswer === 'cancel') {
6665
- process.exit(0);
6666
- }
6667
- if (reviewAnswer === 'edit') {
6668
- options.openInEditor = true;
6669
- }
6670
- if (reviewAnswer === 'retryFull') {
6671
- context = '';
6672
- result = '';
6673
- options.prompt = '';
6674
- continue;
6675
- }
6676
- if (reviewAnswer === 'retryMessageOnly') {
6677
- modifyPrompt = false;
6678
- result = '';
6679
- continue;
6680
- }
6681
- if (reviewAnswer === 'modifyPrompt') {
6682
- modifyPrompt = true;
6683
- result = '';
6684
- continue;
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");
6685
6564
  }
6686
- // Only edit the result in interactive mode if approved
6687
- result = await editResult(result, options);
6688
- }
6689
- else {
6690
- // In non-interactive mode, we return the result as is to be output to stdout by the caller.
6691
- const displayResult = reviewParser ? reviewParser(result, options) : result;
6692
- // In non-interactive mode, ensure we return the properly formatted result
6693
- result = displayResult;
6565
+ this.outputParser = this.prompt.outputParser;
6694
6566
  }
6695
- // if we're here, we're done.
6696
- continueLoop = false;
6697
6567
  }
6698
- return result;
6699
- }
6700
-
6701
- const logSuccess = () => {
6702
- console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
6703
- };
6704
-
6705
- async function handleResult({ result, mode, interactiveModeCallback }) {
6706
- switch (mode) {
6707
- case 'interactive':
6708
- if (interactiveModeCallback) {
6709
- await interactiveModeCallback(result);
6710
- }
6711
- else {
6712
- console.warn('No result handler provided for interactive mode.');
6713
- logSuccess();
6714
- }
6715
- break;
6716
- case 'stdout':
6717
- default:
6718
- // Ensure we write the result to stdout in non-interactive mode
6719
- process.stdout.write(result + '\n', 'utf8');
6720
- break;
6721
- }
6722
- process.exit(0);
6723
- }
6724
-
6725
- const template$3 = `Write informative git changelog, in the imperative, based on a series of individual messages.
6726
-
6727
- - Annotate each change with the git commit hash as reference, including just the first 7 characters
6728
- - Logically group changes, and if necessary, summarize dependency updates
6729
- - Include a descriptive title for the changelog, to give a high-level overview of the changes
6730
- - Depending on the size of the changes, consider breaking the changelog into sections
6731
- - Avoid generlizations like "various bug fixes" or "improvements" or "enhancements"
6732
-
6733
- {{format_instructions}}
6734
-
6735
- """{{summary}}"""`;
6736
- const inputVariables$2 = ['format_instructions', 'summary'];
6737
- const CHANGELOG_PROMPT = new prompts$1.PromptTemplate({
6738
- template: template$3,
6739
- inputVariables: inputVariables$2,
6740
- });
6741
-
6742
- const handler$4 = async (argv, logger) => {
6743
- const config = loadConfig(argv);
6744
- const git = getRepo();
6745
- const key = getApiKeyForModel(config);
6746
- const { provider, model } = getModelAndProviderFromConfig(config);
6747
- if (config.service.authentication.type !== 'None' && !key) {
6748
- logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
6749
- process.exit(1);
6568
+ getCallKeys() {
6569
+ const callKeys = "callKeys" in this.llm ? this.llm.callKeys : [];
6570
+ return callKeys;
6750
6571
  }
6751
- const llm = getLlm(provider, model, config);
6752
- const INTERACTIVE = isInteractive(config);
6753
- if (INTERACTIVE) {
6754
- if (!config.hideCocoBanner) {
6755
- logger.log(LOGO);
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
+ }
6756
6580
  }
6581
+ return valuesForMemory;
6757
6582
  }
6758
- async function factory() {
6759
- const branchName = await getCurrentBranchName({ git });
6760
- if (config.sinceLastTag) {
6761
- logger.verbose(`Generating commit log since the last tag`, { color: 'yellow' });
6762
- return {
6763
- branch: branchName,
6764
- commits: await getChangesSinceLastTag({ git, logger }),
6765
- };
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());
6766
6588
  }
6767
- if (config.range && config.range.includes(':')) {
6768
- const [from, to] = config.range.split(':');
6769
- if (!from || !to) {
6770
- logger.log(`Invalid range provided. Expected format is <from>:<to>`, { color: 'red' });
6771
- process.exit(1);
6589
+ else {
6590
+ finalCompletion = generations[0].text;
6591
+ }
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
+ }
6772
6616
  }
6773
- return {
6774
- branch: branchName,
6775
- commits: await getCommitLogRange(from, to, { git, noMerges: true }),
6776
- };
6777
6617
  }
6778
- if (argv.branch) {
6779
- logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
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());
6780
6621
  return {
6781
- branch: branchName,
6782
- commits: await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch }),
6622
+ [this.outputKey]: await this._getFinalOutput(generations[0], promptValue, runManager),
6783
6623
  };
6784
6624
  }
6785
- logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, {
6786
- color: 'yellow',
6787
- });
6788
- const commits = await getCommitLogCurrentBranch({ git, logger });
6625
+ const modelWithParser = this.outputParser
6626
+ ? this.llm.pipe(this.outputParser)
6627
+ : this.llm;
6628
+ const response = await modelWithParser.invoke(promptValue, runManager?.getChild());
6789
6629
  return {
6790
- branch: branchName,
6791
- commits,
6630
+ [this.outputKey]: response,
6792
6631
  };
6793
6632
  }
6794
- async function parser({ branch, commits }) {
6795
- let result;
6796
- if (!commits || commits.length === 0) {
6797
- result = `## ${branch}\n\nNo commits found.`;
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");
6798
6656
  }
6799
- else {
6800
- result = `## ${branch}\n\n${commits.map((commit) => commit.trim()).join('\n\n')}`;
6657
+ if (!prompt) {
6658
+ throw new Error("LLMChain must have prompt");
6801
6659
  }
6802
- return result;
6660
+ return new LLMChain({
6661
+ llm: await base.BaseLanguageModel.deserialize(llm),
6662
+ prompt: await prompts$1.BasePromptTemplate.deserialize(prompt),
6663
+ });
6803
6664
  }
6804
- const changelogMsg = await generateAndReviewLoop({
6805
- label: 'changelog',
6806
- options: {
6807
- ...config,
6808
- prompt: config.prompt || CHANGELOG_PROMPT.template,
6809
- logger,
6810
- interactive: INTERACTIVE,
6811
- review: {
6812
- enableFullRetry: false,
6813
- },
6814
- },
6815
- factory,
6816
- parser,
6817
- agent: async (context, options) => {
6818
- const parser = new output_parsers.StructuredOutputParser(ChangelogResponseSchema);
6819
- const prompt = getPrompt({
6820
- template: options.prompt,
6821
- variables: CHANGELOG_PROMPT.inputVariables,
6822
- fallback: CHANGELOG_PROMPT,
6823
- });
6824
- 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.";
6825
- const changelog = await executeChain({
6826
- llm,
6827
- prompt,
6828
- variables: {
6829
- summary: context,
6830
- format_instructions: formatInstructions,
6831
- },
6832
- parser,
6833
- });
6834
- const branchName = await getCurrentBranchName({ git });
6835
- const ticketId = extractTicketIdFromBranchName(branchName);
6836
- const footer = ticketId ? `\n\nPart of **${ticketId}**` : '';
6837
- return `${changelog.title}\n\n${changelog.content}${footer}`;
6838
- },
6839
- noResult: async () => {
6840
- if (config.range) {
6841
- logger.log(`No commits found in the provided range.`, { color: 'red' });
6842
- process.exit(0);
6843
- }
6844
- logger.log(`No commits found in the current branch.`, { color: 'red' });
6845
- process.exit(0);
6846
- },
6847
- });
6848
- const MODE = (INTERACTIVE && 'interactive') || (config.commit && 'interactive') || config?.mode || 'stdout';
6849
- handleResult({
6850
- result: changelogMsg,
6851
- interactiveModeCallback: async () => {
6852
- logSuccess();
6853
- },
6854
- mode: MODE,
6855
- });
6856
- };
6857
-
6858
- var changelog = {
6859
- command: command$4,
6860
- desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
6861
- builder: builder$4,
6862
- handler: commandExecutor(handler$4),
6863
- options: options$4,
6864
- };
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);
6676
+ }
6677
+ }
6865
6678
 
6866
- const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:/;
6867
- // Regular commit message schema with basic validation
6868
- const CommitMessageResponseSchema = objectType({
6869
- title: stringType(),
6870
- body: stringType(),
6871
- });
6872
- // Conventional commit message schema with strict formatting rules
6873
- const ConventionalCommitMessageResponseSchema = objectType({
6874
- title: stringType()
6875
- .max(50, "Title must be 50 characters or less")
6876
- .refine((title) => conventionalTypeRegex.test(title), "Title must follow Conventional Commits format (e.g., 'feat: add new feature' or 'fix(scope): fix bug')"),
6877
- body: stringType()
6878
- // .max(280, "Body must be 280 characters or less"),
6679
+ var llm_chain = /*#__PURE__*/Object.freeze({
6680
+ __proto__: null,
6681
+ LLMChain: LLMChain
6879
6682
  });
6880
- const command$3 = 'commit';
6881
- /**
6882
- * Command line options via yargs
6883
- */
6884
- const options$3 = {
6885
- i: {
6886
- alias: 'interactive',
6887
- description: 'Toggle interactive mode',
6888
- type: 'boolean',
6889
- },
6890
- ignoredFiles: {
6891
- description: 'Ignored files',
6892
- type: 'array',
6893
- },
6894
- ignoredExtensions: {
6895
- description: 'Ignored extensions',
6896
- type: 'array',
6897
- },
6898
- append: {
6899
- description: 'Add content to the end of the generated commit message',
6900
- type: 'string',
6901
- },
6902
- appendTicket: {
6903
- description: 'Append ticket ID from branch name to the commit message',
6904
- type: 'boolean',
6905
- alias: 't',
6906
- },
6907
- additional: {
6908
- description: 'Add extra contextual information to the prompt',
6909
- type: 'string',
6910
- alias: 'a',
6911
- },
6912
- withPreviousCommits: {
6913
- description: 'Include previous commits as context (specify number of commits, 0 for none)',
6914
- type: 'number',
6915
- default: 0,
6916
- alias: 'p',
6917
- },
6918
- conventional: {
6919
- description: 'Generate commit message in Conventional Commits format',
6920
- type: 'boolean',
6921
- default: false,
6922
- alias: 'c',
6923
- },
6924
- includeBranchName: {
6925
- description: 'Include the current branch name in the commit prompt for context',
6926
- type: 'boolean',
6927
- default: true,
6928
- },
6929
- };
6930
- const builder$3 = (yargs) => {
6931
- return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
6932
- };
6933
6683
 
6934
- new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
6684
+ const NAIVE_FIX_TEMPLATE = `Instructions:
6685
+ --------------
6686
+ {instructions}
6687
+ --------------
6688
+ Completion:
6689
+ --------------
6690
+ {completion}
6691
+ --------------
6692
+
6693
+ Above, the Completion did not satisfy the constraints given in the Instructions.
6694
+ Error:
6695
+ --------------
6696
+ {error}
6697
+ --------------
6698
+
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);
6935
6702
 
6703
+ function isLLMChain(x) {
6704
+ return (x.prompt !== undefined && x.llm !== undefined);
6705
+ }
6936
6706
  /**
6937
- * Base interface that all chains must implement.
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.
6938
6710
  */
6939
- class BaseChain extends base.BaseLangChain {
6940
- get lc_namespace() {
6941
- return ["langchain", "chains", this._chainType()];
6711
+ class OutputFixingParser extends output_parsers.BaseOutputParser {
6712
+ static lc_name() {
6713
+ return "OutputFixingParser";
6942
6714
  }
6943
- constructor(fields,
6944
- /** @deprecated */
6945
- verbose,
6946
- /** @deprecated */
6947
- callbacks) {
6948
- if (arguments.length === 1 &&
6949
- typeof fields === "object" &&
6950
- !("saveContext" in fields)) {
6951
- // fields is not a BaseMemory
6952
- const { memory, callbackManager, ...rest } = fields;
6953
- super({ ...rest, callbacks: callbackManager ?? rest.callbacks });
6954
- this.memory = memory;
6955
- }
6956
- else {
6957
- // fields is a BaseMemory
6958
- super({ verbose, callbacks });
6959
- this.memory = fields;
6960
- }
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 });
6961
6727
  }
6962
- /** @ignore */
6963
- _selectMemoryInputs(values) {
6964
- const valuesForMemory = { ...values };
6965
- if ("signal" in valuesForMemory) {
6966
- delete valuesForMemory.signal;
6967
- }
6968
- if ("timeout" in valuesForMemory) {
6969
- delete valuesForMemory.timeout;
6970
- }
6971
- return valuesForMemory;
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"]
6735
+ });
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;
6972
6756
  }
6973
6757
  /**
6974
- * Invoke the chain with the provided input and returns the output.
6975
- * @param input Input values for the chain run.
6976
- * @param config Optional configuration for the Runnable.
6977
- * @returns Promise that resolves with the output of the chain run.
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.
6978
6764
  */
6979
- async invoke(input, options) {
6980
- const config = runnables.ensureConfig(options);
6981
- const fullValues = await this._formatValues(input);
6982
- const callbackManager_ = await manager.CallbackManager.configure(config?.callbacks, this.callbacks, config?.tags, this.tags, config?.metadata, this.metadata, { verbose: this.verbose });
6983
- const runManager = await callbackManager_?.handleChainStart(this.toJSON(), fullValues, undefined, undefined, undefined, undefined, config?.runName);
6984
- let outputValues;
6765
+ async parse(completion, callbacks) {
6985
6766
  try {
6986
- outputValues = await (fullValues.signal
6987
- ? Promise.race([
6988
- this._call(fullValues, runManager, config),
6989
- new Promise((_, reject) => {
6990
- fullValues.signal?.addEventListener("abort", () => {
6991
- reject(new Error("AbortError"));
6992
- });
6993
- }),
6994
- ])
6995
- : this._call(fullValues, runManager, config));
6767
+ return await this.parser.parse(completion, callbacks);
6996
6768
  }
6997
6769
  catch (e) {
6998
- await runManager?.handleChainError(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
+ }
6999
6789
  throw e;
7000
6790
  }
7001
- if (!(this.memory == null)) {
7002
- await this.memory.saveContext(this._selectMemoryInputs(input), outputValues);
7003
- }
7004
- await runManager?.handleChainEnd(outputValues);
7005
- // add the runManager's currentRunId to the outputValues
7006
- Object.defineProperty(outputValues, outputs.RUN_KEY, {
7007
- value: runManager ? { runId: runManager?.runId } : undefined,
7008
- configurable: true,
7009
- });
7010
- return outputValues;
7011
- }
7012
- _validateOutputs(outputs) {
7013
- const missingKeys = this.outputKeys.filter((k) => !(k in outputs));
7014
- if (missingKeys.length) {
7015
- throw new Error(`Missing output keys: ${missingKeys.join(", ")} from chain ${this._chainType()}`);
7016
- }
7017
- }
7018
- async prepOutputs(inputs, outputs, returnOnlyOutputs = false) {
7019
- this._validateOutputs(outputs);
7020
- if (this.memory) {
7021
- await this.memory.saveContext(inputs, outputs);
7022
- }
7023
- if (returnOnlyOutputs) {
7024
- return outputs;
7025
- }
7026
- return { ...inputs, ...outputs };
7027
6791
  }
7028
6792
  /**
7029
- * Return a json-like object representing this chain.
6793
+ * Method to get the format instructions for the parser.
6794
+ * @returns The format instructions for the parser.
7030
6795
  */
7031
- serialize() {
7032
- throw new Error("Method not implemented.");
6796
+ getFormatInstructions() {
6797
+ return this.parser.getFormatInstructions();
7033
6798
  }
7034
- /** @deprecated Use .invoke() instead. Will be removed in 0.2.0. */
7035
- async run(
7036
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7037
- input, config) {
7038
- const inputKeys = this.inputKeys.filter((k) => !this.memory?.memoryKeys.includes(k) ?? true);
7039
- const isKeylessInput = inputKeys.length <= 1;
7040
- if (!isKeylessInput) {
7041
- throw new Error(`Chain ${this._chainType()} expects multiple inputs, cannot use 'run' `);
7042
- }
7043
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7044
- const values = inputKeys.length ? { [inputKeys[0]]: input } : {};
7045
- const returnValues = await this.call(values, config);
7046
- const keys = Object.keys(returnValues);
7047
- if (keys.length === 1) {
7048
- return returnValues[keys[0]];
7049
- }
7050
- throw new Error("return values have multiple keys, `run` only supported when one key currently");
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) });
7051
6871
  }
7052
- async _formatValues(values) {
7053
- const fullValues = { ...values };
7054
- if (fullValues.timeout && !fullValues.signal) {
7055
- fullValues.signal = AbortSignal.timeout(fullValues.timeout);
7056
- delete fullValues.timeout;
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 });
7057
6877
  }
7058
- if (!(this.memory == null)) {
7059
- const newValues = await this.memory.loadMemoryVariables(this._selectMemoryInputs(values));
7060
- for (const [key, value] of Object.entries(newValues)) {
7061
- fullValues[key] = value;
7062
- }
6878
+ return result;
6879
+ }
6880
+ catch (error) {
6881
+ // Re-throw LangChain errors as-is
6882
+ if (error instanceof LangChainExecutionError) {
6883
+ throw error;
7063
6884
  }
7064
- return fullValues;
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
+ });
7065
6891
  }
7066
- /**
7067
- * @deprecated Use .invoke() instead. Will be removed in 0.2.0.
7068
- *
7069
- * Run the core logic of this chain and add to output if desired.
7070
- *
7071
- * Wraps _call and handles memory.
7072
- */
7073
- async call(values, config,
7074
- /** @deprecated */
7075
- tags) {
7076
- const parsedConfig = { tags, ...manager.parseCallbackConfigArg(config) };
7077
- return this.invoke(values, parsedConfig);
6892
+ };
6893
+
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
+ }
6899
+
6900
+ /**
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.
6912
+ */
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}>`);
6915
+ };
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);
7078
6923
  }
7079
- /**
7080
- * @deprecated Use .batch() instead. Will be removed in 0.2.0.
7081
- *
7082
- * Call the chain on all inputs in the list
7083
- */
7084
- async apply(inputs, config) {
7085
- return Promise.all(inputs.map(async (i, idx) => this.call(i, config?.[idx])));
6924
+ else {
6925
+ return ['No tags found in the repository.'];
7086
6926
  }
7087
- /**
7088
- * Load a chain from a json-like object describing it.
7089
- */
7090
- static async deserialize(data, values = {}) {
7091
- switch (data._type) {
7092
- case "llm_chain": {
7093
- const { LLMChain } = await Promise.resolve().then(function () { return llm_chain; });
7094
- return LLMChain.deserialize(data);
7095
- }
7096
- case "sequential_chain": {
7097
- const { SequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
7098
- return SequentialChain.deserialize(data);
7099
- }
7100
- case "simple_sequential_chain": {
7101
- const { SimpleSequentialChain } = await Promise.resolve().then(function () { return sequential_chain; });
7102
- return SimpleSequentialChain.deserialize(data);
7103
- }
7104
- case "stuff_documents_chain": {
7105
- const { StuffDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
7106
- return StuffDocumentsChain.deserialize(data);
7107
- }
7108
- case "map_reduce_documents_chain": {
7109
- const { MapReduceDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
7110
- return MapReduceDocumentsChain.deserialize(data);
7111
- }
7112
- case "refine_documents_chain": {
7113
- const { RefineDocumentsChain } = await Promise.resolve().then(function () { return combine_docs_chain; });
7114
- return RefineDocumentsChain.deserialize(data);
7115
- }
7116
- case "vector_db_qa": {
7117
- const { VectorDBQAChain } = await Promise.resolve().then(function () { return vector_db_qa; });
7118
- return VectorDBQAChain.deserialize(data, values);
6927
+ };
6928
+
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}>`);
7119
6969
  }
7120
- case "api_chain": {
7121
- const { APIChain } = await Promise.resolve().then(function () { return api_chain; });
7122
- return APIChain.deserialize(data);
6970
+ catch (fallbackError) {
6971
+ throw fallbackError;
7123
6972
  }
7124
- default:
7125
- throw new Error(`Invalid prompt type in config: ${data._type}`);
7126
6973
  }
6974
+ throw error;
7127
6975
  }
7128
6976
  }
7129
6977
 
7130
6978
  /**
7131
- * The NoOpOutputParser class is a type of output parser that does not
7132
- * perform any operations on the output. It extends the BaseOutputParser
7133
- * class and is part of the LangChain's output parsers module. This class
7134
- * is useful in scenarios where the raw output of the Large Language
7135
- * Models (LLMs) is required.
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.
7136
6983
  */
7137
- class NoOpOutputParser extends output_parsers.BaseOutputParser {
7138
- constructor() {
7139
- super(...arguments);
7140
- Object.defineProperty(this, "lc_namespace", {
7141
- enumerable: true,
7142
- configurable: true,
7143
- writable: true,
7144
- value: ["langchain", "output_parsers", "default"]
7145
- });
7146
- Object.defineProperty(this, "lc_serializable", {
7147
- enumerable: true,
7148
- configurable: true,
7149
- writable: true,
7150
- value: true
6984
+ async function getCurrentBranchName({ git }) {
6985
+ return await git.revparse(['--abbrev-ref', 'HEAD']);
6986
+ }
6987
+
6988
+ /**
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.
6996
+ */
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 [];
7012
+ }
7013
+ // Retrieve commit log with messages
7014
+ return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
7015
+ }
7016
+ catch (error) {
7017
+ logger?.log('Encountered an error getting commit log between branches', { color: 'red' });
7018
+ }
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 [];
7039
+ }
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();
7047
+ }
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();
7054
+ }
7055
+ logger?.verbose(`Found ${uniqueCommits.length} unique commits on "${branchName}"`, {
7056
+ color: 'blue',
7151
7057
  });
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 [];
7065
+ }
7066
+ return await getCommitLogRange(firstCommit, lastCommit, { git, noMerges: true });
7152
7067
  }
7153
- static lc_name() {
7154
- return "NoOpOutputParser";
7155
- }
7156
- /**
7157
- * This method takes a string as input and returns the same string as
7158
- * output. It does not perform any operations on the input string.
7159
- * @param text The input string to be parsed.
7160
- * @returns The same input string without any operations performed on it.
7161
- */
7162
- parse(text) {
7163
- return Promise.resolve(text);
7164
- }
7165
- /**
7166
- * This method returns an empty string. It does not provide any formatting
7167
- * instructions.
7168
- * @returns An empty string, indicating no formatting instructions.
7169
- */
7170
- getFormatInstructions() {
7171
- return "";
7068
+ catch (error) {
7069
+ logger?.log('Encountered an error getting commit log from current branch', { color: 'red' });
7172
7070
  }
7071
+ return [];
7173
7072
  }
7174
7073
 
7175
- function isBaseLanguageModel(llmLike) {
7176
- return typeof llmLike._llmType === "function";
7177
- }
7178
- function _getLanguageModel(llmLike) {
7179
- if (isBaseLanguageModel(llmLike)) {
7180
- return llmLike;
7181
- }
7182
- else if ("bound" in llmLike && runnables.Runnable.isRunnable(llmLike.bound)) {
7183
- return _getLanguageModel(llmLike.bound);
7184
- }
7185
- else if ("runnable" in llmLike &&
7186
- "fallbacks" in llmLike &&
7187
- runnables.Runnable.isRunnable(llmLike.runnable)) {
7188
- return _getLanguageModel(llmLike.runnable);
7189
- }
7190
- else if ("default" in llmLike && runnables.Runnable.isRunnable(llmLike.default)) {
7191
- return _getLanguageModel(llmLike.default);
7074
+ /**
7075
+ * Retrieves the SimpleGit instance for the repository.
7076
+ * @returns {SimpleGit} The SimpleGit instance.
7077
+ */
7078
+ const getRepo = () => {
7079
+ let git;
7080
+ try {
7081
+ git = simpleGit.simpleGit();
7192
7082
  }
7193
- else {
7194
- throw new Error("Unable to extract BaseLanguageModel from llmLike object.");
7083
+ catch (e) {
7084
+ console.log('Error initializing git repo', e);
7085
+ process.exit(1);
7195
7086
  }
7196
- }
7087
+ return git;
7088
+ };
7089
+
7197
7090
  /**
7198
- * @deprecated This class will be removed in 1.0.0. Use the LangChain Expression Language (LCEL) instead.
7199
- * See the example below for how to use LCEL with the LLMChain class:
7200
- *
7201
- * Chain to run queries against LLMs.
7202
- *
7203
- * @example
7204
- * ```ts
7205
- * import { ChatPromptTemplate } from "@langchain/core/prompts";
7206
- * import { ChatOpenAI } from "@langchain/openai";
7091
+ * Template for generating git commit messages based on code changes
7207
7092
  *
7208
- * const prompt = ChatPromptTemplate.fromTemplate("Tell me a {adjective} joke");
7209
- * const llm = new ChatOpenAI();
7210
- * const chain = prompt.pipe(llm);
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
7211
7191
  *
7212
- * const response = await chain.invoke({ adjective: "funny" });
7213
- * ```
7192
+ * @param text template string
7193
+ * @param inputVariables template variables
7194
+ * @throws Error if validation fails
7214
7195
  */
7215
- class LLMChain extends BaseChain {
7216
- static lc_name() {
7217
- return "LLMChain";
7196
+ function validatePromptTemplate(text, inputVariables) {
7197
+ if (!text || text.trim() === '') {
7198
+ throw new Error('Prompt template cannot be empty');
7218
7199
  }
7219
- get inputKeys() {
7220
- return this.prompt.inputVariables;
7200
+ if (!inputVariables || inputVariables.length === 0) {
7201
+ return; // No variables to validate
7221
7202
  }
7222
- get outputKeys() {
7223
- return [this.outputKey];
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]);
7224
7211
  }
7225
- constructor(fields) {
7226
- super(fields);
7227
- Object.defineProperty(this, "lc_serializable", {
7228
- enumerable: true,
7229
- configurable: true,
7230
- writable: true,
7231
- value: true
7232
- });
7233
- Object.defineProperty(this, "prompt", {
7234
- enumerable: true,
7235
- configurable: true,
7236
- writable: true,
7237
- value: void 0
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'),
7238
7250
  });
7239
- Object.defineProperty(this, "llm", {
7240
- enumerable: true,
7241
- configurable: true,
7242
- writable: true,
7243
- value: void 0
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`,
7244
7268
  });
7245
- Object.defineProperty(this, "llmKwargs", {
7246
- enumerable: true,
7247
- configurable: true,
7248
- writable: true,
7249
- value: void 0
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}`,
7250
7275
  });
7251
- Object.defineProperty(this, "outputKey", {
7252
- enumerable: true,
7253
- configurable: true,
7254
- writable: true,
7255
- value: "text"
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}`,
7256
7283
  });
7257
- Object.defineProperty(this, "outputParser", {
7258
- enumerable: true,
7259
- configurable: true,
7260
- writable: true,
7261
- value: void 0
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}`,
7262
7291
  });
7263
- this.prompt = fields.prompt;
7264
- this.llm = fields.llm;
7265
- this.llmKwargs = fields.llmKwargs;
7266
- this.outputKey = fields.outputKey ?? this.outputKey;
7267
- this.outputParser =
7268
- fields.outputParser ?? new NoOpOutputParser();
7269
- if (this.prompt.outputParser) {
7270
- if (fields.outputParser) {
7271
- throw new Error("Cannot set both outputParser and prompt.outputParser");
7272
- }
7273
- this.outputParser = this.prompt.outputParser;
7274
- }
7275
7292
  }
7276
- getCallKeys() {
7277
- const callKeys = "callKeys" in this.llm ? this.llm.callKeys : [];
7278
- return callKeys;
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);
7279
7318
  }
7280
- /** @ignore */
7281
- _selectMemoryInputs(values) {
7282
- const valuesForMemory = super._selectMemoryInputs(values);
7283
- const callKeys = this.getCallKeys();
7284
- for (const key of callKeys) {
7285
- if (key in values) {
7286
- delete valuesForMemory[key];
7287
- }
7319
+ while (continueLoop) {
7320
+ if (!context.length) {
7321
+ context = await parser(changes, result, options);
7288
7322
  }
7289
- return valuesForMemory;
7290
- }
7291
- /** @ignore */
7292
- async _getFinalOutput(generations, promptValue, runManager) {
7293
- let finalCompletion;
7294
- if (this.outputParser) {
7295
- 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);
7296
7326
  }
7297
- else {
7298
- finalCompletion = generations[0].text;
7327
+ if (modifyPrompt) {
7328
+ options.prompt = await editPrompt(options);
7299
7329
  }
7300
- return finalCompletion;
7301
- }
7302
- /**
7303
- * Run the core logic of this chain and add to output if desired.
7304
- *
7305
- * Wraps _call and handles memory.
7306
- */
7307
- call(values, config) {
7308
- return super.call(values, config);
7309
- }
7310
- /** @ignore */
7311
- async _call(values, runManager) {
7312
- const valuesForPrompt = { ...values };
7313
- const valuesForLLM = {
7314
- ...this.llmKwargs,
7315
- };
7316
- const callKeys = this.getCallKeys();
7317
- for (const key of callKeys) {
7318
- if (key in values) {
7319
- if (valuesForLLM) {
7320
- valuesForLLM[key] =
7321
- values[key];
7322
- delete valuesForPrompt[key];
7323
- }
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);
7324
7341
  }
7325
7342
  }
7326
- const promptValue = await this.prompt.formatPromptValue(valuesForPrompt);
7327
- if ("generatePrompt" in this.llm) {
7328
- const { generations } = await this.llm.generatePrompt([promptValue], valuesForLLM, runManager?.getChild());
7329
- return {
7330
- [this.outputKey]: await this._getFinalOutput(generations[0], promptValue, runManager),
7331
- };
7332
- }
7333
- const modelWithParser = this.outputParser
7334
- ? this.llm.pipe(this.outputParser)
7335
- : this.llm;
7336
- const response = await modelWithParser.invoke(promptValue, runManager?.getChild());
7337
- return {
7338
- [this.outputKey]: response,
7339
- };
7340
- }
7341
- /**
7342
- * Format prompt with values and pass to LLM
7343
- *
7344
- * @param values - keys to pass to prompt template
7345
- * @param callbackManager - CallbackManager to use
7346
- * @returns Completion from LLM.
7347
- *
7348
- * @example
7349
- * ```ts
7350
- * llm.predict({ adjective: "funny" })
7351
- * ```
7352
- */
7353
- async predict(values, callbackManager) {
7354
- const output = await this.call(values, callbackManager);
7355
- return output[this.outputKey];
7356
- }
7357
- _chainType() {
7358
- return "llm";
7359
- }
7360
- static async deserialize(data) {
7361
- const { llm, prompt } = data;
7362
- if (!llm) {
7363
- throw new Error("LLMChain must have llm");
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;
7364
7355
  }
7365
- if (!prompt) {
7366
- throw new Error("LLMChain must have prompt");
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);
7367
7392
  }
7368
- return new LLMChain({
7369
- llm: await base.BaseLanguageModel.deserialize(llm),
7370
- prompt: await prompts$1.BasePromptTemplate.deserialize(prompt),
7371
- });
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;
7398
+ }
7399
+ // if we're here, we're done.
7400
+ continueLoop = false;
7372
7401
  }
7373
- /** @deprecated */
7374
- serialize() {
7375
- const serialize = "serialize" in this.llm ? this.llm.serialize() : undefined;
7376
- return {
7377
- _type: `${this._chainType()}_chain`,
7378
- llm: serialize,
7379
- prompt: this.prompt.serialize(),
7380
- };
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;
7381
7425
  }
7382
- _getNumTokens(text) {
7383
- return _getLanguageModel(this.llm).getNumTokens(text);
7426
+ if (process.env.NODE_ENV !== 'test') {
7427
+ process.exit(0);
7384
7428
  }
7385
7429
  }
7386
7430
 
7387
- var llm_chain = /*#__PURE__*/Object.freeze({
7388
- __proto__: null,
7389
- LLMChain: LLMChain
7390
- });
7431
+ const template$3 = `Write informative git changelog, in the imperative, based on a series of individual messages.
7391
7432
 
7392
- const NAIVE_FIX_TEMPLATE = `Instructions:
7393
- --------------
7394
- {instructions}
7395
- --------------
7396
- Completion:
7397
- --------------
7398
- {completion}
7399
- --------------
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"
7400
7438
 
7401
- Above, the Completion did not satisfy the constraints given in the Instructions.
7402
- Error:
7403
- --------------
7404
- {error}
7405
- --------------
7439
+ {{format_instructions}}
7406
7440
 
7407
- Please try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:`;
7408
- const NAIVE_FIX_PROMPT =
7409
- /* #__PURE__ */ prompts$1.PromptTemplate.fromTemplate(NAIVE_FIX_TEMPLATE);
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
+ });
7410
7447
 
7411
- function isLLMChain(x) {
7412
- return (x.prompt !== undefined && x.llm !== undefined);
7413
- }
7414
- /**
7415
- * Class that extends the BaseOutputParser to handle situations where the
7416
- * initial parsing attempt fails. It contains a retryChain for retrying
7417
- * the parsing process in case of a failure.
7418
- */
7419
- class OutputFixingParser extends output_parsers.BaseOutputParser {
7420
- static lc_name() {
7421
- return "OutputFixingParser";
7422
- }
7423
- /**
7424
- * Static method to create a new instance of OutputFixingParser using a
7425
- * given language model, parser, and optional fields.
7426
- * @param llm The language model to be used.
7427
- * @param parser The parser to be used.
7428
- * @param fields Optional fields which may contain a prompt.
7429
- * @returns A new instance of OutputFixingParser.
7430
- */
7431
- static fromLLM(llm, parser, fields) {
7432
- const prompt = fields?.prompt ?? NAIVE_FIX_PROMPT;
7433
- const chain = new LLMChain({ llm, prompt });
7434
- 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);
7435
7456
  }
7436
- constructor({ parser, retryChain, }) {
7437
- super(...arguments);
7438
- Object.defineProperty(this, "lc_namespace", {
7439
- enumerable: true,
7440
- configurable: true,
7441
- writable: true,
7442
- value: ["langchain", "output_parsers", "fix"]
7443
- });
7444
- Object.defineProperty(this, "lc_serializable", {
7445
- enumerable: true,
7446
- configurable: true,
7447
- writable: true,
7448
- value: true
7449
- });
7450
- Object.defineProperty(this, "parser", {
7451
- enumerable: true,
7452
- configurable: true,
7453
- writable: true,
7454
- value: void 0
7455
- });
7456
- Object.defineProperty(this, "retryChain", {
7457
- enumerable: true,
7458
- configurable: true,
7459
- writable: true,
7460
- value: void 0
7461
- });
7462
- this.parser = parser;
7463
- 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
+ }
7464
7463
  }
7465
- /**
7466
- * Method to parse the completion using the parser. If the initial parsing
7467
- * fails, it uses the retryChain to attempt to fix the output and retry
7468
- * the parsing process.
7469
- * @param completion The completion to be parsed.
7470
- * @param callbacks Optional callbacks to be used during parsing.
7471
- * @returns The parsed output.
7472
- */
7473
- async parse(completion, callbacks) {
7474
- try {
7475
- 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
+ };
7476
7472
  }
7477
- catch (e) {
7478
- // eslint-disable-next-line no-instanceof/no-instanceof
7479
- if (e instanceof output_parsers.OutputParserException) {
7480
- const retryInput = {
7481
- instructions: this.parser.getFormatInstructions(),
7482
- completion,
7483
- error: e,
7484
- };
7485
- if (isLLMChain(this.retryChain)) {
7486
- const result = await this.retryChain.call(retryInput, callbacks);
7487
- const newCompletion = result[this.retryChain.outputKey];
7488
- return this.parser.parse(newCompletion, callbacks);
7489
- }
7490
- else {
7491
- const result = await this.retryChain.invoke(retryInput, {
7492
- callbacks,
7493
- });
7494
- return result;
7495
- }
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);
7496
7478
  }
7497
- throw e;
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
+ };
7498
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
+ };
7499
7499
  }
7500
- /**
7501
- * Method to get the format instructions for the parser.
7502
- * @returns The format instructions for the parser.
7503
- */
7504
- getFormatInstructions() {
7505
- return this.parser.getFormatInstructions();
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;
7506
7509
  }
7507
- }
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
+ };
7508
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';
7509
7587
  /**
7510
- * Creates a parser with built-in retry logic for schema-based generation
7511
- * @param schema - Zod schema for the expected output structure
7512
- * @param llm - LLM instance for retry attempts
7513
- * @param options - Configuration options for retry behavior
7514
- * @returns OutputFixingParser configured with retry logic
7515
- * @throws LangChainExecutionError if parser creation fails
7588
+ * Command line options via yargs
7516
7589
  */
7517
- function createSchemaParser(schema, llm, options = {}) {
7518
- validateRequired(schema, 'schema', 'createSchemaParser');
7519
- validateRequired(llm, 'llm', 'createSchemaParser');
7520
- validateRequired(options, 'options', 'createSchemaParser');
7521
- // Validate schema is actually a Zod schema
7522
- if (typeof schema.parse !== 'function') {
7523
- throw new LangChainExecutionError('createSchemaParser: Schema must be a valid Zod schema with a parse method', { schemaType: typeof schema, hasParseMethod: typeof schema.parse });
7524
- }
7525
- // Validate options structure
7526
- if (typeof options !== 'object' || Array.isArray(options)) {
7527
- throw new LangChainExecutionError('createSchemaParser: Options must be a non-array object', { options, type: typeof options, isArray: Array.isArray(options) });
7528
- }
7529
- const { retryTemplate } = options;
7530
- // Validate retryTemplate if provided
7531
- if (retryTemplate !== undefined && typeof retryTemplate !== 'string') {
7532
- throw new LangChainExecutionError('createSchemaParser: retryTemplate must be a string when provided', { retryTemplate, type: typeof retryTemplate });
7533
- }
7534
- try {
7535
- const baseParser = new output_parsers.StructuredOutputParser(schema);
7536
- const defaultRetryTemplate = `The following text failed to parse as valid JSON. Please convert it into a valid JSON object that matches the required schema.
7537
-
7538
- ## Text to fix:
7539
- {completion}
7540
-
7541
- ## Instructions:
7542
- {instructions}
7543
-
7544
- You must return ONLY valid JSON that matches the schema exactly. Do not include any additional text, explanations, or markdown formatting:`;
7545
- const retryPromptTemplate = new prompts$1.PromptTemplate({
7546
- template: retryTemplate || defaultRetryTemplate,
7547
- inputVariables: ['completion', 'instructions'],
7548
- });
7549
- const retryChain = retryPromptTemplate.pipe(llm).pipe(baseParser);
7550
- return new OutputFixingParser({
7551
- parser: baseParser,
7552
- retryChain: retryChain,
7553
- });
7554
- }
7555
- catch (error) {
7556
- handleLangChainError(error, 'createSchemaParser: Failed to create schema parser', {
7557
- schemaName: schema.constructor.name,
7558
- llmType: llm.constructor.name,
7559
- hasRetryTemplate: !!retryTemplate
7560
- });
7561
- }
7562
- }
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
+ };
7563
7644
 
7564
7645
  /**
7565
7646
  * High-level function that combines chain execution with schema-based parsing
@@ -7845,7 +7926,38 @@ async function parseDefaultFileDiff(nodeFile, commit = '--staged', git) {
7845
7926
  throw new Error(`Error reading untracked file: ${error?.message || 'Unknown error'}`);
7846
7927
  }
7847
7928
  }
7848
- return await git.diff([commit, nodeFile.filePath]);
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
+ }
7849
7961
  }
7850
7962
  /**
7851
7963
  * Parses the diff for a renamed file.
@@ -10980,14 +11092,24 @@ const handler$3 = async (argv, logger) => {
10980
11092
  });
10981
11093
  }
10982
11094
  async function factory() {
10983
- const changes = await getChanges({
10984
- git,
10985
- options: {
10986
- ignoredFiles: config.ignoredFiles || undefined,
10987
- ignoredExtensions: config.ignoredExtensions || undefined,
10988
- },
10989
- });
10990
- return changes.staged;
11095
+ if (config.noDiff) {
11096
+ const status = await git.status();
11097
+ return status.files.map(file => ({
11098
+ filePath: file.path,
11099
+ status: (file.index === 'A' || file.index === '?' ? 'added' : 'modified'),
11100
+ summary: file.path, // Simplified summary for noDiff
11101
+ }));
11102
+ }
11103
+ else {
11104
+ const changes = await getChanges({
11105
+ git,
11106
+ options: {
11107
+ ignoredFiles: config.ignoredFiles || undefined,
11108
+ ignoredExtensions: config.ignoredExtensions || undefined,
11109
+ },
11110
+ });
11111
+ return changes.staged;
11112
+ }
10991
11113
  }
10992
11114
  async function parser(changes) {
10993
11115
  return await fileChangeParser({
@@ -11690,6 +11812,10 @@ const options$1 = {
11690
11812
  type: 'boolean',
11691
11813
  description: 'Recap for last tag',
11692
11814
  },
11815
+ currentBranch: {
11816
+ type: 'boolean',
11817
+ description: 'Recap for the current branch',
11818
+ },
11693
11819
  i: {
11694
11820
  type: 'boolean',
11695
11821
  alias: 'interactive',
@@ -11705,8 +11831,89 @@ const getChangesByTimestamp = async ({ since, git }) => {
11705
11831
  return formatCommitLog(commitLog);
11706
11832
  };
11707
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
+
11708
11914
  async function noResult$1({ logger }) {
11709
11915
  logger.log('No repo changes detected. 👀', { color: 'blue' });
11916
+ throw new Error('NO_CHANGES_DETECTED');
11710
11917
  }
11711
11918
 
11712
11919
  const template$1 = `Following the formatting instructions, summarize the following changes in the underlying git repository/branch.
@@ -11754,7 +11961,9 @@ const handler$1 = async (argv, logger) => {
11754
11961
  ? 'yesterday'
11755
11962
  : lastWeek
11756
11963
  ? 'last-week'
11757
- : 'current';
11964
+ : argv.currentBranch || config.currentBranch
11965
+ ? 'currentBranch'
11966
+ : 'current';
11758
11967
  logger.log(`Generating recap for timeframe: ${timeframe}`);
11759
11968
  async function factory() {
11760
11969
  switch (timeframe) {
@@ -11795,6 +12004,25 @@ const handler$1 = async (argv, logger) => {
11795
12004
  case 'last-tag':
11796
12005
  const tags = await getChangesSinceLastTag({ git });
11797
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];
11798
12026
  default:
11799
12027
  logger.log(`Invalid timeframe: ${timeframe}`, { color: 'red' });
11800
12028
  return [];
@@ -11835,7 +12063,7 @@ const handler$1 = async (argv, logger) => {
11835
12063
  fallback: RECAP_PROMPT,
11836
12064
  });
11837
12065
  try {
11838
- const parser = new output_parsers.StructuredOutputParser(RecapLlmResponseSchema);
12066
+ const parser = createSchemaParser(RecapLlmResponseSchema, llm);
11839
12067
  const response = await executeChain({
11840
12068
  llm,
11841
12069
  prompt,
@@ -11867,11 +12095,13 @@ ${errorMessage}
11867
12095
  },
11868
12096
  noResult: async () => {
11869
12097
  await noResult$1({ git, logger });
11870
- process.exit(0);
12098
+ if (process.env.NODE_ENV !== 'test') {
12099
+ process.exit(0);
12100
+ }
11871
12101
  },
11872
12102
  });
11873
12103
  // Handle the result based on the mode (interactive or stdout)
11874
- const MODE = (INTERACTIVE && 'interactive') || (config.recap && 'interactive') || config?.mode || 'stdout'; // Default to stdout
12104
+ const MODE = (INTERACTIVE && 'interactive') ?? (config.recap && 'interactive') ?? config?.mode ?? 'stdout'; // Default to stdout
11875
12105
  handleResult({
11876
12106
  result: recapResult,
11877
12107
  interactiveModeCallback: async () => {
@@ -11929,46 +12159,6 @@ const builder = (yargs) => {
11929
12159
  return yargs.options(options).usage(getCommandUsageHeader(command));
11930
12160
  };
11931
12161
 
11932
- /**
11933
- * Retrieves the diff between the current branch and a specified target branch.
11934
- *
11935
- * @param {Object} options - The options for retrieving the diff.
11936
- * @param {SimpleGit} options.git - The SimpleGit instance.
11937
- * @param {Logger} options.logger - The logger for logging messages.
11938
- * @param {string} options.targetBranch - The target branch to compare against.
11939
- * @param {string[]} options.ignoredFiles - Array of specific files to ignore.
11940
- * @param {string[]} options.ignoredExtensions - Array of file extensions to ignore.
11941
- * @returns {Promise<string>} The diff between the current branch and the target branch.
11942
- */
11943
- async function getDiffForBranch({ git, logger, targetBranch, ignoredFiles = [], ignoredExtensions = [], }) {
11944
- try {
11945
- // Get the current branch name
11946
- const currentBranch = await getCurrentBranchName({ git });
11947
- // Prepare ignore patterns
11948
- const ignorePatterns = [
11949
- ...ignoredFiles.map((file) => `:!${file}`),
11950
- ...ignoredExtensions.map((ext) => `:!*${ext}`),
11951
- ];
11952
- // Construct the diff command
11953
- const diffArgs = [`${targetBranch}..${currentBranch}`];
11954
- if (ignorePatterns.length > 0) {
11955
- diffArgs.push('--');
11956
- diffArgs.push(...ignorePatterns);
11957
- }
11958
- // Get the diff
11959
- const diff = await git.diff(diffArgs);
11960
- logger?.verbose(`Generated diff between "${currentBranch}" and "${targetBranch}"`, {
11961
- color: 'blue',
11962
- });
11963
- return diff;
11964
- }
11965
- catch (error) {
11966
- console.error('Error in getDiffForBranch:', error);
11967
- logger?.log('Encountered an error getting diff between branches', { color: 'red' });
11968
- return '';
11969
- }
11970
- }
11971
-
11972
12162
  /******************************************************************************
11973
12163
  Copyright (c) Microsoft Corporation.
11974
12164
 
@@ -14185,14 +14375,23 @@ const handler = async (argv, logger) => {
14185
14375
  async function factory() {
14186
14376
  if (argv.branch) {
14187
14377
  logger.verbose(`Generating diff for branch: ${argv.branch}`, { color: 'yellow' });
14378
+ const currentBranch = await getCurrentBranchName({ git });
14188
14379
  const diff = await getDiffForBranch({
14189
14380
  git,
14190
14381
  logger,
14191
- targetBranch: argv.branch,
14192
- ignoredFiles: config.ignoredFiles || [],
14193
- ignoredExtensions: config.ignoredExtensions || [],
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 },
14194
14393
  });
14195
- return [diff];
14394
+ return [branchChanges];
14196
14395
  }
14197
14396
  else {
14198
14397
  const { staged, unstaged, untracked } = await getChanges({
@@ -14256,7 +14455,7 @@ const handler = async (argv, logger) => {
14256
14455
  factory,
14257
14456
  parser,
14258
14457
  agent: async (context, options) => {
14259
- const parser = new output_parsers.StructuredOutputParser(ReviewFeedbackItemArraySchema);
14458
+ const parser = createSchemaParser(ReviewFeedbackItemArraySchema, llm);
14260
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.";
14261
14460
  const prompt = getPrompt({
14262
14461
  template: options.prompt,