git-coco 0.23.0 → 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 CHANGED
@@ -173,6 +173,7 @@ interface BaseCommandOptions extends BaseArgvOptions {
173
173
  interface ChangelogOptions extends BaseCommandOptions {
174
174
  range: string;
175
175
  branch: string;
176
+ tag: string;
176
177
  sinceLastTag: boolean;
177
178
  withDiff?: boolean;
178
179
  onlyDiff?: boolean;
@@ -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.23.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 codeBlockPatterns = [
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*$/ // Raw JSON without blocks
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 codeBlockPatterns) {
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 (jsonString !== result || jsonString.startsWith('{')) {
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, continue to fallback
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 exact structure):
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
- title: text.split('\n')[0] || 'Auto-generated commit',
11681
- body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
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',
@@ -11688,11 +11816,15 @@ IMPORTANT RULES:
11688
11816
  });
11689
11817
  // Construct the full commit message using the utility function
11690
11818
  const ticketId = extractTicketIdFromBranchName(branchName);
11819
+ // Debug: Log what commitMsg looks like before formatting
11820
+ logger.verbose(`commitMsg before formatting: ${JSON.stringify(commitMsg)}`, { color: 'blue' });
11691
11821
  const fullMessage = formatCommitMessage(commitMsg, {
11692
11822
  append: argv.append,
11693
11823
  ticketId: ticketId || undefined,
11694
11824
  appendTicket: argv.appendTicket,
11695
11825
  });
11826
+ // Debug: Log what fullMessage looks like after formatting
11827
+ logger.verbose(`fullMessage after formatting: ${typeof fullMessage === 'string' ? fullMessage : JSON.stringify(fullMessage)}`, { color: 'blue' });
11696
11828
  // If commitlint validation is needed and not skipped, validate the message
11697
11829
  if ((USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) && !shouldSkipCommitlintValidation) {
11698
11830
  const { validateCommitMessage, CommitlintValidationError } = await Promise.resolve().then(function () { return commitlintValidator; });
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.23.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 codeBlockPatterns = [
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*$/ // Raw JSON without blocks
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 codeBlockPatterns) {
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 (jsonString !== result || jsonString.startsWith('{')) {
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, continue to fallback
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 exact structure):
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
- title: text.split('\n')[0] || 'Auto-generated commit',
11703
- body: text.split('\n').slice(1).join('\n') || 'Generated commit message',
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',
@@ -11710,11 +11838,15 @@ IMPORTANT RULES:
11710
11838
  });
11711
11839
  // Construct the full commit message using the utility function
11712
11840
  const ticketId = extractTicketIdFromBranchName(branchName);
11841
+ // Debug: Log what commitMsg looks like before formatting
11842
+ logger.verbose(`commitMsg before formatting: ${JSON.stringify(commitMsg)}`, { color: 'blue' });
11713
11843
  const fullMessage = formatCommitMessage(commitMsg, {
11714
11844
  append: argv.append,
11715
11845
  ticketId: ticketId || undefined,
11716
11846
  appendTicket: argv.appendTicket,
11717
11847
  });
11848
+ // Debug: Log what fullMessage looks like after formatting
11849
+ logger.verbose(`fullMessage after formatting: ${typeof fullMessage === 'string' ? fullMessage : JSON.stringify(fullMessage)}`, { color: 'blue' });
11718
11850
  // If commitlint validation is needed and not skipped, validate the message
11719
11851
  if ((USE_CONVENTIONAL_COMMITS || hasCommitLintConfig) && !shouldSkipCommitlintValidation) {
11720
11852
  const { validateCommitMessage, CommitlintValidationError } = await Promise.resolve().then(function () { return commitlintValidator; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",