@releasekit/notes 0.2.0-next.8 → 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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/{chunk-BLWJTLRD.js → chunk-DGZ6TM5J.js} +180 -63
- package/dist/cli.cjs +174 -57
- package/dist/cli.js +1 -1
- package/dist/index.cjs +174 -57
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +1 -1
- package/package.json +41 -30
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sam Maister
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ async function parsePackageVersionerStdin() {
|
|
|
148
148
|
// src/output/markdown.ts
|
|
149
149
|
import * as fs2 from "fs";
|
|
150
150
|
import * as path from "path";
|
|
151
|
-
import { info, success } from "@releasekit/core";
|
|
151
|
+
import { debug, info, success } from "@releasekit/core";
|
|
152
152
|
var TYPE_ORDER = ["added", "changed", "deprecated", "removed", "fixed", "security"];
|
|
153
153
|
var TYPE_LABELS = {
|
|
154
154
|
added: "Added",
|
|
@@ -247,9 +247,10 @@ ${body}`;
|
|
|
247
247
|
function writeMarkdown(outputPath, contexts, config, dryRun) {
|
|
248
248
|
const content = renderMarkdown(contexts);
|
|
249
249
|
if (dryRun) {
|
|
250
|
-
info(
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
info(`Would write changelog to ${outputPath}`);
|
|
251
|
+
debug("--- Changelog Preview ---");
|
|
252
|
+
debug(content);
|
|
253
|
+
debug("--- End Preview ---");
|
|
253
254
|
return;
|
|
254
255
|
}
|
|
255
256
|
const dir = path.dirname(outputPath);
|
|
@@ -275,7 +276,7 @@ function writeMarkdown(outputPath, contexts, config, dryRun) {
|
|
|
275
276
|
// src/output/json.ts
|
|
276
277
|
import * as fs3 from "fs";
|
|
277
278
|
import * as path2 from "path";
|
|
278
|
-
import { info as info2, success as success2 } from "@releasekit/core";
|
|
279
|
+
import { debug as debug2, info as info2, success as success2 } from "@releasekit/core";
|
|
279
280
|
function renderJson(contexts) {
|
|
280
281
|
return JSON.stringify(
|
|
281
282
|
{
|
|
@@ -295,9 +296,10 @@ function renderJson(contexts) {
|
|
|
295
296
|
function writeJson(outputPath, contexts, dryRun) {
|
|
296
297
|
const content = renderJson(contexts);
|
|
297
298
|
if (dryRun) {
|
|
298
|
-
info2(
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
info2(`Would write JSON output to ${outputPath}`);
|
|
300
|
+
debug2("--- JSON Output Preview ---");
|
|
301
|
+
debug2(content);
|
|
302
|
+
debug2("--- End Preview ---");
|
|
301
303
|
return;
|
|
302
304
|
}
|
|
303
305
|
const dir = path2.dirname(outputPath);
|
|
@@ -311,7 +313,7 @@ function writeJson(outputPath, contexts, dryRun) {
|
|
|
311
313
|
// src/core/pipeline.ts
|
|
312
314
|
import * as fs8 from "fs";
|
|
313
315
|
import * as path6 from "path";
|
|
314
|
-
import { debug, info as info4, success as success4, warn as warn3 } from "@releasekit/core";
|
|
316
|
+
import { debug as debug3, info as info4, success as success4, warn as warn3 } from "@releasekit/core";
|
|
315
317
|
|
|
316
318
|
// src/llm/defaults.ts
|
|
317
319
|
var LLM_DEFAULTS = {
|
|
@@ -508,6 +510,83 @@ var OpenAICompatibleProvider = class extends BaseLLMProvider {
|
|
|
508
510
|
|
|
509
511
|
// src/llm/tasks/categorize.ts
|
|
510
512
|
import { warn } from "@releasekit/core";
|
|
513
|
+
|
|
514
|
+
// src/llm/prompts.ts
|
|
515
|
+
function resolvePrompt(taskName, defaultPrompt, promptsConfig) {
|
|
516
|
+
if (!promptsConfig) return defaultPrompt;
|
|
517
|
+
const fullTemplate = promptsConfig.templates?.[taskName];
|
|
518
|
+
if (fullTemplate) return fullTemplate;
|
|
519
|
+
const additionalInstructions = promptsConfig.instructions?.[taskName];
|
|
520
|
+
if (additionalInstructions) {
|
|
521
|
+
const insertionPoint = defaultPrompt.lastIndexOf("Output only valid JSON");
|
|
522
|
+
if (insertionPoint !== -1) {
|
|
523
|
+
return `${defaultPrompt.slice(0, insertionPoint)}Additional instructions:
|
|
524
|
+
${additionalInstructions}
|
|
525
|
+
|
|
526
|
+
${defaultPrompt.slice(insertionPoint)}`;
|
|
527
|
+
}
|
|
528
|
+
return `${defaultPrompt}
|
|
529
|
+
|
|
530
|
+
Additional instructions:
|
|
531
|
+
${additionalInstructions}`;
|
|
532
|
+
}
|
|
533
|
+
return defaultPrompt;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/llm/scopes.ts
|
|
537
|
+
function getAllowedScopesFromCategories(categories) {
|
|
538
|
+
const scopeMap = /* @__PURE__ */ new Map();
|
|
539
|
+
for (const cat of categories) {
|
|
540
|
+
if (cat.scopes && cat.scopes.length > 0) {
|
|
541
|
+
scopeMap.set(cat.name, cat.scopes);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return scopeMap;
|
|
545
|
+
}
|
|
546
|
+
function resolveAllowedScopes(scopeConfig, categories, packageNames) {
|
|
547
|
+
if (!scopeConfig || scopeConfig.mode === "unrestricted") return null;
|
|
548
|
+
if (scopeConfig.mode === "none") return [];
|
|
549
|
+
if (scopeConfig.mode === "packages") return packageNames ?? [];
|
|
550
|
+
if (scopeConfig.mode === "restricted") {
|
|
551
|
+
const explicit = scopeConfig.rules?.allowed ?? [];
|
|
552
|
+
const all = new Set(explicit);
|
|
553
|
+
if (categories) {
|
|
554
|
+
const fromCategories = getAllowedScopesFromCategories(categories);
|
|
555
|
+
for (const scopes of fromCategories.values()) {
|
|
556
|
+
for (const s of scopes) all.add(s);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return [...all];
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
function validateScope(scope, allowedScopes, rules) {
|
|
564
|
+
if (!scope || allowedScopes === null) return scope;
|
|
565
|
+
if (allowedScopes.length === 0) return void 0;
|
|
566
|
+
const caseSensitive = rules?.caseSensitive ?? false;
|
|
567
|
+
const normalise = (s) => caseSensitive ? s : s.toLowerCase();
|
|
568
|
+
const isAllowed = allowedScopes.some((a) => normalise(a) === normalise(scope));
|
|
569
|
+
if (isAllowed) return scope;
|
|
570
|
+
switch (rules?.invalidScopeAction ?? "remove") {
|
|
571
|
+
case "keep":
|
|
572
|
+
return scope;
|
|
573
|
+
case "fallback":
|
|
574
|
+
return rules?.fallbackScope;
|
|
575
|
+
case "remove":
|
|
576
|
+
default:
|
|
577
|
+
return void 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function validateEntryScopes(entries, scopeConfig, categories) {
|
|
581
|
+
const allowedScopes = resolveAllowedScopes(scopeConfig, categories);
|
|
582
|
+
if (allowedScopes === null) return entries;
|
|
583
|
+
return entries.map((entry) => ({
|
|
584
|
+
...entry,
|
|
585
|
+
scope: validateScope(entry.scope, allowedScopes, scopeConfig?.rules)
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/llm/tasks/categorize.ts
|
|
511
590
|
var DEFAULT_CATEGORIZE_PROMPT = `You are categorizing changelog entries for a software release.
|
|
512
591
|
|
|
513
592
|
Given the following entries, group them into meaningful categories (e.g., "Core", "UI", "API", "Performance", "Bug Fixes", "Documentation").
|
|
@@ -519,20 +598,21 @@ Entries:
|
|
|
519
598
|
|
|
520
599
|
Output only valid JSON, nothing else:`;
|
|
521
600
|
function buildCustomCategorizePrompt(categories) {
|
|
522
|
-
const categoryList = categories.map((c) =>
|
|
523
|
-
|
|
601
|
+
const categoryList = categories.map((c) => {
|
|
602
|
+
const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
|
|
603
|
+
return `- "${c.name}": ${c.description}${scopeInfo}`;
|
|
604
|
+
}).join("\n");
|
|
605
|
+
const scopeMap = getAllowedScopesFromCategories(categories);
|
|
524
606
|
let scopeInstructions = "";
|
|
525
|
-
if (
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (scopes.length > 0) {
|
|
530
|
-
scopeInstructions = `
|
|
531
|
-
|
|
532
|
-
For the "Developer" category, you MUST assign a scope from this exact list: ${scopes.join(", ")}.
|
|
533
|
-
`;
|
|
534
|
-
}
|
|
607
|
+
if (scopeMap.size > 0) {
|
|
608
|
+
const entries = [];
|
|
609
|
+
for (const [catName, scopes] of scopeMap) {
|
|
610
|
+
entries.push(`For "${catName}", assign a scope from: ${scopes.join(", ")}.`);
|
|
535
611
|
}
|
|
612
|
+
scopeInstructions = `
|
|
613
|
+
|
|
614
|
+
${entries.join("\n")}
|
|
615
|
+
Only use scopes from these predefined lists. If an entry does not fit any scope, set scope to null.`;
|
|
536
616
|
}
|
|
537
617
|
return `You are categorizing changelog entries for a software release.
|
|
538
618
|
|
|
@@ -540,9 +620,10 @@ Given the following entries, group them into the specified categories. Only use
|
|
|
540
620
|
|
|
541
621
|
Categories:
|
|
542
622
|
${categoryList}${scopeInstructions}
|
|
623
|
+
|
|
543
624
|
Output a JSON object with two fields:
|
|
544
625
|
- "categories": an object where keys are category names and values are arrays of entry indices (0-based)
|
|
545
|
-
- "scopes": an object where keys are entry indices (as strings) and values are scope labels
|
|
626
|
+
- "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.
|
|
546
627
|
|
|
547
628
|
Entries:
|
|
548
629
|
{{entries}}
|
|
@@ -553,9 +634,11 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
553
634
|
if (entries.length === 0) {
|
|
554
635
|
return [];
|
|
555
636
|
}
|
|
556
|
-
const
|
|
637
|
+
const entriesCopy = entries.map((e) => ({ ...e, scope: void 0 }));
|
|
638
|
+
const entriesText = entriesCopy.map((e, i) => `${i}. [${e.type}]: ${e.description}`).join("\n");
|
|
557
639
|
const hasCustomCategories = context.categories && context.categories.length > 0;
|
|
558
|
-
const
|
|
640
|
+
const defaultPrompt = hasCustomCategories ? buildCustomCategorizePrompt(context.categories) : DEFAULT_CATEGORIZE_PROMPT;
|
|
641
|
+
const promptTemplate = resolvePrompt("categorize", defaultPrompt, context.prompts);
|
|
559
642
|
const prompt = promptTemplate.replace("{{entries}}", entriesText);
|
|
560
643
|
try {
|
|
561
644
|
const response = await provider.complete(prompt);
|
|
@@ -567,13 +650,14 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
567
650
|
const scopeMap = parsed.scopes || {};
|
|
568
651
|
for (const [indexStr, scope] of Object.entries(scopeMap)) {
|
|
569
652
|
const idx = Number.parseInt(indexStr, 10);
|
|
570
|
-
if (
|
|
571
|
-
|
|
653
|
+
if (entriesCopy[idx] && scope && scope.trim()) {
|
|
654
|
+
entriesCopy[idx] = { ...entriesCopy[idx], scope: scope.trim() };
|
|
572
655
|
}
|
|
573
656
|
}
|
|
657
|
+
const validatedEntries = validateEntryScopes(entriesCopy, context.scopes, context.categories);
|
|
574
658
|
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
575
659
|
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
576
|
-
const categoryEntries = indices.map((i) =>
|
|
660
|
+
const categoryEntries = indices.map((i) => validatedEntries[i]).filter((e) => e !== void 0);
|
|
577
661
|
if (categoryEntries.length > 0) {
|
|
578
662
|
result.push({ category, entries: categoryEntries });
|
|
579
663
|
}
|
|
@@ -582,7 +666,7 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
582
666
|
const categoryMap = parsed;
|
|
583
667
|
for (const [category, rawIndices] of Object.entries(categoryMap)) {
|
|
584
668
|
const indices = Array.isArray(rawIndices) ? rawIndices : [];
|
|
585
|
-
const categoryEntries = indices.map((i) =>
|
|
669
|
+
const categoryEntries = indices.map((i) => entriesCopy[i]).filter((e) => e !== void 0);
|
|
586
670
|
if (categoryEntries.length > 0) {
|
|
587
671
|
result.push({ category, entries: categoryEntries });
|
|
588
672
|
}
|
|
@@ -593,12 +677,12 @@ async function categorizeEntries(provider, entries, context) {
|
|
|
593
677
|
warn(
|
|
594
678
|
`LLM categorization failed, falling back to General: ${error instanceof Error ? error.message : String(error)}`
|
|
595
679
|
);
|
|
596
|
-
return [{ category: "General", entries }];
|
|
680
|
+
return [{ category: "General", entries: entriesCopy }];
|
|
597
681
|
}
|
|
598
682
|
}
|
|
599
683
|
|
|
600
684
|
// src/llm/tasks/enhance.ts
|
|
601
|
-
var
|
|
685
|
+
var DEFAULT_ENHANCE_PROMPT = `You are improving changelog entries for a software project.
|
|
602
686
|
Given a technical commit message, rewrite it as a clear, user-friendly changelog entry.
|
|
603
687
|
|
|
604
688
|
Rules:
|
|
@@ -614,9 +698,10 @@ Type: {{type}}
|
|
|
614
698
|
Description: {{description}}
|
|
615
699
|
|
|
616
700
|
Rewritten description (only output the new description, nothing else):`;
|
|
617
|
-
async function enhanceEntry(provider, entry,
|
|
618
|
-
const styleText =
|
|
619
|
-
const
|
|
701
|
+
async function enhanceEntry(provider, entry, context) {
|
|
702
|
+
const styleText = context.style ? `- ${context.style}` : '- Use present tense ("Add feature" not "Added feature")';
|
|
703
|
+
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);
|
|
704
|
+
const prompt = resolvePrompt("enhance", defaultPrompt, context.prompts);
|
|
620
705
|
const response = await provider.complete(prompt);
|
|
621
706
|
return response.trim();
|
|
622
707
|
}
|
|
@@ -672,7 +757,23 @@ function buildPrompt(entries, categories, style) {
|
|
|
672
757
|
const entriesText = entries.map((e, i) => `${i}. [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
|
|
673
758
|
const styleText = style || 'Use present tense ("Add feature" not "Added feature"). Be concise.';
|
|
674
759
|
const categorySection = categories ? `Categories (use ONLY these):
|
|
675
|
-
${categories.map((c) =>
|
|
760
|
+
${categories.map((c) => {
|
|
761
|
+
const scopeInfo = c.scopes?.length ? ` Allowed scopes: ${c.scopes.join(", ")}.` : "";
|
|
762
|
+
return `- "${c.name}": ${c.description}${scopeInfo}`;
|
|
763
|
+
}).join("\n")}` : `Categories: Group into meaningful categories (e.g., "New", "Fixed", "Changed", "Removed").`;
|
|
764
|
+
let scopeInstruction = "";
|
|
765
|
+
if (categories) {
|
|
766
|
+
const scopeMap = getAllowedScopesFromCategories(categories);
|
|
767
|
+
if (scopeMap.size > 0) {
|
|
768
|
+
const parts = [];
|
|
769
|
+
for (const [catName, scopes] of scopeMap) {
|
|
770
|
+
parts.push(`For "${catName}" entries, assign a scope from: ${scopes.join(", ")}.`);
|
|
771
|
+
}
|
|
772
|
+
scopeInstruction = `
|
|
773
|
+
${parts.join("\n")}
|
|
774
|
+
Only use scopes from these predefined lists. Set scope to null if no scope applies.`;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
676
777
|
return `You are generating release notes for a software project. Given the following changelog entries, do two things:
|
|
677
778
|
|
|
678
779
|
1. **Rewrite** each entry as a clear, user-friendly description
|
|
@@ -683,9 +784,7 @@ Style guidelines:
|
|
|
683
784
|
- Be concise (1 short sentence per entry)
|
|
684
785
|
- Focus on what changed, not implementation details
|
|
685
786
|
|
|
686
|
-
${categorySection}
|
|
687
|
-
|
|
688
|
-
${categories ? 'For entries in categories involving internal/developer changes, set a "scope" field with a short subcategory label (e.g., "CI", "Dependencies", "Testing").' : ""}
|
|
787
|
+
${categorySection}${scopeInstruction}
|
|
689
788
|
|
|
690
789
|
Entries:
|
|
691
790
|
${entriesText}
|
|
@@ -702,7 +801,8 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
702
801
|
const retryOpts = LLM_DEFAULTS.retry;
|
|
703
802
|
try {
|
|
704
803
|
return await withRetry(async () => {
|
|
705
|
-
const
|
|
804
|
+
const defaultPrompt = buildPrompt(entries, context.categories, context.style);
|
|
805
|
+
const prompt = resolvePrompt("enhanceAndCategorize", defaultPrompt, context.prompts);
|
|
706
806
|
const response = await provider.complete(prompt);
|
|
707
807
|
const cleaned = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
708
808
|
const parsed = JSON.parse(cleaned);
|
|
@@ -718,22 +818,23 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
718
818
|
scope: result.scope || original.scope
|
|
719
819
|
};
|
|
720
820
|
});
|
|
821
|
+
const validatedEntries = validateEntryScopes(enhancedEntries, context.scopes, context.categories);
|
|
721
822
|
const categoryMap = /* @__PURE__ */ new Map();
|
|
722
823
|
for (let i = 0; i < parsed.entries.length; i++) {
|
|
723
824
|
const result = parsed.entries[i];
|
|
724
825
|
const category = result?.category || "General";
|
|
725
|
-
const entry =
|
|
826
|
+
const entry = validatedEntries[i];
|
|
726
827
|
if (!entry) continue;
|
|
727
828
|
if (!categoryMap.has(category)) {
|
|
728
829
|
categoryMap.set(category, []);
|
|
729
830
|
}
|
|
730
|
-
categoryMap.get(category)
|
|
831
|
+
categoryMap.get(category)?.push(entry);
|
|
731
832
|
}
|
|
732
833
|
const categories = [];
|
|
733
834
|
for (const [category, catEntries] of categoryMap) {
|
|
734
835
|
categories.push({ category, entries: catEntries });
|
|
735
836
|
}
|
|
736
|
-
return { enhancedEntries, categories };
|
|
837
|
+
return { enhancedEntries: validatedEntries, categories };
|
|
737
838
|
}, retryOpts);
|
|
738
839
|
} catch (error) {
|
|
739
840
|
warn2(
|
|
@@ -747,7 +848,7 @@ async function enhanceAndCategorize(provider, entries, context) {
|
|
|
747
848
|
}
|
|
748
849
|
|
|
749
850
|
// src/llm/tasks/release-notes.ts
|
|
750
|
-
var
|
|
851
|
+
var DEFAULT_RELEASE_NOTES_PROMPT = `You are writing release notes for a software project.
|
|
751
852
|
|
|
752
853
|
Create engaging, user-friendly release notes for the following changes.
|
|
753
854
|
|
|
@@ -780,16 +881,17 @@ No notable changes in this release.`;
|
|
|
780
881
|
if (e.breaking) line += " **BREAKING**";
|
|
781
882
|
return line;
|
|
782
883
|
}).join("\n");
|
|
783
|
-
const
|
|
884
|
+
const defaultPrompt = DEFAULT_RELEASE_NOTES_PROMPT.replace("{{version}}", context.version ?? "v1.0.0").replace(
|
|
784
885
|
"{{#if previousVersion}}Previous version: {{previousVersion}}{{/if}}",
|
|
785
886
|
context.previousVersion ? `Previous version: ${context.previousVersion}` : ""
|
|
786
887
|
).replace("{{date}}", context.date ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "").replace("{{entries}}", entriesText);
|
|
888
|
+
const prompt = resolvePrompt("releaseNotes", defaultPrompt, context.prompts);
|
|
787
889
|
const response = await provider.complete(prompt);
|
|
788
890
|
return response.trim();
|
|
789
891
|
}
|
|
790
892
|
|
|
791
893
|
// src/llm/tasks/summarize.ts
|
|
792
|
-
var
|
|
894
|
+
var DEFAULT_SUMMARIZE_PROMPT = `You are creating a summary of changes for a software release.
|
|
793
895
|
|
|
794
896
|
Given the following changelog entries, create a brief summary (2-3 sentences) that captures the main themes of this release.
|
|
795
897
|
|
|
@@ -797,12 +899,13 @@ Entries:
|
|
|
797
899
|
{{entries}}
|
|
798
900
|
|
|
799
901
|
Summary (only output the summary, nothing else):`;
|
|
800
|
-
async function summarizeEntries(provider, entries,
|
|
902
|
+
async function summarizeEntries(provider, entries, context) {
|
|
801
903
|
if (entries.length === 0) {
|
|
802
904
|
return "";
|
|
803
905
|
}
|
|
804
906
|
const entriesText = entries.map((e) => `- [${e.type}]${e.scope ? ` (${e.scope})` : ""}: ${e.description}`).join("\n");
|
|
805
|
-
const
|
|
907
|
+
const defaultPrompt = DEFAULT_SUMMARIZE_PROMPT.replace("{{entries}}", entriesText);
|
|
908
|
+
const prompt = resolvePrompt("summarize", defaultPrompt, context.prompts);
|
|
806
909
|
const response = await provider.complete(prompt);
|
|
807
910
|
return response.trim();
|
|
808
911
|
}
|
|
@@ -1214,17 +1317,27 @@ function renderTemplate(templatePath, context, engine) {
|
|
|
1214
1317
|
}
|
|
1215
1318
|
|
|
1216
1319
|
// src/core/pipeline.ts
|
|
1217
|
-
function generateCompareUrl(repoUrl, from, to) {
|
|
1320
|
+
function generateCompareUrl(repoUrl, from, to, packageName) {
|
|
1321
|
+
const isPackageSpecific = from.includes("@") && packageName && from.includes(packageName);
|
|
1322
|
+
let fromVersion;
|
|
1323
|
+
let toVersion;
|
|
1324
|
+
if (isPackageSpecific) {
|
|
1325
|
+
fromVersion = from;
|
|
1326
|
+
toVersion = `${packageName}@${to.startsWith("v") ? "" : "v"}${to}`;
|
|
1327
|
+
} else {
|
|
1328
|
+
fromVersion = from.replace(/^v/, "");
|
|
1329
|
+
toVersion = to.replace(/^v/, "");
|
|
1330
|
+
}
|
|
1218
1331
|
if (/gitlab\.com/i.test(repoUrl)) {
|
|
1219
|
-
return `${repoUrl}/-/compare/${
|
|
1332
|
+
return `${repoUrl}/-/compare/${fromVersion}...${toVersion}`;
|
|
1220
1333
|
}
|
|
1221
1334
|
if (/bitbucket\.org/i.test(repoUrl)) {
|
|
1222
|
-
return `${repoUrl}/branches/compare/${
|
|
1335
|
+
return `${repoUrl}/branches/compare/${fromVersion}..${toVersion}`;
|
|
1223
1336
|
}
|
|
1224
|
-
return `${repoUrl}/compare/${
|
|
1337
|
+
return `${repoUrl}/compare/${fromVersion}...${toVersion}`;
|
|
1225
1338
|
}
|
|
1226
1339
|
function createTemplateContext(pkg) {
|
|
1227
|
-
const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version) : void 0;
|
|
1340
|
+
const compareUrl = pkg.repoUrl && pkg.previousVersion ? generateCompareUrl(pkg.repoUrl, pkg.previousVersion, pkg.version, pkg.packageName) : void 0;
|
|
1228
1341
|
return {
|
|
1229
1342
|
packageName: pkg.packageName,
|
|
1230
1343
|
version: pkg.version,
|
|
@@ -1262,7 +1375,9 @@ async function processWithLLM(context, config) {
|
|
|
1262
1375
|
previousVersion: context.previousVersion ?? void 0,
|
|
1263
1376
|
date: context.date,
|
|
1264
1377
|
categories: config.llm.categories,
|
|
1265
|
-
style: config.llm.style
|
|
1378
|
+
style: config.llm.style,
|
|
1379
|
+
scopes: config.llm.scopes,
|
|
1380
|
+
prompts: config.llm.prompts
|
|
1266
1381
|
};
|
|
1267
1382
|
const enhanced = {
|
|
1268
1383
|
entries: context.entries
|
|
@@ -1310,7 +1425,7 @@ async function processWithLLM(context, config) {
|
|
|
1310
1425
|
enhanced.summary = await summarizeEntries(provider, enhanced.entries, llmContext);
|
|
1311
1426
|
if (enhanced.summary) {
|
|
1312
1427
|
info4("Summary generated successfully");
|
|
1313
|
-
|
|
1428
|
+
debug3(`Summary: ${enhanced.summary.substring(0, 100)}...`);
|
|
1314
1429
|
} else {
|
|
1315
1430
|
warn3("Summary generation returned empty result");
|
|
1316
1431
|
}
|
|
@@ -1326,7 +1441,6 @@ async function processWithLLM(context, config) {
|
|
|
1326
1441
|
}
|
|
1327
1442
|
return {
|
|
1328
1443
|
...context,
|
|
1329
|
-
entries: enhanced.entries,
|
|
1330
1444
|
enhanced
|
|
1331
1445
|
};
|
|
1332
1446
|
} catch (error) {
|
|
@@ -1359,9 +1473,10 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
|
1359
1473
|
);
|
|
1360
1474
|
const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
|
|
1361
1475
|
if (dryRun) {
|
|
1362
|
-
info4(
|
|
1363
|
-
|
|
1364
|
-
|
|
1476
|
+
info4(`Would write templated output to ${outputPath}`);
|
|
1477
|
+
debug3("--- Changelog Preview ---");
|
|
1478
|
+
debug3(result.content);
|
|
1479
|
+
debug3("--- End Preview ---");
|
|
1365
1480
|
return;
|
|
1366
1481
|
}
|
|
1367
1482
|
if (outputPath === "-") {
|
|
@@ -1376,7 +1491,7 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
|
|
|
1376
1491
|
success4(`Changelog written to ${outputPath} (using ${result.engine} template)`);
|
|
1377
1492
|
}
|
|
1378
1493
|
async function runPipeline(input, config, dryRun) {
|
|
1379
|
-
|
|
1494
|
+
debug3(`Processing ${input.packages.length} package(s)`);
|
|
1380
1495
|
let contexts = input.packages.map(createTemplateContext);
|
|
1381
1496
|
if (config.llm && !process.env.CHANGELOG_NO_LLM) {
|
|
1382
1497
|
info4("Processing with LLM enhancement");
|
|
@@ -1387,8 +1502,10 @@ async function runPipeline(input, config, dryRun) {
|
|
|
1387
1502
|
switch (output.format) {
|
|
1388
1503
|
case "markdown": {
|
|
1389
1504
|
const file = output.file ?? "CHANGELOG.md";
|
|
1390
|
-
|
|
1391
|
-
|
|
1505
|
+
const effectiveTemplateConfig = output.templates ?? config.templates;
|
|
1506
|
+
if (effectiveTemplateConfig?.path || output.options?.template) {
|
|
1507
|
+
const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
|
|
1508
|
+
await generateWithTemplate(contexts, configWithTemplate, file, dryRun);
|
|
1392
1509
|
} else {
|
|
1393
1510
|
writeMarkdown(file, contexts, config, dryRun);
|
|
1394
1511
|
}
|
|
@@ -1438,7 +1555,7 @@ async function processInput(inputJson, config, dryRun) {
|
|
|
1438
1555
|
// src/monorepo/aggregator.ts
|
|
1439
1556
|
import * as fs9 from "fs";
|
|
1440
1557
|
import * as path7 from "path";
|
|
1441
|
-
import { info as info5, success as success5 } from "@releasekit/core";
|
|
1558
|
+
import { debug as debug4, info as info5, success as success5 } from "@releasekit/core";
|
|
1442
1559
|
|
|
1443
1560
|
// src/monorepo/splitter.ts
|
|
1444
1561
|
function splitByPackage(contexts) {
|
|
@@ -1452,8 +1569,8 @@ function splitByPackage(contexts) {
|
|
|
1452
1569
|
// src/monorepo/aggregator.ts
|
|
1453
1570
|
function writeFile(outputPath, content, dryRun) {
|
|
1454
1571
|
if (dryRun) {
|
|
1455
|
-
info5(`
|
|
1456
|
-
|
|
1572
|
+
info5(`Would write to ${outputPath}`);
|
|
1573
|
+
debug4(content);
|
|
1457
1574
|
return;
|
|
1458
1575
|
}
|
|
1459
1576
|
const dir = path7.dirname(outputPath);
|