@releasekit/notes 0.3.1 → 0.4.1

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.
@@ -4,11 +4,10 @@ import {
4
4
  debug,
5
5
  formatVersion,
6
6
  info,
7
- renderMarkdown,
8
7
  success,
9
8
  warn,
10
9
  writeMarkdown
11
- } from "./chunk-E4454SIS.js";
10
+ } from "./chunk-7TJSPQPW.js";
12
11
 
13
12
  // ../config/dist/index.js
14
13
  import * as fs from "fs";
@@ -118,14 +117,14 @@ var GitHubReleaseConfigSchema = z.object({
118
117
  perPackage: z.boolean().default(true),
119
118
  prerelease: z.union([z.literal("auto"), z.boolean()]).default("auto"),
120
119
  /**
121
- * Controls how release notes are sourced for GitHub releases.
122
- * - 'auto': Use RELEASE_NOTES.md if it exists, then per-package changelog
123
- * data from the version output, then GitHub's auto-generated notes.
124
- * - 'github': Always use GitHub's auto-generated notes.
125
- * - 'none': No notes body.
126
- * - Any other string: Treated as a file path to read notes from.
120
+ * Controls the source for the GitHub release body.
121
+ * - 'auto': Use release notes if enabled, else changelog, else GitHub auto-generated.
122
+ * - 'releaseNotes': Use LLM-generated release notes (requires notes.releaseNotes.enabled: true).
123
+ * - 'changelog': Use formatted changelog entries.
124
+ * - 'generated': Use GitHub's auto-generated notes.
125
+ * - 'none': No body.
127
126
  */
128
- releaseNotes: z.union([z.literal("auto"), z.literal("github"), z.literal("none"), z.string()]).default("auto")
127
+ body: z.enum(["auto", "releaseNotes", "changelog", "generated", "none"]).default("auto")
129
128
  });
130
129
  var VerifyRegistryConfigSchema = z.object({
131
130
  enabled: z.boolean().default(true),
@@ -169,7 +168,7 @@ var PublishConfigSchema = z.object({
169
168
  draft: true,
170
169
  perPackage: true,
171
170
  prerelease: "auto",
172
- releaseNotes: "auto"
171
+ body: "auto"
173
172
  }),
174
173
  verify: VerifyConfigSchema.default({
175
174
  npm: {
@@ -190,10 +189,10 @@ var TemplateConfigSchema = z.object({
190
189
  path: z.string().optional(),
191
190
  engine: z.enum(["handlebars", "liquid", "ejs"]).optional()
192
191
  });
193
- var OutputConfigSchema = z.object({
194
- format: z.enum(["markdown", "github-release", "json"]),
192
+ var LocationModeSchema = z.enum(["root", "packages", "both"]);
193
+ var ChangelogConfigSchema = z.object({
194
+ mode: LocationModeSchema.optional(),
195
195
  file: z.string().optional(),
196
- options: z.record(z.string(), z.unknown()).optional(),
197
196
  templates: TemplateConfigSchema.optional()
198
197
  });
199
198
  var LLMOptionsSchema = z.object({
@@ -253,17 +252,20 @@ var LLMConfigSchema = z.object({
253
252
  scopes: ScopeConfigSchema.optional(),
254
253
  prompts: LLMPromptsConfigSchema.optional()
255
254
  });
255
+ var ReleaseNotesConfigSchema = z.object({
256
+ mode: LocationModeSchema.optional(),
257
+ file: z.string().optional(),
258
+ templates: TemplateConfigSchema.optional(),
259
+ llm: LLMConfigSchema.optional()
260
+ });
256
261
  var NotesInputConfigSchema = z.object({
257
262
  source: z.string().optional(),
258
263
  file: z.string().optional()
259
264
  });
260
265
  var NotesConfigSchema = z.object({
261
- input: NotesInputConfigSchema.optional(),
262
- output: z.array(OutputConfigSchema).default([{ format: "markdown", file: "CHANGELOG.md" }]),
263
- monorepo: MonorepoConfigSchema.optional(),
264
- templates: TemplateConfigSchema.optional(),
265
- llm: LLMConfigSchema.optional(),
266
- updateStrategy: z.enum(["prepend", "regenerate"]).default("prepend")
266
+ changelog: z.union([z.literal(false), ChangelogConfigSchema]).optional(),
267
+ releaseNotes: z.union([z.literal(false), ReleaseNotesConfigSchema]).optional(),
268
+ updateStrategy: z.enum(["prepend", "regenerate"]).optional()
267
269
  });
268
270
  var CILabelsConfigSchema = z.object({
269
271
  stable: z.string().default("release:stable"),
@@ -410,22 +412,6 @@ function loadConfig(options) {
410
412
  const configPath = options?.configPath ?? path3.join(cwd, CONFIG_FILE);
411
413
  return loadConfigFile(configPath);
412
414
  }
413
- function loadNotesConfig(options) {
414
- const config = loadConfig(options);
415
- return config.notes;
416
- }
417
-
418
- // src/core/config.ts
419
- function loadConfig2(projectDir = process.cwd(), configFile) {
420
- const options = { cwd: projectDir, configPath: configFile };
421
- return loadNotesConfig(options) ?? getDefaultConfig();
422
- }
423
- function getDefaultConfig() {
424
- return {
425
- output: [{ format: "markdown", file: "CHANGELOG.md" }],
426
- updateStrategy: "prepend"
427
- };
428
- }
429
415
 
430
416
  // src/errors/index.ts
431
417
  var NotesError = class extends ReleaseKitError {
@@ -555,50 +541,28 @@ async function parseVersionOutputStdin() {
555
541
  return parseVersionOutput(content);
556
542
  }
557
543
 
558
- // src/output/json.ts
559
- import * as fs5 from "fs";
560
- import * as path4 from "path";
561
- function renderJson(contexts) {
562
- return JSON.stringify(
563
- {
564
- versions: contexts.map((ctx) => ({
565
- packageName: ctx.packageName,
566
- version: ctx.version,
567
- previousVersion: ctx.previousVersion,
568
- date: ctx.date,
569
- entries: ctx.entries,
570
- compareUrl: ctx.compareUrl
571
- }))
572
- },
573
- null,
574
- 2
575
- );
576
- }
577
- function writeJson(outputPath, contexts, dryRun) {
578
- const content = renderJson(contexts);
579
- if (dryRun) {
580
- info(`Would write JSON output to ${outputPath}`);
581
- debug("--- JSON Output Preview ---");
582
- debug(content);
583
- debug("--- End Preview ---");
584
- return;
585
- }
586
- const dir = path4.dirname(outputPath);
587
- if (!fs5.existsSync(dir)) {
588
- fs5.mkdirSync(dir, { recursive: true });
544
+ // src/core/config.ts
545
+ function loadConfig2(projectDir = process.cwd(), configFile) {
546
+ const options = { cwd: projectDir, configPath: configFile };
547
+ const fullConfig = loadConfig(options);
548
+ const config = fullConfig.notes ?? getDefaultConfig();
549
+ if (fullConfig.monorepo && !config.monorepo) {
550
+ config.monorepo = fullConfig.monorepo;
589
551
  }
590
- fs5.writeFileSync(outputPath, content, "utf-8");
591
- success(`JSON output written to ${outputPath}`);
552
+ return config;
553
+ }
554
+ function getDefaultConfig() {
555
+ return {};
592
556
  }
593
557
 
594
558
  // src/core/pipeline.ts
595
- import * as fs10 from "fs";
596
- import * as path8 from "path";
559
+ import * as fs9 from "fs";
560
+ import * as path7 from "path";
597
561
 
598
562
  // src/llm/defaults.ts
599
563
  var LLM_DEFAULTS = {
600
564
  timeout: 6e4,
601
- maxTokens: 4e3,
565
+ maxTokens: 16384,
602
566
  temperature: 0.7,
603
567
  concurrency: 5,
604
568
  retry: {
@@ -610,7 +574,7 @@ var LLM_DEFAULTS = {
610
574
  models: {
611
575
  openai: "gpt-4o-mini",
612
576
  "openai-compatible": "gpt-4o-mini",
613
- anthropic: "claude-3-5-haiku-latest",
577
+ anthropic: "claude-haiku-4-5-20251001",
614
578
  ollama: "llama3.2"
615
579
  }
616
580
  };
@@ -700,6 +664,13 @@ var OllamaProvider = class extends BaseLLMProvider {
700
664
  body: JSON.stringify(requestBody)
701
665
  });
702
666
  if (!response.ok) {
667
+ if (response.status === 401 || response.status === 403) {
668
+ const text2 = await response.text();
669
+ const keyHint = this.apiKey ? "OLLAMA_API_KEY is set but may be invalid or rejected by the server." : "OLLAMA_API_KEY is not set. Set the environment variable or use --no-llm to skip LLM processing.";
670
+ throw new LLMError(
671
+ `Ollama request failed: ${response.status} ${response.statusText}${text2 ? ` - ${text2}` : ""}. ${keyHint}`
672
+ );
673
+ }
703
674
  const text = await response.text();
704
675
  throw new LLMError(`Ollama request failed: ${response.status} ${text}`);
705
676
  }
@@ -1228,122 +1199,8 @@ function createProvider(config) {
1228
1199
  }
1229
1200
  }
1230
1201
 
1231
- // src/output/github-release.ts
1232
- import { Octokit } from "@octokit/rest";
1233
- var GitHubClient = class {
1234
- octokit;
1235
- owner;
1236
- repo;
1237
- constructor(options) {
1238
- const token = options.token ?? process.env.GITHUB_TOKEN;
1239
- if (!token) {
1240
- throw new GitHubError("GITHUB_TOKEN not set. Set it as an environment variable.");
1241
- }
1242
- this.octokit = new Octokit({ auth: token });
1243
- this.owner = options.owner;
1244
- this.repo = options.repo;
1245
- }
1246
- async createRelease(context, options = {}) {
1247
- const tagName = `v${context.version}`;
1248
- let body;
1249
- if (context.enhanced?.releaseNotes) {
1250
- body = context.enhanced.releaseNotes;
1251
- } else {
1252
- body = renderMarkdown([context]);
1253
- }
1254
- info(`Creating GitHub release for ${tagName}`);
1255
- try {
1256
- const response = await this.octokit.repos.createRelease({
1257
- owner: this.owner,
1258
- repo: this.repo,
1259
- tag_name: tagName,
1260
- name: tagName,
1261
- body,
1262
- draft: options.draft ?? false,
1263
- prerelease: options.prerelease ?? false,
1264
- generate_release_notes: options.generateNotes ?? false
1265
- });
1266
- success(`Release created: ${response.data.html_url}`);
1267
- return {
1268
- id: response.data.id,
1269
- htmlUrl: response.data.html_url,
1270
- tagName
1271
- };
1272
- } catch (error) {
1273
- throw new GitHubError(`Failed to create release: ${error instanceof Error ? error.message : String(error)}`);
1274
- }
1275
- }
1276
- async updateRelease(releaseId, context, options = {}) {
1277
- const tagName = `v${context.version}`;
1278
- let body;
1279
- if (context.enhanced?.releaseNotes) {
1280
- body = context.enhanced.releaseNotes;
1281
- } else {
1282
- body = renderMarkdown([context]);
1283
- }
1284
- info(`Updating GitHub release ${releaseId}`);
1285
- try {
1286
- const response = await this.octokit.repos.updateRelease({
1287
- owner: this.owner,
1288
- repo: this.repo,
1289
- release_id: releaseId,
1290
- tag_name: tagName,
1291
- name: tagName,
1292
- body,
1293
- draft: options.draft ?? false,
1294
- prerelease: options.prerelease ?? false
1295
- });
1296
- success(`Release updated: ${response.data.html_url}`);
1297
- return {
1298
- id: response.data.id,
1299
- htmlUrl: response.data.html_url,
1300
- tagName
1301
- };
1302
- } catch (error) {
1303
- throw new GitHubError(`Failed to update release: ${error instanceof Error ? error.message : String(error)}`);
1304
- }
1305
- }
1306
- async getReleaseByTag(tag) {
1307
- try {
1308
- const response = await this.octokit.repos.getReleaseByTag({
1309
- owner: this.owner,
1310
- repo: this.repo,
1311
- tag
1312
- });
1313
- return {
1314
- id: response.data.id,
1315
- htmlUrl: response.data.html_url,
1316
- tagName: response.data.tag_name
1317
- };
1318
- } catch {
1319
- return null;
1320
- }
1321
- }
1322
- };
1323
- function parseRepoUrl(repoUrl) {
1324
- const patterns = [
1325
- /^https:\/\/github\.com\/([^/]+)\/([^/]+)/,
1326
- /^git@github\.com:([^/]+)\/([^/]+)/,
1327
- /^github\.com\/([^/]+)\/([^/]+)/
1328
- ];
1329
- for (const pattern of patterns) {
1330
- const match = repoUrl.match(pattern);
1331
- if (match?.[1] && match[2]) {
1332
- return {
1333
- owner: match[1],
1334
- repo: match[2].replace(/\.git$/, "")
1335
- };
1336
- }
1337
- }
1338
- return null;
1339
- }
1340
- async function createGitHubRelease(context, options) {
1341
- const client = new GitHubClient(options);
1342
- return client.createRelease(context, options);
1343
- }
1344
-
1345
1202
  // src/templates/ejs.ts
1346
- import * as fs6 from "fs";
1203
+ import * as fs5 from "fs";
1347
1204
  import ejs from "ejs";
1348
1205
  function renderEjs(template, context) {
1349
1206
  try {
@@ -1353,16 +1210,16 @@ function renderEjs(template, context) {
1353
1210
  }
1354
1211
  }
1355
1212
  function renderEjsFile(filePath, context) {
1356
- if (!fs6.existsSync(filePath)) {
1213
+ if (!fs5.existsSync(filePath)) {
1357
1214
  throw new TemplateError(`Template file not found: ${filePath}`);
1358
1215
  }
1359
- const template = fs6.readFileSync(filePath, "utf-8");
1216
+ const template = fs5.readFileSync(filePath, "utf-8");
1360
1217
  return renderEjs(template, context);
1361
1218
  }
1362
1219
 
1363
1220
  // src/templates/handlebars.ts
1364
- import * as fs7 from "fs";
1365
- import * as path5 from "path";
1221
+ import * as fs6 from "fs";
1222
+ import * as path4 from "path";
1366
1223
  import Handlebars from "handlebars";
1367
1224
  function registerHandlebarsHelpers() {
1368
1225
  Handlebars.registerHelper("capitalize", (str) => {
@@ -1388,28 +1245,28 @@ function renderHandlebars(template, context) {
1388
1245
  }
1389
1246
  }
1390
1247
  function renderHandlebarsFile(filePath, context) {
1391
- if (!fs7.existsSync(filePath)) {
1248
+ if (!fs6.existsSync(filePath)) {
1392
1249
  throw new TemplateError(`Template file not found: ${filePath}`);
1393
1250
  }
1394
- const template = fs7.readFileSync(filePath, "utf-8");
1251
+ const template = fs6.readFileSync(filePath, "utf-8");
1395
1252
  return renderHandlebars(template, context);
1396
1253
  }
1397
1254
  function renderHandlebarsComposable(templateDir, context) {
1398
1255
  registerHandlebarsHelpers();
1399
- const versionPath = path5.join(templateDir, "version.hbs");
1400
- const entryPath = path5.join(templateDir, "entry.hbs");
1401
- const documentPath = path5.join(templateDir, "document.hbs");
1402
- if (!fs7.existsSync(documentPath)) {
1256
+ const versionPath = path4.join(templateDir, "version.hbs");
1257
+ const entryPath = path4.join(templateDir, "entry.hbs");
1258
+ const documentPath = path4.join(templateDir, "document.hbs");
1259
+ if (!fs6.existsSync(documentPath)) {
1403
1260
  throw new TemplateError(`Document template not found: ${documentPath}`);
1404
1261
  }
1405
- if (fs7.existsSync(versionPath)) {
1406
- Handlebars.registerPartial("version", fs7.readFileSync(versionPath, "utf-8"));
1262
+ if (fs6.existsSync(versionPath)) {
1263
+ Handlebars.registerPartial("version", fs6.readFileSync(versionPath, "utf-8"));
1407
1264
  }
1408
- if (fs7.existsSync(entryPath)) {
1409
- Handlebars.registerPartial("entry", fs7.readFileSync(entryPath, "utf-8"));
1265
+ if (fs6.existsSync(entryPath)) {
1266
+ Handlebars.registerPartial("entry", fs6.readFileSync(entryPath, "utf-8"));
1410
1267
  }
1411
1268
  try {
1412
- const compiled = Handlebars.compile(fs7.readFileSync(documentPath, "utf-8"));
1269
+ const compiled = Handlebars.compile(fs6.readFileSync(documentPath, "utf-8"));
1413
1270
  return compiled(context);
1414
1271
  } catch (error) {
1415
1272
  throw new TemplateError(`Handlebars render error: ${error instanceof Error ? error.message : String(error)}`);
@@ -1417,8 +1274,8 @@ function renderHandlebarsComposable(templateDir, context) {
1417
1274
  }
1418
1275
 
1419
1276
  // src/templates/liquid.ts
1420
- import * as fs8 from "fs";
1421
- import * as path6 from "path";
1277
+ import * as fs7 from "fs";
1278
+ import * as path5 from "path";
1422
1279
  import { Liquid } from "liquidjs";
1423
1280
  function createLiquidEngine(root) {
1424
1281
  return new Liquid({
@@ -1436,15 +1293,15 @@ function renderLiquid(template, context) {
1436
1293
  }
1437
1294
  }
1438
1295
  function renderLiquidFile(filePath, context) {
1439
- if (!fs8.existsSync(filePath)) {
1296
+ if (!fs7.existsSync(filePath)) {
1440
1297
  throw new TemplateError(`Template file not found: ${filePath}`);
1441
1298
  }
1442
- const template = fs8.readFileSync(filePath, "utf-8");
1299
+ const template = fs7.readFileSync(filePath, "utf-8");
1443
1300
  return renderLiquid(template, context);
1444
1301
  }
1445
1302
  function renderLiquidComposable(templateDir, context) {
1446
- const documentPath = path6.join(templateDir, "document.liquid");
1447
- if (!fs8.existsSync(documentPath)) {
1303
+ const documentPath = path5.join(templateDir, "document.liquid");
1304
+ if (!fs7.existsSync(documentPath)) {
1448
1305
  throw new TemplateError(`Document template not found: ${documentPath}`);
1449
1306
  }
1450
1307
  const engine = createLiquidEngine(templateDir);
@@ -1456,10 +1313,10 @@ function renderLiquidComposable(templateDir, context) {
1456
1313
  }
1457
1314
 
1458
1315
  // src/templates/loader.ts
1459
- import * as fs9 from "fs";
1460
- import * as path7 from "path";
1316
+ import * as fs8 from "fs";
1317
+ import * as path6 from "path";
1461
1318
  function getEngineFromFile(filePath) {
1462
- const ext = path7.extname(filePath).toLowerCase();
1319
+ const ext = path6.extname(filePath).toLowerCase();
1463
1320
  switch (ext) {
1464
1321
  case ".liquid":
1465
1322
  return "liquid";
@@ -1493,10 +1350,10 @@ function getRenderFileFn(engine) {
1493
1350
  }
1494
1351
  }
1495
1352
  function detectTemplateMode(templatePath) {
1496
- if (!fs9.existsSync(templatePath)) {
1353
+ if (!fs8.existsSync(templatePath)) {
1497
1354
  throw new TemplateError(`Template path not found: ${templatePath}`);
1498
1355
  }
1499
- const stat = fs9.statSync(templatePath);
1356
+ const stat = fs8.statSync(templatePath);
1500
1357
  if (stat.isFile()) {
1501
1358
  return "single";
1502
1359
  }
@@ -1514,7 +1371,7 @@ function renderSingleFile(templatePath, context, engine) {
1514
1371
  };
1515
1372
  }
1516
1373
  function renderComposable(templateDir, context, engine) {
1517
- const files = fs9.readdirSync(templateDir);
1374
+ const files = fs8.readdirSync(templateDir);
1518
1375
  const engineMap = {
1519
1376
  liquid: { document: "document.liquid", version: "version.liquid", entry: "entry.liquid" },
1520
1377
  handlebars: { document: "document.hbs", version: "version.hbs", entry: "entry.hbs" },
@@ -1537,15 +1394,15 @@ function renderComposable(templateDir, context, engine) {
1537
1394
  return { content: renderHandlebarsComposable(templateDir, context), engine: resolvedEngine };
1538
1395
  }
1539
1396
  const expectedFiles = engineMap[resolvedEngine];
1540
- const documentPath = path7.join(templateDir, expectedFiles.document);
1541
- if (!fs9.existsSync(documentPath)) {
1397
+ const documentPath = path6.join(templateDir, expectedFiles.document);
1398
+ if (!fs8.existsSync(documentPath)) {
1542
1399
  throw new TemplateError(`Document template not found: ${expectedFiles.document}`);
1543
1400
  }
1544
- const versionPath = path7.join(templateDir, expectedFiles.version);
1545
- const entryPath = path7.join(templateDir, expectedFiles.entry);
1401
+ const versionPath = path6.join(templateDir, expectedFiles.version);
1402
+ const entryPath = path6.join(templateDir, expectedFiles.entry);
1546
1403
  const render = getRenderFn(resolvedEngine);
1547
- const entryTemplate = fs9.existsSync(entryPath) ? fs9.readFileSync(entryPath, "utf-8") : null;
1548
- const versionTemplate = fs9.existsSync(versionPath) ? fs9.readFileSync(versionPath, "utf-8") : null;
1404
+ const entryTemplate = fs8.existsSync(entryPath) ? fs8.readFileSync(entryPath, "utf-8") : null;
1405
+ const versionTemplate = fs8.existsSync(versionPath) ? fs8.readFileSync(versionPath, "utf-8") : null;
1549
1406
  if (entryTemplate && versionTemplate) {
1550
1407
  const versionsWithEntries = context.versions.map((versionCtx) => {
1551
1408
  const entries = versionCtx.entries.map((entry) => {
@@ -1556,7 +1413,7 @@ function renderComposable(templateDir, context, engine) {
1556
1413
  });
1557
1414
  const docContext = { ...context, renderedVersions: versionsWithEntries };
1558
1415
  return {
1559
- content: render(fs9.readFileSync(documentPath, "utf-8"), docContext),
1416
+ content: render(fs8.readFileSync(documentPath, "utf-8"), docContext),
1560
1417
  engine: resolvedEngine
1561
1418
  };
1562
1419
  }
@@ -1654,36 +1511,31 @@ function createDocumentContext(contexts, repoUrl) {
1654
1511
  compareUrls: Object.keys(compareUrls).length > 0 ? compareUrls : void 0
1655
1512
  };
1656
1513
  }
1657
- async function processWithLLM(context, config) {
1658
- if (!config.llm) {
1659
- return context;
1660
- }
1661
- const tasks = config.llm.tasks ?? {};
1514
+ async function processWithLLM(context, llmConfig) {
1515
+ const tasks = llmConfig.tasks ?? {};
1662
1516
  const llmContext = {
1663
1517
  packageName: context.packageName,
1664
1518
  version: context.version,
1665
1519
  previousVersion: context.previousVersion ?? void 0,
1666
1520
  date: context.date,
1667
- categories: config.llm.categories,
1668
- style: config.llm.style,
1669
- scopes: config.llm.scopes,
1670
- prompts: config.llm.prompts
1521
+ categories: llmConfig.categories,
1522
+ style: llmConfig.style,
1523
+ scopes: llmConfig.scopes,
1524
+ prompts: llmConfig.prompts
1671
1525
  };
1672
1526
  const enhanced = {
1673
1527
  entries: context.entries
1674
1528
  };
1675
1529
  try {
1676
- info(`Using LLM provider: ${config.llm.provider}${config.llm.model ? ` (${config.llm.model})` : ""}`);
1677
- if (config.llm.baseURL) {
1678
- info(`LLM base URL: ${config.llm.baseURL}`);
1530
+ info(`Using LLM provider: ${llmConfig.provider}${llmConfig.model ? ` (${llmConfig.model})` : ""}`);
1531
+ if (llmConfig.baseURL) {
1532
+ info(`LLM base URL: ${llmConfig.baseURL}`);
1679
1533
  }
1680
- const rawProvider = createProvider(config.llm);
1681
- const retryOpts = config.llm.retry ?? LLM_DEFAULTS.retry;
1682
- const configOptions = config.llm.options;
1534
+ const rawProvider = createProvider(llmConfig);
1535
+ const retryOpts = llmConfig.retry ?? LLM_DEFAULTS.retry;
1536
+ const configOptions = llmConfig.options;
1683
1537
  const provider = {
1684
1538
  name: rawProvider.name,
1685
- // Merge user-configured options (timeout, maxTokens, temperature) as base defaults,
1686
- // allowing any per-call overrides to take precedence.
1687
1539
  complete: (prompt, opts) => withRetry(() => rawProvider.complete(prompt, { ...configOptions, ...opts }), retryOpts)
1688
1540
  };
1689
1541
  const activeTasks = Object.entries(tasks).filter(([, enabled]) => enabled).map(([name]) => name);
@@ -1697,7 +1549,7 @@ async function processWithLLM(context, config) {
1697
1549
  } else {
1698
1550
  if (tasks.enhance) {
1699
1551
  info("Enhancing entries with LLM...");
1700
- enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, config.llm.concurrency);
1552
+ enhanced.entries = await enhanceEntries(provider, context.entries, llmContext, llmConfig.concurrency);
1701
1553
  info(`Enhanced ${enhanced.entries.length} entries`);
1702
1554
  }
1703
1555
  if (tasks.categorize) {
@@ -1740,25 +1592,22 @@ function getBuiltinTemplatePath(style) {
1740
1592
  let packageRoot;
1741
1593
  try {
1742
1594
  const currentUrl = import.meta.url;
1743
- packageRoot = path8.dirname(new URL(currentUrl).pathname);
1744
- packageRoot = path8.join(packageRoot, "..", "..");
1595
+ packageRoot = path7.dirname(new URL(currentUrl).pathname);
1596
+ packageRoot = path7.join(packageRoot, "..", "..");
1745
1597
  } catch {
1746
1598
  packageRoot = __dirname;
1747
1599
  }
1748
- return path8.join(packageRoot, "templates", style);
1600
+ return path7.join(packageRoot, "templates", style);
1749
1601
  }
1750
- async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1602
+ async function generateWithTemplate(contexts, templatesConfig, outputPath, repoUrl, dryRun) {
1751
1603
  let templatePath;
1752
- if (config.templates?.path) {
1753
- templatePath = path8.resolve(config.templates.path);
1604
+ if (templatesConfig?.path) {
1605
+ templatePath = path7.resolve(templatesConfig.path);
1754
1606
  } else {
1755
1607
  templatePath = getBuiltinTemplatePath("keep-a-changelog");
1756
1608
  }
1757
- const documentContext = createDocumentContext(
1758
- contexts,
1759
- config.templates?.path ? void 0 : contexts[0]?.repoUrl ?? void 0
1760
- );
1761
- const result = renderTemplate(templatePath, documentContext, config.templates?.engine);
1609
+ const documentContext = createDocumentContext(contexts, templatesConfig?.path ? void 0 : repoUrl);
1610
+ const result = renderTemplate(templatePath, documentContext, templatesConfig?.engine);
1762
1611
  if (dryRun) {
1763
1612
  info(`[DRY RUN] Changelog preview (would write to ${outputPath}):`);
1764
1613
  info(result.content);
@@ -1768,121 +1617,128 @@ async function generateWithTemplate(contexts, config, outputPath, dryRun) {
1768
1617
  process.stdout.write(result.content);
1769
1618
  return;
1770
1619
  }
1771
- const dir = path8.dirname(outputPath);
1772
- if (!fs10.existsSync(dir)) {
1773
- fs10.mkdirSync(dir, { recursive: true });
1620
+ const dir = path7.dirname(outputPath);
1621
+ if (!fs9.existsSync(dir)) {
1622
+ fs9.mkdirSync(dir, { recursive: true });
1774
1623
  }
1775
- fs10.writeFileSync(outputPath, result.content, "utf-8");
1624
+ fs9.writeFileSync(outputPath, result.content, "utf-8");
1776
1625
  const label = /changelog/i.test(outputPath) ? "Changelog" : "Release notes";
1777
1626
  success(`${label} written to ${outputPath} (using ${result.engine} template)`);
1778
1627
  }
1779
1628
  async function runPipeline(input, config, dryRun) {
1780
1629
  debug(`Processing ${input.packages.length} package(s)`);
1781
1630
  let contexts = input.packages.map(createTemplateContext);
1782
- if (config.llm && !process.env.CHANGELOG_NO_LLM) {
1631
+ const changelogConfig = config.changelog === false ? false : { mode: "root", ...config.changelog ?? {} };
1632
+ const releaseNotesConfig = config.releaseNotes === false || config.releaseNotes === void 0 ? void 0 : config.releaseNotes.mode !== void 0 || config.releaseNotes.file !== void 0 ? { mode: "root", ...config.releaseNotes } : config.releaseNotes;
1633
+ const llmConfig = releaseNotesConfig?.llm;
1634
+ if (llmConfig && !process.env.CHANGELOG_NO_LLM) {
1783
1635
  info("Processing with LLM enhancement");
1784
- contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, config)));
1636
+ contexts = await Promise.all(contexts.map((ctx) => processWithLLM(ctx, llmConfig)));
1785
1637
  }
1786
1638
  const files = [];
1787
1639
  const fmtOpts = {
1788
1640
  includePackageName: contexts.length > 1 || contexts.some((c) => c.packageName.includes("/"))
1789
1641
  };
1790
- for (const output of config.output) {
1791
- const file = output.file ?? (output.format === "json" ? "changelog.json" : "CHANGELOG.md");
1792
- const isChangelog = /changelog/i.test(file);
1793
- const outputKind = isChangelog ? "changelog" : "release notes";
1794
- info(`Generating ${outputKind} \u2192 ${file}`);
1795
- switch (output.format) {
1796
- case "markdown": {
1797
- const file2 = output.file ?? "CHANGELOG.md";
1798
- try {
1799
- const effectiveTemplateConfig = output.templates ?? config.templates;
1800
- if (effectiveTemplateConfig?.path || output.options?.template) {
1801
- const configWithTemplate = { ...config, templates: effectiveTemplateConfig };
1802
- await generateWithTemplate(contexts, configWithTemplate, file2, dryRun);
1803
- } else {
1804
- writeMarkdown(file2, contexts, config, dryRun, fmtOpts);
1805
- }
1806
- if (!dryRun) files.push(file2);
1807
- } catch (error) {
1808
- warn(`Failed to write ${file2}: ${error instanceof Error ? error.message : String(error)}`);
1809
- }
1810
- break;
1811
- }
1812
- case "json": {
1813
- const file2 = output.file ?? "changelog.json";
1814
- try {
1815
- writeJson(file2, contexts, dryRun);
1816
- if (!dryRun) files.push(file2);
1817
- } catch (error) {
1818
- warn(`Failed to write ${file2}: ${error instanceof Error ? error.message : String(error)}`);
1642
+ if (changelogConfig !== false && changelogConfig.mode) {
1643
+ const fileName = changelogConfig.file ?? "CHANGELOG.md";
1644
+ const mode = changelogConfig.mode;
1645
+ info(`Generating changelog \u2192 ${fileName}`);
1646
+ try {
1647
+ if (mode === "root" || mode === "both") {
1648
+ if (changelogConfig.templates?.path) {
1649
+ await generateWithTemplate(
1650
+ contexts,
1651
+ changelogConfig.templates,
1652
+ fileName,
1653
+ contexts[0]?.repoUrl ?? void 0,
1654
+ dryRun
1655
+ );
1656
+ } else {
1657
+ writeMarkdown(fileName, contexts, config, dryRun, fmtOpts);
1819
1658
  }
1820
- break;
1659
+ if (!dryRun) files.push(fileName);
1821
1660
  }
1822
- case "github-release": {
1823
- if (dryRun) {
1824
- info("[DRY RUN] Would create GitHub release");
1825
- break;
1826
- }
1827
- const firstContext = contexts[0];
1828
- if (!firstContext) {
1829
- warn("No context available for GitHub release");
1830
- break;
1831
- }
1832
- const repoUrl = firstContext.repoUrl;
1833
- if (!repoUrl) {
1834
- warn("No repo URL available, cannot create GitHub release");
1835
- break;
1836
- }
1837
- const parsed = parseRepoUrl(repoUrl);
1838
- if (!parsed) {
1839
- warn(`Could not parse repo URL: ${repoUrl}`);
1840
- break;
1841
- }
1842
- await createGitHubRelease(firstContext, {
1843
- owner: parsed.owner,
1844
- repo: parsed.repo,
1845
- draft: output.options?.draft,
1846
- prerelease: output.options?.prerelease
1847
- });
1848
- break;
1661
+ if (mode === "packages" || mode === "both") {
1662
+ const monoFiles = await writeMonorepoFiles(contexts, config, dryRun, changelogConfig.file ?? "CHANGELOG.md");
1663
+ files.push(...monoFiles);
1849
1664
  }
1665
+ } catch (error) {
1666
+ warn(`Failed to write changelog: ${error instanceof Error ? error.message : String(error)}`);
1850
1667
  }
1851
1668
  }
1852
- if (config.monorepo?.mode) {
1853
- const { detectMonorepo, writeMonorepoChangelogs } = await import("./aggregator-XJ2EILO3.js");
1854
- const cwd = process.cwd();
1855
- const detected = detectMonorepo(cwd);
1856
- if (detected.isMonorepo) {
1857
- const monoFiles = writeMonorepoChangelogs(
1858
- contexts,
1859
- {
1860
- rootPath: config.monorepo.rootPath ?? cwd,
1861
- packagesPath: config.monorepo.packagesPath ?? detected.packagesPath,
1862
- mode: config.monorepo.mode
1863
- },
1864
- config,
1865
- dryRun
1866
- );
1867
- files.push(...monoFiles);
1669
+ if (releaseNotesConfig?.mode) {
1670
+ const fileName = releaseNotesConfig.file ?? "RELEASE_NOTES.md";
1671
+ const mode = releaseNotesConfig.mode;
1672
+ info(`Generating release notes \u2192 ${fileName}`);
1673
+ try {
1674
+ if (mode === "root" || mode === "both") {
1675
+ if (releaseNotesConfig.templates?.path) {
1676
+ await generateWithTemplate(
1677
+ contexts,
1678
+ releaseNotesConfig.templates,
1679
+ fileName,
1680
+ contexts[0]?.repoUrl ?? void 0,
1681
+ dryRun
1682
+ );
1683
+ } else {
1684
+ writeMarkdown(fileName, contexts, config, dryRun, fmtOpts);
1685
+ }
1686
+ if (!dryRun) files.push(fileName);
1687
+ }
1688
+ if (mode === "packages" || mode === "both") {
1689
+ const monoFiles = await writeMonorepoFiles(
1690
+ contexts,
1691
+ config,
1692
+ dryRun,
1693
+ releaseNotesConfig.file ?? "RELEASE_NOTES.md"
1694
+ );
1695
+ files.push(...monoFiles);
1696
+ }
1697
+ } catch (error) {
1698
+ warn(`Failed to write release notes: ${error instanceof Error ? error.message : String(error)}`);
1868
1699
  }
1869
1700
  }
1870
1701
  const packageNotes = {};
1702
+ const releaseNotesResult = {};
1871
1703
  for (const ctx of contexts) {
1872
1704
  packageNotes[ctx.packageName] = formatVersion(ctx);
1705
+ if (ctx.enhanced?.releaseNotes) {
1706
+ releaseNotesResult[ctx.packageName] = ctx.enhanced.releaseNotes;
1707
+ }
1873
1708
  }
1874
- return { packageNotes, files };
1709
+ return {
1710
+ packageNotes,
1711
+ files,
1712
+ releaseNotes: Object.keys(releaseNotesResult).length > 0 ? releaseNotesResult : void 0
1713
+ };
1875
1714
  }
1876
1715
  async function processInput(inputJson, config, dryRun) {
1877
1716
  const input = parseVersionOutput(inputJson);
1878
1717
  return runPipeline(input, config, dryRun);
1879
1718
  }
1719
+ async function writeMonorepoFiles(contexts, config, dryRun, fileName) {
1720
+ const { detectMonorepo, writeMonorepoChangelogs } = await import("./aggregator-IUQUAVJC.js");
1721
+ const cwd = process.cwd();
1722
+ const detected = detectMonorepo(cwd);
1723
+ if (!detected.isMonorepo) return [];
1724
+ const monoFiles = writeMonorepoChangelogs(
1725
+ contexts,
1726
+ {
1727
+ rootPath: config.monorepo?.rootPath ?? cwd,
1728
+ packagesPath: config.monorepo?.packagesPath ?? detected.packagesPath,
1729
+ mode: "packages",
1730
+ fileName
1731
+ },
1732
+ config,
1733
+ dryRun
1734
+ );
1735
+ return monoFiles;
1736
+ }
1880
1737
 
1881
1738
  export {
1882
1739
  loadAuth,
1883
1740
  saveAuth,
1884
- loadConfig2 as loadConfig,
1885
- getDefaultConfig,
1741
+ loadConfig,
1886
1742
  NotesError,
1887
1743
  InputParseError,
1888
1744
  TemplateError,
@@ -1893,8 +1749,8 @@ export {
1893
1749
  parseVersionOutput,
1894
1750
  parseVersionOutputFile,
1895
1751
  parseVersionOutputStdin,
1896
- renderJson,
1897
- writeJson,
1752
+ loadConfig2,
1753
+ getDefaultConfig,
1898
1754
  createTemplateContext,
1899
1755
  runPipeline,
1900
1756
  processInput