git-coco 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/index.esm.mjs +484 -129
- package/dist/index.js +483 -128
- package/package.json +5 -5
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.1";
|
|
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(),
|
|
7555
|
-
});
|
|
7609
|
+
title: stringType().describe("Title of the commit message"),
|
|
7610
|
+
body: stringType().describe("Body of the commit message"),
|
|
7611
|
+
}).describe("Object with commit message 'title' and 'body'");
|
|
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("Object with Conventional Commit message 'title' and 'body' adhering to Conventional Commits specification");
|
|
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
|
}
|
|
@@ -11090,6 +11222,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
11090
11222
|
}
|
|
11091
11223
|
}
|
|
11092
11224
|
async function parser(changes) {
|
|
11225
|
+
if (config.noDiff) {
|
|
11226
|
+
// When noDiff is enabled, just return a simple summary without parsing file contents
|
|
11227
|
+
const filesSummary = changes
|
|
11228
|
+
.map((change) => `${change.status}: ${change.filePath}`)
|
|
11229
|
+
.join('\n');
|
|
11230
|
+
return `Staged files:\n${filesSummary}`;
|
|
11231
|
+
}
|
|
11093
11232
|
return await fileChangeParser({
|
|
11094
11233
|
changes,
|
|
11095
11234
|
commit: '--staged',
|
|
@@ -11101,7 +11240,7 @@ const handler$3 = async (argv, logger) => {
|
|
|
11101
11240
|
options: {
|
|
11102
11241
|
...config,
|
|
11103
11242
|
prompt: config.prompt ||
|
|
11104
|
-
(
|
|
11243
|
+
(USE_CONVENTIONAL_COMMITS
|
|
11105
11244
|
? CONVENTIONAL_COMMIT_PROMPT.template
|
|
11106
11245
|
: COMMIT_PROMPT.template),
|
|
11107
11246
|
logger,
|
|
@@ -11114,21 +11253,27 @@ const handler$3 = async (argv, logger) => {
|
|
|
11114
11253
|
retryMessageOnly: 'Restart the function execution from generating the commit message',
|
|
11115
11254
|
retryFull: 'Restart the function execution from the beginning, regenerating both the diff summary and commit message',
|
|
11116
11255
|
},
|
|
11256
|
+
customEditFunction: async (message, options) => {
|
|
11257
|
+
const { editCommitMessage } = await Promise.resolve().then(function () { return editCommitMessage$1; });
|
|
11258
|
+
return editCommitMessage(message, options);
|
|
11259
|
+
},
|
|
11117
11260
|
},
|
|
11118
11261
|
},
|
|
11119
11262
|
factory,
|
|
11120
11263
|
parser,
|
|
11121
11264
|
agent: async (context, options) => {
|
|
11122
|
-
// Check if conventional commits are enabled via config or CLI flag
|
|
11123
|
-
const useConventional = config.conventionalCommits || argv.conventional;
|
|
11124
11265
|
// Select the appropriate schema based on whether conventional commits are enabled
|
|
11125
|
-
const schema =
|
|
11266
|
+
const schema = USE_CONVENTIONAL_COMMITS
|
|
11126
11267
|
? ConventionalCommitMessageResponseSchema
|
|
11127
11268
|
: CommitMessageResponseSchema;
|
|
11128
11269
|
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
|
-
{
|
|
11270
|
+
${schema.description}
|
|
11271
|
+
{
|
|
11272
|
+
"title": "The commit title",
|
|
11273
|
+
"body": "The commit body"
|
|
11274
|
+
}`;
|
|
11130
11275
|
// Use conventional commit prompt if enabled
|
|
11131
|
-
const promptTemplate =
|
|
11276
|
+
const promptTemplate = USE_CONVENTIONAL_COMMITS ? CONVENTIONAL_COMMIT_PROMPT : COMMIT_PROMPT;
|
|
11132
11277
|
const prompt = getPrompt({
|
|
11133
11278
|
template: options.prompt,
|
|
11134
11279
|
variables: promptTemplate.inputVariables,
|
|
@@ -11158,6 +11303,13 @@ const handler$3 = async (argv, logger) => {
|
|
|
11158
11303
|
: config.includeBranchName !== false; // Default to true if not explicitly set to false
|
|
11159
11304
|
// Create branch name context string based on the configuration
|
|
11160
11305
|
const branchNameContext = includeBranchName ? `Current git branch name: ${branchName}` : '';
|
|
11306
|
+
// Load commitlint rules context if available
|
|
11307
|
+
const hasCommitLintConfig = await hasCommitlintConfig();
|
|
11308
|
+
let commitlint_rules_context = '';
|
|
11309
|
+
if (USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) {
|
|
11310
|
+
const { getCommitlintRulesContext } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
11311
|
+
commitlint_rules_context = await getCommitlintRulesContext();
|
|
11312
|
+
}
|
|
11161
11313
|
// Get variables for the prompt
|
|
11162
11314
|
const variables = {
|
|
11163
11315
|
summary: context,
|
|
@@ -11165,59 +11317,106 @@ const handler$3 = async (argv, logger) => {
|
|
|
11165
11317
|
additional_context: additional_context,
|
|
11166
11318
|
commit_history: commit_history,
|
|
11167
11319
|
branch_name_context: branchNameContext,
|
|
11320
|
+
commitlint_rules_context: commitlint_rules_context,
|
|
11168
11321
|
};
|
|
11169
11322
|
const maxAttempts = config.service.provider === 'ollama' && 'maxParsingAttempts' in config.service
|
|
11170
11323
|
? config.service.maxParsingAttempts || 3
|
|
11171
11324
|
: 3;
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11325
|
+
// Custom retry logic for commitlint validation
|
|
11326
|
+
let retryCount = 0;
|
|
11327
|
+
let validationErrors = '';
|
|
11328
|
+
const generateCommitMessage = async () => {
|
|
11329
|
+
// Update variables with validation errors for retry attempts
|
|
11330
|
+
const currentVariables = {
|
|
11331
|
+
...variables,
|
|
11332
|
+
additional_context: validationErrors
|
|
11333
|
+
? `${variables.additional_context}\n\n## Validation Errors from Previous Attempt\nPlease fix the following issues:\n${validationErrors}`
|
|
11334
|
+
: variables.additional_context,
|
|
11335
|
+
};
|
|
11336
|
+
const commitMsg = await executeChainWithSchema(schema, llm, prompt, currentVariables, {
|
|
11337
|
+
retryOptions: {
|
|
11338
|
+
maxAttempts,
|
|
11339
|
+
onRetry: (attempt, error) => {
|
|
11340
|
+
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
11341
|
+
},
|
|
11177
11342
|
},
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11343
|
+
fallbackParser: (text) => ({
|
|
11344
|
+
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
11345
|
+
body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
|
|
11346
|
+
}),
|
|
11347
|
+
onFallback: () => {
|
|
11348
|
+
logger.verbose('Max retry attempts reached. Falling back to simple text output.', {
|
|
11349
|
+
color: 'red',
|
|
11350
|
+
});
|
|
11351
|
+
},
|
|
11352
|
+
});
|
|
11353
|
+
// Construct the full commit message
|
|
11354
|
+
const appendedText = argv.append ? `\n\n${argv.append}` : '';
|
|
11355
|
+
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
11356
|
+
const ticketFooter = argv.appendTicket && ticketId ? `\n\nPart of **${ticketId}**` : '';
|
|
11357
|
+
const fullMessage = `${commitMsg.title}\n\n${commitMsg.body}${appendedText}${ticketFooter}`;
|
|
11358
|
+
// If commitlint validation is needed, validate the message
|
|
11359
|
+
if (USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) {
|
|
11360
|
+
const { validateCommitMessage, CommitlintValidationError } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
11361
|
+
const validationResult = await validateCommitMessage(fullMessage);
|
|
11362
|
+
logger.verbose(`Validation result: ${JSON.stringify(validationResult)}`, {
|
|
11363
|
+
color: 'yellow',
|
|
11186
11364
|
});
|
|
11365
|
+
if (!validationResult.valid) {
|
|
11366
|
+
retryCount++;
|
|
11367
|
+
// Format validation errors for next attempt
|
|
11368
|
+
validationErrors = validationResult.errors.map((error) => `- ${error}`).join('\n');
|
|
11369
|
+
// Auto-retry up to 2 times
|
|
11370
|
+
if (retryCount <= 2) {
|
|
11371
|
+
logger.verbose(`Commit message validation failed (attempt ${retryCount}/2). Retrying with error feedback...`, { color: 'yellow' });
|
|
11372
|
+
throw new CommitlintValidationError(`Validation failed: ${validationResult.errors.join('; ')}`, validationResult, fullMessage);
|
|
11373
|
+
}
|
|
11374
|
+
// After 2 failed attempts, let the user decide
|
|
11375
|
+
const { handleValidationErrors } = await Promise.resolve().then(function () { return commitValidationHandler; });
|
|
11376
|
+
const validationHandlerResult = await handleValidationErrors(fullMessage, validationResult, {
|
|
11377
|
+
logger,
|
|
11378
|
+
interactive: INTERACTIVE,
|
|
11379
|
+
openInEditor: config.openInEditor,
|
|
11380
|
+
});
|
|
11381
|
+
logger.verbose(`Validation handler result: ${JSON.stringify(validationHandlerResult)}`, {
|
|
11382
|
+
color: 'blue',
|
|
11383
|
+
});
|
|
11384
|
+
switch (validationHandlerResult.action) {
|
|
11385
|
+
case 'proceed':
|
|
11386
|
+
return validationHandlerResult.message;
|
|
11387
|
+
case 'edit':
|
|
11388
|
+
return validationHandlerResult.message;
|
|
11389
|
+
case 'regenerate':
|
|
11390
|
+
// Reset retry count and validation errors for fresh attempts
|
|
11391
|
+
retryCount = 0;
|
|
11392
|
+
validationErrors = '';
|
|
11393
|
+
throw new CommitlintValidationError('User requested regeneration', validationResult, fullMessage);
|
|
11394
|
+
case 'abort':
|
|
11395
|
+
logger.log('\nAborting commit due to validation errors.', { color: 'red' });
|
|
11396
|
+
process.exit(1);
|
|
11397
|
+
}
|
|
11398
|
+
}
|
|
11399
|
+
}
|
|
11400
|
+
return fullMessage;
|
|
11401
|
+
};
|
|
11402
|
+
// Custom shouldRetry function for commitlint errors
|
|
11403
|
+
const shouldRetryCommitlint = (error) => {
|
|
11404
|
+
return error.name === 'CommitlintValidationError';
|
|
11405
|
+
};
|
|
11406
|
+
// Use retry wrapper for commitlint validation with up to 4 total attempts
|
|
11407
|
+
// (2 automatic retries + 2 more if user chooses "Try again")
|
|
11408
|
+
return await withRetry(generateCommitMessage, {
|
|
11409
|
+
maxAttempts: 6, // Allow for multiple user retry requests
|
|
11410
|
+
shouldRetry: shouldRetryCommitlint,
|
|
11411
|
+
backoffMs: 0, // No delay needed for commitlint retries
|
|
11412
|
+
onRetry: (attempt, error) => {
|
|
11413
|
+
if (error.name === 'CommitlintValidationError' && attempt <= 2) {
|
|
11414
|
+
// Don't log for auto-retries, we already log in the function
|
|
11415
|
+
return;
|
|
11416
|
+
}
|
|
11417
|
+
logger.verbose(`Retrying commit message generation (attempt ${attempt}): ${error.message}`, { color: 'yellow' });
|
|
11187
11418
|
},
|
|
11188
11419
|
});
|
|
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
11420
|
},
|
|
11222
11421
|
noResult: async () => {
|
|
11223
11422
|
await noResult$2({ git, logger });
|
|
@@ -11258,28 +11457,6 @@ const builder$2 = (yargs) => {
|
|
|
11258
11457
|
return yargs.options(options$2).usage(getCommandUsageHeader(command$2));
|
|
11259
11458
|
};
|
|
11260
11459
|
|
|
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
11460
|
/**
|
|
11284
11461
|
* Executes a command as a Promise and returns the result.
|
|
11285
11462
|
*
|
|
@@ -13199,11 +13376,13 @@ function isObservable(obj) {
|
|
|
13199
13376
|
return !!obj && (obj instanceof Observable || (isFunction(obj.lift) && isFunction(obj.subscribe)));
|
|
13200
13377
|
}
|
|
13201
13378
|
|
|
13202
|
-
var EmptyError = createErrorClass(function (_super) {
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13379
|
+
var EmptyError = createErrorClass(function (_super) {
|
|
13380
|
+
return function EmptyErrorImpl() {
|
|
13381
|
+
_super(this);
|
|
13382
|
+
this.name = 'EmptyError';
|
|
13383
|
+
this.message = 'no elements in sequence';
|
|
13384
|
+
};
|
|
13385
|
+
});
|
|
13207
13386
|
|
|
13208
13387
|
function lastValueFrom(source, config) {
|
|
13209
13388
|
var hasConfig = typeof config === 'object';
|
|
@@ -14490,23 +14669,183 @@ y.command(review.command, review.desc, review.builder, review.handler);
|
|
|
14490
14669
|
y.command(init.command, init.desc, init.builder, init.handler);
|
|
14491
14670
|
y.help().parse(process.argv.slice(2));
|
|
14492
14671
|
|
|
14672
|
+
/**
|
|
14673
|
+
* Edit a commit message with commitlint validation if config exists
|
|
14674
|
+
*/
|
|
14675
|
+
async function editCommitMessage(message, options) {
|
|
14676
|
+
// First, let the user edit the message
|
|
14677
|
+
const editedMessage = await editResult(message, options);
|
|
14678
|
+
// Then validate it against commitlint if config exists
|
|
14679
|
+
const { hasCommitlintConfig } = await Promise.resolve().then(function () { return hasCommitlintConfig$1; });
|
|
14680
|
+
const hasConfig = await hasCommitlintConfig();
|
|
14681
|
+
if (hasConfig) {
|
|
14682
|
+
const { validateCommitMessage } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
14683
|
+
const { handleValidationErrors } = await Promise.resolve().then(function () { return commitValidationHandler; });
|
|
14684
|
+
const validationResult = await validateCommitMessage(editedMessage);
|
|
14685
|
+
if (!validationResult.valid) {
|
|
14686
|
+
// Show validation errors and get user action
|
|
14687
|
+
const validationHandlerResult = await handleValidationErrors(editedMessage, validationResult, {
|
|
14688
|
+
logger: options.logger,
|
|
14689
|
+
interactive: options.interactive,
|
|
14690
|
+
openInEditor: options.openInEditor,
|
|
14691
|
+
});
|
|
14692
|
+
// Return the result from the validation handler
|
|
14693
|
+
return validationHandlerResult.message;
|
|
14694
|
+
}
|
|
14695
|
+
}
|
|
14696
|
+
return editedMessage;
|
|
14697
|
+
}
|
|
14698
|
+
|
|
14699
|
+
var editCommitMessage$1 = /*#__PURE__*/Object.freeze({
|
|
14700
|
+
__proto__: null,
|
|
14701
|
+
editCommitMessage: editCommitMessage
|
|
14702
|
+
});
|
|
14703
|
+
|
|
14704
|
+
/**
|
|
14705
|
+
* Custom error for commitlint validation failures
|
|
14706
|
+
* This allows the retry system to identify these errors specifically
|
|
14707
|
+
*/
|
|
14708
|
+
class CommitlintValidationError extends Error {
|
|
14709
|
+
constructor(message, validationResult, commitMessage) {
|
|
14710
|
+
super(message);
|
|
14711
|
+
this.name = 'CommitlintValidationError';
|
|
14712
|
+
this.validationResult = validationResult;
|
|
14713
|
+
this.commitMessage = commitMessage;
|
|
14714
|
+
}
|
|
14715
|
+
}
|
|
14493
14716
|
/**
|
|
14494
14717
|
* Load commitlint configuration
|
|
14495
14718
|
*/
|
|
14496
14719
|
async function loadCommitlintConfig() {
|
|
14497
|
-
|
|
14498
|
-
const
|
|
14499
|
-
|
|
14720
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
14721
|
+
const cwd = projectRoot || process.cwd();
|
|
14722
|
+
// @commitlint/load has issues with ESM configs (e.g. commitlint.config.js with `export default`).
|
|
14723
|
+
// Let's try to load them manually first.
|
|
14724
|
+
const esmConfigCandidates = COMMITLINT_CONFIG_FILES.filter((file) => file.endsWith('.js'));
|
|
14725
|
+
for (const configFile of esmConfigCandidates) {
|
|
14726
|
+
const configPath = join(cwd, configFile);
|
|
14727
|
+
if (existsSync(configPath)) {
|
|
14728
|
+
try {
|
|
14729
|
+
const module = await import(pathToFileURL(configPath).href);
|
|
14730
|
+
if (module.default &&
|
|
14731
|
+
(Object.keys(module.default.rules || {}).length > 0 ||
|
|
14732
|
+
(module.default.extends && module.default.extends.length > 0))) {
|
|
14733
|
+
// We found a config, now let commitlint process it (for extends etc)
|
|
14734
|
+
return await load(module.default, { cwd });
|
|
14735
|
+
}
|
|
14736
|
+
}
|
|
14737
|
+
catch (error) {
|
|
14738
|
+
// Failed to import, maybe not an ESM file after all or syntax error.
|
|
14739
|
+
// We will let the standard load take a chance.
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
}
|
|
14500
14743
|
try {
|
|
14501
|
-
//
|
|
14502
|
-
const config = await load();
|
|
14503
|
-
|
|
14744
|
+
// Let @commitlint/load try to find the config. This works for CJS, JSON, and YAML.
|
|
14745
|
+
const config = await load({}, { cwd });
|
|
14746
|
+
// Check if a real config was loaded.
|
|
14747
|
+
if (config.extends.length > 0 || Object.keys(config.rules).length > 0) {
|
|
14748
|
+
return config;
|
|
14749
|
+
}
|
|
14504
14750
|
}
|
|
14505
|
-
catch {
|
|
14506
|
-
//
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14751
|
+
catch (error) {
|
|
14752
|
+
// Could be an error parsing, or just not found. Fall through to default.
|
|
14753
|
+
}
|
|
14754
|
+
// If nothing worked, fallback to conventional config
|
|
14755
|
+
return load({
|
|
14756
|
+
extends: ['@commitlint/config-conventional'],
|
|
14757
|
+
});
|
|
14758
|
+
}
|
|
14759
|
+
/**
|
|
14760
|
+
* Format commitlint rules into a human-readable string for AI prompts
|
|
14761
|
+
*/
|
|
14762
|
+
function formatCommitlintRulesForPrompt(config) {
|
|
14763
|
+
if (!config.rules || Object.keys(config.rules).length === 0) {
|
|
14764
|
+
return '';
|
|
14765
|
+
}
|
|
14766
|
+
const ruleDescriptions = [];
|
|
14767
|
+
// Add information about extends if present
|
|
14768
|
+
if (config.extends && config.extends.length > 0) {
|
|
14769
|
+
ruleDescriptions.push(`Following ${config.extends.join(', ')} configuration`);
|
|
14770
|
+
}
|
|
14771
|
+
// Process key rules that affect commit message format
|
|
14772
|
+
const rules = config.rules;
|
|
14773
|
+
// Header length rules
|
|
14774
|
+
if (rules['header-max-length']) {
|
|
14775
|
+
const [level, , maxLength] = rules['header-max-length'];
|
|
14776
|
+
if (level > 0) {
|
|
14777
|
+
ruleDescriptions.push(`Header (title) must be ${maxLength} characters or less (including spaces)`);
|
|
14778
|
+
}
|
|
14779
|
+
}
|
|
14780
|
+
if (rules['header-min-length']) {
|
|
14781
|
+
const [level, , minLength] = rules['header-min-length'];
|
|
14782
|
+
if (level > 0) {
|
|
14783
|
+
ruleDescriptions.push(`Header (title) must be at least ${minLength} characters (including spaces)`);
|
|
14784
|
+
}
|
|
14785
|
+
}
|
|
14786
|
+
// Body length rules
|
|
14787
|
+
if (rules['body-max-line-length']) {
|
|
14788
|
+
const [level, , maxLength] = rules['body-max-line-length'];
|
|
14789
|
+
if (level > 0) {
|
|
14790
|
+
ruleDescriptions.push(`Body lines must be ${maxLength} characters or less (including spaces)`);
|
|
14791
|
+
}
|
|
14792
|
+
}
|
|
14793
|
+
// Type rules
|
|
14794
|
+
if (rules['type-enum']) {
|
|
14795
|
+
const [level, , allowedTypes] = rules['type-enum'];
|
|
14796
|
+
if (level > 0 && Array.isArray(allowedTypes)) {
|
|
14797
|
+
ruleDescriptions.push(`Allowed types: ${allowedTypes.join(', ')}`);
|
|
14798
|
+
}
|
|
14799
|
+
}
|
|
14800
|
+
// Case rules
|
|
14801
|
+
if (rules['type-case']) {
|
|
14802
|
+
const [level, , caseType] = rules['type-case'];
|
|
14803
|
+
if (level > 0) {
|
|
14804
|
+
ruleDescriptions.push(`Type must be ${caseType} case`);
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
if (rules['subject-case']) {
|
|
14808
|
+
const [level, , caseType] = rules['subject-case'];
|
|
14809
|
+
if (level > 0) {
|
|
14810
|
+
ruleDescriptions.push(`Subject must be ${caseType} case`);
|
|
14811
|
+
}
|
|
14812
|
+
}
|
|
14813
|
+
// Scope rules
|
|
14814
|
+
if (rules['scope-enum']) {
|
|
14815
|
+
const [level, , allowedScopes] = rules['scope-enum'];
|
|
14816
|
+
if (level > 0 && Array.isArray(allowedScopes)) {
|
|
14817
|
+
ruleDescriptions.push(`Allowed scopes: ${allowedScopes.join(', ')}`);
|
|
14818
|
+
}
|
|
14819
|
+
}
|
|
14820
|
+
// Subject rules
|
|
14821
|
+
if (rules['subject-full-stop']) {
|
|
14822
|
+
const [level, condition] = rules['subject-full-stop'];
|
|
14823
|
+
if (level > 0) {
|
|
14824
|
+
const verb = condition === 'always' ? 'must' : 'must not';
|
|
14825
|
+
ruleDescriptions.push(`Subject ${verb} end with a period`);
|
|
14826
|
+
}
|
|
14827
|
+
}
|
|
14828
|
+
if (rules['subject-empty']) {
|
|
14829
|
+
const [level, condition] = rules['subject-empty'];
|
|
14830
|
+
if (level > 0) {
|
|
14831
|
+
const requirement = condition === 'never' ? 'must not be empty' : 'must be empty';
|
|
14832
|
+
ruleDescriptions.push(`Subject ${requirement}`);
|
|
14833
|
+
}
|
|
14834
|
+
}
|
|
14835
|
+
return ruleDescriptions.length > 0
|
|
14836
|
+
? `## Commitlint Rules\nYour commit message must follow these project-specific rules:\n${ruleDescriptions.map(rule => `- ${rule}`).join('\n')}\n`
|
|
14837
|
+
: '';
|
|
14838
|
+
}
|
|
14839
|
+
/**
|
|
14840
|
+
* Get commitlint rules context for prompt if config exists
|
|
14841
|
+
*/
|
|
14842
|
+
async function getCommitlintRulesContext() {
|
|
14843
|
+
try {
|
|
14844
|
+
const config = await loadCommitlintConfig();
|
|
14845
|
+
return formatCommitlintRulesForPrompt(config);
|
|
14846
|
+
}
|
|
14847
|
+
catch (error) {
|
|
14848
|
+
return '';
|
|
14510
14849
|
}
|
|
14511
14850
|
}
|
|
14512
14851
|
/**
|
|
@@ -14515,9 +14854,6 @@ async function loadCommitlintConfig() {
|
|
|
14515
14854
|
async function validateCommitMessage(message, options = {}) {
|
|
14516
14855
|
try {
|
|
14517
14856
|
const config = await loadCommitlintConfig();
|
|
14518
|
-
// Dynamically import commitlint lint function
|
|
14519
|
-
const commitlint = await import('@commitlint/core');
|
|
14520
|
-
const { lint } = commitlint;
|
|
14521
14857
|
const result = await lint(message, config.rules, options);
|
|
14522
14858
|
return {
|
|
14523
14859
|
valid: result.valid,
|
|
@@ -14536,6 +14872,9 @@ async function validateCommitMessage(message, options = {}) {
|
|
|
14536
14872
|
|
|
14537
14873
|
var commitlintValidator = /*#__PURE__*/Object.freeze({
|
|
14538
14874
|
__proto__: null,
|
|
14875
|
+
CommitlintValidationError: CommitlintValidationError,
|
|
14876
|
+
formatCommitlintRulesForPrompt: formatCommitlintRulesForPrompt,
|
|
14877
|
+
getCommitlintRulesContext: getCommitlintRulesContext,
|
|
14539
14878
|
loadCommitlintConfig: loadCommitlintConfig,
|
|
14540
14879
|
validateCommitMessage: validateCommitMessage
|
|
14541
14880
|
});
|
|
@@ -14572,29 +14911,45 @@ async function handleValidationErrors(message, validationResult, options) {
|
|
|
14572
14911
|
message: 'How would you like to proceed?:',
|
|
14573
14912
|
choices: [
|
|
14574
14913
|
{
|
|
14575
|
-
name: '
|
|
14576
|
-
value: '
|
|
14577
|
-
description: '
|
|
14914
|
+
name: 'Try 2 more attempts',
|
|
14915
|
+
value: 'retry',
|
|
14916
|
+
description: 'Let the AI try generating 2 more commit messages with error feedback',
|
|
14578
14917
|
},
|
|
14579
14918
|
{
|
|
14580
|
-
name: '
|
|
14581
|
-
value: '
|
|
14582
|
-
description: '
|
|
14919
|
+
name: 'Edit manually',
|
|
14920
|
+
value: 'edit',
|
|
14921
|
+
description: 'Edit the commit message manually to fix the issues',
|
|
14583
14922
|
},
|
|
14584
14923
|
{
|
|
14585
14924
|
name: 'Abort',
|
|
14586
14925
|
value: 'abort',
|
|
14587
|
-
description: 'Abort the commit',
|
|
14926
|
+
description: 'Abort the commit process',
|
|
14588
14927
|
},
|
|
14589
14928
|
],
|
|
14590
14929
|
});
|
|
14591
14930
|
switch (choice) {
|
|
14592
|
-
case '
|
|
14931
|
+
case 'edit': {
|
|
14593
14932
|
// Edit message manually
|
|
14594
14933
|
const editedMessage = await editResult(message, options);
|
|
14934
|
+
// Validate the manually edited message if commitlint config exists
|
|
14935
|
+
const { hasCommitlintConfig } = await Promise.resolve().then(function () { return hasCommitlintConfig$1; });
|
|
14936
|
+
const hasConfig = await hasCommitlintConfig();
|
|
14937
|
+
if (hasConfig) {
|
|
14938
|
+
const { validateCommitMessage } = await Promise.resolve().then(function () { return commitlintValidator; });
|
|
14939
|
+
const editedValidationResult = await validateCommitMessage(editedMessage);
|
|
14940
|
+
if (!editedValidationResult.valid) {
|
|
14941
|
+
// Show validation errors for the edited message
|
|
14942
|
+
options.logger.log('\nEdited commit message also has validation issues:', { color: 'yellow' });
|
|
14943
|
+
editedValidationResult.errors.forEach((error) => {
|
|
14944
|
+
options.logger.log(` • ${error}`, { color: 'red' });
|
|
14945
|
+
});
|
|
14946
|
+
// Recursively handle validation errors for the edited message
|
|
14947
|
+
return await handleValidationErrors(editedMessage, editedValidationResult, options);
|
|
14948
|
+
}
|
|
14949
|
+
}
|
|
14595
14950
|
return { message: editedMessage, action: 'edit' };
|
|
14596
14951
|
}
|
|
14597
|
-
case '
|
|
14952
|
+
case 'retry':
|
|
14598
14953
|
// Regenerate message
|
|
14599
14954
|
return { message, action: 'regenerate' };
|
|
14600
14955
|
default:
|