@releasekit/notes 0.2.0-next.2 → 0.2.0-next.3

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.
@@ -307,7 +307,7 @@ function writeJson(outputPath, contexts, dryRun) {
307
307
  // src/core/pipeline.ts
308
308
  import * as fs8 from "fs";
309
309
  import * as path6 from "path";
310
- import { debug, info as info4, success as success4, warn as warn2 } from "@releasekit/core";
310
+ import { debug, info as info4, success as success4, warn as warn3 } from "@releasekit/core";
311
311
 
312
312
  // src/llm/defaults.ts
313
313
  var LLM_DEFAULTS = {
@@ -624,6 +624,81 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
624
624
  return results;
625
625
  }
626
626
 
627
+ // src/llm/tasks/enhance-and-categorize.ts
628
+ import { warn as warn2 } from "@releasekit/core";
629
+ function buildPrompt(entries, categories, style) {
630
+ const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
631
+ const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
632
+ const categorySection = categories ? `Categories (use ONLY these):
633
+ ${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
634
+ return `You are generating release notes for a software project. Given the following changelog entries, do two things:
635
+
636
+ 1. **Rewrite** each entry as a clear, user-friendly description
637
+ 2. **Categorize** each entry into the appropriate category
638
+
639
+ Style guidelines:
640
+ - ${styleText}
641
+ - Be concise (1 short sentence per entry)
642
+ - Focus on what changed, not implementation details
643
+
644
+ ${categorySection}
645
+
646
+ ${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
647
+
648
+ Entries:
649
+ ${entriesText}
650
+
651
+ Output a JSON object with:
652
+ - "entries": array of objects, one per input entry (same order), each with: { "description": "rewritten text", "category": "CategoryName", "scope": "optional subcategory label or null" }
653
+
654
+ Output only valid JSON, nothing else:`;
655
+ }
656
+ async function enhanceAndCategorize(provider, entries, context) {
657
+ if (entries.length === 0) {
658
+ return { enhancedEntries: [], categories: [] };
659
+ }
660
+ const prompt = buildPrompt(entries, context.categories, context.style);
661
+ try {
662
+ const response = await provider.complete(prompt);
663
+ const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
664
+ const parsed = JSON.parse(cleaned);
665
+ if (!Array.isArray(parsed.entries)) {
666
+ throw new Error('Response missing "entries" array');
667
+ }
668
+ const enhancedEntries = entries.map((original, i) => {
669
+ const result = parsed.entries[i];
670
+ if (!result) return original;
671
+ return {
672
+ ...original,
673
+ description: result.description || original.description,
674
+ scope: result.scope || original.scope
675
+ };
676
+ });
677
+ const categoryMap = /* @__PURE__ */ new Map();
678
+ for (let i = 0; i < parsed.entries.length; i++) {
679
+ const result = parsed.entries[i];
680
+ const category = result?.category || "General";
681
+ const entry = enhancedEntries[i];
682
+ if (!entry) continue;
683
+ if (!categoryMap.has(category)) {
684
+ categoryMap.set(category, []);
685
+ }
686
+ categoryMap.get(category).push(entry);
687
+ }
688
+ const categories = [];
689
+ for (const [category, catEntries] of categoryMap) {
690
+ categories.push({ category, entries: catEntries });
691
+ }
692
+ return { enhancedEntries, categories };
693
+ } catch (error) {
694
+ warn2(`Combined enhance+categorize failed: ${error instanceof Error ? error.message : String(error)}`);
695
+ return {
696
+ enhancedEntries: entries,
697
+ categories: [{ category: "General", entries }]
698
+ };
699
+ }
700
+ }
701
+
627
702
  // src/llm/tasks/release-notes.ts
628
703
  var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
629
704
 
@@ -1183,10 +1258,30 @@ async function processWithLLM(context, config) {
1183
1258
  };
1184
1259
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1185
1260
  info4(`Running LLM tasks: ${activeTasks.join(", ")}`);
1186
- if (tasks.enhance) {
1187
- info4("Enhancing entries with LLM...");
1188
- enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1189
- info4(`Enhanced ${enhanced.entries.length} entries`);
1261
+ if (tasks.enhance && tasks.categorize) {
1262
+ info4("Enhancing and categorizing entries with LLM (single call)...");
1263
+ const result = await enhanceAndCategorize(provider, context.entries, llmContext);
1264
+ enhanced.entries = result.enhancedEntries;
1265
+ enhanced.categories = {};
1266
+ for (const cat of result.categories) {
1267
+ enhanced.categories[cat.category] = cat.entries;
1268
+ }
1269
+ info4(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1270
+ } else {
1271
+ if (tasks.enhance) {
1272
+ info4("Enhancing entries with LLM...");
1273
+ enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1274
+ info4(`Enhanced ${enhanced.entries.length} entries`);
1275
+ }
1276
+ if (tasks.categorize) {
1277
+ info4("Categorizing entries with LLM...");
1278
+ const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1279
+ enhanced.categories = {};
1280
+ for (const cat of categorized) {
1281
+ enhanced.categories[cat.category] = cat.entries;
1282
+ }
1283
+ info4(`Created ${categorized.length} categories`);
1284
+ }
1190
1285
  }
1191
1286
  if (tasks.summarize) {
1192
1287
  info4("Summarizing entries with LLM...");
@@ -1195,17 +1290,8 @@ async function processWithLLM(context, config) {
1195
1290
  info4("Summary generated successfully");
1196
1291
  debug(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1197
1292
  } else {
1198
- warn2("Summary generation returned empty result");
1199
- }
1200
- }
1201
- if (tasks.categorize) {
1202
- info4("Categorizing entries with LLM...");
1203
- const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1204
- enhanced.categories = {};
1205
- for (const cat of categorized) {
1206
- enhanced.categories[cat.category] = cat.entries;
1293
+ warn3("Summary generation returned empty result");
1207
1294
  }
1208
- info4(`Created ${categorized.length} categories`);
1209
1295
  }
1210
1296
  if (tasks.releaseNotes) {
1211
1297
  info4("Generating release notes with LLM...");
@@ -1213,7 +1299,7 @@ async function processWithLLM(context, config) {
1213
1299
  if (enhanced.releaseNotes) {
1214
1300
  info4("Release notes generated successfully");
1215
1301
  } else {
1216
- warn2("Release notes generation returned empty result");
1302
+ warn3("Release notes generation returned empty result");
1217
1303
  }
1218
1304
  }
1219
1305
  return {
@@ -1222,8 +1308,8 @@ async function processWithLLM(context, config) {
1222
1308
  enhanced
1223
1309
  };
1224
1310
  } catch (error) {
1225
- warn2(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1226
- warn2("Falling back to raw entries");
1311
+ warn3(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1312
+ warn3("Falling back to raw entries");
1227
1313
  return context;
1228
1314
  }
1229
1315
  }
@@ -1294,17 +1380,17 @@ async function runPipeline(input, config, dryRun) {
1294
1380
  }
1295
1381
  const firstContext = contexts[0];
1296
1382
  if (!firstContext) {
1297
- warn2("No context available for GitHub release");
1383
+ warn3("No context available for GitHub release");
1298
1384
  break;
1299
1385
  }
1300
1386
  const repoUrl = firstContext.repoUrl;
1301
1387
  if (!repoUrl) {
1302
- warn2("No repo URL available, cannot create GitHub release");
1388
+ warn3("No repo URL available, cannot create GitHub release");
1303
1389
  break;
1304
1390
  }
1305
1391
  const parsed = parseRepoUrl(repoUrl);
1306
1392
  if (!parsed) {
1307
- warn2(`Could not parse repo URL: ${repoUrl}`);
1393
+ warn3(`Could not parse repo URL: ${repoUrl}`);
1308
1394
  break;
1309
1395
  }
1310
1396
  await createGitHubRelease(firstContext, {
package/dist/cli.cjs CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli.ts
27
27
  var fs10 = __toESM(require("fs"), 1);
28
28
  var readline = __toESM(require("readline"), 1);
29
- var import_core9 = require("@releasekit/core");
29
+ var import_core10 = require("@releasekit/core");
30
30
  var import_commander = require("commander");
31
31
 
32
32
  // src/core/config.ts
@@ -45,7 +45,7 @@ function getDefaultConfig() {
45
45
  // src/core/pipeline.ts
46
46
  var fs8 = __toESM(require("fs"), 1);
47
47
  var path6 = __toESM(require("path"), 1);
48
- var import_core7 = require("@releasekit/core");
48
+ var import_core8 = require("@releasekit/core");
49
49
 
50
50
  // src/input/package-versioner.ts
51
51
  var fs = __toESM(require("fs"), 1);
@@ -474,6 +474,81 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
474
474
  return results;
475
475
  }
476
476
 
477
+ // src/llm/tasks/enhance-and-categorize.ts
478
+ var import_core4 = require("@releasekit/core");
479
+ function buildPrompt(entries, categories, style) {
480
+ const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
481
+ const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
482
+ const categorySection = categories ? `Categories (use ONLY these):
483
+ ${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
484
+ return `You are generating release notes for a software project. Given the following changelog entries, do two things:
485
+
486
+ 1. **Rewrite** each entry as a clear, user-friendly description
487
+ 2. **Categorize** each entry into the appropriate category
488
+
489
+ Style guidelines:
490
+ - ${styleText}
491
+ - Be concise (1 short sentence per entry)
492
+ - Focus on what changed, not implementation details
493
+
494
+ ${categorySection}
495
+
496
+ ${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
497
+
498
+ Entries:
499
+ ${entriesText}
500
+
501
+ Output a JSON object with:
502
+ - "entries": array of objects, one per input entry (same order), each with: { "description": "rewritten text", "category": "CategoryName", "scope": "optional subcategory label or null" }
503
+
504
+ Output only valid JSON, nothing else:`;
505
+ }
506
+ async function enhanceAndCategorize(provider, entries, context) {
507
+ if (entries.length === 0) {
508
+ return { enhancedEntries: [], categories: [] };
509
+ }
510
+ const prompt = buildPrompt(entries, context.categories, context.style);
511
+ try {
512
+ const response = await provider.complete(prompt);
513
+ const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
514
+ const parsed = JSON.parse(cleaned);
515
+ if (!Array.isArray(parsed.entries)) {
516
+ throw new Error('Response missing "entries" array');
517
+ }
518
+ const enhancedEntries = entries.map((original, i) => {
519
+ const result = parsed.entries[i];
520
+ if (!result) return original;
521
+ return {
522
+ ...original,
523
+ description: result.description || original.description,
524
+ scope: result.scope || original.scope
525
+ };
526
+ });
527
+ const categoryMap = /* @__PURE__ */ new Map();
528
+ for (let i = 0; i < parsed.entries.length; i++) {
529
+ const result = parsed.entries[i];
530
+ const category = result?.category || "General";
531
+ const entry = enhancedEntries[i];
532
+ if (!entry) continue;
533
+ if (!categoryMap.has(category)) {
534
+ categoryMap.set(category, []);
535
+ }
536
+ categoryMap.get(category).push(entry);
537
+ }
538
+ const categories = [];
539
+ for (const [category, catEntries] of categoryMap) {
540
+ categories.push({ category, entries: catEntries });
541
+ }
542
+ return { enhancedEntries, categories };
543
+ } catch (error2) {
544
+ (0, import_core4.warn)(`Combined enhance+categorize failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
545
+ return {
546
+ enhancedEntries: entries,
547
+ categories: [{ category: "General", entries }]
548
+ };
549
+ }
550
+ }
551
+
477
552
  // src/llm/tasks/release-notes.ts
478
553
  var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
479
554
 
@@ -574,12 +649,12 @@ function createProvider(config) {
574
649
 
575
650
  // src/output/github-release.ts
576
651
  var import_rest = require("@octokit/rest");
577
- var import_core5 = require("@releasekit/core");
652
+ var import_core6 = require("@releasekit/core");
578
653
 
579
654
  // src/output/markdown.ts
580
655
  var fs2 = __toESM(require("fs"), 1);
581
656
  var path = __toESM(require("path"), 1);
582
- var import_core4 = require("@releasekit/core");
657
+ var import_core5 = require("@releasekit/core");
583
658
  var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
584
659
  var TYPE_LABELS = {
585
660
  added: "Added",
@@ -678,9 +753,9 @@ ${body}`;
678
753
  function writeMarkdown(outputPath, contexts, config, dryRun) {
679
754
  const content = renderMarkdown(contexts);
680
755
  if (dryRun) {
681
- (0, import_core4.info)("--- Changelog Preview ---");
756
+ (0, import_core5.info)("--- Changelog Preview ---");
682
757
  console.log(content);
683
- (0, import_core4.info)("--- End Preview ---");
758
+ (0, import_core5.info)("--- End Preview ---");
684
759
  return;
685
760
  }
686
761
  const dir = path.dirname(outputPath);
@@ -696,7 +771,7 @@ function writeMarkdown(outputPath, contexts, config, dryRun) {
696
771
  } else {
697
772
  fs2.writeFileSync(outputPath, content, "utf-8");
698
773
  }
699
- (0, import_core4.success)(`Changelog written to ${outputPath}`);
774
+ (0, import_core5.success)(`Changelog written to ${outputPath}`);
700
775
  }
701
776
 
702
777
  // src/output/github-release.ts
@@ -721,7 +796,7 @@ var GitHubClient = class {
721
796
  } else {
722
797
  body = renderMarkdown([context]);
723
798
  }
724
- (0, import_core5.info)(`Creating GitHub release for ${tagName}`);
799
+ (0, import_core6.info)(`Creating GitHub release for ${tagName}`);
725
800
  try {
726
801
  const response = await this.octokit.repos.createRelease({
727
802
  owner: this.owner,
@@ -733,7 +808,7 @@ var GitHubClient = class {
733
808
  prerelease: options.prerelease ?? false,
734
809
  generate_release_notes: options.generateNotes ?? false
735
810
  });
736
- (0, import_core5.success)(`Release created: ${response.data.html_url}`);
811
+ (0, import_core6.success)(`Release created: ${response.data.html_url}`);
737
812
  return {
738
813
  id: response.data.id,
739
814
  htmlUrl: response.data.html_url,
@@ -751,7 +826,7 @@ var GitHubClient = class {
751
826
  } else {
752
827
  body = renderMarkdown([context]);
753
828
  }
754
- (0, import_core5.info)(`Updating GitHub release ${releaseId}`);
829
+ (0, import_core6.info)(`Updating GitHub release ${releaseId}`);
755
830
  try {
756
831
  const response = await this.octokit.repos.updateRelease({
757
832
  owner: this.owner,
@@ -763,7 +838,7 @@ var GitHubClient = class {
763
838
  draft: options.draft ?? false,
764
839
  prerelease: options.prerelease ?? false
765
840
  });
766
- (0, import_core5.success)(`Release updated: ${response.data.html_url}`);
841
+ (0, import_core6.success)(`Release updated: ${response.data.html_url}`);
767
842
  return {
768
843
  id: response.data.id,
769
844
  htmlUrl: response.data.html_url,
@@ -815,7 +890,7 @@ async function createGitHubRelease(context, options) {
815
890
  // src/output/json.ts
816
891
  var fs3 = __toESM(require("fs"), 1);
817
892
  var path2 = __toESM(require("path"), 1);
818
- var import_core6 = require("@releasekit/core");
893
+ var import_core7 = require("@releasekit/core");
819
894
  function renderJson(contexts) {
820
895
  return JSON.stringify(
821
896
  {
@@ -835,9 +910,9 @@ function renderJson(contexts) {
835
910
  function writeJson(outputPath, contexts, dryRun) {
836
911
  const content = renderJson(contexts);
837
912
  if (dryRun) {
838
- (0, import_core6.info)("--- JSON Output Preview ---");
913
+ (0, import_core7.info)("--- JSON Output Preview ---");
839
914
  console.log(content);
840
- (0, import_core6.info)("--- End Preview ---");
915
+ (0, import_core7.info)("--- End Preview ---");
841
916
  return;
842
917
  }
843
918
  const dir = path2.dirname(outputPath);
@@ -845,7 +920,7 @@ function writeJson(outputPath, contexts, dryRun) {
845
920
  fs3.mkdirSync(dir, { recursive: true });
846
921
  }
847
922
  fs3.writeFileSync(outputPath, content, "utf-8");
848
- (0, import_core6.success)(`JSON output written to ${outputPath}`);
923
+ (0, import_core7.success)(`JSON output written to ${outputPath}`);
849
924
  }
850
925
 
851
926
  // src/templates/ejs.ts
@@ -1183,9 +1258,9 @@ async function processWithLLM(context, config) {
1183
1258
  entries: context.entries
1184
1259
  };
1185
1260
  try {
1186
- (0, import_core7.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1261
+ (0, import_core8.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1187
1262
  if (config.llm.baseURL) {
1188
- (0, import_core7.info)(`LLM base URL: ${config.llm.baseURL}`);
1263
+ (0, import_core8.info)(`LLM base URL: ${config.llm.baseURL}`);
1189
1264
  }
1190
1265
  const rawProvider = createProvider(config.llm);
1191
1266
  const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
@@ -1194,38 +1269,49 @@ async function processWithLLM(context, config) {
1194
1269
  complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
1195
1270
  };
1196
1271
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1197
- (0, import_core7.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
1198
- if (tasks.enhance) {
1199
- (0, import_core7.info)("Enhancing entries with LLM...");
1200
- enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1201
- (0, import_core7.info)(`Enhanced ${enhanced.entries.length} entries`);
1272
+ (0, import_core8.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
1273
+ if (tasks.enhance && tasks.categorize) {
1274
+ (0, import_core8.info)("Enhancing and categorizing entries with LLM (single call)...");
1275
+ const result = await enhanceAndCategorize(provider, context.entries, llmContext);
1276
+ enhanced.entries = result.enhancedEntries;
1277
+ enhanced.categories = {};
1278
+ for (const cat of result.categories) {
1279
+ enhanced.categories[cat.category] = cat.entries;
1280
+ }
1281
+ (0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1282
+ } else {
1283
+ if (tasks.enhance) {
1284
+ (0, import_core8.info)("Enhancing entries with LLM...");
1285
+ enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1286
+ (0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries`);
1287
+ }
1288
+ if (tasks.categorize) {
1289
+ (0, import_core8.info)("Categorizing entries with LLM...");
1290
+ const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1291
+ enhanced.categories = {};
1292
+ for (const cat of categorized) {
1293
+ enhanced.categories[cat.category] = cat.entries;
1294
+ }
1295
+ (0, import_core8.info)(`Created ${categorized.length} categories`);
1296
+ }
1202
1297
  }
1203
1298
  if (tasks.summarize) {
1204
- (0, import_core7.info)("Summarizing entries with LLM...");
1299
+ (0, import_core8.info)("Summarizing entries with LLM...");
1205
1300
  enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
1206
1301
  if (enhanced.summary) {
1207
- (0, import_core7.info)("Summary generated successfully");
1208
- (0, import_core7.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1302
+ (0, import_core8.info)("Summary generated successfully");
1303
+ (0, import_core8.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1209
1304
  } else {
1210
- (0, import_core7.warn)("Summary generation returned empty result");
1305
+ (0, import_core8.warn)("Summary generation returned empty result");
1211
1306
  }
1212
1307
  }
1213
- if (tasks.categorize) {
1214
- (0, import_core7.info)("Categorizing entries with LLM...");
1215
- const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1216
- enhanced.categories = {};
1217
- for (const cat of categorized) {
1218
- enhanced.categories[cat.category] = cat.entries;
1219
- }
1220
- (0, import_core7.info)(`Created ${categorized.length} categories`);
1221
- }
1222
1308
  if (tasks.releaseNotes) {
1223
- (0, import_core7.info)("Generating release notes with LLM...");
1309
+ (0, import_core8.info)("Generating release notes with LLM...");
1224
1310
  enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
1225
1311
  if (enhanced.releaseNotes) {
1226
- (0, import_core7.info)("Release notes generated successfully");
1312
+ (0, import_core8.info)("Release notes generated successfully");
1227
1313
  } else {
1228
- (0, import_core7.warn)("Release notes generation returned empty result");
1314
+ (0, import_core8.warn)("Release notes generation returned empty result");
1229
1315
  }
1230
1316
  }
1231
1317
  return {
@@ -1234,8 +1320,8 @@ async function processWithLLM(context, config) {
1234
1320
  enhanced
1235
1321
  };
1236
1322
  } catch (error2) {
1237
- (0, import_core7.warn)(`LLM processing failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
1238
- (0, import_core7.warn)("Falling back to raw entries");
1323
+ (0, import_core8.warn)(`LLM processing failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
1324
+ (0, import_core8.warn)("Falling back to raw entries");
1239
1325
  return context;
1240
1326
  }
1241
1327
  }
@@ -1263,9 +1349,9 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1263
1349
  );
1264
1350
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1265
1351
  if (dryRun) {
1266
- (0, import_core7.info)("--- Changelog Preview ---");
1352
+ (0, import_core8.info)("--- Changelog Preview ---");
1267
1353
  console.log(result.content);
1268
- (0, import_core7.info)("--- End Preview ---");
1354
+ (0, import_core8.info)("--- End Preview ---");
1269
1355
  return;
1270
1356
  }
1271
1357
  const dir = path6.dirname(outputPath);
@@ -1273,17 +1359,17 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1273
1359
  fs8.mkdirSync(dir, { recursive: true });
1274
1360
  }
1275
1361
  fs8.writeFileSync(outputPath, result.content, "utf-8");
1276
- (0, import_core7.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1362
+ (0, import_core8.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1277
1363
  }
1278
1364
  async function runPipeline(input, config, dryRun) {
1279
- (0, import_core7.debug)(`Processing ${input.packages.length} package(s)`);
1365
+ (0, import_core8.debug)(`Processing ${input.packages.length} package(s)`);
1280
1366
  let contexts = input.packages.map(createTemplateContext);
1281
1367
  if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1282
- (0, import_core7.info)("Processing with LLM enhancement");
1368
+ (0, import_core8.info)("Processing with LLM enhancement");
1283
1369
  contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1284
1370
  }
1285
1371
  for (const output of config.output) {
1286
- (0, import_core7.info)(`Generating ${output.format} output`);
1372
+ (0, import_core8.info)(`Generating ${output.format} output`);
1287
1373
  switch (output.format) {
1288
1374
  case "markdown": {
1289
1375
  const file = output.file ?? "CHANGELOG.md";
@@ -1301,22 +1387,22 @@ async function runPipeline(input, config, dryRun) {
1301
1387
  }
1302
1388
  case "github-release": {
1303
1389
  if (dryRun) {
1304
- (0, import_core7.info)("[DRY RUN] Would create GitHub release");
1390
+ (0, import_core8.info)("[DRY RUN] Would create GitHub release");
1305
1391
  break;
1306
1392
  }
1307
1393
  const firstContext = contexts[0];
1308
1394
  if (!firstContext) {
1309
- (0, import_core7.warn)("No context available for GitHub release");
1395
+ (0, import_core8.warn)("No context available for GitHub release");
1310
1396
  break;
1311
1397
  }
1312
1398
  const repoUrl = firstContext.repoUrl;
1313
1399
  if (!repoUrl) {
1314
- (0, import_core7.warn)("No repo URL available, cannot create GitHub release");
1400
+ (0, import_core8.warn)("No repo URL available, cannot create GitHub release");
1315
1401
  break;
1316
1402
  }
1317
1403
  const parsed = parseRepoUrl(repoUrl);
1318
1404
  if (!parsed) {
1319
- (0, import_core7.warn)(`Could not parse repo URL: ${repoUrl}`);
1405
+ (0, import_core8.warn)(`Could not parse repo URL: ${repoUrl}`);
1320
1406
  break;
1321
1407
  }
1322
1408
  await createGitHubRelease(firstContext, {
@@ -1334,7 +1420,7 @@ async function runPipeline(input, config, dryRun) {
1334
1420
  // src/monorepo/aggregator.ts
1335
1421
  var fs9 = __toESM(require("fs"), 1);
1336
1422
  var path7 = __toESM(require("path"), 1);
1337
- var import_core8 = require("@releasekit/core");
1423
+ var import_core9 = require("@releasekit/core");
1338
1424
 
1339
1425
  // src/monorepo/splitter.ts
1340
1426
  function splitByPackage(contexts) {
@@ -1348,7 +1434,7 @@ function splitByPackage(contexts) {
1348
1434
  // src/monorepo/aggregator.ts
1349
1435
  function writeFile(outputPath, content, dryRun) {
1350
1436
  if (dryRun) {
1351
- (0, import_core8.info)(`[DRY RUN] Would write to ${outputPath}`);
1437
+ (0, import_core9.info)(`[DRY RUN] Would write to ${outputPath}`);
1352
1438
  console.log(content);
1353
1439
  return;
1354
1440
  }
@@ -1357,7 +1443,7 @@ function writeFile(outputPath, content, dryRun) {
1357
1443
  fs9.mkdirSync(dir, { recursive: true });
1358
1444
  }
1359
1445
  fs9.writeFileSync(outputPath, content, "utf-8");
1360
- (0, import_core8.success)(`Changelog written to ${outputPath}`);
1446
+ (0, import_core9.success)(`Changelog written to ${outputPath}`);
1361
1447
  }
1362
1448
  function aggregateToRoot(contexts) {
1363
1449
  const aggregated = {
@@ -1382,7 +1468,7 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1382
1468
  if (options.mode === "root" || options.mode === "both") {
1383
1469
  const aggregated = aggregateToRoot(contexts);
1384
1470
  const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
1385
- (0, import_core8.info)(`Writing root changelog to ${rootPath}`);
1471
+ (0, import_core9.info)(`Writing root changelog to ${rootPath}`);
1386
1472
  const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
1387
1473
  writeFile(rootPath, rootContent, dryRun);
1388
1474
  }
@@ -1394,11 +1480,11 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1394
1480
  const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
1395
1481
  if (packageDir) {
1396
1482
  const changelogPath = path7.join(packageDir, "CHANGELOG.md");
1397
- (0, import_core8.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1483
+ (0, import_core9.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1398
1484
  const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
1399
1485
  writeFile(changelogPath, pkgContent, dryRun);
1400
1486
  } else {
1401
- (0, import_core8.info)(`Could not find directory for package ${packageName}, skipping`);
1487
+ (0, import_core9.info)(`Could not find directory for package ${packageName}, skipping`);
1402
1488
  }
1403
1489
  }
1404
1490
  }
@@ -1465,7 +1551,7 @@ var program = new import_commander.Command();
1465
1551
  program.name("releasekit-notes").description("Generate changelogs with LLM-powered enhancement and flexible templating").version("0.1.0");
1466
1552
  program.command("generate", { isDefault: true }).description("Generate changelog from input data").option("-i, --input <file>", "Input file (default: stdin)").option("-o, --output <spec>", "Output spec (format:file)", collectOutputs, []).option("-t, --template <path>", "Template file or directory").option("-e, --engine <engine>", "Template engine (handlebars|liquid|ejs)").option("--monorepo <mode>", "Monorepo mode (root|packages|both)").option("--llm-provider <provider>", "LLM provider").option("--llm-model <model>", "LLM model").option("--llm-base-url <url>", "LLM base URL (for openai-compatible provider)").option("--llm-tasks <tasks>", "Comma-separated LLM tasks").option("--no-llm", "Disable LLM processing").option("--target <package>", "Filter to a specific package name").option("--config <path>", "Config file path").option("--dry-run", "Preview without writing").option("--regenerate", "Regenerate entire changelog").option("-v, --verbose", "Increase verbosity", increaseVerbosity, 0).option("-q, --quiet", "Suppress non-error output").action(async (options) => {
1467
1553
  setVerbosity(options.verbose);
1468
- if (options.quiet) (0, import_core9.setQuietMode)(true);
1554
+ if (options.quiet) (0, import_core10.setQuietMode)(true);
1469
1555
  try {
1470
1556
  const config = loadConfig(process.cwd(), options.config);
1471
1557
  if (options.output.length > 0) {
@@ -1484,7 +1570,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1484
1570
  config.templates = { ...config.templates, engine: options.engine };
1485
1571
  }
1486
1572
  if (options.llm === false) {
1487
- (0, import_core9.info)("LLM processing disabled via --no-llm flag");
1573
+ (0, import_core10.info)("LLM processing disabled via --no-llm flag");
1488
1574
  delete config.llm;
1489
1575
  } else if (options.llmProvider || options.llmModel || options.llmBaseUrl || options.llmTasks) {
1490
1576
  config.llm = config.llm ?? { provider: "openai-compatible", model: "" };
@@ -1500,13 +1586,13 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1500
1586
  releaseNotes: taskNames.includes("release-notes") || taskNames.includes("releaseNotes")
1501
1587
  };
1502
1588
  }
1503
- (0, import_core9.info)(`LLM configured: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1589
+ (0, import_core10.info)(`LLM configured: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1504
1590
  if (config.llm.baseURL) {
1505
- (0, import_core9.info)(`LLM base URL: ${config.llm.baseURL}`);
1591
+ (0, import_core10.info)(`LLM base URL: ${config.llm.baseURL}`);
1506
1592
  }
1507
1593
  const taskList = Object.entries(config.llm.tasks || {}).filter(([, enabled]) => enabled).map(([name]) => name).join(", ");
1508
1594
  if (taskList) {
1509
- (0, import_core9.info)(`LLM tasks: ${taskList}`);
1595
+ (0, import_core10.info)(`LLM tasks: ${taskList}`);
1510
1596
  }
1511
1597
  }
1512
1598
  let inputJson;
@@ -1520,19 +1606,19 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1520
1606
  const before = input.packages.length;
1521
1607
  input.packages = input.packages.filter((p) => p.packageName === options.target);
1522
1608
  if (input.packages.length === 0) {
1523
- (0, import_core9.info)(`No changelog found for package "${options.target}" (had ${before} package(s))`);
1609
+ (0, import_core10.info)(`No changelog found for package "${options.target}" (had ${before} package(s))`);
1524
1610
  return;
1525
1611
  }
1526
- (0, import_core9.info)(`Filtered to package: ${options.target}`);
1612
+ (0, import_core10.info)(`Filtered to package: ${options.target}`);
1527
1613
  }
1528
1614
  if (options.monorepo || config.monorepo) {
1529
1615
  const monorepoMode = options.monorepo ?? config.monorepo?.mode ?? "both";
1530
1616
  const detected = detectMonorepo(process.cwd());
1531
1617
  if (!detected.isMonorepo) {
1532
- (0, import_core9.info)("No monorepo detected, using single package mode");
1618
+ (0, import_core10.info)("No monorepo detected, using single package mode");
1533
1619
  await runPipeline(input, config, options.dryRun ?? false);
1534
1620
  } else {
1535
- (0, import_core9.info)(`Monorepo detected with packages at ${detected.packagesPath}`);
1621
+ (0, import_core10.info)(`Monorepo detected with packages at ${detected.packagesPath}`);
1536
1622
  const contexts = input.packages.map(createTemplateContext);
1537
1623
  writeMonorepoChangelogs(
1538
1624
  contexts,
@@ -1549,9 +1635,9 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1549
1635
  await runPipeline(input, config, options.dryRun ?? false);
1550
1636
  }
1551
1637
  if (options.dryRun) {
1552
- (0, import_core9.info)("Dry run complete - no files were written");
1638
+ (0, import_core10.info)("Dry run complete - no files were written");
1553
1639
  } else {
1554
- (0, import_core9.success)("Changelog generation complete");
1640
+ (0, import_core10.success)("Changelog generation complete");
1555
1641
  }
1556
1642
  } catch (err) {
1557
1643
  handleError(err);
@@ -1560,7 +1646,7 @@ program.command("generate", { isDefault: true }).description("Generate changelog
1560
1646
  program.command("init").description("Create default configuration file").option("-f, --force", "Overwrite existing config").action((options) => {
1561
1647
  const configPath = "releasekit.config.json";
1562
1648
  if (fs10.existsSync(configPath) && !options.force) {
1563
- (0, import_core9.error)(`Config file already exists at ${configPath}. Use --force to overwrite.`);
1649
+ (0, import_core10.error)(`Config file already exists at ${configPath}. Use --force to overwrite.`);
1564
1650
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1565
1651
  }
1566
1652
  const defaultConfig = {
@@ -1571,7 +1657,7 @@ program.command("init").description("Create default configuration file").option(
1571
1657
  }
1572
1658
  };
1573
1659
  fs10.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
1574
- (0, import_core9.success)(`Created config file at ${configPath}`);
1660
+ (0, import_core10.success)(`Created config file at ${configPath}`);
1575
1661
  });
1576
1662
  program.command("auth <provider>").description("Configure API key for an LLM provider").option("--key <key>", "API key (omit to be prompted)").action(async (provider, options) => {
1577
1663
  let apiKey;
@@ -1581,14 +1667,14 @@ program.command("auth <provider>").description("Configure API key for an LLM pro
1581
1667
  apiKey = await promptSecret(`Enter API key for ${provider}: `);
1582
1668
  }
1583
1669
  if (!apiKey.trim()) {
1584
- (0, import_core9.error)("API key cannot be empty");
1670
+ (0, import_core10.error)("API key cannot be empty");
1585
1671
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1586
1672
  }
1587
1673
  (0, import_config.saveAuth)(provider, apiKey.trim());
1588
- (0, import_core9.success)(`API key saved for ${provider}`);
1674
+ (0, import_core10.success)(`API key saved for ${provider}`);
1589
1675
  });
1590
1676
  program.command("providers").description("List available LLM providers").action(() => {
1591
- (0, import_core9.info)("Available LLM providers:");
1677
+ (0, import_core10.info)("Available LLM providers:");
1592
1678
  console.log(" openai - OpenAI (GPT models)");
1593
1679
  console.log(" anthropic - Anthropic (Claude models)");
1594
1680
  console.log(" ollama - Ollama (local models)");
@@ -1609,7 +1695,7 @@ function increaseVerbosity(_, previous) {
1609
1695
  }
1610
1696
  function setVerbosity(level) {
1611
1697
  const levels = ["info", "debug", "trace"];
1612
- (0, import_core9.setLogLevel)(levels[Math.min(level, levels.length - 1)] ?? "info");
1698
+ (0, import_core10.setLogLevel)(levels[Math.min(level, levels.length - 1)] ?? "info");
1613
1699
  }
1614
1700
  async function readStdin() {
1615
1701
  const chunks = [];
@@ -1632,7 +1718,7 @@ function handleError(err) {
1632
1718
  err.logError();
1633
1719
  process.exit(getExitCode(err));
1634
1720
  }
1635
- (0, import_core9.error)(err instanceof Error ? err.message : String(err));
1721
+ (0, import_core10.error)(err instanceof Error ? err.message : String(err));
1636
1722
  process.exit(import_core2.EXIT_CODES.GENERAL_ERROR);
1637
1723
  }
1638
1724
  program.parse();
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  runPipeline,
12
12
  saveAuth,
13
13
  writeMonorepoChangelogs
14
- } from "./chunk-W7DVGQ7D.js";
14
+ } from "./chunk-H44KSHFJ.js";
15
15
 
16
16
  // src/cli.ts
17
17
  import * as fs from "fs";
package/dist/index.cjs CHANGED
@@ -75,7 +75,7 @@ function getDefaultConfig() {
75
75
  // src/core/pipeline.ts
76
76
  var fs8 = __toESM(require("fs"), 1);
77
77
  var path6 = __toESM(require("path"), 1);
78
- var import_core7 = require("@releasekit/core");
78
+ var import_core8 = require("@releasekit/core");
79
79
 
80
80
  // src/input/package-versioner.ts
81
81
  var fs = __toESM(require("fs"), 1);
@@ -524,6 +524,81 @@ async function enhanceEntries(provider, entries, context, concurrency = LLM_DEFA
524
524
  return results;
525
525
  }
526
526
 
527
+ // src/llm/tasks/enhance-and-categorize.ts
528
+ var import_core4 = require("@releasekit/core");
529
+ function buildPrompt(entries, categories, style) {
530
+ const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
531
+ const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
532
+ const categorySection = categories ? `Categories (use ONLY these):
533
+ ${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
534
+ return `You are generating release notes for a software project. Given the following changelog entries, do two things:
535
+
536
+ 1. **Rewrite** each entry as a clear, user-friendly description
537
+ 2. **Categorize** each entry into the appropriate category
538
+
539
+ Style guidelines:
540
+ - ${styleText}
541
+ - Be concise (1 short sentence per entry)
542
+ - Focus on what changed, not implementation details
543
+
544
+ ${categorySection}
545
+
546
+ ${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
547
+
548
+ Entries:
549
+ ${entriesText}
550
+
551
+ Output a JSON object with:
552
+ - "entries": array of objects, one per input entry (same order), each with: { "description": "rewritten text", "category": "CategoryName", "scope": "optional subcategory label or null" }
553
+
554
+ Output only valid JSON, nothing else:`;
555
+ }
556
+ async function enhanceAndCategorize(provider, entries, context) {
557
+ if (entries.length === 0) {
558
+ return { enhancedEntries: [], categories: [] };
559
+ }
560
+ const prompt = buildPrompt(entries, context.categories, context.style);
561
+ try {
562
+ const response = await provider.complete(prompt);
563
+ const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
564
+ const parsed = JSON.parse(cleaned);
565
+ if (!Array.isArray(parsed.entries)) {
566
+ throw new Error('Response missing "entries" array');
567
+ }
568
+ const enhancedEntries = entries.map((original, i) => {
569
+ const result = parsed.entries[i];
570
+ if (!result) return original;
571
+ return {
572
+ ...original,
573
+ description: result.description || original.description,
574
+ scope: result.scope || original.scope
575
+ };
576
+ });
577
+ const categoryMap = /* @__PURE__ */ new Map();
578
+ for (let i = 0; i < parsed.entries.length; i++) {
579
+ const result = parsed.entries[i];
580
+ const category = result?.category || "General";
581
+ const entry = enhancedEntries[i];
582
+ if (!entry) continue;
583
+ if (!categoryMap.has(category)) {
584
+ categoryMap.set(category, []);
585
+ }
586
+ categoryMap.get(category).push(entry);
587
+ }
588
+ const categories = [];
589
+ for (const [category, catEntries] of categoryMap) {
590
+ categories.push({ category, entries: catEntries });
591
+ }
592
+ return { enhancedEntries, categories };
593
+ } catch (error) {
594
+ (0, import_core4.warn)(`Combined enhance+categorize failed: ${error instanceof Error ? error.message : String(error)}`);
595
+ return {
596
+ enhancedEntries: entries,
597
+ categories: [{ category: "General", entries }]
598
+ };
599
+ }
600
+ }
601
+
527
602
  // src/llm/tasks/release-notes.ts
528
603
  var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
529
604
 
@@ -624,12 +699,12 @@ function createProvider(config) {
624
699
 
625
700
  // src/output/github-release.ts
626
701
  var import_rest = require("@octokit/rest");
627
- var import_core5 = require("@releasekit/core");
702
+ var import_core6 = require("@releasekit/core");
628
703
 
629
704
  // src/output/markdown.ts
630
705
  var fs2 = __toESM(require("fs"), 1);
631
706
  var path = __toESM(require("path"), 1);
632
- var import_core4 = require("@releasekit/core");
707
+ var import_core5 = require("@releasekit/core");
633
708
  var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
634
709
  var TYPE_LABELS = {
635
710
  added: "Added",
@@ -728,9 +803,9 @@ ${body}`;
728
803
  function writeMarkdown(outputPath, contexts, config, dryRun) {
729
804
  const content = renderMarkdown(contexts);
730
805
  if (dryRun) {
731
- (0, import_core4.info)("--- Changelog Preview ---");
806
+ (0, import_core5.info)("--- Changelog Preview ---");
732
807
  console.log(content);
733
- (0, import_core4.info)("--- End Preview ---");
808
+ (0, import_core5.info)("--- End Preview ---");
734
809
  return;
735
810
  }
736
811
  const dir = path.dirname(outputPath);
@@ -746,7 +821,7 @@ function writeMarkdown(outputPath, contexts, config, dryRun) {
746
821
  } else {
747
822
  fs2.writeFileSync(outputPath, content, "utf-8");
748
823
  }
749
- (0, import_core4.success)(`Changelog written to ${outputPath}`);
824
+ (0, import_core5.success)(`Changelog written to ${outputPath}`);
750
825
  }
751
826
 
752
827
  // src/output/github-release.ts
@@ -771,7 +846,7 @@ var GitHubClient = class {
771
846
  } else {
772
847
  body = renderMarkdown([context]);
773
848
  }
774
- (0, import_core5.info)(`Creating GitHub release for ${tagName}`);
849
+ (0, import_core6.info)(`Creating GitHub release for ${tagName}`);
775
850
  try {
776
851
  const response = await this.octokit.repos.createRelease({
777
852
  owner: this.owner,
@@ -783,7 +858,7 @@ var GitHubClient = class {
783
858
  prerelease: options.prerelease ?? false,
784
859
  generate_release_notes: options.generateNotes ?? false
785
860
  });
786
- (0, import_core5.success)(`Release created: ${response.data.html_url}`);
861
+ (0, import_core6.success)(`Release created: ${response.data.html_url}`);
787
862
  return {
788
863
  id: response.data.id,
789
864
  htmlUrl: response.data.html_url,
@@ -801,7 +876,7 @@ var GitHubClient = class {
801
876
  } else {
802
877
  body = renderMarkdown([context]);
803
878
  }
804
- (0, import_core5.info)(`Updating GitHub release ${releaseId}`);
879
+ (0, import_core6.info)(`Updating GitHub release ${releaseId}`);
805
880
  try {
806
881
  const response = await this.octokit.repos.updateRelease({
807
882
  owner: this.owner,
@@ -813,7 +888,7 @@ var GitHubClient = class {
813
888
  draft: options.draft ?? false,
814
889
  prerelease: options.prerelease ?? false
815
890
  });
816
- (0, import_core5.success)(`Release updated: ${response.data.html_url}`);
891
+ (0, import_core6.success)(`Release updated: ${response.data.html_url}`);
817
892
  return {
818
893
  id: response.data.id,
819
894
  htmlUrl: response.data.html_url,
@@ -865,7 +940,7 @@ async function createGitHubRelease(context, options) {
865
940
  // src/output/json.ts
866
941
  var fs3 = __toESM(require("fs"), 1);
867
942
  var path2 = __toESM(require("path"), 1);
868
- var import_core6 = require("@releasekit/core");
943
+ var import_core7 = require("@releasekit/core");
869
944
  function renderJson(contexts) {
870
945
  return JSON.stringify(
871
946
  {
@@ -885,9 +960,9 @@ function renderJson(contexts) {
885
960
  function writeJson(outputPath, contexts, dryRun) {
886
961
  const content = renderJson(contexts);
887
962
  if (dryRun) {
888
- (0, import_core6.info)("--- JSON Output Preview ---");
963
+ (0, import_core7.info)("--- JSON Output Preview ---");
889
964
  console.log(content);
890
- (0, import_core6.info)("--- End Preview ---");
965
+ (0, import_core7.info)("--- End Preview ---");
891
966
  return;
892
967
  }
893
968
  const dir = path2.dirname(outputPath);
@@ -895,7 +970,7 @@ function writeJson(outputPath, contexts, dryRun) {
895
970
  fs3.mkdirSync(dir, { recursive: true });
896
971
  }
897
972
  fs3.writeFileSync(outputPath, content, "utf-8");
898
- (0, import_core6.success)(`JSON output written to ${outputPath}`);
973
+ (0, import_core7.success)(`JSON output written to ${outputPath}`);
899
974
  }
900
975
 
901
976
  // src/templates/ejs.ts
@@ -1233,9 +1308,9 @@ async function processWithLLM(context, config) {
1233
1308
  entries: context.entries
1234
1309
  };
1235
1310
  try {
1236
- (0, import_core7.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1311
+ (0, import_core8.info)(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1237
1312
  if (config.llm.baseURL) {
1238
- (0, import_core7.info)(`LLM base URL: ${config.llm.baseURL}`);
1313
+ (0, import_core8.info)(`LLM base URL: ${config.llm.baseURL}`);
1239
1314
  }
1240
1315
  const rawProvider = createProvider(config.llm);
1241
1316
  const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
@@ -1244,38 +1319,49 @@ async function processWithLLM(context, config) {
1244
1319
  complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, opts), retryOpts)
1245
1320
  };
1246
1321
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
1247
- (0, import_core7.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
1248
- if (tasks.enhance) {
1249
- (0, import_core7.info)("Enhancing entries with LLM...");
1250
- enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1251
- (0, import_core7.info)(`Enhanced ${enhanced.entries.length} entries`);
1322
+ (0, import_core8.info)(`Running LLM tasks: ${activeTasks.join(", ")}`);
1323
+ if (tasks.enhance && tasks.categorize) {
1324
+ (0, import_core8.info)("Enhancing and categorizing entries with LLM (single call)...");
1325
+ const result = await enhanceAndCategorize(provider, context.entries, llmContext);
1326
+ enhanced.entries = result.enhancedEntries;
1327
+ enhanced.categories = {};
1328
+ for (const cat of result.categories) {
1329
+ enhanced.categories[cat.category] = cat.entries;
1330
+ }
1331
+ (0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries into ${result.categories.length} categories`);
1332
+ } else {
1333
+ if (tasks.enhance) {
1334
+ (0, import_core8.info)("Enhancing entries with LLM...");
1335
+ enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1336
+ (0, import_core8.info)(`Enhanced ${enhanced.entries.length} entries`);
1337
+ }
1338
+ if (tasks.categorize) {
1339
+ (0, import_core8.info)("Categorizing entries with LLM...");
1340
+ const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1341
+ enhanced.categories = {};
1342
+ for (const cat of categorized) {
1343
+ enhanced.categories[cat.category] = cat.entries;
1344
+ }
1345
+ (0, import_core8.info)(`Created ${categorized.length} categories`);
1346
+ }
1252
1347
  }
1253
1348
  if (tasks.summarize) {
1254
- (0, import_core7.info)("Summarizing entries with LLM...");
1349
+ (0, import_core8.info)("Summarizing entries with LLM...");
1255
1350
  enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
1256
1351
  if (enhanced.summary) {
1257
- (0, import_core7.info)("Summary generated successfully");
1258
- (0, import_core7.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1352
+ (0, import_core8.info)("Summary generated successfully");
1353
+ (0, import_core8.debug)(`Summary: ${enhanced.summary.substring(0, 100)}...`);
1259
1354
  } else {
1260
- (0, import_core7.warn)("Summary generation returned empty result");
1355
+ (0, import_core8.warn)("Summary generation returned empty result");
1261
1356
  }
1262
1357
  }
1263
- if (tasks.categorize) {
1264
- (0, import_core7.info)("Categorizing entries with LLM...");
1265
- const categorized = await categorizeEntries(provider, enhanced.entries, llmContext);
1266
- enhanced.categories = {};
1267
- for (const cat of categorized) {
1268
- enhanced.categories[cat.category] = cat.entries;
1269
- }
1270
- (0, import_core7.info)(`Created ${categorized.length} categories`);
1271
- }
1272
1358
  if (tasks.releaseNotes) {
1273
- (0, import_core7.info)("Generating release notes with LLM...");
1359
+ (0, import_core8.info)("Generating release notes with LLM...");
1274
1360
  enhanced.releaseNotes = await generateReleaseNotes(provider, enhanced.entries, llmContext);
1275
1361
  if (enhanced.releaseNotes) {
1276
- (0, import_core7.info)("Release notes generated successfully");
1362
+ (0, import_core8.info)("Release notes generated successfully");
1277
1363
  } else {
1278
- (0, import_core7.warn)("Release notes generation returned empty result");
1364
+ (0, import_core8.warn)("Release notes generation returned empty result");
1279
1365
  }
1280
1366
  }
1281
1367
  return {
@@ -1284,8 +1370,8 @@ async function processWithLLM(context, config) {
1284
1370
  enhanced
1285
1371
  };
1286
1372
  } catch (error) {
1287
- (0, import_core7.warn)(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1288
- (0, import_core7.warn)("Falling back to raw entries");
1373
+ (0, import_core8.warn)(`LLM processing failed: ${error instanceof Error ? error.message : String(error)}`);
1374
+ (0, import_core8.warn)("Falling back to raw entries");
1289
1375
  return context;
1290
1376
  }
1291
1377
  }
@@ -1313,9 +1399,9 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1313
1399
  );
1314
1400
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1315
1401
  if (dryRun) {
1316
- (0, import_core7.info)("--- Changelog Preview ---");
1402
+ (0, import_core8.info)("--- Changelog Preview ---");
1317
1403
  console.log(result.content);
1318
- (0, import_core7.info)("--- End Preview ---");
1404
+ (0, import_core8.info)("--- End Preview ---");
1319
1405
  return;
1320
1406
  }
1321
1407
  const dir = path6.dirname(outputPath);
@@ -1323,17 +1409,17 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1323
1409
  fs8.mkdirSync(dir, { recursive: true });
1324
1410
  }
1325
1411
  fs8.writeFileSync(outputPath, result.content, "utf-8");
1326
- (0, import_core7.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1412
+ (0, import_core8.success)(`Changelog written to ${outputPath} (using ${result.engine} template)`);
1327
1413
  }
1328
1414
  async function runPipeline(input, config, dryRun) {
1329
- (0, import_core7.debug)(`Processing ${input.packages.length} package(s)`);
1415
+ (0, import_core8.debug)(`Processing ${input.packages.length} package(s)`);
1330
1416
  let contexts = input.packages.map(createTemplateContext);
1331
1417
  if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1332
- (0, import_core7.info)("Processing with LLM enhancement");
1418
+ (0, import_core8.info)("Processing with LLM enhancement");
1333
1419
  contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1334
1420
  }
1335
1421
  for (const output of config.output) {
1336
- (0, import_core7.info)(`Generating ${output.format} output`);
1422
+ (0, import_core8.info)(`Generating ${output.format} output`);
1337
1423
  switch (output.format) {
1338
1424
  case "markdown": {
1339
1425
  const file = output.file ?? "CHANGELOG.md";
@@ -1351,22 +1437,22 @@ async function runPipeline(input, config, dryRun) {
1351
1437
  }
1352
1438
  case "github-release": {
1353
1439
  if (dryRun) {
1354
- (0, import_core7.info)("[DRY RUN] Would create GitHub release");
1440
+ (0, import_core8.info)("[DRY RUN] Would create GitHub release");
1355
1441
  break;
1356
1442
  }
1357
1443
  const firstContext = contexts[0];
1358
1444
  if (!firstContext) {
1359
- (0, import_core7.warn)("No context available for GitHub release");
1445
+ (0, import_core8.warn)("No context available for GitHub release");
1360
1446
  break;
1361
1447
  }
1362
1448
  const repoUrl = firstContext.repoUrl;
1363
1449
  if (!repoUrl) {
1364
- (0, import_core7.warn)("No repo URL available, cannot create GitHub release");
1450
+ (0, import_core8.warn)("No repo URL available, cannot create GitHub release");
1365
1451
  break;
1366
1452
  }
1367
1453
  const parsed = parseRepoUrl(repoUrl);
1368
1454
  if (!parsed) {
1369
- (0, import_core7.warn)(`Could not parse repo URL: ${repoUrl}`);
1455
+ (0, import_core8.warn)(`Could not parse repo URL: ${repoUrl}`);
1370
1456
  break;
1371
1457
  }
1372
1458
  await createGitHubRelease(firstContext, {
@@ -1388,7 +1474,7 @@ async function processInput(inputJson, config, dryRun) {
1388
1474
  // src/monorepo/aggregator.ts
1389
1475
  var fs9 = __toESM(require("fs"), 1);
1390
1476
  var path7 = __toESM(require("path"), 1);
1391
- var import_core8 = require("@releasekit/core");
1477
+ var import_core9 = require("@releasekit/core");
1392
1478
 
1393
1479
  // src/monorepo/splitter.ts
1394
1480
  function splitByPackage(contexts) {
@@ -1402,7 +1488,7 @@ function splitByPackage(contexts) {
1402
1488
  // src/monorepo/aggregator.ts
1403
1489
  function writeFile(outputPath, content, dryRun) {
1404
1490
  if (dryRun) {
1405
- (0, import_core8.info)(`[DRY RUN] Would write to ${outputPath}`);
1491
+ (0, import_core9.info)(`[DRY RUN] Would write to ${outputPath}`);
1406
1492
  console.log(content);
1407
1493
  return;
1408
1494
  }
@@ -1411,7 +1497,7 @@ function writeFile(outputPath, content, dryRun) {
1411
1497
  fs9.mkdirSync(dir, { recursive: true });
1412
1498
  }
1413
1499
  fs9.writeFileSync(outputPath, content, "utf-8");
1414
- (0, import_core8.success)(`Changelog written to ${outputPath}`);
1500
+ (0, import_core9.success)(`Changelog written to ${outputPath}`);
1415
1501
  }
1416
1502
  function aggregateToRoot(contexts) {
1417
1503
  const aggregated = {
@@ -1436,7 +1522,7 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1436
1522
  if (options.mode === "root" || options.mode === "both") {
1437
1523
  const aggregated = aggregateToRoot(contexts);
1438
1524
  const rootPath = path7.join(options.rootPath, "CHANGELOG.md");
1439
- (0, import_core8.info)(`Writing root changelog to ${rootPath}`);
1525
+ (0, import_core9.info)(`Writing root changelog to ${rootPath}`);
1440
1526
  const rootContent = config.updateStrategy === "prepend" && fs9.existsSync(rootPath) ? prependVersion(rootPath, aggregated) : renderMarkdown([aggregated]);
1441
1527
  writeFile(rootPath, rootContent, dryRun);
1442
1528
  }
@@ -1448,11 +1534,11 @@ function writeMonorepoChangelogs(contexts, options, config, dryRun) {
1448
1534
  const packageDir = packageDirMap.get(packageName) ?? (simpleName ? packageDirMap.get(simpleName) : void 0) ?? null;
1449
1535
  if (packageDir) {
1450
1536
  const changelogPath = path7.join(packageDir, "CHANGELOG.md");
1451
- (0, import_core8.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1537
+ (0, import_core9.info)(`Writing changelog for ${packageName} to ${changelogPath}`);
1452
1538
  const pkgContent = config.updateStrategy === "prepend" && fs9.existsSync(changelogPath) ? prependVersion(changelogPath, ctx) : renderMarkdown([ctx]);
1453
1539
  writeFile(changelogPath, pkgContent, dryRun);
1454
1540
  } else {
1455
- (0, import_core8.info)(`Could not find directory for package ${packageName}, skipping`);
1541
+ (0, import_core9.info)(`Could not find directory for package ${packageName}, skipping`);
1456
1542
  }
1457
1543
  }
1458
1544
  }
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  writeJson,
25
25
  writeMarkdown,
26
26
  writeMonorepoChangelogs
27
- } from "./chunk-W7DVGQ7D.js";
27
+ } from "./chunk-H44KSHFJ.js";
28
28
  export {
29
29
  NotesError as ChangelogCreatorError,
30
30
  ConfigError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@releasekit/notes",
3
- "version": "0.2.0-next.2",
3
+ "version": "0.2.0-next.3",
4
4
  "description": "Changelog generation with LLM-powered enhancement and flexible templating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",