@releasekit/notes 0.2.0-next.9 → 0.2.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.cjs CHANGED
@@ -404,6 +404,83 @@ var OpenAICompatibleProvider = class extends BaseLLMProvider {
404
404
 
405
405
  // src/llm/tasks/categorize.ts
406
406
  var import_core3 = require("@releasekit/core");
407
+
408
+ // src/llm/prompts.ts
409
+ function resolvePrompt(taskName, defaultPrompt, promptsConfig) {
410
+ if (!promptsConfig) return defaultPrompt;
411
+ const fullTemplate = promptsConfig.templates?.[taskName];
412
+ if (fullTemplate) return fullTemplate;
413
+ const additionalInstructions = promptsConfig.instructions?.[taskName];
414
+ if (additionalInstructions) {
415
+ const insertionPoint = defaultPrompt.lastIndexOf("Output only valid JSON");
416
+ if (insertionPoint !== -1) {
417
+ return `${defaultPrompt.slice(0, insertionPoint)}Additional instructions:
418
+ ${additionalInstructions}
419
+
420
+ ${defaultPrompt.slice(insertionPoint)}`;
421
+ }
422
+ return `${defaultPrompt}
423
+
424
+ Additional instructions:
425
+ ${additionalInstructions}`;
426
+ }
427
+ return defaultPrompt;
428
+ }
429
+
430
+ // src/llm/scopes.ts
431
+ function getAllowedScopesFromCategories(categories) {
432
+ const scopeMap = /* @__PURE__ */ new Map();
433
+ for (const cat of categories) {
434
+ if (cat.scopes && cat.scopes.length > 0) {
435
+ scopeMap.set(cat.name, cat.scopes);
436
+ }
437
+ }
438
+ return scopeMap;
439
+ }
440
+ function resolveAllowedScopes(scopeConfig, categories, packageNames) {
441
+ if (!scopeConfig || scopeConfig.mode === "unrestricted") return null;
442
+ if (scopeConfig.mode === "none") return [];
443
+ if (scopeConfig.mode === "packages") return packageNames ?? [];
444
+ if (scopeConfig.mode === "restricted") {
445
+ const explicit = scopeConfig.rules?.allowed ?? [];
446
+ const all = new Set(explicit);
447
+ if (categories) {
448
+ const fromCategories = getAllowedScopesFromCategories(categories);
449
+ for (const scopes of fromCategories.values()) {
450
+ for (const s of scopes) all.add(s);
451
+ }
452
+ }
453
+ return [...all];
454
+ }
455
+ return null;
456
+ }
457
+ function validateScope(scope, allowedScopes, rules) {
458
+ if (!scope || allowedScopes === null) return scope;
459
+ if (allowedScopes.length === 0) return void 0;
460
+ const caseSensitive = rules?.caseSensitive ?? false;
461
+ const normalise = (s) => caseSensitive ? s : s.toLowerCase();
462
+ const isAllowed = allowedScopes.some((a) => normalise(a) === normalise(scope));
463
+ if (isAllowed) return scope;
464
+ switch (rules?.invalidScopeAction ?? "remove") {
465
+ case "keep":
466
+ return scope;
467
+ case "fallback":
468
+ return rules?.fallbackScope;
469
+ case "remove":
470
+ default:
471
+ return void 0;
472
+ }
473
+ }
474
+ function validateEntryScopes(entries, scopeConfig, categories) {
475
+ const allowedScopes = resolveAllowedScopes(scopeConfig, categories);
476
+ if (allowedScopes === null) return entries;
477
+ return entries.map((entry) => ({
478
+ ...entry,
479
+ scope: validateScope(entry.scope, allowedScopes, scopeConfig?.rules)
480
+ }));
481
+ }
482
+
483
+ // src/llm/tasks/categorize.ts
407
484
  var DEFAULT_CATEGORIZE_PROMPT = `You are categorizing changelog entries for a software release.
408
485
 
409
486
  Given the following entries, group them into meaningful categories (e.g., "Core", "UI", "API", "Performance", "Bug Fixes", "Documentation").
@@ -415,20 +492,21 @@ Entries:
415
492
 
416
493
  Output only valid JSON, nothing else:`;
417
494
  function buildCustomCategorizePrompt(categories) {
418
- const categoryList = categories.map((c) => `- "${c.name}": ${c.description}`).join("\n");
419
- const developerCategory = categories.find((c) => c.name === "Developer");
495
+ const categoryList = categories.map((c) => {
496
+ const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
497
+ return `- "${c.name}": ${c.description}${scopeInfo}`;
498
+ }).join("\n");
499
+ const scopeMap = getAllowedScopesFromCategories(categories);
420
500
  let scopeInstructions = "";
421
- if (developerCategory) {
422
- const scopeMatch = developerCategory.description.match(/from:\s*([^.]+)/);
423
- if (scopeMatch?.[1]) {
424
- const scopes = scopeMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
425
- if (scopes.length > 0) {
426
- scopeInstructions = `
427
-
428
- For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
429
- `;
430
- }
501
+ if (scopeMap.size > 0) {
502
+ const entries = [];
503
+ for (const [catName, scopes] of scopeMap) {
504
+ entries.push(`For "${catName}", assign a scope from: ${scopes.join(", ")}.`);
431
505
  }
506
+ scopeInstructions = `
507
+
508
+ ${entries.join("\n")}
509
+ Only use scopes from these predefined lists. If an entry does not fit any scope, set scope to null.`;
432
510
  }
433
511
  return `You are categorizing changelog entries for a software release.
434
512
 
@@ -436,9 +514,10 @@ Given the following entries, group them into the specified categories. Only use
436
514
 
437
515
  Categories:
438
516
  ${categoryList}${scopeInstructions}
517
+
439
518
  Output a JSON object with two fields:
440
519
  - "categories": an object where keys are category names and values are arrays of entry indices (0-based)
441
- - "scopes": an object where keys are entry indices (as strings) and values are scope labels
520
+ - "scopes": an object where keys are entry indices (as strings) and values are scope labels. Only include entries that have a valid scope from the predefined list.
442
521
 
443
522
  Entries:
444
523
  {{entries}}
@@ -449,9 +528,11 @@ async function categorizeEntries(provider, entries, context) {
449
528
  if (entries.length === 0) {
450
529
  return [];
451
530
  }
452
- const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
531
+ const entriesCopy = entries.map((e) => ({ ...e, scope: void 0 }));
532
+ const entriesText = entriesCopy.map((e, i) => `${i}. [${e.type}]: ${e.description}`).join("\n");
453
533
  const hasCustomCategories = context.categories && context.categories.length > 0;
454
- const promptTemplate = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
534
+ const defaultPrompt = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
535
+ const promptTemplate = resolvePrompt("categorize", defaultPrompt, context.prompts);
455
536
  const prompt = promptTemplate.replace("{{entries}}", entriesText);
456
537
  try {
457
538
  const response = await provider.complete(prompt);
@@ -463,13 +544,14 @@ async function categorizeEntries(provider, entries, context) {
463
544
  const scopeMap = parsed.scopes || {};
464
545
  for (const [indexStr, scope] of Object.entries(scopeMap)) {
465
546
  const idx = Number.parseInt(indexStr, 10);
466
- if (entries[idx] && scope) {
467
- entries[idx] = { ...entries[idx], scope };
547
+ if (entriesCopy[idx] && scope && scope.trim()) {
548
+ entriesCopy[idx] = { ...entriesCopy[idx], scope: scope.trim() };
468
549
  }
469
550
  }
551
+ const validatedEntries = validateEntryScopes(entriesCopy, context.scopes, context.categories);
470
552
  for (const [category, rawIndices] of Object.entries(categoryMap)) {
471
553
  const indices = Array.isArray(rawIndices) ? rawIndices : [];
472
- const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
554
+ const categoryEntries = indices.map((i) => validatedEntries[i]).filter((e) => e !== void 0);
473
555
  if (categoryEntries.length > 0) {
474
556
  result.push({ category, entries: categoryEntries });
475
557
  }
@@ -478,7 +560,7 @@ async function categorizeEntries(provider, entries, context) {
478
560
  const categoryMap = parsed;
479
561
  for (const [category, rawIndices] of Object.entries(categoryMap)) {
480
562
  const indices = Array.isArray(rawIndices) ? rawIndices : [];
481
- const categoryEntries = indices.map((i) => entries[i]).filter((e) => e !== void 0);
563
+ const categoryEntries = indices.map((i) => entriesCopy[i]).filter((e) => e !== void 0);
482
564
  if (categoryEntries.length > 0) {
483
565
  result.push({ category, entries: categoryEntries });
484
566
  }
@@ -489,12 +571,12 @@ async function categorizeEntries(provider, entries, context) {
489
571
  (0, import_core3.warn)(
490
572
  `LLM categorization failed, falling back to General: ${error instanceof Error ? error.message : String(error)}`
491
573
  );
492
- return [{ category: "General", entries }];
574
+ return [{ category: "General", entries: entriesCopy }];
493
575
  }
494
576
  }
495
577
 
496
578
  // src/llm/tasks/enhance.ts
497
- var ENHANCE_PROMPT = `You are improving changelog entries for a software project.
579
+ var DEFAULT_ENHANCE_PROMPT = `You are improving changelog entries for a software project.
498
580
  Given a technical commit message, rewrite it as a clear, user-friendly changelog entry.
499
581
 
500
582
  Rules:
@@ -510,9 +592,10 @@ Type: {{type}}
510
592
  Description: {{description}}
511
593
 
512
594
  Rewritten description (only output the new description, nothing else):`;
513
- async function enhanceEntry(provider, entry, _context) {
514
- const styleText = _context.style ? `- ${_context.style}` : '- Use present tense ("Add feature" not "Added feature")';
515
- const prompt = ENHANCE_PROMPT.replace("{{style}}", styleText).replace("{{type}}", entry.type).replace("{{#if scope}}Scope: {{scope}}{{/if}}", entry.scope ? `Scope: ${entry.scope}` : "").replace("{{description}}", entry.description);
595
+ async function enhanceEntry(provider, entry, context) {
596
+ const styleText = context.style ? `- ${context.style}` : '- Use present tense ("Add feature" not "Added feature")';
597
+ const defaultPrompt = DEFAULT_ENHANCE_PROMPT.replace("{{style}}", styleText).replace("{{type}}", entry.type).replace("{{#if scope}}Scope: {{scope}}{{/if}}", entry.scope ? `Scope: ${entry.scope}` : "").replace("{{description}}", entry.description);
598
+ const prompt = resolvePrompt("enhance", defaultPrompt, context.prompts);
516
599
  const response = await provider.complete(prompt);
517
600
  return response.trim();
518
601
  }
@@ -568,7 +651,23 @@ function buildPrompt(entries, categories, style) {
568
651
  const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
569
652
  const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
570
653
  const categorySection = categories ? `Categories (use ONLY these):
571
- ${categories.map((c) => `- "${c.name}": ${c.description}`).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
654
+ ${categories.map((c) => {
655
+ const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
656
+ return `- "${c.name}": ${c.description}${scopeInfo}`;
657
+ }).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
658
+ let scopeInstruction = "";
659
+ if (categories) {
660
+ const scopeMap = getAllowedScopesFromCategories(categories);
661
+ if (scopeMap.size > 0) {
662
+ const parts = [];
663
+ for (const [catName, scopes] of scopeMap) {
664
+ parts.push(`For "${catName}" entries, assign a scope from: ${scopes.join(", ")}.`);
665
+ }
666
+ scopeInstruction = `
667
+ ${parts.join("\n")}
668
+ Only use scopes from these predefined lists. Set scope to null if no scope applies.`;
669
+ }
670
+ }
572
671
  return `You are generating release notes for a software project. Given the following changelog entries, do two things:
573
672
 
574
673
  1. **Rewrite** each entry as a clear, user-friendly description
@@ -579,9 +678,7 @@ Style guidelines:
579
678
  - Be concise (1 short sentence per entry)
580
679
  - Focus on what changed, not implementation details
581
680
 
582
- ${categorySection}
583
-
584
- ${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
681
+ ${categorySection}${scopeInstruction}
585
682
 
586
683
  Entries:
587
684
  ${entriesText}
@@ -598,7 +695,8 @@ async function enhanceAndCategorize(provider, entries, context) {
598
695
  const retryOpts = LLM_DEFAULTS.retry;
599
696
  try {
600
697
  return await withRetry(async () => {
601
- const prompt = buildPrompt(entries, context.categories, context.style);
698
+ const defaultPrompt = buildPrompt(entries, context.categories, context.style);
699
+ const prompt = resolvePrompt("enhanceAndCategorize", defaultPrompt, context.prompts);
602
700
  const response = await provider.complete(prompt);
603
701
  const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
604
702
  const parsed = JSON.parse(cleaned);
@@ -614,22 +712,23 @@ async function enhanceAndCategorize(provider, entries, context) {
614
712
  scope: result.scope || original.scope
615
713
  };
616
714
  });
715
+ const validatedEntries = validateEntryScopes(enhancedEntries, context.scopes, context.categories);
617
716
  const categoryMap = /* @__PURE__ */ new Map();
618
717
  for (let i = 0; i < parsed.entries.length; i++) {
619
718
  const result = parsed.entries[i];
620
719
  const category = result?.category || "General";
621
- const entry = enhancedEntries[i];
720
+ const entry = validatedEntries[i];
622
721
  if (!entry) continue;
623
722
  if (!categoryMap.has(category)) {
624
723
  categoryMap.set(category, []);
625
724
  }
626
- categoryMap.get(category).push(entry);
725
+ categoryMap.get(category)?.push(entry);
627
726
  }
628
727
  const categories = [];
629
728
  for (const [category, catEntries] of categoryMap) {
630
729
  categories.push({ category, entries: catEntries });
631
730
  }
632
- return { enhancedEntries, categories };
731
+ return { enhancedEntries: validatedEntries, categories };
633
732
  }, retryOpts);
634
733
  } catch (error) {
635
734
  (0, import_core4.warn)(
@@ -643,7 +742,7 @@ async function enhanceAndCategorize(provider, entries, context) {
643
742
  }
644
743
 
645
744
  // src/llm/tasks/release-notes.ts
646
- var RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
745
+ var DEFAULT_RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
647
746
 
648
747
  Create engaging, user-friendly release notes for the following changes.
649
748
 
@@ -676,16 +775,17 @@ No notable changes in this release.`;
676
775
  if (e.breaking) line += " **BREAKING**";
677
776
  return line;
678
777
  }).join("\n");
679
- const prompt = RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
778
+ const defaultPrompt = DEFAULT_RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
680
779
  "{{#if previousVersion}}Previous version: {{previousVersion}}{{/if}}",
681
780
  context.previousVersion ? `Previous version: ${context.previousVersion}` : ""
682
781
  ).replace("{{date}}", context.date ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "").replace("{{entries}}", entriesText);
782
+ const prompt = resolvePrompt("releaseNotes", defaultPrompt, context.prompts);
683
783
  const response = await provider.complete(prompt);
684
784
  return response.trim();
685
785
  }
686
786
 
687
787
  // src/llm/tasks/summarize.ts
688
- var SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
788
+ var DEFAULT_SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
689
789
 
690
790
  Given the following changelog entries, create a brief summary (2-3 sentences) that captures the main themes of this release.
691
791
 
@@ -693,12 +793,13 @@ Entries:
693
793
  {{entries}}
694
794
 
695
795
  Summary (only output the summary, nothing else):`;
696
- async function summarizeEntries(provider, entries, _context) {
796
+ async function summarizeEntries(provider, entries, context) {
697
797
  if (entries.length === 0) {
698
798
  return "";
699
799
  }
700
800
  const entriesText = entries.map((e) => `- [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
701
- const prompt = SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
801
+ const defaultPrompt = DEFAULT_SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
802
+ const prompt = resolvePrompt("summarize", defaultPrompt, context.prompts);
702
803
  const response = await provider.complete(prompt);
703
804
  return response.trim();
704
805
  }
@@ -846,9 +947,10 @@ ${body}`;
846
947
  function writeMarkdown(outputPath, contexts, config, dryRun) {
847
948
  const content = renderMarkdown(contexts);
848
949
  if (dryRun) {
849
- (0, import_core5.info)("--- Changelog Preview ---");
850
- console.log(content);
851
- (0, import_core5.info)("--- End Preview ---");
950
+ (0, import_core5.info)(`Would write changelog to ${outputPath}`);
951
+ (0, import_core5.debug)("--- Changelog Preview ---");
952
+ (0, import_core5.debug)(content);
953
+ (0, import_core5.debug)("--- End Preview ---");
852
954
  return;
853
955
  }
854
956
  const dir = path.dirname(outputPath);
@@ -1007,9 +1109,10 @@ function renderJson(contexts) {
1007
1109
  function writeJson(outputPath, contexts, dryRun) {
1008
1110
  const content = renderJson(contexts);
1009
1111
  if (dryRun) {
1010
- (0, import_core7.info)("--- JSON Output Preview ---");
1011
- console.log(content);
1012
- (0, import_core7.info)("--- End Preview ---");
1112
+ (0, import_core7.info)(`Would write JSON output to ${outputPath}`);
1113
+ (0, import_core7.debug)("--- JSON Output Preview ---");
1114
+ (0, import_core7.debug)(content);
1115
+ (0, import_core7.debug)("--- End Preview ---");
1013
1116
  return;
1014
1117
  }
1015
1118
  const dir = path2.dirname(outputPath);
@@ -1276,17 +1379,27 @@ function renderTemplate(templatePath, context, engine) {
1276
1379
 
1277
1380
  // src/core/pipeline.ts
1278
1381
  var import_meta = {};
1279
- function generateCompareUrl(repoUrl, from, to) {
1382
+ function generateCompareUrl(repoUrl, from, to, packageName) {
1383
+ const isPackageSpecific = from.includes("@") && packageName && from.includes(packageName);
1384
+ let fromVersion;
1385
+ let toVersion;
1386
+ if (isPackageSpecific) {
1387
+ fromVersion = from;
1388
+ toVersion = `${packageName}@${to.startsWith("v") ? "" : "v"}${to}`;
1389
+ } else {
1390
+ fromVersion = from.replace(/^v/, "");
1391
+ toVersion = to.replace(/^v/, "");
1392
+ }
1280
1393
  if (/gitlab\.com/i.test(repoUrl)) {
1281
- return `${repoUrl}/-/compare/${from}...${to}`;
1394
+ return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
1282
1395
  }
1283
1396
  if (/bitbucket\.org/i.test(repoUrl)) {
1284
- return `${repoUrl}/branches/compare/${from}..${to}`;
1397
+ return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
1285
1398
  }
1286
- return `${repoUrl}/compare/${from}...${to}`;
1399
+ return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
1287
1400
  }
1288
1401
  function createTemplateContext(pkg) {
1289
- const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
1402
+ const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version, pkg.packageName) : void 0;
1290
1403
  return {
1291
1404
  packageName: pkg.packageName,
1292
1405
  version: pkg.version,
@@ -1324,7 +1437,9 @@ async function processWithLLM(context, config) {
1324
1437
  previousVersion: context.previousVersion ?? void 0,
1325
1438
  date: context.date,
1326
1439
  categories: config.llm.categories,
1327
- style: config.llm.style
1440
+ style: config.llm.style,
1441
+ scopes: config.llm.scopes,
1442
+ prompts: config.llm.prompts
1328
1443
  };
1329
1444
  const enhanced = {
1330
1445
  entries: context.entries
@@ -1388,7 +1503,6 @@ async function processWithLLM(context, config) {
1388
1503
  }
1389
1504
  return {
1390
1505
  ...context,
1391
- entries: enhanced.entries,
1392
1506
  enhanced
1393
1507
  };
1394
1508
  } catch (error) {
@@ -1421,9 +1535,10 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1421
1535
  );
1422
1536
  const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1423
1537
  if (dryRun) {
1424
- (0, import_core8.info)("--- Changelog Preview ---");
1425
- console.log(result.content);
1426
- (0, import_core8.info)("--- End Preview ---");
1538
+ (0, import_core8.info)(`Would write templated output to ${outputPath}`);
1539
+ (0, import_core8.debug)("--- Changelog Preview ---");
1540
+ (0, import_core8.debug)(result.content);
1541
+ (0, import_core8.debug)("--- End Preview ---");
1427
1542
  return;
1428
1543
  }
1429
1544
  if (outputPath === "-") {
@@ -1449,8 +1564,10 @@ async function runPipeline(input, config, dryRun) {
1449
1564
  switch (output.format) {
1450
1565
  case "markdown": {
1451
1566
  const file = output.file ?? "CHANGELOG.md";
1452
- if (config.templates?.path || output.options?.template) {
1453
- await generateWithTemplate(contexts, config, file, dryRun);
1567
+ const effectiveTemplateConfig = output.templates ?? config.templates;
1568
+ if (effectiveTemplateConfig?.path || output.options?.template) {
1569
+ const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
1570
+ await generateWithTemplate(contexts, configWithTemplate, file, dryRun);
1454
1571
  } else {
1455
1572
  writeMarkdown(file, contexts, config, dryRun);
1456
1573
  }
@@ -1514,8 +1631,8 @@ function splitByPackage(contexts) {
1514
1631
  // src/monorepo/aggregator.ts
1515
1632
  function writeFile(outputPath, content, dryRun) {
1516
1633
  if (dryRun) {
1517
- (0, import_core9.info)(`[DRY RUN] Would write to ${outputPath}`);
1518
- console.log(content);
1634
+ (0, import_core9.info)(`Would write to ${outputPath}`);
1635
+ (0, import_core9.debug)(content);
1519
1636
  return;
1520
1637
  }
1521
1638
  const dir = path7.dirname(outputPath);
package/dist/index.d.cts CHANGED
@@ -70,6 +70,32 @@ interface LLMOptions {
70
70
  maxTokens?: number;
71
71
  temperature?: number;
72
72
  }
73
+ interface ScopeRules {
74
+ allowed?: string[];
75
+ caseSensitive?: boolean;
76
+ invalidScopeAction?: 'remove' | 'keep' | 'fallback';
77
+ fallbackScope?: string;
78
+ }
79
+ interface ScopeConfig {
80
+ mode?: 'restricted' | 'packages' | 'none' | 'unrestricted';
81
+ rules?: ScopeRules;
82
+ }
83
+ interface LLMPromptOverrides {
84
+ enhance?: string;
85
+ categorize?: string;
86
+ enhanceAndCategorize?: string;
87
+ summarize?: string;
88
+ releaseNotes?: string;
89
+ }
90
+ interface LLMPromptsConfig {
91
+ instructions?: LLMPromptOverrides;
92
+ templates?: LLMPromptOverrides;
93
+ }
94
+ interface LLMCategory {
95
+ name: string;
96
+ description: string;
97
+ scopes?: string[];
98
+ }
73
99
  interface LLMConfig {
74
100
  provider: string;
75
101
  model: string;
@@ -84,17 +110,17 @@ interface LLMConfig {
84
110
  categorize?: boolean;
85
111
  releaseNotes?: boolean;
86
112
  };
87
- categories?: Array<{
88
- name: string;
89
- description: string;
90
- }>;
113
+ categories?: LLMCategory[];
91
114
  style?: string;
115
+ scopes?: ScopeConfig;
116
+ prompts?: LLMPromptsConfig;
92
117
  }
93
118
  type OutputFormat = 'markdown' | 'github-release' | 'json';
94
119
  interface OutputConfig {
95
120
  format: OutputFormat;
96
121
  file?: string;
97
122
  options?: Record<string, unknown>;
123
+ templates?: TemplateConfig;
98
124
  }
99
125
  type MonorepoMode = 'root' | 'packages' | 'both';
100
126
  interface MonorepoConfig {
@@ -179,4 +205,4 @@ declare function writeJson(outputPath: string, contexts: TemplateContext[], dryR
179
205
  declare function renderMarkdown(contexts: TemplateContext[]): string;
180
206
  declare function writeMarkdown(outputPath: string, contexts: TemplateContext[], config: Config, dryRun: boolean): void;
181
207
 
182
- export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMConfig, LLMError, type LLMOptions, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
208
+ export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMCategory, type LLMConfig, LLMError, type LLMOptions, type LLMPromptOverrides, type LLMPromptsConfig, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type ScopeConfig, type ScopeRules, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
package/dist/index.d.ts CHANGED
@@ -70,6 +70,32 @@ interface LLMOptions {
70
70
  maxTokens?: number;
71
71
  temperature?: number;
72
72
  }
73
+ interface ScopeRules {
74
+ allowed?: string[];
75
+ caseSensitive?: boolean;
76
+ invalidScopeAction?: 'remove' | 'keep' | 'fallback';
77
+ fallbackScope?: string;
78
+ }
79
+ interface ScopeConfig {
80
+ mode?: 'restricted' | 'packages' | 'none' | 'unrestricted';
81
+ rules?: ScopeRules;
82
+ }
83
+ interface LLMPromptOverrides {
84
+ enhance?: string;
85
+ categorize?: string;
86
+ enhanceAndCategorize?: string;
87
+ summarize?: string;
88
+ releaseNotes?: string;
89
+ }
90
+ interface LLMPromptsConfig {
91
+ instructions?: LLMPromptOverrides;
92
+ templates?: LLMPromptOverrides;
93
+ }
94
+ interface LLMCategory {
95
+ name: string;
96
+ description: string;
97
+ scopes?: string[];
98
+ }
73
99
  interface LLMConfig {
74
100
  provider: string;
75
101
  model: string;
@@ -84,17 +110,17 @@ interface LLMConfig {
84
110
  categorize?: boolean;
85
111
  releaseNotes?: boolean;
86
112
  };
87
- categories?: Array<{
88
- name: string;
89
- description: string;
90
- }>;
113
+ categories?: LLMCategory[];
91
114
  style?: string;
115
+ scopes?: ScopeConfig;
116
+ prompts?: LLMPromptsConfig;
92
117
  }
93
118
  type OutputFormat = 'markdown' | 'github-release' | 'json';
94
119
  interface OutputConfig {
95
120
  format: OutputFormat;
96
121
  file?: string;
97
122
  options?: Record<string, unknown>;
123
+ templates?: TemplateConfig;
98
124
  }
99
125
  type MonorepoMode = 'root' | 'packages' | 'both';
100
126
  interface MonorepoConfig {
@@ -179,4 +205,4 @@ declare function writeJson(outputPath: string, contexts: TemplateContext[], dryR
179
205
  declare function renderMarkdown(contexts: TemplateContext[]): string;
180
206
  declare function writeMarkdown(outputPath: string, contexts: TemplateContext[], config: Config, dryRun: boolean): void;
181
207
 
182
- export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMConfig, LLMError, type LLMOptions, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
208
+ export { NotesError as ChangelogCreatorError, type ChangelogEntry, type ChangelogInput, type ChangelogType, type CompleteOptions, type Config, ConfigError, type DocumentContext, type EnhancedData, GitHubError, InputParseError, type InputSource, type LLMCategory, type LLMConfig, LLMError, type LLMOptions, type LLMPromptOverrides, type LLMPromptsConfig, type MonorepoConfig, type MonorepoMode, NotesError, type OutputConfig, type OutputFormat, type PackageChangelog, type RetryOptions, type ScopeConfig, type ScopeRules, type TemplateConfig, type TemplateContext, type TemplateEngine, TemplateError, type UpdateStrategy, aggregateToRoot, createTemplateContext, detectMonorepo, getDefaultConfig, getExitCode, loadConfig, parsePackageVersioner, parsePackageVersionerFile, parsePackageVersionerStdin, processInput, renderJson, renderMarkdown, runPipeline, writeJson, writeMarkdown, writeMonorepoChangelogs };
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  writeJson,
25
25
  writeMarkdown,
26
26
  writeMonorepoChangelogs
27
- } from "./chunk-BLWJTLRD.js";
27
+ } from "./chunk-DGZ6TM5J.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.9",
3
+ "version": "0.2.0",
4
4
  "description": "Changelog generation with LLM-powered enhancement and flexible templating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,17 +21,13 @@
21
21
  "bin": {
22
22
  "releasekit-notes": "dist/cli.js"
23
23
  },
24
- "scripts": {
25
- "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
26
- "dev": "tsup src/index.ts src/cli.ts --format esm,cjs --watch --dts",
27
- "clean": "rm -rf dist coverage .turbo",
28
- "test": "vitest run",
29
- "test:unit": "vitest run --coverage",
30
- "test:coverage": "vitest run --coverage",
31
- "lint": "biome check .",
32
- "typecheck": "tsc --noEmit"
33
- },
34
- "keywords": ["changelog", "release-notes", "llm", "template", "cli"],
24
+ "keywords": [
25
+ "changelog",
26
+ "release-notes",
27
+ "llm",
28
+ "template",
29
+ "cli"
30
+ ],
35
31
  "author": "Sam Maister <goosewobbler@protonmail.com>",
36
32
  "license": "MIT",
37
33
  "repository": {
@@ -39,33 +35,48 @@
39
35
  "url": "git+https://github.com/goosewobbler/releasekit.git",
40
36
  "directory": "packages/notes"
41
37
  },
42
- "files": ["dist", "templates", "README.md", "LICENSE"],
38
+ "files": [
39
+ "dist",
40
+ "templates",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
43
44
  "publishConfig": {
44
45
  "access": "public"
45
46
  },
46
47
  "dependencies": {
47
- "@anthropic-ai/sdk": "^0.39.0",
48
- "@octokit/rest": "^21.1.1",
49
- "@releasekit/config": "workspace:*",
50
- "@releasekit/core": "workspace:*",
51
- "chalk": "catalog:",
52
- "commander": "catalog:",
53
- "ejs": "^3.1.10",
48
+ "@anthropic-ai/sdk": "^0.78.0",
49
+ "@octokit/rest": "^22.0.1",
50
+ "chalk": "^5.6.2",
51
+ "commander": "^14.0.3",
52
+ "ejs": "^4.0.1",
54
53
  "handlebars": "^4.7.8",
55
- "liquidjs": "^10.21.0",
56
- "openai": "^4.87.0",
57
- "zod": "catalog:"
54
+ "liquidjs": "^10.25.0",
55
+ "openai": "^6.27.0",
56
+ "zod": "^4.3.6",
57
+ "@releasekit/config": "0.1.0",
58
+ "@releasekit/core": "0.1.0"
58
59
  },
59
60
  "devDependencies": {
60
- "@biomejs/biome": "catalog:",
61
+ "@biomejs/biome": "^2.4.6",
61
62
  "@types/ejs": "^3.1.5",
62
- "@types/node": "catalog:",
63
- "@vitest/coverage-v8": "catalog:",
64
- "tsup": "catalog:",
65
- "typescript": "catalog:",
66
- "vitest": "catalog:"
63
+ "@types/node": "^22.19.15",
64
+ "@vitest/coverage-v8": "^4.1.0",
65
+ "tsup": "^8.5.1",
66
+ "typescript": "^5.9.3",
67
+ "vitest": "^4.1.0"
67
68
  },
68
69
  "engines": {
69
70
  "node": ">=20"
71
+ },
72
+ "scripts": {
73
+ "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
74
+ "dev": "tsup src/index.ts src/cli.ts --format esm,cjs --watch --dts",
75
+ "clean": "rm -rf dist coverage .turbo",
76
+ "test": "vitest run",
77
+ "test:unit": "vitest run --coverage",
78
+ "test:coverage": "vitest run --coverage",
79
+ "lint": "biome check .",
80
+ "typecheck": "tsc --noEmit"
70
81
  }
71
- }
82
+ }