@kitschpatrol/chagent 0.3.0 → 0.4.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/bin/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { C as version, S as maxTimeoutMinutes, b as defaultModels, i as generateChangelog, l as agentNames, n as previewChangelog, r as reviewChangelog, s as writeChangelog, t as assertReviewTerminal, u as efforts, x as defaults, y as setLogger } from "../review-ClBbl31P.js";
3
- import { n as assertAmendable, t as amendRelease } from "../lib-advgvAk1.js";
2
+ import { C as maxTimeoutMinutes, S as defaults, b as setLogger, d as rootChangelogs, i as generateChangelog, l as agentNames, n as previewChangelog, r as reviewChangelog, s as writeChangelog, t as assertReviewTerminal, u as efforts, w as version, x as defaultModels } from "../review-DftIAoEl.js";
3
+ import { n as assertAmendable, t as amendRelease } from "../lib-Byxdn2st.js";
4
4
  import { t as createProgressLine } from "../progress-DQtRnH1N.js";
5
5
  import { createLogger } from "lognow";
6
6
  import path from "node:path";
@@ -109,6 +109,16 @@ try {
109
109
  default: false,
110
110
  description: "Amend a verified unpublished release commit and matching local tags",
111
111
  type: "boolean"
112
+ }).option("root-changelog", {
113
+ choices: rootChangelogs,
114
+ defaultDescription: defaults.rootChangelog,
115
+ description: "Monorepo root changelog: a summary of package releases, the root package alone, or none",
116
+ type: "string"
117
+ }).option("skip-package", {
118
+ array: true,
119
+ defaultDescription: "none",
120
+ description: "Workspace package directory glob to leave without a changelog; repeatable",
121
+ type: "string"
112
122
  }).option("github", {
113
123
  defaultDescription: String(defaults.github),
114
124
  description: "Enrich Git evidence using gh when available; disable with --no-github",
@@ -148,7 +158,9 @@ try {
148
158
  reporter.report(message);
149
159
  },
150
160
  rebuild: args.rebuild,
161
+ rootChangelog: args.rootChangelog,
151
162
  signal: controller.signal,
163
+ skipPackage: args.skipPackage,
152
164
  timeout: args.timeout
153
165
  }));
154
166
  try {
@@ -1,4 +1,4 @@
1
- import { u as GenerateOptions } from "../types-COQP1I5w.js";
1
+ import { u as GenerateOptions } from "../types-B6FOOkTQ.js";
2
2
  import { VersionBumpOptions } from "bumpp";
3
3
  type ExecuteHook = Exclude<NonNullable<VersionBumpOptions['execute']>, string>;
4
4
  export type BumppHookOptions = Omit<GenerateOptions, 'cwd' | 'release'> & {
package/dist/lib/bumpp.js CHANGED
@@ -1,4 +1,4 @@
1
- import { g as git, i as generateChangelog, m as localDate, r as reviewChangelog, s as writeChangelog, t as assertReviewTerminal } from "../review-ClBbl31P.js";
1
+ import { _ as git, h as localDate, i as generateChangelog, r as reviewChangelog, s as writeChangelog, t as assertReviewTerminal } from "../review-DftIAoEl.js";
2
2
  import { t as createProgressLine } from "../progress-DQtRnH1N.js";
3
3
 
4
4
  // src/lib/bumpp.ts
@@ -1,4 +1,4 @@
1
- import { _ as Config, a as ChangeSet, c as Evidence, d as GenerationRequest, f as GenerationResponse, g as RunSettings, h as ReleaseSummary, i as Change, l as FileEdit, m as ReleaseRequest, n as AgentName, o as ChangelogPlan, p as ReleaseContext, r as Category, s as Effort, t as AgentAdapter, u as GenerateOptions, v as defineConfig, y as loadConfig } from "../types-COQP1I5w.js";
1
+ import { _ as RunSettings, a as ChangeSet, b as loadConfig, c as Evidence, d as GenerationRequest, f as GenerationResponse, g as RootChangelog, h as ReleaseSummary, i as Change, l as FileEdit, m as ReleaseRequest, n as AgentName, o as ChangelogPlan, p as ReleaseContext, r as Category, s as Effort, t as AgentAdapter, u as GenerateOptions, v as Config, y as defineConfig } from "../types-B6FOOkTQ.js";
2
2
  import { ILogBasic, ILogLayer } from "lognow";
3
3
  /** Create an adapter for an installed CLI using its existing authentication. */
4
4
  export declare function createAgent(name: AgentName): AgentAdapter;
@@ -28,4 +28,4 @@ export declare function reviewChangelog(plan: ChangelogPlan, options?: {
28
28
  * writes.
29
29
  */
30
30
  export declare function writeChangelog(plan: ChangelogPlan): Promise<string[]>;
31
- export { type AgentAdapter, type AgentName, type Category, type Change, type ChangeSet, type ChangelogPlan, type Config, type Effort, type Evidence, type FileEdit, type GenerateOptions, type GenerationRequest, type GenerationResponse, type ReleaseContext, type ReleaseRequest, type ReleaseSummary, type RunSettings, defineConfig, loadConfig };
31
+ export { type AgentAdapter, type AgentName, type Category, type Change, type ChangeSet, type ChangelogPlan, type Config, type Effort, type Evidence, type FileEdit, type GenerateOptions, type GenerationRequest, type GenerationResponse, type ReleaseContext, type ReleaseRequest, type ReleaseSummary, type RootChangelog, type RunSettings, defineConfig, loadConfig };
package/dist/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineConfig, c as createAgent, i as generateChangelog, n as previewChangelog, o as loadConfig, r as reviewChangelog, s as writeChangelog, y as setLogger } from "../review-ClBbl31P.js";
2
- import { t as amendRelease } from "../lib-advgvAk1.js";
1
+ import { a as defineConfig, b as setLogger, c as createAgent, i as generateChangelog, n as previewChangelog, o as loadConfig, r as reviewChangelog, s as writeChangelog } from "../review-DftIAoEl.js";
2
+ import { t as amendRelease } from "../lib-Byxdn2st.js";
3
3
 
4
4
  export { amendRelease, createAgent, defineConfig, generateChangelog, loadConfig, previewChangelog, reviewChangelog, setLogger, writeChangelog };
@@ -1,4 +1,4 @@
1
- import { _ as isAncestor, d as LOCKFILE_PATH, f as inspectRepository, g as git, h as manifestAt, p as isVersionOnlyChange, s as writeChangelog, v as runCommand } from "./review-ClBbl31P.js";
1
+ import { _ as git, f as LOCKFILE_PATH, g as manifestAt, m as isVersionOnlyChange, p as inspectRepository, s as writeChangelog, v as isAncestor, y as runCommand } from "./review-DftIAoEl.js";
2
2
  import path from "node:path";
3
3
  import { realpath } from "node:fs/promises";
4
4
 
@@ -15,7 +15,7 @@ import { isCancel, select } from "@clack/prompts";
15
15
 
16
16
  // package.json
17
17
  var name = "@kitschpatrol/chagent";
18
- var version = "0.3.0";
18
+ var version = "0.4.0";
19
19
  var engines = { "node": "^24.16.0 || >=26.3.0" };
20
20
 
21
21
  // src/lib/defaults.ts
@@ -25,6 +25,7 @@ const defaults = {
25
25
  effort: "xhigh",
26
26
  github: true,
27
27
  maxInputChars: 8e4,
28
+ rootChangelog: "summary",
28
29
  timeout: 20
29
30
  };
30
31
  const defaultModels = {
@@ -486,21 +487,15 @@ function pnpmWorkspaceGlobs(text) {
486
487
  if (!workspace.success) throw new Error(`Invalid pnpm-workspace.yaml:\n${z.prettifyError(workspace.error)}`, { cause: workspace.error });
487
488
  return workspace.data?.packages;
488
489
  }
489
- async function workspacePackages(root, manifest, config) {
490
- let patterns = Array.isArray(manifest?.workspaces) ? manifest.workspaces : manifest?.workspaces?.packages ?? [];
491
- const workspaceText = await readOptional(path.join(root, "pnpm-workspace.yaml"));
492
- if (workspaceText !== void 0) patterns = pnpmWorkspaceGlobs(workspaceText) ?? patterns;
493
- patterns = config.packages ?? patterns;
490
+ /** Find directories matching globs, as POSIX paths relative to the root. */
491
+ async function matchDirectories(root, patterns, exclude = []) {
494
492
  const directories = [];
495
- const positive = patterns.filter((pattern) => !pattern.startsWith("!"));
496
- const negative = patterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
497
- const matches = glob(positive, {
493
+ const matches = glob(patterns, {
498
494
  cwd: root,
499
495
  exclude: [
500
496
  "**/node_modules/**",
501
497
  "**/.git/**",
502
- ...negative,
503
- ...config.exclude ?? []
498
+ ...exclude
504
499
  ],
505
500
  withFileTypes: true
506
501
  });
@@ -508,6 +503,27 @@ async function workspacePackages(root, manifest, config) {
508
503
  if (!entry.isDirectory()) continue;
509
504
  directories.push(path.relative(root, path.join(entry.parentPath, entry.name)).split(path.sep).join("/"));
510
505
  }
506
+ return directories;
507
+ }
508
+ /** Mark the packages that remain boundaries but get no changelog. */
509
+ async function markSkipped(root, packages, patterns) {
510
+ const skipped = /* @__PURE__ */ new Set();
511
+ for (const pattern of patterns) {
512
+ const matches = (await matchDirectories(root, [pattern])).filter((directory) => packages.some((pkg) => pkg.directory === directory));
513
+ if (matches.length === 0) throw new Error(`skipPackage pattern "${pattern}" matches no workspace package directory. Use a directory glob relative to the repository root, such as "packages/docs"; rootChangelog controls the root changelog.`);
514
+ for (const directory of matches) skipped.add(directory);
515
+ }
516
+ return packages.map((pkg) => skipped.has(pkg.directory) ? {
517
+ ...pkg,
518
+ skipped: true
519
+ } : pkg);
520
+ }
521
+ async function workspacePackages(root, manifest, config) {
522
+ let patterns = Array.isArray(manifest?.workspaces) ? manifest.workspaces : manifest?.workspaces?.packages ?? [];
523
+ const workspaceText = await readOptional(path.join(root, "pnpm-workspace.yaml"));
524
+ if (workspaceText !== void 0) patterns = pnpmWorkspaceGlobs(workspaceText) ?? patterns;
525
+ patterns = config.packages ?? patterns;
526
+ const directories = await matchDirectories(root, patterns.filter((pattern) => !pattern.startsWith("!")), [...patterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1)), ...config.exclude ?? []]);
511
527
  const packages = [];
512
528
  for (const directory of directories.toSorted()) {
513
529
  if (!(await realpath(path.join(root, directory))).startsWith(root + path.sep)) throw new Error(`Workspace package escapes the repository: ${directory}`);
@@ -515,7 +531,11 @@ async function workspacePackages(root, manifest, config) {
515
531
  if (text === void 0 && config.packages === void 0) continue;
516
532
  packages.push(packageInfo(text === void 0 ? void 0 : parseManifest(text, `${directory}/package.json`), directory, path.posix.basename(directory)));
517
533
  }
518
- return packages;
534
+ return markSkipped(root, packages, config.skipPackage ?? []);
535
+ }
536
+ /** Select the workspace packages that get changelogs of their own. */
537
+ function describedPackages(repository) {
538
+ return repository.packages.filter((pkg) => pkg.skipped !== true);
519
539
  }
520
540
  async function taggedReleases(repository, pkg, config) {
521
541
  const candidates = [];
@@ -735,6 +755,11 @@ const efforts = [
735
755
  "xhigh",
736
756
  "max"
737
757
  ];
758
+ const rootChangelogs = [
759
+ "summary",
760
+ "package",
761
+ "none"
762
+ ];
738
763
 
739
764
  // src/lib/agents.ts
740
765
  const CONTROL_CHARACTERS = /\p{Cc}/v;
@@ -1062,6 +1087,13 @@ const configSchema = z.strictObject({
1062
1087
  model: z.string().min(1).optional(),
1063
1088
  /** Workspace directory globs, overriding manifest workspace discovery. */
1064
1089
  packages: z.array(z.string()).optional(),
1090
+ /**
1091
+ * What a monorepo's root changelog records: a summary of package releases,
1092
+ * the root package's own changes, or nothing.
1093
+ */
1094
+ rootChangelog: z.enum(rootChangelogs).optional(),
1095
+ /** Workspace package directory globs that get no changelog of their own. */
1096
+ skipPackage: z.array(z.string()).optional(),
1065
1097
  /** Templates containing {version} and optionally {name}. */
1066
1098
  tagPatterns: z.array(z.string().includes("{version}")).optional(),
1067
1099
  /** Minutes each agent session may run. */
@@ -1073,6 +1105,7 @@ const resolvedConfigSchema = configSchema.required({
1073
1105
  effort: true,
1074
1106
  github: true,
1075
1107
  maxInputChars: true,
1108
+ rootChangelog: true,
1076
1109
  timeout: true
1077
1110
  }).transform(({ timeout, ...config }) => ({
1078
1111
  ...config,
@@ -1331,8 +1364,11 @@ function ignored(file, packages) {
1331
1364
  const relative = owner === void 0 ? file : file.slice(owner.directory.length + 1);
1332
1365
  return LOCKFILE_PATH.test(file) || GENERATED_ANYWHERE.test(file) || GENERATED_DIRECTORY.test(relative);
1333
1366
  }
1334
- async function scopes(repository, pkg, ref) {
1335
- if (pkg.directory === ".") return ["."];
1367
+ /**
1368
+ * A package's directory and, transitively, those of the workspace packages it
1369
+ * depends on at a ref.
1370
+ */
1371
+ async function packageScope(repository, pkg, ref) {
1336
1372
  const directories = /* @__PURE__ */ new Set([pkg.directory]);
1337
1373
  const visit = async (current) => {
1338
1374
  const manifest = await manifestAt(repository.root, current.directory, ref);
@@ -1402,7 +1438,7 @@ async function commitsInRange(root, range, paths) {
1402
1438
  /** Collect source changes, excluding generated notes and release-only commits. */
1403
1439
  async function collectEvidence(repository, pkg, release, excludeDirectories = []) {
1404
1440
  const { packages, root } = repository;
1405
- const paths = [...(await scopes(repository, pkg, release.ref)).map((directory) => `:(literal)${directory}`), ...excludeDirectories.map((directory) => `:(exclude,literal)${directory}`)];
1441
+ const paths = [...(pkg.directory === "." ? ["."] : await packageScope(repository, pkg, release.ref)).map((directory) => `:(literal)${directory}`), ...excludeDirectories.map((directory) => `:(exclude,literal)${directory}`)];
1406
1442
  const range = release.previousRef === void 0 ? release.ref : `${release.previousRef}..${release.ref}`;
1407
1443
  const from = release.previousRef ?? await git(root, [
1408
1444
  "hash-object",
@@ -1642,7 +1678,7 @@ function researchEvidence(evidence, limit) {
1642
1678
  async function prepareRelease(context, pkg, release, existingEntries, mode, from, onlyRepositoryFiles = false) {
1643
1679
  const { options, repository } = context;
1644
1680
  options.signal?.throwIfAborted();
1645
- const excludeDirectories = onlyRepositoryFiles ? repository.packages.map((item) => item.directory) : [];
1681
+ const excludeDirectories = await excludedPackages(context, pkg, release.ref, onlyRepositoryFiles);
1646
1682
  return {
1647
1683
  evidence: await collectEvidence(repository, pkg, from === void 0 ? release : {
1648
1684
  ...release,
@@ -1659,10 +1695,24 @@ async function prepareRelease(context, pkg, release, existingEntries, mode, from
1659
1695
  from: from ?? release.previousRef,
1660
1696
  to: release.ref
1661
1697
  },
1662
- scope: pkg.directory === "." && repository.packages.length > 0 ? "repository" : "package",
1698
+ scope: pkg.directory === "." && repository.packages.length > 0 && options.rootChangelog === "summary" ? "repository" : "package",
1663
1699
  version: release.version
1664
1700
  };
1665
1701
  }
1702
+ /**
1703
+ * Select the package directories a release leaves out: every package when
1704
+ * describing repository files alone, skipped packages in a whole-repository
1705
+ * summary, or, when the root is a package itself, those it does not depend on.
1706
+ */
1707
+ async function excludedPackages(context, pkg, ref, onlyRepositoryFiles) {
1708
+ const { options, repository } = context;
1709
+ const directories = repository.packages.map((item) => item.directory);
1710
+ if (onlyRepositoryFiles || directories.length === 0) return directories;
1711
+ if (pkg.directory !== ".") return [];
1712
+ if (options.rootChangelog === "summary") return repository.packages.filter((item) => item.skipped === true).map((item) => item.directory);
1713
+ const scope = new Set(await packageScope(repository, pkg, ref));
1714
+ return directories.filter((directory) => !scope.has(directory));
1715
+ }
1666
1716
  function cacheKey(context, release) {
1667
1717
  const { effort, maxInputChars, model } = context.options;
1668
1718
  return JSON.stringify({
@@ -2076,7 +2126,7 @@ function aggregateShared(context, prepared, results, exception, quiet) {
2076
2126
  const rootEdit = plan.files.find((file) => path.dirname(file.path) === repository.root);
2077
2127
  if (rootEdit === void 0) return;
2078
2128
  const additions = prepared.summaries.map((summary) => ({
2079
- changes: aggregateChanges(repository, plan.releases.filter((item) => item.version === summary.version && !quiet.has(`${item.changelogPath}@${item.version}`))),
2129
+ changes: aggregateChanges(repository, plan.releases.filter((item) => item.version === summary.version && item.ref === summary.ref && !quiet.has(`${item.changelogPath}@${item.version}`))),
2080
2130
  existingBody: parseChangelog(rootEdit.after).sections.find((section) => section.version === summary.version)?.body,
2081
2131
  release: prepared.releases.find((item) => item.version === summary.version)
2082
2132
  }));
@@ -2089,11 +2139,11 @@ async function generateWorkspace(context) {
2089
2139
  const shared = rootReleases.some((release) => release.version !== "Unreleased");
2090
2140
  const rootFile = await findChangelog(repository.root);
2091
2141
  const rootContent = options.rebuild ? void 0 : rootFile.content;
2092
- const rootMissing = shared ? new Set(missingReleases(rootReleases, rootContent).map((release) => release.version)) : void 0;
2093
- const needsPackageHistory = rootMissing === void 0 || rootMissing.values().some((version) => version !== "Unreleased");
2094
- const recordedAtRoot = (summary) => rootMissing === void 0 ? rootContent !== void 0 && missingPackageSummaries([summary], rootContent).length === 0 : !rootMissing.has(summary.version);
2142
+ const rootMissing = shared ? missingReleases(rootReleases, rootContent) : void 0;
2143
+ const needsPackageHistory = rootMissing === void 0 || rootMissing.some((release) => release.version !== "Unreleased");
2144
+ const recordedAtRoot = (summary) => rootMissing === void 0 ? rootContent !== void 0 && missingPackageSummaries([summary], rootContent).length === 0 : rootMissing.every((release) => release.version !== summary.version || release.ref !== summary.ref);
2095
2145
  const packages = [];
2096
- for (const pkg of repository.packages) packages.push(await preparePackage(context, pkg, needsPackageHistory, recordedAtRoot));
2146
+ for (const pkg of describedPackages(repository)) packages.push(await preparePackage(context, pkg, needsPackageHistory, recordedAtRoot));
2097
2147
  const root = shared ? await preparePackage(context, repository.rootPackage) : void 0;
2098
2148
  const independent = shared ? void 0 : await prepareIndependent(context, packages.flatMap((pkg) => pkg.summaries));
2099
2149
  const requests = [...packages, ...root === void 0 ? [] : [root]].flatMap((pkg) => pkg.work.map((item) => item.request));
@@ -2127,6 +2177,7 @@ async function generateChangelog(options = {}) {
2127
2177
  log.debug(`Resolved options: ${JSON.stringify(config)}`);
2128
2178
  const repository = await inspectRepository(root, config);
2129
2179
  log.debug(`Repository ${root} at ${repository.head}: ${repository.tags.length} reachable tags, ${repository.packages.length} workspace packages.`);
2180
+ if (config.rootChangelog === "none" && describedPackages(repository).length === 0) throw new Error("The root changelog is set to \"none\", but no workspace packages remain to describe, so there is no changelog to generate. Choose another root changelog mode, or configure packages that are not skipped.");
2130
2181
  const agent = typeof selected === "object" ? selected : createAgent(config.agent);
2131
2182
  const plan = {
2132
2183
  checkpoints: [],
@@ -2154,14 +2205,20 @@ async function generateChangelog(options = {}) {
2154
2205
  plan,
2155
2206
  repository
2156
2207
  };
2157
- if (repository.packages.length === 0) {
2158
- const prepared = await preparePackage(context, repository.rootPackage);
2159
- const results = await generateReleases(context, prepared.work.map((item) => item.request));
2160
- plan.releases.push(...renderPackage(context, prepared, results));
2161
- } else await generateWorkspace(context);
2208
+ if (repository.packages.length > 0 && config.rootChangelog === "summary") await generateWorkspace(context);
2209
+ else await generatePackages(context);
2162
2210
  if (rebuild) keepEmptyRebuilds(plan);
2163
2211
  return plan;
2164
2212
  }
2213
+ /** Generate each changelog on its own, with no root summary. */
2214
+ async function generatePackages(context) {
2215
+ const { options, plan, repository } = context;
2216
+ const packages = options.rootChangelog === "none" ? describedPackages(repository) : [repository.rootPackage, ...describedPackages(repository)];
2217
+ const prepared = [];
2218
+ for (const pkg of packages) prepared.push(await preparePackage(context, pkg));
2219
+ const results = await generateReleases(context, prepared.flatMap((item) => item.work.map((work) => work.request)));
2220
+ for (const item of prepared) plan.releases.push(...renderPackage(context, item, results));
2221
+ }
2165
2222
  /** A rebuild that finds nothing to record must not erase an existing changelog. */
2166
2223
  function keepEmptyRebuilds(plan) {
2167
2224
  plan.files = plan.files.filter((edit) => {
@@ -2322,4 +2379,4 @@ async function reviewChangelog(plan, options = {}) {
2322
2379
  }
2323
2380
  }
2324
2381
 
2325
- export { version as C, maxTimeoutMinutes as S, isAncestor as _, defineConfig as a, defaultModels as b, createAgent as c, LOCKFILE_PATH as d, inspectRepository as f, git as g, manifestAt as h, generateChangelog as i, agentNames as l, localDate as m, previewChangelog as n, loadConfig as o, isVersionOnlyChange as p, reviewChangelog as r, writeChangelog as s, assertReviewTerminal as t, efforts as u, runCommand as v, defaults as x, setLogger as y };
2382
+ export { maxTimeoutMinutes as C, defaults as S, git as _, defineConfig as a, setLogger as b, createAgent as c, rootChangelogs as d, LOCKFILE_PATH as f, manifestAt as g, localDate as h, generateChangelog as i, agentNames as l, isVersionOnlyChange as m, previewChangelog as n, loadConfig as o, inspectRepository as p, reviewChangelog as r, writeChangelog as s, assertReviewTerminal as t, efforts as u, isAncestor as v, version as w, defaultModels as x, runCommand as y };
@@ -17,6 +17,12 @@ declare const configSchema: z.ZodObject<{
17
17
  maxInputChars: z.ZodOptional<z.ZodNumber>;
18
18
  model: z.ZodOptional<z.ZodString>;
19
19
  packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
+ rootChangelog: z.ZodOptional<z.ZodEnum<{
21
+ summary: "summary";
22
+ package: "package";
23
+ none: "none";
24
+ }>>;
25
+ skipPackage: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
26
  tagPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
27
  timeout: z.ZodOptional<z.ZodNumber>;
22
28
  }, z.core.$strict>;
@@ -29,8 +35,10 @@ declare const categories: readonly ["Added", "Changed", "Deprecated", "Removed",
29
35
  type Category = (typeof categories)[number];
30
36
  declare const agentNames: readonly ["codex", "claude"];
31
37
  declare const efforts: readonly ["low", "medium", "high", "xhigh", "max"];
38
+ declare const rootChangelogs: readonly ["summary", "package", "none"];
32
39
  type AgentName = (typeof agentNames)[number];
33
40
  type Effort = (typeof efforts)[number];
41
+ type RootChangelog = (typeof rootChangelogs)[number];
34
42
  type Change = {
35
43
  breaking: boolean;
36
44
  category: Category;
@@ -159,4 +167,4 @@ type ChangelogPlan = {
159
167
  releases: ReleaseSummary[];
160
168
  root: string;
161
169
  };
162
- export { Config as _, ChangeSet as a, Evidence as c, GenerationRequest as d, GenerationResponse as f, RunSettings as g, ReleaseSummary as h, Change as i, FileEdit as l, ReleaseRequest as m, AgentName as n, ChangelogPlan as o, ReleaseContext as p, Category as r, Effort as s, AgentAdapter as t, GenerateOptions as u, defineConfig as v, loadConfig as y };
170
+ export { RunSettings as _, ChangeSet as a, loadConfig as b, Evidence as c, GenerationRequest as d, GenerationResponse as f, RootChangelog as g, ReleaseSummary as h, Change as i, FileEdit as l, ReleaseRequest as m, AgentName as n, ChangelogPlan as o, ReleaseContext as p, Category as r, Effort as s, AgentAdapter as t, GenerateOptions as u, Config as v, defineConfig as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitschpatrol/chagent",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Changelog agent. CLI tool and TypeScript library to generate and update changelogs with a little help from your LLMs.",
5
5
  "keywords": [
6
6
  "changelog",
package/readme.md CHANGED
@@ -132,24 +132,26 @@ Usage:
132
132
  chagent
133
133
  ```
134
134
 
135
- | Option | Description | Type | Default |
136
- | ----------------------- | --------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
137
- | `--cwd` | Repository directory | `string` | current directory |
138
- | `--agent` | Installed agent CLI | `"codex"` `"claude"` | `codex` |
139
- | `--model` | Agent model | `string` | gpt-6.1-sol (Codex); claude-opus-5-5 (Claude) |
140
- | `--effort` | Reasoning effort | `"low"` `"medium"` `"high"` `"xhigh"` `"max"` | `xhigh` |
141
- | `--interactive`<br>`-i` | Review a diff and optionally edit before writing | `boolean` | `false` |
142
- | `--editor` | Editor command for interactive review; configure GUI editors to wait | `string` | git var GIT_EDITOR |
143
- | `--dry-run` | Print a diff without applying changes | `boolean` | `false` |
144
- | `--rebuild` | Reconstruct all history, replacing existing release text | `boolean` | `false` |
145
- | `--amend` | Amend a verified unpublished release commit and matching local tags | `boolean` | `false` |
146
- | `--github` | Enrich Git evidence using gh when available; disable with --no-github | `boolean` | `true` |
147
- | `--cache` | Cache drafts and incremental state; disable with --no-cache | `boolean` | `true` |
148
- | `--config` | Configuration file path | `string` | auto-detected from repository root |
149
- | `--timeout` | Agent timeout in minutes | `number` | `20` |
150
- | `--verbose` | Log commands, cache decisions, and session details to stderr | `boolean` | `false` |
151
- | `--help`<br>`-h` | Show help | `boolean` | |
152
- | `--version`<br>`-v` | Show version number | `boolean` | |
135
+ | Option | Description | Type | Default |
136
+ | ----------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
137
+ | `--cwd` | Repository directory | `string` | current directory |
138
+ | `--agent` | Installed agent CLI | `"codex"` `"claude"` | `codex` |
139
+ | `--model` | Agent model | `string` | gpt-6.1-sol (Codex); claude-opus-5-5 (Claude) |
140
+ | `--effort` | Reasoning effort | `"low"` `"medium"` `"high"` `"xhigh"` `"max"` | `xhigh` |
141
+ | `--interactive`<br>`-i` | Review a diff and optionally edit before writing | `boolean` | `false` |
142
+ | `--editor` | Editor command for interactive review; configure GUI editors to wait | `string` | git var GIT_EDITOR |
143
+ | `--dry-run` | Print a diff without applying changes | `boolean` | `false` |
144
+ | `--rebuild` | Reconstruct all history, replacing existing release text | `boolean` | `false` |
145
+ | `--amend` | Amend a verified unpublished release commit and matching local tags | `boolean` | `false` |
146
+ | `--root-changelog` | Monorepo root changelog: a summary of package releases, the root package alone, or none | `"summary"` `"package"` `"none"` | `summary` |
147
+ | `--skip-package` | Workspace package directory glob to leave without a changelog; repeatable | `array` | `none` |
148
+ | `--github` | Enrich Git evidence using gh when available; disable with --no-github | `boolean` | `true` |
149
+ | `--cache` | Cache drafts and incremental state; disable with --no-cache | `boolean` | `true` |
150
+ | `--config` | Configuration file path | `string` | auto-detected from repository root |
151
+ | `--timeout` | Agent timeout in minutes | `number` | `20` |
152
+ | `--verbose` | Log commands, cache decisions, and session details to stderr | `boolean` | `false` |
153
+ | `--help`<br>`-h` | Show help | `boolean` | |
154
+ | `--version`<br>`-v` | Show version number | `boolean` | |
153
155
 
154
156
  <!-- /cli-help -->
155
157
 
@@ -246,6 +248,10 @@ For an existing post-bump workflow, `chagent --amend` explicitly amends an unpub
246
248
 
247
249
  Workspace declarations in `pnpm-workspace.yaml` or `package.json` determine the packages to process. Each current workspace package gets its own changelog. The root contains repository highlights and links to package releases.
248
250
 
251
+ Use the `rootChangelog` option or `--root-changelog` flag to choose what the root changelog records. The default, `summary`, works as described above. `package` treats the root as a package of its own, for repositories whose main package lives at the root: its changelog describes the root's files and the workspace packages it depends on in full detail, without changes from other packages or links to their releases. `none` writes only the package changelogs. After changing the mode of an existing root changelog, run with `--rebuild` to reconstruct its history in the new form.
252
+
253
+ Use `skipPackage` directory globs, or repeat `--skip-package`, to leave workspace packages such as documentation sites or examples without changelogs. A skipped package is left out of the root changelog, and its changes are not counted among the root's own, but packages that depend on it still include its relevant changes. To count a directory's changes as the root's instead, `exclude` it from workspace discovery. A pattern that matches no workspace package is an error.
254
+
249
255
  For other monorepo layouts, configure `packages` directory globs. These directories do not need npm manifests; their Git tags identify releases, using the directory basename as the package name.
250
256
 
251
257
  Shared-version repositories use repository versions at the root. Independently versioned repositories use package-qualified headings such as `@example/package@1.2.0`. Repository infrastructure changes appear in the root's `Unreleased` section. Explicit workspace dependencies contribute relevant source context to dependent packages. Dependency and tooling maintenance belongs under `Maintenance`. `build`, `coverage`, and `dist` directories at the top of a package count as generated output and are excluded, so a package named `packages/build` is still treated as source.
@@ -266,6 +272,7 @@ export default defineConfig({
266
272
  github: true,
267
273
  maxInputChars: 80_000,
268
274
  model: 'gpt-6.1-sol',
275
+ rootChangelog: 'summary',
269
276
  timeout: 20,
270
277
  })
271
278
  ```
@@ -274,7 +281,7 @@ Importing `defineConfig` requires chagent in the repository's own dependencies.
274
281
 
275
282
  Also supported: `chagent.config.mjs`, `chagent.config.js`, `chagent.config.json`, or a `chagent` key in `package.json`. CLI flags override configured values. `--config` selects a specific file.
276
283
 
277
- Use `packages` and `exclude` directory globs to customize workspace discovery. Use `tagPatterns` to select unusual release tag conventions. Templates support `{name}` and `{version}`; defaults recognize `v1.2.3`, `1.2.3`, `@scope/package@1.2.3`, and `@scope/package/v1.2.3`.
284
+ Use `packages` and `exclude` directory globs to customize workspace discovery, and `skipPackage` to leave discovered packages without changelogs. Use `tagPatterns` to select unusual release tag conventions. Templates support `{name}` and `{version}`; defaults recognize `v1.2.3`, `1.2.3`, `@scope/package@1.2.3`, and `@scope/package/v1.2.3`.
278
285
 
279
286
  ## Background
280
287