git-coco 0.20.1 → 0.21.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/README.md +15 -1
- package/dist/index.esm.mjs +473 -128
- package/dist/index.js +472 -127
- package/package.json +3 -3
package/dist/index.esm.mjs
CHANGED
|
@@ -4,13 +4,13 @@ import { ConditionalPromptSelector, isChatModel } from '@langchain/core/example_
|
|
|
4
4
|
import yargs from 'yargs';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
|
-
import fs__default, { promises } from 'fs';
|
|
7
|
+
import fs__default, { promises, existsSync, readFileSync } from 'fs';
|
|
8
8
|
import { confirm, editor, select, password, input, Separator, number, rawlist, expand, checkbox, search } from '@inquirer/prompts';
|
|
9
9
|
import * as ini from 'ini';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import os__default from 'os';
|
|
12
12
|
import * as path from 'path';
|
|
13
|
-
import path__default from 'path';
|
|
13
|
+
import path__default, { join } from 'path';
|
|
14
14
|
import Ajv from 'ajv';
|
|
15
15
|
import ora from 'ora';
|
|
16
16
|
import now from 'performance-now';
|
|
@@ -19,6 +19,8 @@ import { ChatAnthropic } from '@langchain/anthropic';
|
|
|
19
19
|
import { ChatOllama } from '@langchain/ollama';
|
|
20
20
|
import { ChatOpenAI } from '@langchain/openai';
|
|
21
21
|
import { BaseOutputParser, OutputParserException, StructuredOutputParser, StringOutputParser } from '@langchain/core/output_parsers';
|
|
22
|
+
import '@langchain/core/utils/json_schema';
|
|
23
|
+
import '@langchain/core/utils/types';
|
|
22
24
|
import { BaseLangChain, BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
23
25
|
import { ensureConfig, Runnable } from '@langchain/core/runnables';
|
|
24
26
|
import { RUN_KEY } from '@langchain/core/outputs';
|
|
@@ -40,13 +42,15 @@ import { exec } from 'child_process';
|
|
|
40
42
|
import readline$1 from 'node:readline';
|
|
41
43
|
import require$$0 from 'stream';
|
|
42
44
|
import * as readline from 'readline';
|
|
45
|
+
import { lint, load } from '@commitlint/core';
|
|
46
|
+
import { pathToFileURL } from 'url';
|
|
43
47
|
|
|
44
48
|
// This file is auto-generated - DO NOT EDIT
|
|
45
49
|
/* eslint-disable */
|
|
46
50
|
/**
|
|
47
51
|
* Current build version from package.json
|
|
48
52
|
*/
|
|
49
|
-
const BUILD_VERSION = "0.
|
|
53
|
+
const BUILD_VERSION = "0.21.0";
|
|
50
54
|
|
|
51
55
|
const isInteractive = (config) => {
|
|
52
56
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -1178,6 +1182,8 @@ const schema$1 = {
|
|
|
1178
1182
|
"text-davinci-edit-001",
|
|
1179
1183
|
"code-davinci-edit-001",
|
|
1180
1184
|
"text-embedding-ada-002",
|
|
1185
|
+
"text-embedding-3-small",
|
|
1186
|
+
"text-embedding-3-large",
|
|
1181
1187
|
"text-similarity-davinci-001",
|
|
1182
1188
|
"text-similarity-curie-001",
|
|
1183
1189
|
"text-similarity-babbage-001",
|
|
@@ -1213,10 +1219,54 @@ const schema$1 = {
|
|
|
1213
1219
|
"gpt-4-vision-preview",
|
|
1214
1220
|
"gpt-4o",
|
|
1215
1221
|
"gpt-4o-2024-05-13",
|
|
1222
|
+
"gpt-4o-2024-08-06",
|
|
1223
|
+
"gpt-4o-2024-11-20",
|
|
1224
|
+
"gpt-4o-mini-2024-07-18",
|
|
1216
1225
|
"gpt-4o-mini",
|
|
1226
|
+
"gpt-4o-search-preview",
|
|
1227
|
+
"gpt-4o-search-preview-2025-03-11",
|
|
1228
|
+
"gpt-4o-mini-search-preview",
|
|
1229
|
+
"gpt-4o-mini-search-preview-2025-03-11",
|
|
1230
|
+
"gpt-4o-audio-preview",
|
|
1231
|
+
"gpt-4o-audio-preview-2024-12-17",
|
|
1232
|
+
"gpt-4o-audio-preview-2024-10-01",
|
|
1233
|
+
"gpt-4o-mini-audio-preview",
|
|
1234
|
+
"gpt-4o-mini-audio-preview-2024-12-17",
|
|
1235
|
+
"o1",
|
|
1236
|
+
"o1-2024-12-17",
|
|
1237
|
+
"o1-mini",
|
|
1238
|
+
"o1-mini-2024-09-12",
|
|
1239
|
+
"o1-preview",
|
|
1240
|
+
"o1-preview-2024-09-12",
|
|
1241
|
+
"o1-pro",
|
|
1242
|
+
"o1-pro-2025-03-19",
|
|
1243
|
+
"o3",
|
|
1244
|
+
"o3-2025-04-16",
|
|
1245
|
+
"o3-mini",
|
|
1246
|
+
"o3-mini-2025-01-31",
|
|
1247
|
+
"o4-mini",
|
|
1248
|
+
"o4-mini-2025-04-16",
|
|
1249
|
+
"chatgpt-4o-latest",
|
|
1250
|
+
"gpt-4o-realtime",
|
|
1251
|
+
"gpt-4o-realtime-preview-2024-10-01",
|
|
1252
|
+
"gpt-4o-realtime-preview-2024-12-17",
|
|
1253
|
+
"gpt-4o-mini-realtime-preview",
|
|
1254
|
+
"gpt-4o-mini-realtime-preview-2024-12-17",
|
|
1217
1255
|
"gpt-4.1",
|
|
1256
|
+
"gpt-4.1-2025-04-14",
|
|
1218
1257
|
"gpt-4.1-mini",
|
|
1219
|
-
"gpt-4.1-
|
|
1258
|
+
"gpt-4.1-mini-2025-04-14",
|
|
1259
|
+
"gpt-4.1-nano",
|
|
1260
|
+
"gpt-4.1-nano-2025-04-14",
|
|
1261
|
+
"gpt-4.5-preview",
|
|
1262
|
+
"gpt-4.5-preview-2025-02-27",
|
|
1263
|
+
"gpt-5",
|
|
1264
|
+
"gpt-5-2025-08-07",
|
|
1265
|
+
"gpt-5-nano",
|
|
1266
|
+
"gpt-5-nano-2025-08-07",
|
|
1267
|
+
"gpt-5-mini",
|
|
1268
|
+
"gpt-5-mini-2025-08-07",
|
|
1269
|
+
"gpt-5-chat-latest"
|
|
1220
1270
|
]
|
|
1221
1271
|
},
|
|
1222
1272
|
"OllamaModel": {
|
|
@@ -1478,6 +1528,13 @@ const schema$1 = {
|
|
|
1478
1528
|
"ChatModel": {
|
|
1479
1529
|
"type": "string",
|
|
1480
1530
|
"enum": [
|
|
1531
|
+
"gpt-5",
|
|
1532
|
+
"gpt-5-mini",
|
|
1533
|
+
"gpt-5-nano",
|
|
1534
|
+
"gpt-5-2025-08-07",
|
|
1535
|
+
"gpt-5-mini-2025-08-07",
|
|
1536
|
+
"gpt-5-nano-2025-08-07",
|
|
1537
|
+
"gpt-5-chat-latest",
|
|
1481
1538
|
"gpt-4.1",
|
|
1482
1539
|
"gpt-4.1-mini",
|
|
1483
1540
|
"gpt-4.1-nano",
|
|
@@ -6201,8 +6258,6 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
6201
6258
|
throw new LangChainExecutionError('getPrompt: Unexpected execution path - neither template nor fallback available', { template, fallback, variables });
|
|
6202
6259
|
}
|
|
6203
6260
|
|
|
6204
|
-
new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
|
|
6205
|
-
|
|
6206
6261
|
/**
|
|
6207
6262
|
* Base interface that all chains must implement.
|
|
6208
6263
|
*/
|
|
@@ -6305,7 +6360,7 @@ class BaseChain extends BaseLangChain {
|
|
|
6305
6360
|
async run(
|
|
6306
6361
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6307
6362
|
input, config) {
|
|
6308
|
-
const inputKeys = this.inputKeys.filter((k) => !this.memory?.memoryKeys.includes(k)
|
|
6363
|
+
const inputKeys = this.inputKeys.filter((k) => !this.memory?.memoryKeys.includes(k));
|
|
6309
6364
|
const isKeylessInput = inputKeys.length <= 1;
|
|
6310
6365
|
if (!isKeylessInput) {
|
|
6311
6366
|
throw new Error(`Chain ${this._chainType()} expects multiple inputs, cannot use 'run' `);
|
|
@@ -6476,7 +6531,7 @@ function _getLanguageModel(llmLike) {
|
|
|
6476
6531
|
* import { ChatOpenAI } from "@langchain/openai";
|
|
6477
6532
|
*
|
|
6478
6533
|
* const prompt = ChatPromptTemplate.fromTemplate("Tell me a {adjective} joke");
|
|
6479
|
-
* const llm = new ChatOpenAI();
|
|
6534
|
+
* const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
|
|
6480
6535
|
* const chain = prompt.pipe(llm);
|
|
6481
6536
|
*
|
|
6482
6537
|
* const response = await chain.invoke({ adjective: "funny" });
|
|
@@ -7092,6 +7147,8 @@ Please follow the guidelines below when writing your commit message:
|
|
|
7092
7147
|
|
|
7093
7148
|
{{branch_name_context}}
|
|
7094
7149
|
|
|
7150
|
+
{{commitlint_rules_context}}
|
|
7151
|
+
|
|
7095
7152
|
{{format_instructions}}
|
|
7096
7153
|
|
|
7097
7154
|
{{commit_history}}
|
|
@@ -7099,7 +7156,7 @@ Please follow the guidelines below when writing your commit message:
|
|
|
7099
7156
|
{{additional_context}}
|
|
7100
7157
|
`;
|
|
7101
7158
|
// Define the variables that will be passed to the prompt template
|
|
7102
|
-
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history', 'branch_name_context'];
|
|
7159
|
+
const inputVariables$3 = ['summary', 'format_instructions', 'additional_context', 'commit_history', 'branch_name_context', 'commitlint_rules_context'];
|
|
7103
7160
|
const COMMIT_PROMPT = new PromptTemplate({
|
|
7104
7161
|
template: template$4,
|
|
7105
7162
|
inputVariables: inputVariables$3,
|
|
@@ -7141,23 +7198,20 @@ Based on the following diff summary, generate a conventional commit message that
|
|
|
7141
7198
|
|
|
7142
7199
|
{{branch_name_context}}
|
|
7143
7200
|
|
|
7201
|
+
{{commitlint_rules_context}}
|
|
7202
|
+
|
|
7144
7203
|
{{format_instructions}}
|
|
7145
7204
|
|
|
7146
7205
|
{{commit_history}}
|
|
7147
7206
|
|
|
7148
|
-
{{additional_context}}
|
|
7149
|
-
|
|
7150
|
-
Remember:
|
|
7151
|
-
- Be concise and precise
|
|
7152
|
-
- Focus on WHAT and WHY, not HOW
|
|
7153
|
-
- Use imperative mood in both title and body
|
|
7154
|
-
- Ensure the title alone provides enough context to understand the change`;
|
|
7207
|
+
{{additional_context}}`;
|
|
7155
7208
|
const conventionalInputVariables = [
|
|
7156
7209
|
'summary',
|
|
7157
7210
|
'additional_context',
|
|
7158
7211
|
'commit_history',
|
|
7159
7212
|
'format_instructions',
|
|
7160
7213
|
'branch_name_context',
|
|
7214
|
+
'commitlint_rules_context',
|
|
7161
7215
|
];
|
|
7162
7216
|
const CONVENTIONAL_COMMIT_PROMPT = new PromptTemplate({
|
|
7163
7217
|
template: CONVENTIONAL_TEMPLATE,
|
|
@@ -7366,7 +7420,9 @@ async function generateAndReviewLoop({ label, factory, parser, noResult, agent,
|
|
|
7366
7420
|
continue;
|
|
7367
7421
|
}
|
|
7368
7422
|
// Only edit the result in interactive mode if approved
|
|
7369
|
-
|
|
7423
|
+
// Use custom edit function if provided, otherwise use default editResult
|
|
7424
|
+
const editFunction = options.review?.customEditFunction || editResult;
|
|
7425
|
+
result = await editFunction(result, options);
|
|
7370
7426
|
}
|
|
7371
7427
|
else {
|
|
7372
7428
|
// In non-interactive mode, we return the result as is to be output to stdout by the caller.
|
|
@@ -7550,17 +7606,17 @@ var changelog = {
|
|
|
7550
7606
|
const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:/;
|
|
7551
7607
|
// Regular commit message schema with basic validation
|
|
7552
7608
|
const CommitMessageResponseSchema = objectType({
|
|
7553
|
-
title: stringType(),
|
|
7554
|
-
body: stringType(),
|
|
7609
|
+
title: stringType().describe("Title of the commit message"),
|
|
7610
|
+
body: stringType().describe("Body of the commit message"),
|
|
7555
7611
|
});
|
|
7556
7612
|
// Conventional commit message schema with strict formatting rules
|
|
7557
7613
|
const ConventionalCommitMessageResponseSchema = objectType({
|
|
7558
7614
|
title: stringType()
|
|
7559
7615
|
.max(50, "Title must be 50 characters or less")
|
|
7560
|
-
.refine((title) => conventionalTypeRegex.test(title), "Title must follow Conventional Commits format (e.g., 'feat: add new feature' or 'fix(scope): fix bug')"),
|
|
7561
|
-
body: stringType()
|
|
7616
|
+
.refine((title) => conventionalTypeRegex.test(title), "Title must follow Conventional Commits format (e.g., 'feat: add new feature' or 'fix(scope): fix bug')").describe("Title of the commit message"),
|
|
7617
|
+
body: stringType().describe("Body of the commit message")
|
|
7562
7618
|
// .max(280, "Body must be 280 characters or less"),
|
|
7563
|
-
});
|
|
7619
|
+
}).describe("Conventional commit message schema with strict formatting rules");
|
|
7564
7620
|
const command$3 = 'commit';
|
|
7565
7621
|
/**
|
|
7566
7622
|
* Command line options via yargs
|
|
@@ -8219,12 +8275,12 @@ function formatSet(input) {
|
|
|
8219
8275
|
* const overallChain = new SequentialChain({
|
|
8220
8276
|
* chains: [
|
|
8221
8277
|
* new LLMChain({
|
|
8222
|
-
* llm: new ChatOpenAI({ temperature: 0 }),
|
|
8278
|
+
* llm: new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }),
|
|
8223
8279
|
* prompt: promptTemplate,
|
|
8224
8280
|
* outputKey: "synopsis",
|
|
8225
8281
|
* }),
|
|
8226
8282
|
* new LLMChain({
|
|
8227
|
-
* llm: new OpenAI({ temperature: 0 }),
|
|
8283
|
+
* llm: new OpenAI({ model: "gpt-4o-mini", temperature: 0 }),
|
|
8228
8284
|
* prompt: reviewPromptTemplate,
|
|
8229
8285
|
* outputKey: "review",
|
|
8230
8286
|
* }),
|
|
@@ -8461,7 +8517,8 @@ class SimpleSequentialChain extends BaseChain {
|
|
|
8461
8517
|
/** @ignore */
|
|
8462
8518
|
_validateChains() {
|
|
8463
8519
|
for (const chain of this.chains) {
|
|
8464
|
-
if (chain.inputKeys.filter((k) => !chain.memory?.memoryKeys.includes(k)
|
|
8520
|
+
if (chain.inputKeys.filter((k) => !chain.memory?.memoryKeys.includes(k))
|
|
8521
|
+
.length !== 1) {
|
|
8465
8522
|
throw new Error(`Chains used in SimpleSequentialChain should all have one input, got ${chain.inputKeys.length} for ${chain._chainType()}.`);
|
|
8466
8523
|
}
|
|
8467
8524
|
if (chain.outputKeys.length !== 1) {
|
|
@@ -11015,6 +11072,75 @@ const getTokenCounter = async (modelName) => {
|
|
|
11015
11072
|
});
|
|
11016
11073
|
};
|
|
11017
11074
|
|
|
11075
|
+
const COMMITLINT_CONFIG_FILES = [
|
|
11076
|
+
'.commitlintrc',
|
|
11077
|
+
'.commitlintrc.json',
|
|
11078
|
+
'.commitlintrc.yaml',
|
|
11079
|
+
'.commitlintrc.yml',
|
|
11080
|
+
'.commitlintrc.js',
|
|
11081
|
+
'.commitlintrc.cjs',
|
|
11082
|
+
'commitlint.config.js',
|
|
11083
|
+
'commitlint.config.cjs',
|
|
11084
|
+
];
|
|
11085
|
+
|
|
11086
|
+
/**
|
|
11087
|
+
* Finds the project root directory starting from the given current directory.
|
|
11088
|
+
* It checks if the `.git` directory or `package.json` file exists in the current directory or any of its parent directories.
|
|
11089
|
+
* If found, it returns the path to the project root directory.
|
|
11090
|
+
* If not found, it throws an error.
|
|
11091
|
+
*
|
|
11092
|
+
* @param currentDir - The current directory to start searching from.
|
|
11093
|
+
* @returns The path to the project root directory.
|
|
11094
|
+
* @throws Error if the project root directory cannot be found.
|
|
11095
|
+
*/
|
|
11096
|
+
function findProjectRoot(currentDir) {
|
|
11097
|
+
const root = path__default.parse(currentDir).root;
|
|
11098
|
+
while (currentDir !== root) {
|
|
11099
|
+
if (fs__default.existsSync(path__default.join(currentDir, '.git')) ||
|
|
11100
|
+
fs__default.existsSync(path__default.join(currentDir, 'package.json'))) {
|
|
11101
|
+
return currentDir;
|
|
11102
|
+
}
|
|
11103
|
+
currentDir = path__default.dirname(currentDir);
|
|
11104
|
+
}
|
|
11105
|
+
throw new Error('Unable to find project root. Are you in the right directory?');
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
/**
|
|
11109
|
+
* Check if a commitlint configuration exists in the project root.
|
|
11110
|
+
*/
|
|
11111
|
+
async function hasCommitlintConfig() {
|
|
11112
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
11113
|
+
if (!projectRoot) {
|
|
11114
|
+
return false;
|
|
11115
|
+
}
|
|
11116
|
+
// Check for dedicated commitlint config files
|
|
11117
|
+
for (const file of COMMITLINT_CONFIG_FILES) {
|
|
11118
|
+
if (existsSync(join(projectRoot, file))) {
|
|
11119
|
+
return true;
|
|
11120
|
+
}
|
|
11121
|
+
}
|
|
11122
|
+
// Check for commitlint config in package.json
|
|
11123
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
11124
|
+
if (existsSync(pkgPath)) {
|
|
11125
|
+
try {
|
|
11126
|
+
const pkgContent = readFileSync(pkgPath, 'utf8');
|
|
11127
|
+
const pkg = JSON.parse(pkgContent);
|
|
11128
|
+
if (pkg.commitlint) {
|
|
11129
|
+
return true;
|
|
11130
|
+
}
|
|
11131
|
+
}
|
|
11132
|
+
catch (error) {
|
|
11133
|
+
// Ignore errors reading or parsing package.json
|
|
11134
|
+
}
|
|
11135
|
+
}
|
|
11136
|
+
return false;
|
|
11137
|
+
}
|
|
11138
|
+
|
|
11139
|
+
var hasCommitlintConfig$1 = /*#__PURE__*/Object.freeze({
|
|
11140
|
+
__proto__: null,
|
|
11141
|
+
hasCommitlintConfig: hasCommitlintConfig
|
|
11142
|
+
});
|
|
11143
|
+
|
|
11018
11144
|
async function noResult$2({ git, logger }) {
|
|
11019
11145
|
const { staged, unstaged, untracked } = await getChanges({ git });
|
|
11020
11146
|
const hasStaged = staged && staged.length > 0;
|
|
@@ -11069,12 +11195,18 @@ const handler$3 = async (argv, logger) => {
|
|
|
11069
11195
|
color: 'yellow',
|
|
11070
11196
|
});
|
|
11071
11197
|
}
|
|
11198
|
+
logger.verbose(`→ ${provider} (${model})`, {
|
|
11199
|
+
color: 'green',
|
|
11200
|
+
});
|
|
11201
|
+
const USE_CONVENTIONAL_COMMITS = config.conventionalCommits || argv.conventional;
|
|
11072
11202
|
async function factory() {
|
|
11073
11203
|
if (config.noDiff) {
|
|
11074
11204
|
const status = await git.status();
|
|
11075
|
-
return status.files.map(file => ({
|
|
11205
|
+
return status.files.map((file) => ({
|
|
11076
11206
|
filePath: file.path,
|
|
11077
|
-
status: (file.index === 'A' || file.index === '?'
|
|
11207
|
+
status: (file.index === 'A' || file.index === '?'
|
|
11208
|
+
? 'added'
|
|
11209
|
+
: 'modified'),
|
|
11078
11210
|
summary: file.path, // Simplified summary for noDiff
|
|
11079
11211
|
}));
|
|
11080
11212
|
}
|
|
@@ -11096,12 +11228,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
11096
11228
|
options: { tokenizer, git, llm, logger },
|
|
11097
11229
|
});
|
|
11098
11230
|
}
|
|
11231
|
+
logger.log(`Generating commit message...${JSON.stringify(config.prompt)}`, { color: 'blue' });
|
|
11099
11232
|
const commitMsg = await generateAndReviewLoop({
|
|
11100
11233
|
label: 'commit message',
|
|
11101
11234
|
options: {
|
|
11102
11235
|
...config,
|
|
11103
11236
|
prompt: config.prompt ||
|
|
11104
|
-
(
|
|
11237
|
+
(USE_CONVENTIONAL_COMMITS
|
|
11105
11238
|
? CONVENTIONAL_COMMIT_PROMPT.template
|
|
11106
11239
|
: COMMIT_PROMPT.template),
|
|
11107
11240
|
logger,
|
|
@@ -11114,21 +11247,23 @@ const handler$3 = async (argv, logger) => {
|
|
|
11114
11247
|
retryMessageOnly: 'Restart the function execution from generating the commit message',
|
|
11115
11248
|
retryFull: 'Restart the function execution from the beginning, regenerating both the diff summary and commit message',
|
|
11116
11249
|
},
|
|
11250
|
+
customEditFunction: async (message, options) => {
|
|
11251
|
+
const { editCommitMessage } = await Promise.resolve().then(function () { return editCommitMessage$1; });
|
|
11252
|
+
return editCommitMessage(message, options);
|
|
11253
|
+
},
|
|
11117
11254
|
},
|
|
11118
11255
|
},
|
|
11119
11256
|
factory,
|
|
11120
11257
|
parser,
|
|
11121
11258
|
agent: async (context, options) => {
|
|
11122
|
-
// Check if conventional commits are enabled via config or CLI flag
|
|
11123
|
-
const useConventional = config.conventionalCommits || argv.conventional;
|
|
11124
11259
|
// Select the appropriate schema based on whether conventional commits are enabled
|
|
11125
|
-
const schema =
|
|
11260
|
+
const schema = USE_CONVENTIONAL_COMMITS
|
|
11126
11261
|
? ConventionalCommitMessageResponseSchema
|
|
11127
11262
|
: CommitMessageResponseSchema;
|
|
11128
11263
|
const formatInstructions = `You must always return valid JSON fenced by a markdown code block. Do not return any additional text. The JSON object you return should match the following schema:
|
|
11129
|
-
{
|
|
11264
|
+
${schema.description}`;
|
|
11130
11265
|
// Use conventional commit prompt if enabled
|
|
11131
|
-
const promptTemplate =
|
|
11266
|
+
const promptTemplate = USE_CONVENTIONAL_COMMITS ? CONVENTIONAL_COMMIT_PROMPT : COMMIT_PROMPT;
|
|
11132
11267
|
const prompt = getPrompt({
|
|
11133
11268
|
template: options.prompt,
|
|
11134
11269
|
variables: promptTemplate.inputVariables,
|
|
@@ -11158,6 +11293,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
11158
11293
|
: config.includeBranchName !== false; // Default to true if not explicitly set to false
|
|
11159
11294
|
// Create branch name context string based on the configuration
|
|
11160
11295
|
const branchNameContext = includeBranchName ? `Current git branch name: ${branchName}` : '';
|
|
11296
|
+
// Load commitlint rules context if available
|
|
11297
|
+
const hasCommitLintConfig = await hasCommitlintConfig();
|
|
11298
|
+
let commitlint_rules_context = '';
|
|
11299
|
+
if (USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) {
|
|
11300
|
+
const { getCommitlintRulesContext } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
11301
|
+
commitlint_rules_context = await getCommitlintRulesContext();
|
|
11302
|
+
}
|
|
11161
11303
|
// Get variables for the prompt
|
|
11162
11304
|
const variables = {
|
|
11163
11305
|
summary: context,
|
|
@@ -11165,59 +11307,106 @@ const handler$3 = async (argv, logger) => {
|
|
|
11165
11307
|
additional_context: additional_context,
|
|
11166
11308
|
commit_history: commit_history,
|
|
11167
11309
|
branch_name_context: branchNameContext,
|
|
11310
|
+
commitlint_rules_context: commitlint_rules_context,
|
|
11168
11311
|
};
|
|
11169
11312
|
const maxAttempts = config.service.provider === 'ollama' && 'maxParsingAttempts' in config.service
|
|
11170
11313
|
? config.service.maxParsingAttempts || 3
|
|
11171
11314
|
: 3;
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11315
|
+
// Custom retry logic for commitlint validation
|
|
11316
|
+
let retryCount = 0;
|
|
11317
|
+
let validationErrors = '';
|
|
11318
|
+
const generateCommitMessage = async () => {
|
|
11319
|
+
// Update variables with validation errors for retry attempts
|
|
11320
|
+
const currentVariables = {
|
|
11321
|
+
...variables,
|
|
11322
|
+
additional_context: validationErrors
|
|
11323
|
+
? `${variables.additional_context}\n\n## Validation Errors from Previous Attempt\nPlease fix the following issues:\n${validationErrors}`
|
|
11324
|
+
: variables.additional_context,
|
|
11325
|
+
};
|
|
11326
|
+
const commitMsg = await executeChainWithSchema(schema, llm, prompt, currentVariables, {
|
|
11327
|
+
retryOptions: {
|
|
11328
|
+
maxAttempts,
|
|
11329
|
+
onRetry: (attempt, error) => {
|
|
11330
|
+
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
11331
|
+
},
|
|
11177
11332
|
},
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11333
|
+
fallbackParser: (text) => ({
|
|
11334
|
+
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
11335
|
+
body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
|
|
11336
|
+
}),
|
|
11337
|
+
onFallback: () => {
|
|
11338
|
+
logger.verbose('Max retry attempts reached. Falling back to simple text output.', {
|
|
11339
|
+
color: 'red',
|
|
11340
|
+
});
|
|
11341
|
+
},
|
|
11342
|
+
});
|
|
11343
|
+
// Construct the full commit message
|
|
11344
|
+
const appendedText = argv.append ? `\n\n${argv.append}` : '';
|
|
11345
|
+
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
11346
|
+
const ticketFooter = argv.appendTicket && ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
11347
|
+
const fullMessage = `${commitMsg.title}\n\n${commitMsg.body}${appendedText}${ticketFooter}`;
|
|
11348
|
+
// If commitlint validation is needed, validate the message
|
|
11349
|
+
if (USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) {
|
|
11350
|
+
const { validateCommitMessage, CommitlintValidationError } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
11351
|
+
const validationResult = await validateCommitMessage(fullMessage);
|
|
11352
|
+
logger.verbose(`Validation result: ${JSON.stringify(validationResult)}`, {
|
|
11353
|
+
color: 'yellow',
|
|
11186
11354
|
});
|
|
11355
|
+
if (!validationResult.valid) {
|
|
11356
|
+
retryCount++;
|
|
11357
|
+
// Format validation errors for next attempt
|
|
11358
|
+
validationErrors = validationResult.errors.map((error) => `- ${error}`).join('\n');
|
|
11359
|
+
// Auto-retry up to 2 times
|
|
11360
|
+
if (retryCount <= 2) {
|
|
11361
|
+
logger.verbose(`Commit message validation failed (attempt ${retryCount}/2). Retrying with error feedback...`, { color: 'yellow' });
|
|
11362
|
+
throw new CommitlintValidationError(`Validation failed: ${validationResult.errors.join('; ')}`, validationResult, fullMessage);
|
|
11363
|
+
}
|
|
11364
|
+
// After 2 failed attempts, let the user decide
|
|
11365
|
+
const { handleValidationErrors } = await Promise.resolve().then(function () { return commitValidationHandler; });
|
|
11366
|
+
const validationHandlerResult = await handleValidationErrors(fullMessage, validationResult, {
|
|
11367
|
+
logger,
|
|
11368
|
+
interactive: INTERACTIVE,
|
|
11369
|
+
openInEditor: config.openInEditor,
|
|
11370
|
+
});
|
|
11371
|
+
logger.verbose(`Validation handler result: ${JSON.stringify(validationHandlerResult)}`, {
|
|
11372
|
+
color: 'blue',
|
|
11373
|
+
});
|
|
11374
|
+
switch (validationHandlerResult.action) {
|
|
11375
|
+
case 'proceed':
|
|
11376
|
+
return validationHandlerResult.message;
|
|
11377
|
+
case 'edit':
|
|
11378
|
+
return validationHandlerResult.message;
|
|
11379
|
+
case 'regenerate':
|
|
11380
|
+
// Reset retry count and validation errors for fresh attempts
|
|
11381
|
+
retryCount = 0;
|
|
11382
|
+
validationErrors = '';
|
|
11383
|
+
throw new CommitlintValidationError('User requested regeneration', validationResult, fullMessage);
|
|
11384
|
+
case 'abort':
|
|
11385
|
+
logger.log('\nAborting commit due to validation errors.', { color: 'red' });
|
|
11386
|
+
process.exit(1);
|
|
11387
|
+
}
|
|
11388
|
+
}
|
|
11389
|
+
}
|
|
11390
|
+
return fullMessage;
|
|
11391
|
+
};
|
|
11392
|
+
// Custom shouldRetry function for commitlint errors
|
|
11393
|
+
const shouldRetryCommitlint = (error) => {
|
|
11394
|
+
return error.name === 'CommitlintValidationError';
|
|
11395
|
+
};
|
|
11396
|
+
// Use retry wrapper for commitlint validation with up to 4 total attempts
|
|
11397
|
+
// (2 automatic retries + 2 more if user chooses "Try again")
|
|
11398
|
+
return await withRetry(generateCommitMessage, {
|
|
11399
|
+
maxAttempts: 6, // Allow for multiple user retry requests
|
|
11400
|
+
shouldRetry: shouldRetryCommitlint,
|
|
11401
|
+
backoffMs: 0, // No delay needed for commitlint retries
|
|
11402
|
+
onRetry: (attempt, error) => {
|
|
11403
|
+
if (error.name === 'CommitlintValidationError' && attempt <= 2) {
|
|
11404
|
+
// Don't log for auto-retries, we already log in the function
|
|
11405
|
+
return;
|
|
11406
|
+
}
|
|
11407
|
+
logger.verbose(`Retrying commit message generation (attempt ${attempt}): ${error.message}`, { color: 'yellow' });
|
|
11187
11408
|
},
|
|
11188
11409
|
});
|
|
11189
|
-
// Construct the full commit message
|
|
11190
|
-
const appendedText = argv.append ? `\n\n${argv.append}` : '';
|
|
11191
|
-
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
11192
|
-
const ticketFooter = argv.appendTicket && ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
11193
|
-
const fullMessage = `${commitMsg.title}\n\n${commitMsg.body}${appendedText}${ticketFooter}`;
|
|
11194
|
-
// If conventional commits are enabled, validate with commitlint
|
|
11195
|
-
if (useConventional) {
|
|
11196
|
-
const { validateCommitMessage } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
11197
|
-
const { handleValidationErrors } = await Promise.resolve().then(function () { return commitValidationHandler; });
|
|
11198
|
-
const validationResult = await validateCommitMessage(fullMessage);
|
|
11199
|
-
const validationHandlerResult = await handleValidationErrors(fullMessage, validationResult, {
|
|
11200
|
-
logger,
|
|
11201
|
-
interactive: INTERACTIVE,
|
|
11202
|
-
openInEditor: config.openInEditor,
|
|
11203
|
-
});
|
|
11204
|
-
switch (validationHandlerResult.action) {
|
|
11205
|
-
case 'proceed':
|
|
11206
|
-
// Validation passed, use the message as is
|
|
11207
|
-
return validationHandlerResult.message;
|
|
11208
|
-
case 'edit':
|
|
11209
|
-
// User edited the message, use the edited version
|
|
11210
|
-
return validationHandlerResult.message;
|
|
11211
|
-
case 'regenerate':
|
|
11212
|
-
// User wants to regenerate, throw special error to trigger regeneration
|
|
11213
|
-
throw new Error('REGENERATE_COMMIT_MESSAGE');
|
|
11214
|
-
case 'abort':
|
|
11215
|
-
// User wants to abort or validation failed in non-interactive mode
|
|
11216
|
-
logger.log('\nAborting commit due to validation errors.', { color: 'red' });
|
|
11217
|
-
process.exit(1);
|
|
11218
|
-
}
|
|
11219
|
-
}
|
|
11220
|
-
return fullMessage;
|
|
11221
11410
|
},
|
|
11222
11411
|
noResult: async () => {
|
|
11223
11412
|
await noResult$2({ git, logger });
|
|
@@ -11258,28 +11447,6 @@ const builder$2 = (yargs) => {
|
|
|
11258
11447
|
return yargs.options(options$2).usage(getCommandUsageHeader(command$2));
|
|
11259
11448
|
};
|
|
11260
11449
|
|
|
11261
|
-
/**
|
|
11262
|
-
* Finds the project root directory starting from the given current directory.
|
|
11263
|
-
* It checks if the `.git` directory or `package.json` file exists in the current directory or any of its parent directories.
|
|
11264
|
-
* If found, it returns the path to the project root directory.
|
|
11265
|
-
* If not found, it throws an error.
|
|
11266
|
-
*
|
|
11267
|
-
* @param currentDir - The current directory to start searching from.
|
|
11268
|
-
* @returns The path to the project root directory.
|
|
11269
|
-
* @throws Error if the project root directory cannot be found.
|
|
11270
|
-
*/
|
|
11271
|
-
function findProjectRoot(currentDir) {
|
|
11272
|
-
const root = path__default.parse(currentDir).root;
|
|
11273
|
-
while (currentDir !== root) {
|
|
11274
|
-
if (fs__default.existsSync(path__default.join(currentDir, '.git')) ||
|
|
11275
|
-
fs__default.existsSync(path__default.join(currentDir, 'package.json'))) {
|
|
11276
|
-
return currentDir;
|
|
11277
|
-
}
|
|
11278
|
-
currentDir = path__default.dirname(currentDir);
|
|
11279
|
-
}
|
|
11280
|
-
throw new Error('Unable to find project root. Are you in the right directory?');
|
|
11281
|
-
}
|
|
11282
|
-
|
|
11283
11450
|
/**
|
|
11284
11451
|
* Executes a command as a Promise and returns the result.
|
|
11285
11452
|
*
|
|
@@ -13199,11 +13366,13 @@ function isObservable(obj) {
|
|
|
13199
13366
|
return !!obj && (obj instanceof Observable || (isFunction(obj.lift) && isFunction(obj.subscribe)));
|
|
13200
13367
|
}
|
|
13201
13368
|
|
|
13202
|
-
var EmptyError = createErrorClass(function (_super) {
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13369
|
+
var EmptyError = createErrorClass(function (_super) {
|
|
13370
|
+
return function EmptyErrorImpl() {
|
|
13371
|
+
_super(this);
|
|
13372
|
+
this.name = 'EmptyError';
|
|
13373
|
+
this.message = 'no elements in sequence';
|
|
13374
|
+
};
|
|
13375
|
+
});
|
|
13207
13376
|
|
|
13208
13377
|
function lastValueFrom(source, config) {
|
|
13209
13378
|
var hasConfig = typeof config === 'object';
|
|
@@ -14490,23 +14659,183 @@ y.command(review.command, review.desc, review.builder, review.handler);
|
|
|
14490
14659
|
y.command(init.command, init.desc, init.builder, init.handler);
|
|
14491
14660
|
y.help().parse(process.argv.slice(2));
|
|
14492
14661
|
|
|
14662
|
+
/**
|
|
14663
|
+
* Edit a commit message with commitlint validation if config exists
|
|
14664
|
+
*/
|
|
14665
|
+
async function editCommitMessage(message, options) {
|
|
14666
|
+
// First, let the user edit the message
|
|
14667
|
+
const editedMessage = await editResult(message, options);
|
|
14668
|
+
// Then validate it against commitlint if config exists
|
|
14669
|
+
const { hasCommitlintConfig } = await Promise.resolve().then(function () { return hasCommitlintConfig$1; });
|
|
14670
|
+
const hasConfig = await hasCommitlintConfig();
|
|
14671
|
+
if (hasConfig) {
|
|
14672
|
+
const { validateCommitMessage } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
14673
|
+
const { handleValidationErrors } = await Promise.resolve().then(function () { return commitValidationHandler; });
|
|
14674
|
+
const validationResult = await validateCommitMessage(editedMessage);
|
|
14675
|
+
if (!validationResult.valid) {
|
|
14676
|
+
// Show validation errors and get user action
|
|
14677
|
+
const validationHandlerResult = await handleValidationErrors(editedMessage, validationResult, {
|
|
14678
|
+
logger: options.logger,
|
|
14679
|
+
interactive: options.interactive,
|
|
14680
|
+
openInEditor: options.openInEditor,
|
|
14681
|
+
});
|
|
14682
|
+
// Return the result from the validation handler
|
|
14683
|
+
return validationHandlerResult.message;
|
|
14684
|
+
}
|
|
14685
|
+
}
|
|
14686
|
+
return editedMessage;
|
|
14687
|
+
}
|
|
14688
|
+
|
|
14689
|
+
var editCommitMessage$1 = /*#__PURE__*/Object.freeze({
|
|
14690
|
+
__proto__: null,
|
|
14691
|
+
editCommitMessage: editCommitMessage
|
|
14692
|
+
});
|
|
14693
|
+
|
|
14694
|
+
/**
|
|
14695
|
+
* Custom error for commitlint validation failures
|
|
14696
|
+
* This allows the retry system to identify these errors specifically
|
|
14697
|
+
*/
|
|
14698
|
+
class CommitlintValidationError extends Error {
|
|
14699
|
+
constructor(message, validationResult, commitMessage) {
|
|
14700
|
+
super(message);
|
|
14701
|
+
this.name = 'CommitlintValidationError';
|
|
14702
|
+
this.validationResult = validationResult;
|
|
14703
|
+
this.commitMessage = commitMessage;
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14493
14706
|
/**
|
|
14494
14707
|
* Load commitlint configuration
|
|
14495
14708
|
*/
|
|
14496
14709
|
async function loadCommitlintConfig() {
|
|
14497
|
-
|
|
14498
|
-
const
|
|
14499
|
-
|
|
14710
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
14711
|
+
const cwd = projectRoot || process.cwd();
|
|
14712
|
+
// @commitlint/load has issues with ESM configs (e.g. commitlint.config.js with `export default`).
|
|
14713
|
+
// Let's try to load them manually first.
|
|
14714
|
+
const esmConfigCandidates = COMMITLINT_CONFIG_FILES.filter((file) => file.endsWith('.js'));
|
|
14715
|
+
for (const configFile of esmConfigCandidates) {
|
|
14716
|
+
const configPath = join(cwd, configFile);
|
|
14717
|
+
if (existsSync(configPath)) {
|
|
14718
|
+
try {
|
|
14719
|
+
const module = await import(pathToFileURL(configPath).href);
|
|
14720
|
+
if (module.default &&
|
|
14721
|
+
(Object.keys(module.default.rules || {}).length > 0 ||
|
|
14722
|
+
(module.default.extends && module.default.extends.length > 0))) {
|
|
14723
|
+
// We found a config, now let commitlint process it (for extends etc)
|
|
14724
|
+
return await load(module.default, { cwd });
|
|
14725
|
+
}
|
|
14726
|
+
}
|
|
14727
|
+
catch (error) {
|
|
14728
|
+
// Failed to import, maybe not an ESM file after all or syntax error.
|
|
14729
|
+
// We will let the standard load take a chance.
|
|
14730
|
+
}
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14500
14733
|
try {
|
|
14501
|
-
//
|
|
14502
|
-
const config = await load();
|
|
14503
|
-
|
|
14734
|
+
// Let @commitlint/load try to find the config. This works for CJS, JSON, and YAML.
|
|
14735
|
+
const config = await load({}, { cwd });
|
|
14736
|
+
// Check if a real config was loaded.
|
|
14737
|
+
if (config.extends.length > 0 || Object.keys(config.rules).length > 0) {
|
|
14738
|
+
return config;
|
|
14739
|
+
}
|
|
14504
14740
|
}
|
|
14505
|
-
catch {
|
|
14506
|
-
//
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14741
|
+
catch (error) {
|
|
14742
|
+
// Could be an error parsing, or just not found. Fall through to default.
|
|
14743
|
+
}
|
|
14744
|
+
// If nothing worked, fallback to conventional config
|
|
14745
|
+
return load({
|
|
14746
|
+
extends: ['@commitlint/config-conventional'],
|
|
14747
|
+
});
|
|
14748
|
+
}
|
|
14749
|
+
/**
|
|
14750
|
+
* Format commitlint rules into a human-readable string for AI prompts
|
|
14751
|
+
*/
|
|
14752
|
+
function formatCommitlintRulesForPrompt(config) {
|
|
14753
|
+
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
14754
|
+
return '';
|
|
14755
|
+
}
|
|
14756
|
+
const ruleDescriptions = [];
|
|
14757
|
+
// Add information about extends if present
|
|
14758
|
+
if (config.extends && config.extends.length > 0) {
|
|
14759
|
+
ruleDescriptions.push(`Following ${config.extends.join(', ')} configuration`);
|
|
14760
|
+
}
|
|
14761
|
+
// Process key rules that affect commit message format
|
|
14762
|
+
const rules = config.rules;
|
|
14763
|
+
// Header length rules
|
|
14764
|
+
if (rules['header-max-length']) {
|
|
14765
|
+
const [level, , maxLength] = rules['header-max-length'];
|
|
14766
|
+
if (level > 0) {
|
|
14767
|
+
ruleDescriptions.push(`Header (title) must be ${maxLength} characters or less (including spaces)`);
|
|
14768
|
+
}
|
|
14769
|
+
}
|
|
14770
|
+
if (rules['header-min-length']) {
|
|
14771
|
+
const [level, , minLength] = rules['header-min-length'];
|
|
14772
|
+
if (level > 0) {
|
|
14773
|
+
ruleDescriptions.push(`Header (title) must be at least ${minLength} characters (including spaces)`);
|
|
14774
|
+
}
|
|
14775
|
+
}
|
|
14776
|
+
// Body length rules
|
|
14777
|
+
if (rules['body-max-line-length']) {
|
|
14778
|
+
const [level, , maxLength] = rules['body-max-line-length'];
|
|
14779
|
+
if (level > 0) {
|
|
14780
|
+
ruleDescriptions.push(`Body lines must be ${maxLength} characters or less (including spaces)`);
|
|
14781
|
+
}
|
|
14782
|
+
}
|
|
14783
|
+
// Type rules
|
|
14784
|
+
if (rules['type-enum']) {
|
|
14785
|
+
const [level, , allowedTypes] = rules['type-enum'];
|
|
14786
|
+
if (level > 0 && Array.isArray(allowedTypes)) {
|
|
14787
|
+
ruleDescriptions.push(`Allowed types: ${allowedTypes.join(', ')}`);
|
|
14788
|
+
}
|
|
14789
|
+
}
|
|
14790
|
+
// Case rules
|
|
14791
|
+
if (rules['type-case']) {
|
|
14792
|
+
const [level, , caseType] = rules['type-case'];
|
|
14793
|
+
if (level > 0) {
|
|
14794
|
+
ruleDescriptions.push(`Type must be ${caseType} case`);
|
|
14795
|
+
}
|
|
14796
|
+
}
|
|
14797
|
+
if (rules['subject-case']) {
|
|
14798
|
+
const [level, , caseType] = rules['subject-case'];
|
|
14799
|
+
if (level > 0) {
|
|
14800
|
+
ruleDescriptions.push(`Subject must be ${caseType} case`);
|
|
14801
|
+
}
|
|
14802
|
+
}
|
|
14803
|
+
// Scope rules
|
|
14804
|
+
if (rules['scope-enum']) {
|
|
14805
|
+
const [level, , allowedScopes] = rules['scope-enum'];
|
|
14806
|
+
if (level > 0 && Array.isArray(allowedScopes)) {
|
|
14807
|
+
ruleDescriptions.push(`Allowed scopes: ${allowedScopes.join(', ')}`);
|
|
14808
|
+
}
|
|
14809
|
+
}
|
|
14810
|
+
// Subject rules
|
|
14811
|
+
if (rules['subject-full-stop']) {
|
|
14812
|
+
const [level, condition] = rules['subject-full-stop'];
|
|
14813
|
+
if (level > 0) {
|
|
14814
|
+
const verb = condition === 'always' ? 'must' : 'must not';
|
|
14815
|
+
ruleDescriptions.push(`Subject ${verb} end with a period`);
|
|
14816
|
+
}
|
|
14817
|
+
}
|
|
14818
|
+
if (rules['subject-empty']) {
|
|
14819
|
+
const [level, condition] = rules['subject-empty'];
|
|
14820
|
+
if (level > 0) {
|
|
14821
|
+
const requirement = condition === 'never' ? 'must not be empty' : 'must be empty';
|
|
14822
|
+
ruleDescriptions.push(`Subject ${requirement}`);
|
|
14823
|
+
}
|
|
14824
|
+
}
|
|
14825
|
+
return ruleDescriptions.length > 0
|
|
14826
|
+
? `## Commitlint Rules\nYour commit message must follow these project-specific rules:\n${ruleDescriptions.map(rule => `- ${rule}`).join('\n')}\n`
|
|
14827
|
+
: '';
|
|
14828
|
+
}
|
|
14829
|
+
/**
|
|
14830
|
+
* Get commitlint rules context for prompt if config exists
|
|
14831
|
+
*/
|
|
14832
|
+
async function getCommitlintRulesContext() {
|
|
14833
|
+
try {
|
|
14834
|
+
const config = await loadCommitlintConfig();
|
|
14835
|
+
return formatCommitlintRulesForPrompt(config);
|
|
14836
|
+
}
|
|
14837
|
+
catch (error) {
|
|
14838
|
+
return '';
|
|
14510
14839
|
}
|
|
14511
14840
|
}
|
|
14512
14841
|
/**
|
|
@@ -14515,9 +14844,6 @@ async function loadCommitlintConfig() {
|
|
|
14515
14844
|
async function validateCommitMessage(message, options = {}) {
|
|
14516
14845
|
try {
|
|
14517
14846
|
const config = await loadCommitlintConfig();
|
|
14518
|
-
// Dynamically import commitlint lint function
|
|
14519
|
-
const commitlint = await import('@commitlint/core');
|
|
14520
|
-
const { lint } = commitlint;
|
|
14521
14847
|
const result = await lint(message, config.rules, options);
|
|
14522
14848
|
return {
|
|
14523
14849
|
valid: result.valid,
|
|
@@ -14536,6 +14862,9 @@ async function validateCommitMessage(message, options = {}) {
|
|
|
14536
14862
|
|
|
14537
14863
|
var commitlintValidator = /*#__PURE__*/Object.freeze({
|
|
14538
14864
|
__proto__: null,
|
|
14865
|
+
CommitlintValidationError: CommitlintValidationError,
|
|
14866
|
+
formatCommitlintRulesForPrompt: formatCommitlintRulesForPrompt,
|
|
14867
|
+
getCommitlintRulesContext: getCommitlintRulesContext,
|
|
14539
14868
|
loadCommitlintConfig: loadCommitlintConfig,
|
|
14540
14869
|
validateCommitMessage: validateCommitMessage
|
|
14541
14870
|
});
|
|
@@ -14572,29 +14901,45 @@ async function handleValidationErrors(message, validationResult, options) {
|
|
|
14572
14901
|
message: 'How would you like to proceed?:',
|
|
14573
14902
|
choices: [
|
|
14574
14903
|
{
|
|
14575
|
-
name: '
|
|
14576
|
-
value: '
|
|
14577
|
-
description: '
|
|
14904
|
+
name: 'Try 2 more attempts',
|
|
14905
|
+
value: 'retry',
|
|
14906
|
+
description: 'Let the AI try generating 2 more commit messages with error feedback',
|
|
14578
14907
|
},
|
|
14579
14908
|
{
|
|
14580
|
-
name: '
|
|
14581
|
-
value: '
|
|
14582
|
-
description: '
|
|
14909
|
+
name: 'Edit manually',
|
|
14910
|
+
value: 'edit',
|
|
14911
|
+
description: 'Edit the commit message manually to fix the issues',
|
|
14583
14912
|
},
|
|
14584
14913
|
{
|
|
14585
14914
|
name: 'Abort',
|
|
14586
14915
|
value: 'abort',
|
|
14587
|
-
description: 'Abort the commit',
|
|
14916
|
+
description: 'Abort the commit process',
|
|
14588
14917
|
},
|
|
14589
14918
|
],
|
|
14590
14919
|
});
|
|
14591
14920
|
switch (choice) {
|
|
14592
|
-
case '
|
|
14921
|
+
case 'edit': {
|
|
14593
14922
|
// Edit message manually
|
|
14594
14923
|
const editedMessage = await editResult(message, options);
|
|
14924
|
+
// Validate the manually edited message if commitlint config exists
|
|
14925
|
+
const { hasCommitlintConfig } = await Promise.resolve().then(function () { return hasCommitlintConfig$1; });
|
|
14926
|
+
const hasConfig = await hasCommitlintConfig();
|
|
14927
|
+
if (hasConfig) {
|
|
14928
|
+
const { validateCommitMessage } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
14929
|
+
const editedValidationResult = await validateCommitMessage(editedMessage);
|
|
14930
|
+
if (!editedValidationResult.valid) {
|
|
14931
|
+
// Show validation errors for the edited message
|
|
14932
|
+
options.logger.log('\nEdited commit message also has validation issues:', { color: 'yellow' });
|
|
14933
|
+
editedValidationResult.errors.forEach((error) => {
|
|
14934
|
+
options.logger.log(` • ${error}`, { color: 'red' });
|
|
14935
|
+
});
|
|
14936
|
+
// Recursively handle validation errors for the edited message
|
|
14937
|
+
return await handleValidationErrors(editedMessage, editedValidationResult, options);
|
|
14938
|
+
}
|
|
14939
|
+
}
|
|
14595
14940
|
return { message: editedMessage, action: 'edit' };
|
|
14596
14941
|
}
|
|
14597
|
-
case '
|
|
14942
|
+
case 'retry':
|
|
14598
14943
|
// Regenerate message
|
|
14599
14944
|
return { message, action: 'regenerate' };
|
|
14600
14945
|
default:
|