git-coco 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.mjs +147 -19
- package/dist/index.js +147 -19
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.esm.mjs
CHANGED
|
@@ -47,7 +47,7 @@ import { pathToFileURL } from 'url';
|
|
|
47
47
|
/**
|
|
48
48
|
* Current build version from package.json
|
|
49
49
|
*/
|
|
50
|
-
const BUILD_VERSION = "0.
|
|
50
|
+
const BUILD_VERSION = "0.24.0";
|
|
51
51
|
|
|
52
52
|
const isInteractive = (config) => {
|
|
53
53
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -6058,9 +6058,13 @@ const options$4 = {
|
|
|
6058
6058
|
alias: 'b',
|
|
6059
6059
|
description: 'Target branch to compare against',
|
|
6060
6060
|
},
|
|
6061
|
+
tag: {
|
|
6062
|
+
type: 'string',
|
|
6063
|
+
alias: 't',
|
|
6064
|
+
description: 'Target tag to compare against',
|
|
6065
|
+
},
|
|
6061
6066
|
sinceLastTag: {
|
|
6062
6067
|
type: 'boolean',
|
|
6063
|
-
alias: 't',
|
|
6064
6068
|
description: 'Generate changelog for all commits since the last tag',
|
|
6065
6069
|
default: false,
|
|
6066
6070
|
},
|
|
@@ -7074,6 +7078,37 @@ async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
|
|
|
7074
7078
|
return [];
|
|
7075
7079
|
}
|
|
7076
7080
|
|
|
7081
|
+
/**
|
|
7082
|
+
* Retrieves the commit log between the current branch and a specified tag.
|
|
7083
|
+
*
|
|
7084
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
7085
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
7086
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
7087
|
+
* @param {string} options.targetTag - The tag to compare against.
|
|
7088
|
+
* @returns {Promise<CommitDetails[]>} The array of commit messages in the commit log.
|
|
7089
|
+
*/
|
|
7090
|
+
async function getCommitLogAgainstTag({ git, logger, targetTag, }) {
|
|
7091
|
+
try {
|
|
7092
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
7093
|
+
const uniqueCommits = (await git.raw(['rev-list', `${targetTag}..${currentBranch}`]))
|
|
7094
|
+
.split('\n')
|
|
7095
|
+
.filter(Boolean)
|
|
7096
|
+
.reverse();
|
|
7097
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and tag "${targetTag}"`, { color: 'blue' });
|
|
7098
|
+
const firstCommit = uniqueCommits[0];
|
|
7099
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
7100
|
+
if (!firstCommit || !lastCommit) {
|
|
7101
|
+
logger?.log('Unable to determine first and last commit between branch and tag', { color: 'yellow' });
|
|
7102
|
+
return [];
|
|
7103
|
+
}
|
|
7104
|
+
return await getCommitLogRangeDetails(firstCommit, lastCommit, { git, noMerges: true });
|
|
7105
|
+
}
|
|
7106
|
+
catch (error) {
|
|
7107
|
+
logger?.log('Encountered an error getting commit log between branch and tag', { color: 'red' });
|
|
7108
|
+
}
|
|
7109
|
+
return [];
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7077
7112
|
/**
|
|
7078
7113
|
* Retrieves the commit log for the current branch.
|
|
7079
7114
|
*
|
|
@@ -7692,6 +7727,15 @@ const handler$4 = async (argv, logger) => {
|
|
|
7692
7727
|
const git = getRepo();
|
|
7693
7728
|
const key = getApiKeyForModel(config);
|
|
7694
7729
|
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
7730
|
+
const exclusiveOptions = [
|
|
7731
|
+
argv.branch ? '--branch' : null,
|
|
7732
|
+
argv.tag ? '--tag' : null,
|
|
7733
|
+
config.sinceLastTag ? '--since-last-tag' : null,
|
|
7734
|
+
].filter(Boolean);
|
|
7735
|
+
if (exclusiveOptions.length > 1) {
|
|
7736
|
+
logger.log(`Options ${exclusiveOptions.join(', ')} cannot be used together.`, { color: 'red' });
|
|
7737
|
+
process.exit(1);
|
|
7738
|
+
}
|
|
7695
7739
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
7696
7740
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
7697
7741
|
process.exit(1);
|
|
@@ -7733,6 +7777,10 @@ const handler$4 = async (argv, logger) => {
|
|
|
7733
7777
|
logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
|
|
7734
7778
|
commits = await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch });
|
|
7735
7779
|
}
|
|
7780
|
+
else if (argv.tag) {
|
|
7781
|
+
logger.verbose(`Generating commit log against tag: ${argv.tag}`, { color: 'yellow' });
|
|
7782
|
+
commits = await getCommitLogAgainstTag({ git, logger, targetTag: argv.tag });
|
|
7783
|
+
}
|
|
7736
7784
|
else {
|
|
7737
7785
|
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, {
|
|
7738
7786
|
color: 'yellow',
|
|
@@ -8006,6 +8054,45 @@ function repairJson(jsonString) {
|
|
|
8006
8054
|
}
|
|
8007
8055
|
}
|
|
8008
8056
|
|
|
8057
|
+
/**
|
|
8058
|
+
* Extract the first complete JSON object from a string by tracking balanced braces
|
|
8059
|
+
*/
|
|
8060
|
+
function extractFirstJsonObject(text) {
|
|
8061
|
+
const startIndex = text.indexOf('{');
|
|
8062
|
+
if (startIndex === -1)
|
|
8063
|
+
return null;
|
|
8064
|
+
let braceCount = 0;
|
|
8065
|
+
let inString = false;
|
|
8066
|
+
let escapeNext = false;
|
|
8067
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
8068
|
+
const char = text[i];
|
|
8069
|
+
if (escapeNext) {
|
|
8070
|
+
escapeNext = false;
|
|
8071
|
+
continue;
|
|
8072
|
+
}
|
|
8073
|
+
if (char === '\\') {
|
|
8074
|
+
escapeNext = true;
|
|
8075
|
+
continue;
|
|
8076
|
+
}
|
|
8077
|
+
if (char === '"') {
|
|
8078
|
+
inString = !inString;
|
|
8079
|
+
continue;
|
|
8080
|
+
}
|
|
8081
|
+
if (inString)
|
|
8082
|
+
continue;
|
|
8083
|
+
if (char === '{') {
|
|
8084
|
+
braceCount++;
|
|
8085
|
+
}
|
|
8086
|
+
else if (char === '}') {
|
|
8087
|
+
braceCount--;
|
|
8088
|
+
if (braceCount === 0) {
|
|
8089
|
+
// Found the end of the first complete JSON object
|
|
8090
|
+
return text.substring(startIndex, i + 1);
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
return null;
|
|
8095
|
+
}
|
|
8009
8096
|
/**
|
|
8010
8097
|
* Utility function to ensure commit messages are properly formatted as strings
|
|
8011
8098
|
* rather than JSON objects, whether they come as parsed objects or stringified JSON
|
|
@@ -8024,23 +8111,26 @@ function formatCommitMessage(result, options = {}) {
|
|
|
8024
8111
|
if (!result.includes('{') && !result.includes('"title"')) {
|
|
8025
8112
|
return result;
|
|
8026
8113
|
}
|
|
8027
|
-
// Handle multiple markdown code block formats
|
|
8028
|
-
const
|
|
8114
|
+
// Handle multiple markdown code block formats and embedded JSON
|
|
8115
|
+
const extractionPatterns = [
|
|
8029
8116
|
/```(?:json)?\s*(\{[\s\S]*?\})\s*```/, // Standard markdown blocks
|
|
8030
8117
|
/`(\{[\s\S]*?\})`/, // Inline code blocks
|
|
8031
|
-
/^\s*(\{[\s\S]*\})\s
|
|
8118
|
+
/^\s*(\{[\s\S]*\})\s*$/, // Raw JSON without blocks (entire string)
|
|
8119
|
+
/(\{[\s\S]*?\})/ // JSON anywhere in text (fallback)
|
|
8032
8120
|
];
|
|
8033
8121
|
let jsonString = result;
|
|
8122
|
+
let foundMatch = false;
|
|
8034
8123
|
// Try each pattern to extract JSON
|
|
8035
|
-
for (const pattern of
|
|
8124
|
+
for (const pattern of extractionPatterns) {
|
|
8036
8125
|
const match = result.match(pattern);
|
|
8037
8126
|
if (match && match[1]) {
|
|
8038
8127
|
jsonString = match[1].trim();
|
|
8128
|
+
foundMatch = true;
|
|
8039
8129
|
break;
|
|
8040
8130
|
}
|
|
8041
8131
|
}
|
|
8042
8132
|
// Only attempt JSON parsing if we found potential JSON content
|
|
8043
|
-
if (
|
|
8133
|
+
if (foundMatch || jsonString.startsWith('{')) {
|
|
8044
8134
|
try {
|
|
8045
8135
|
// Try to parse as JSON to see if it's a stringified object
|
|
8046
8136
|
const parsed = JSON.parse(jsonString);
|
|
@@ -8070,7 +8160,24 @@ function formatCommitMessage(result, options = {}) {
|
|
|
8070
8160
|
}
|
|
8071
8161
|
}
|
|
8072
8162
|
catch {
|
|
8073
|
-
// Repair failed,
|
|
8163
|
+
// Repair failed, try extracting just the first complete JSON object
|
|
8164
|
+
const firstObject = extractFirstJsonObject(jsonString);
|
|
8165
|
+
if (firstObject) {
|
|
8166
|
+
try {
|
|
8167
|
+
const parsed = JSON.parse(firstObject);
|
|
8168
|
+
if (parsed &&
|
|
8169
|
+
typeof parsed === 'object' &&
|
|
8170
|
+
typeof parsed.title === 'string' &&
|
|
8171
|
+
typeof parsed.body === 'string' &&
|
|
8172
|
+
parsed.title.length > 0 &&
|
|
8173
|
+
parsed.body.length > 0) {
|
|
8174
|
+
return constructMessage(parsed.title, parsed.body);
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
catch {
|
|
8178
|
+
// Even first object extraction failed, continue to fallback
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8074
8181
|
}
|
|
8075
8182
|
}
|
|
8076
8183
|
}
|
|
@@ -11571,18 +11678,16 @@ const handler$3 = async (argv, logger) => {
|
|
|
11571
11678
|
REQUIRED JSON FORMAT:
|
|
11572
11679
|
${schema.description}
|
|
11573
11680
|
|
|
11574
|
-
EXAMPLE (follow this
|
|
11575
|
-
{
|
|
11576
|
-
"title": "feat(auth): add user authentication system",
|
|
11577
|
-
"body": "Implement JWT-based authentication with login and logout functionality. Includes password hashing and session management."
|
|
11578
|
-
}
|
|
11681
|
+
EXAMPLE (follow this EXACT format - compact JSON on a single line or minimal whitespace):
|
|
11682
|
+
{"title": "feat(auth): add user authentication system", "body": "Implement JWT-based authentication with login and logout functionality. Includes password hashing and session management."}
|
|
11579
11683
|
|
|
11580
11684
|
IMPORTANT RULES:
|
|
11685
|
+
- Return ONLY the JSON object - NO markdown code blocks, NO backticks, NO extra text
|
|
11581
11686
|
- ALL string values MUST be enclosed in double quotes
|
|
11687
|
+
- Use compact JSON format (minimal whitespace) for best compatibility
|
|
11582
11688
|
- NO trailing commas
|
|
11583
11689
|
- NO comments or additional text outside the JSON
|
|
11584
|
-
- The "title" and "body" values must be properly quoted strings
|
|
11585
|
-
- Return ONLY the JSON object, nothing else`;
|
|
11690
|
+
- The "title" and "body" values must be properly quoted strings`;
|
|
11586
11691
|
// Use conventional commit prompt if enabled
|
|
11587
11692
|
const promptTemplate = USE_CONVENTIONAL_COMMITS ? CONVENTIONAL_COMMIT_PROMPT : COMMIT_PROMPT;
|
|
11588
11693
|
const prompt = getPrompt({
|
|
@@ -11676,10 +11781,33 @@ IMPORTANT RULES:
|
|
|
11676
11781
|
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
11677
11782
|
},
|
|
11678
11783
|
},
|
|
11679
|
-
fallbackParser: (text) =>
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11784
|
+
fallbackParser: (text) => {
|
|
11785
|
+
// First try to parse as JSON in case it's valid JSON with unusual formatting
|
|
11786
|
+
try {
|
|
11787
|
+
// Remove markdown code blocks if present
|
|
11788
|
+
let cleanText = text.trim();
|
|
11789
|
+
const codeBlockMatch = cleanText.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
|
|
11790
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
11791
|
+
cleanText = codeBlockMatch[1].trim();
|
|
11792
|
+
}
|
|
11793
|
+
const parsed = JSON.parse(cleanText);
|
|
11794
|
+
if (parsed &&
|
|
11795
|
+
typeof parsed === 'object' &&
|
|
11796
|
+
typeof parsed.title === 'string' &&
|
|
11797
|
+
typeof parsed.body === 'string' &&
|
|
11798
|
+
parsed.title.length > 0) {
|
|
11799
|
+
return parsed;
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
catch {
|
|
11803
|
+
// JSON parsing failed, fall through to text splitting
|
|
11804
|
+
}
|
|
11805
|
+
// Fallback to simple text splitting
|
|
11806
|
+
return {
|
|
11807
|
+
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
11808
|
+
body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
|
|
11809
|
+
};
|
|
11810
|
+
},
|
|
11683
11811
|
onFallback: () => {
|
|
11684
11812
|
logger.verbose('Max retry attempts reached. Falling back to simple text output.', {
|
|
11685
11813
|
color: 'red',
|
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
69
69
|
/**
|
|
70
70
|
* Current build version from package.json
|
|
71
71
|
*/
|
|
72
|
-
const BUILD_VERSION = "0.
|
|
72
|
+
const BUILD_VERSION = "0.24.0";
|
|
73
73
|
|
|
74
74
|
const isInteractive = (config) => {
|
|
75
75
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -6080,9 +6080,13 @@ const options$4 = {
|
|
|
6080
6080
|
alias: 'b',
|
|
6081
6081
|
description: 'Target branch to compare against',
|
|
6082
6082
|
},
|
|
6083
|
+
tag: {
|
|
6084
|
+
type: 'string',
|
|
6085
|
+
alias: 't',
|
|
6086
|
+
description: 'Target tag to compare against',
|
|
6087
|
+
},
|
|
6083
6088
|
sinceLastTag: {
|
|
6084
6089
|
type: 'boolean',
|
|
6085
|
-
alias: 't',
|
|
6086
6090
|
description: 'Generate changelog for all commits since the last tag',
|
|
6087
6091
|
default: false,
|
|
6088
6092
|
},
|
|
@@ -7096,6 +7100,37 @@ async function getCommitLogAgainstBranch({ git, logger, targetBranch, }) {
|
|
|
7096
7100
|
return [];
|
|
7097
7101
|
}
|
|
7098
7102
|
|
|
7103
|
+
/**
|
|
7104
|
+
* Retrieves the commit log between the current branch and a specified tag.
|
|
7105
|
+
*
|
|
7106
|
+
* @param {Object} options - The options for retrieving the commit log.
|
|
7107
|
+
* @param {SimpleGit} options.git - The SimpleGit instance.
|
|
7108
|
+
* @param {Logger} options.logger - The logger for logging messages.
|
|
7109
|
+
* @param {string} options.targetTag - The tag to compare against.
|
|
7110
|
+
* @returns {Promise<CommitDetails[]>} The array of commit messages in the commit log.
|
|
7111
|
+
*/
|
|
7112
|
+
async function getCommitLogAgainstTag({ git, logger, targetTag, }) {
|
|
7113
|
+
try {
|
|
7114
|
+
const currentBranch = await getCurrentBranchName({ git });
|
|
7115
|
+
const uniqueCommits = (await git.raw(['rev-list', `${targetTag}..${currentBranch}`]))
|
|
7116
|
+
.split('\n')
|
|
7117
|
+
.filter(Boolean)
|
|
7118
|
+
.reverse();
|
|
7119
|
+
logger?.verbose(`Found ${uniqueCommits.length} unique commits between "${currentBranch}" and tag "${targetTag}"`, { color: 'blue' });
|
|
7120
|
+
const firstCommit = uniqueCommits[0];
|
|
7121
|
+
const lastCommit = uniqueCommits[uniqueCommits.length - 1];
|
|
7122
|
+
if (!firstCommit || !lastCommit) {
|
|
7123
|
+
logger?.log('Unable to determine first and last commit between branch and tag', { color: 'yellow' });
|
|
7124
|
+
return [];
|
|
7125
|
+
}
|
|
7126
|
+
return await getCommitLogRangeDetails(firstCommit, lastCommit, { git, noMerges: true });
|
|
7127
|
+
}
|
|
7128
|
+
catch (error) {
|
|
7129
|
+
logger?.log('Encountered an error getting commit log between branch and tag', { color: 'red' });
|
|
7130
|
+
}
|
|
7131
|
+
return [];
|
|
7132
|
+
}
|
|
7133
|
+
|
|
7099
7134
|
/**
|
|
7100
7135
|
* Retrieves the commit log for the current branch.
|
|
7101
7136
|
*
|
|
@@ -7714,6 +7749,15 @@ const handler$4 = async (argv, logger) => {
|
|
|
7714
7749
|
const git = getRepo();
|
|
7715
7750
|
const key = getApiKeyForModel(config);
|
|
7716
7751
|
const { provider, model } = getModelAndProviderFromConfig(config);
|
|
7752
|
+
const exclusiveOptions = [
|
|
7753
|
+
argv.branch ? '--branch' : null,
|
|
7754
|
+
argv.tag ? '--tag' : null,
|
|
7755
|
+
config.sinceLastTag ? '--since-last-tag' : null,
|
|
7756
|
+
].filter(Boolean);
|
|
7757
|
+
if (exclusiveOptions.length > 1) {
|
|
7758
|
+
logger.log(`Options ${exclusiveOptions.join(', ')} cannot be used together.`, { color: 'red' });
|
|
7759
|
+
process.exit(1);
|
|
7760
|
+
}
|
|
7717
7761
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
7718
7762
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
7719
7763
|
process.exit(1);
|
|
@@ -7755,6 +7799,10 @@ const handler$4 = async (argv, logger) => {
|
|
|
7755
7799
|
logger.verbose(`Generating commit log against branch: ${argv.branch}`, { color: 'yellow' });
|
|
7756
7800
|
commits = await getCommitLogAgainstBranch({ git, logger, targetBranch: argv.branch });
|
|
7757
7801
|
}
|
|
7802
|
+
else if (argv.tag) {
|
|
7803
|
+
logger.verbose(`Generating commit log against tag: ${argv.tag}`, { color: 'yellow' });
|
|
7804
|
+
commits = await getCommitLogAgainstTag({ git, logger, targetTag: argv.tag });
|
|
7805
|
+
}
|
|
7758
7806
|
else {
|
|
7759
7807
|
logger.verbose(`No range, branch, or tag option provided. Defaulting to current branch`, {
|
|
7760
7808
|
color: 'yellow',
|
|
@@ -8028,6 +8076,45 @@ function repairJson(jsonString) {
|
|
|
8028
8076
|
}
|
|
8029
8077
|
}
|
|
8030
8078
|
|
|
8079
|
+
/**
|
|
8080
|
+
* Extract the first complete JSON object from a string by tracking balanced braces
|
|
8081
|
+
*/
|
|
8082
|
+
function extractFirstJsonObject(text) {
|
|
8083
|
+
const startIndex = text.indexOf('{');
|
|
8084
|
+
if (startIndex === -1)
|
|
8085
|
+
return null;
|
|
8086
|
+
let braceCount = 0;
|
|
8087
|
+
let inString = false;
|
|
8088
|
+
let escapeNext = false;
|
|
8089
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
8090
|
+
const char = text[i];
|
|
8091
|
+
if (escapeNext) {
|
|
8092
|
+
escapeNext = false;
|
|
8093
|
+
continue;
|
|
8094
|
+
}
|
|
8095
|
+
if (char === '\\') {
|
|
8096
|
+
escapeNext = true;
|
|
8097
|
+
continue;
|
|
8098
|
+
}
|
|
8099
|
+
if (char === '"') {
|
|
8100
|
+
inString = !inString;
|
|
8101
|
+
continue;
|
|
8102
|
+
}
|
|
8103
|
+
if (inString)
|
|
8104
|
+
continue;
|
|
8105
|
+
if (char === '{') {
|
|
8106
|
+
braceCount++;
|
|
8107
|
+
}
|
|
8108
|
+
else if (char === '}') {
|
|
8109
|
+
braceCount--;
|
|
8110
|
+
if (braceCount === 0) {
|
|
8111
|
+
// Found the end of the first complete JSON object
|
|
8112
|
+
return text.substring(startIndex, i + 1);
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
return null;
|
|
8117
|
+
}
|
|
8031
8118
|
/**
|
|
8032
8119
|
* Utility function to ensure commit messages are properly formatted as strings
|
|
8033
8120
|
* rather than JSON objects, whether they come as parsed objects or stringified JSON
|
|
@@ -8046,23 +8133,26 @@ function formatCommitMessage(result, options = {}) {
|
|
|
8046
8133
|
if (!result.includes('{') && !result.includes('"title"')) {
|
|
8047
8134
|
return result;
|
|
8048
8135
|
}
|
|
8049
|
-
// Handle multiple markdown code block formats
|
|
8050
|
-
const
|
|
8136
|
+
// Handle multiple markdown code block formats and embedded JSON
|
|
8137
|
+
const extractionPatterns = [
|
|
8051
8138
|
/```(?:json)?\s*(\{[\s\S]*?\})\s*```/, // Standard markdown blocks
|
|
8052
8139
|
/`(\{[\s\S]*?\})`/, // Inline code blocks
|
|
8053
|
-
/^\s*(\{[\s\S]*\})\s
|
|
8140
|
+
/^\s*(\{[\s\S]*\})\s*$/, // Raw JSON without blocks (entire string)
|
|
8141
|
+
/(\{[\s\S]*?\})/ // JSON anywhere in text (fallback)
|
|
8054
8142
|
];
|
|
8055
8143
|
let jsonString = result;
|
|
8144
|
+
let foundMatch = false;
|
|
8056
8145
|
// Try each pattern to extract JSON
|
|
8057
|
-
for (const pattern of
|
|
8146
|
+
for (const pattern of extractionPatterns) {
|
|
8058
8147
|
const match = result.match(pattern);
|
|
8059
8148
|
if (match && match[1]) {
|
|
8060
8149
|
jsonString = match[1].trim();
|
|
8150
|
+
foundMatch = true;
|
|
8061
8151
|
break;
|
|
8062
8152
|
}
|
|
8063
8153
|
}
|
|
8064
8154
|
// Only attempt JSON parsing if we found potential JSON content
|
|
8065
|
-
if (
|
|
8155
|
+
if (foundMatch || jsonString.startsWith('{')) {
|
|
8066
8156
|
try {
|
|
8067
8157
|
// Try to parse as JSON to see if it's a stringified object
|
|
8068
8158
|
const parsed = JSON.parse(jsonString);
|
|
@@ -8092,7 +8182,24 @@ function formatCommitMessage(result, options = {}) {
|
|
|
8092
8182
|
}
|
|
8093
8183
|
}
|
|
8094
8184
|
catch {
|
|
8095
|
-
// Repair failed,
|
|
8185
|
+
// Repair failed, try extracting just the first complete JSON object
|
|
8186
|
+
const firstObject = extractFirstJsonObject(jsonString);
|
|
8187
|
+
if (firstObject) {
|
|
8188
|
+
try {
|
|
8189
|
+
const parsed = JSON.parse(firstObject);
|
|
8190
|
+
if (parsed &&
|
|
8191
|
+
typeof parsed === 'object' &&
|
|
8192
|
+
typeof parsed.title === 'string' &&
|
|
8193
|
+
typeof parsed.body === 'string' &&
|
|
8194
|
+
parsed.title.length > 0 &&
|
|
8195
|
+
parsed.body.length > 0) {
|
|
8196
|
+
return constructMessage(parsed.title, parsed.body);
|
|
8197
|
+
}
|
|
8198
|
+
}
|
|
8199
|
+
catch {
|
|
8200
|
+
// Even first object extraction failed, continue to fallback
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8096
8203
|
}
|
|
8097
8204
|
}
|
|
8098
8205
|
}
|
|
@@ -11593,18 +11700,16 @@ const handler$3 = async (argv, logger) => {
|
|
|
11593
11700
|
REQUIRED JSON FORMAT:
|
|
11594
11701
|
${schema.description}
|
|
11595
11702
|
|
|
11596
|
-
EXAMPLE (follow this
|
|
11597
|
-
{
|
|
11598
|
-
"title": "feat(auth): add user authentication system",
|
|
11599
|
-
"body": "Implement JWT-based authentication with login and logout functionality. Includes password hashing and session management."
|
|
11600
|
-
}
|
|
11703
|
+
EXAMPLE (follow this EXACT format - compact JSON on a single line or minimal whitespace):
|
|
11704
|
+
{"title": "feat(auth): add user authentication system", "body": "Implement JWT-based authentication with login and logout functionality. Includes password hashing and session management."}
|
|
11601
11705
|
|
|
11602
11706
|
IMPORTANT RULES:
|
|
11707
|
+
- Return ONLY the JSON object - NO markdown code blocks, NO backticks, NO extra text
|
|
11603
11708
|
- ALL string values MUST be enclosed in double quotes
|
|
11709
|
+
- Use compact JSON format (minimal whitespace) for best compatibility
|
|
11604
11710
|
- NO trailing commas
|
|
11605
11711
|
- NO comments or additional text outside the JSON
|
|
11606
|
-
- The "title" and "body" values must be properly quoted strings
|
|
11607
|
-
- Return ONLY the JSON object, nothing else`;
|
|
11712
|
+
- The "title" and "body" values must be properly quoted strings`;
|
|
11608
11713
|
// Use conventional commit prompt if enabled
|
|
11609
11714
|
const promptTemplate = USE_CONVENTIONAL_COMMITS ? CONVENTIONAL_COMMIT_PROMPT : COMMIT_PROMPT;
|
|
11610
11715
|
const prompt = getPrompt({
|
|
@@ -11698,10 +11803,33 @@ IMPORTANT RULES:
|
|
|
11698
11803
|
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
11699
11804
|
},
|
|
11700
11805
|
},
|
|
11701
|
-
fallbackParser: (text) =>
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11806
|
+
fallbackParser: (text) => {
|
|
11807
|
+
// First try to parse as JSON in case it's valid JSON with unusual formatting
|
|
11808
|
+
try {
|
|
11809
|
+
// Remove markdown code blocks if present
|
|
11810
|
+
let cleanText = text.trim();
|
|
11811
|
+
const codeBlockMatch = cleanText.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
|
|
11812
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
11813
|
+
cleanText = codeBlockMatch[1].trim();
|
|
11814
|
+
}
|
|
11815
|
+
const parsed = JSON.parse(cleanText);
|
|
11816
|
+
if (parsed &&
|
|
11817
|
+
typeof parsed === 'object' &&
|
|
11818
|
+
typeof parsed.title === 'string' &&
|
|
11819
|
+
typeof parsed.body === 'string' &&
|
|
11820
|
+
parsed.title.length > 0) {
|
|
11821
|
+
return parsed;
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
11824
|
+
catch {
|
|
11825
|
+
// JSON parsing failed, fall through to text splitting
|
|
11826
|
+
}
|
|
11827
|
+
// Fallback to simple text splitting
|
|
11828
|
+
return {
|
|
11829
|
+
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
11830
|
+
body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
|
|
11831
|
+
};
|
|
11832
|
+
},
|
|
11705
11833
|
onFallback: () => {
|
|
11706
11834
|
logger.verbose('Max retry attempts reached. Falling back to simple text output.', {
|
|
11707
11835
|
color: 'red',
|