@kitschpatrol/chagent 0.5.0 → 0.6.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 +3 -3
- package/dist/lib/bumpp.js +2 -2
- package/dist/lib/index.js +2 -2
- package/dist/{lib-DVZXk4YE.js → lib-BkbC8akL.js} +1 -1
- package/dist/{progress-DQtRnH1N.js → progress-BYDpiH8C.js} +1 -1
- package/dist/{review-Bl10DPO7.js → review-cNqsjqhu.js} +48 -29
- package/package.json +3 -3
- package/readme.md +2 -0
package/dist/bin/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as defaultModels, E as version, S as setLogger, T as maxTimeoutMinutes, a as writeChangelog, d as efforts, f as rootChangelogs, i as generateChangelog, n as previewChangelog, r as reviewChangelog, t as assertReviewTerminal, u as agentNames, w as defaults } from "../review-
|
|
3
|
-
import { n as assertAmendable, t as amendRelease } from "../lib-
|
|
4
|
-
import { t as createProgressLine } from "../progress-
|
|
2
|
+
import { C as defaultModels, E as version, S as setLogger, T as maxTimeoutMinutes, a as writeChangelog, d as efforts, f as rootChangelogs, i as generateChangelog, n as previewChangelog, r as reviewChangelog, t as assertReviewTerminal, u as agentNames, w as defaults } from "../review-cNqsjqhu.js";
|
|
3
|
+
import { n as assertAmendable, t as amendRelease } from "../lib-BkbC8akL.js";
|
|
4
|
+
import { t as createProgressLine } from "../progress-BYDpiH8C.js";
|
|
5
5
|
import { createLogger } from "lognow";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import yargs from "yargs";
|
package/dist/lib/bumpp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as writeChangelog, g as localDate, i as generateChangelog, r as reviewChangelog, t as assertReviewTerminal, y as git } from "../review-
|
|
2
|
-
import { t as createProgressLine } from "../progress-
|
|
1
|
+
import { a as writeChangelog, g as localDate, i as generateChangelog, r as reviewChangelog, t as assertReviewTerminal, y as git } from "../review-cNqsjqhu.js";
|
|
2
|
+
import { t as createProgressLine } from "../progress-BYDpiH8C.js";
|
|
3
3
|
|
|
4
4
|
// src/lib/bumpp.ts
|
|
5
5
|
const TAG_TOKEN = /\{(version|oldVersion|tag|releaseType|major|minor|patch|date)\}/gv;
|
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as setLogger, a as writeChangelog, i as generateChangelog, l as createAgent, n as previewChangelog, o as defineConfig, r as reviewChangelog, s as loadConfig } from "../review-
|
|
2
|
-
import { t as amendRelease } from "../lib-
|
|
1
|
+
import { S as setLogger, a as writeChangelog, i as generateChangelog, l as createAgent, n as previewChangelog, o as defineConfig, r as reviewChangelog, s as loadConfig } from "../review-cNqsjqhu.js";
|
|
2
|
+
import { t as amendRelease } from "../lib-BkbC8akL.js";
|
|
3
3
|
|
|
4
4
|
export { amendRelease, createAgent, defineConfig, generateChangelog, loadConfig, previewChangelog, reviewChangelog, setLogger, writeChangelog };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as manifestAt, a as writeChangelog, b as isAncestor, c as resolveConfig, h as isVersionOnlyChange, m as inspectRepository, p as LOCKFILE_PATH, s as loadConfig, v as tagVersion, x as runCommand, y as git } from "./review-
|
|
1
|
+
import { _ as manifestAt, a as writeChangelog, b as isAncestor, c as resolveConfig, h as isVersionOnlyChange, m as inspectRepository, p as LOCKFILE_PATH, s as loadConfig, v as tagVersion, x as runCommand, y as git } from "./review-cNqsjqhu.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { realpath } from "node:fs/promises";
|
|
4
4
|
import semver from "semver";
|
|
@@ -38,7 +38,7 @@ function createProgressLine(stream) {
|
|
|
38
38
|
tick += 1;
|
|
39
39
|
const elapsed = formatElapsed(performance.now() - started);
|
|
40
40
|
const { columns = 0 } = stream;
|
|
41
|
-
const width = columns > 0 ? Math.max(0, columns - 1 - frame.length - 2) :
|
|
41
|
+
const width = columns > 0 ? Math.max(0, columns - 1 - frame.length - 2) : 1e400;
|
|
42
42
|
const text = `${message.slice(0, Math.max(0, width - elapsed.length - 1))} ${elapsed}`.slice(0, width);
|
|
43
43
|
return `\r${styleText("magenta", frame, { stream })}${FRAME_PADDING}${text}${CLEAR_TO_LINE_END}`;
|
|
44
44
|
};
|
|
@@ -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.
|
|
18
|
+
var version = "0.6.0";
|
|
19
19
|
var engines = { "node": "^24.16.0 || >=26.3.0" };
|
|
20
20
|
|
|
21
21
|
// src/lib/defaults.ts
|
|
@@ -453,10 +453,26 @@ async function leadsSeries(root, candidate, candidates) {
|
|
|
453
453
|
function pendingTag(pkg, version, previous, patterns) {
|
|
454
454
|
return (previous?.tag?.includes(previous.version) === true ? previous.tag.replace(previous.version, "{version}") : patterns?.[0] ?? "v{version}").replaceAll("{name}", () => pkg.name).replaceAll("{version}", () => version);
|
|
455
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Order releases by Git ancestry, using SemVer only for tags on the same
|
|
458
|
+
* commit.
|
|
459
|
+
*/
|
|
460
|
+
async function sortReleases(repository, releases) {
|
|
461
|
+
const history = await git(repository.root, [
|
|
462
|
+
"rev-list",
|
|
463
|
+
"--topo-order",
|
|
464
|
+
"--reverse",
|
|
465
|
+
repository.head
|
|
466
|
+
]);
|
|
467
|
+
const positions = new Map(history.split("\n").map((ref, index) => [ref, index]));
|
|
468
|
+
return releases.toSorted((a, b) => {
|
|
469
|
+
const difference = (positions.get(a.ref) ?? -1) - (positions.get(b.ref) ?? -1);
|
|
470
|
+
return difference === 0 ? semver.compare(a.version, b.version) : difference;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
456
473
|
/** Resolve release intervals from reachable tags and locally bumped manifests. */
|
|
457
474
|
async function resolveReleases(repository, pkg, config, context) {
|
|
458
|
-
const releases = await taggedReleases(repository, pkg, config);
|
|
459
|
-
releases.sort((a, b) => semver.compare(a.version, b.version));
|
|
475
|
+
const releases = await sortReleases(repository, await taggedReleases(repository, pkg, config));
|
|
460
476
|
for (let index = 1; index < releases.length; index++) {
|
|
461
477
|
const current = releases[index];
|
|
462
478
|
const previous = await precedingRelease(repository.root, releases.slice(0, index), current.ref);
|
|
@@ -465,7 +481,7 @@ async function resolveReleases(repository, pkg, config, context) {
|
|
|
465
481
|
}
|
|
466
482
|
const latest = releases.at(-1);
|
|
467
483
|
const currentVersion = pkg.version ?? (pkg.directory === "." ? context?.version : void 0);
|
|
468
|
-
if (await isPendingRelease(repository, pkg,
|
|
484
|
+
if (await isPendingRelease(repository, pkg, releases, context) && currentVersion !== void 0) releases.push({
|
|
469
485
|
date: context?.date ?? localDate(),
|
|
470
486
|
pending: true,
|
|
471
487
|
previousRef: latest?.ref,
|
|
@@ -617,13 +633,13 @@ async function taggedReleases(repository, pkg, config) {
|
|
|
617
633
|
async function precedingRelease(root, candidates, ref) {
|
|
618
634
|
for (const candidate of candidates.toReversed()) if (await isAncestor(root, candidate.ref, ref)) return candidate;
|
|
619
635
|
}
|
|
620
|
-
async function isPendingRelease(repository, pkg,
|
|
636
|
+
async function isPendingRelease(repository, pkg, releases, context) {
|
|
621
637
|
const currentVersion = pkg.version ?? (pkg.directory === "." ? context?.version : void 0);
|
|
622
638
|
const committed = await manifestAt(repository.root, pkg.directory, repository.head);
|
|
623
639
|
const versionChanged = currentVersion !== void 0 && currentVersion !== committed?.version;
|
|
624
640
|
const parent = await manifestAt(repository.root, pkg.directory, repository.head + "^");
|
|
625
641
|
const committedBump = parent?.version !== void 0 && parent.version !== committed?.version;
|
|
626
|
-
return currentVersion !== void 0 && semver.valid(currentVersion) !== null && (
|
|
642
|
+
return currentVersion !== void 0 && semver.valid(currentVersion) !== null && releases.every((release) => release.version !== semver.valid(currentVersion)) && (versionChanged || context !== void 0 || committedBump && releases.every((release) => release.ref !== repository.head));
|
|
627
643
|
}
|
|
628
644
|
|
|
629
645
|
// src/lib/cache.ts
|
|
@@ -1459,7 +1475,7 @@ function releaseLink(github, release) {
|
|
|
1459
1475
|
return release.previousTag === void 0 ? `${base}/releases/tag/${encodeURIComponent(release.tag)}` : `${base}/compare/${encodeURIComponent(release.previousTag)}...${encodeURIComponent(release.tag)}`;
|
|
1460
1476
|
}
|
|
1461
1477
|
/** Insert new sections and update only the draft and its comparison link. */
|
|
1462
|
-
function updateMarkdown(original, entries, nodeException, github) {
|
|
1478
|
+
function updateMarkdown(original, entries, nodeException, github, releaseOrder) {
|
|
1463
1479
|
const content = original ?? "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n";
|
|
1464
1480
|
const parsed = parseChangelog(content);
|
|
1465
1481
|
const { newline } = parsed;
|
|
@@ -1468,7 +1484,10 @@ function updateMarkdown(original, entries, nodeException, github) {
|
|
|
1468
1484
|
const definitions = [];
|
|
1469
1485
|
let { footer } = parsed;
|
|
1470
1486
|
const draft = parsed.sections.find((section) => section.version === "Unreleased");
|
|
1471
|
-
const released = parsed.sections.filter((section) => section.version !== "Unreleased").map((section) =>
|
|
1487
|
+
const released = parsed.sections.filter((section) => section.version !== "Unreleased").map((section) => ({
|
|
1488
|
+
text: content.slice(section.start, section.end),
|
|
1489
|
+
version: section.version
|
|
1490
|
+
}));
|
|
1472
1491
|
let draftText = draft === void 0 ? "" : content.slice(draft.start, draft.end);
|
|
1473
1492
|
for (const entry of entries) {
|
|
1474
1493
|
const { existingBody, release } = entry;
|
|
@@ -1485,7 +1504,10 @@ function updateMarkdown(original, entries, nodeException, github) {
|
|
|
1485
1504
|
const section = heading + newline + newline + body.trim() + newline + newline;
|
|
1486
1505
|
if (release.version === "Unreleased") draftText = section;
|
|
1487
1506
|
else {
|
|
1488
|
-
additions.push(
|
|
1507
|
+
additions.push({
|
|
1508
|
+
text: section,
|
|
1509
|
+
version: release.version
|
|
1510
|
+
});
|
|
1489
1511
|
if (existingBody !== void 0) draftText = "";
|
|
1490
1512
|
}
|
|
1491
1513
|
if (link === void 0) continue;
|
|
@@ -1497,9 +1519,13 @@ function updateMarkdown(original, entries, nodeException, github) {
|
|
|
1497
1519
|
}
|
|
1498
1520
|
if (entries.length === 0 && header === parsed.header) return content;
|
|
1499
1521
|
if (draftText === "") footer = footer.replace(UNRELEASED_REFERENCE, "");
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1522
|
+
const sections = [...additions.toReversed(), ...released];
|
|
1523
|
+
if (releaseOrder !== void 0) {
|
|
1524
|
+
const positions = new Map(releaseOrder.map((version, index) => [version, index]));
|
|
1525
|
+
sections.sort((a, b) => (positions.get(b.version) ?? -1) - (positions.get(a.version) ?? -1));
|
|
1526
|
+
}
|
|
1527
|
+
const result = sectionSpacing(sectionSpacing(header, newline) + draftText, newline) + sections.map((section) => section.text).join("") + footer;
|
|
1528
|
+
return appendDefinitions(footer === "" && released.length === 0 ? result.trimEnd() + newline : result, footer, definitions, newline);
|
|
1503
1529
|
}
|
|
1504
1530
|
/** Add link reference definitions after any existing footer. */
|
|
1505
1531
|
function appendDefinitions(content, footer, definitions, newline) {
|
|
@@ -2108,16 +2134,15 @@ function leavesGeneratedDraft(entries, previously) {
|
|
|
2108
2134
|
const draft = entries.find((entry) => entry.release.version === "Unreleased");
|
|
2109
2135
|
return previously || draft !== void 0 && (draft.existingBody === void 0 || draft.revise === true);
|
|
2110
2136
|
}
|
|
2111
|
-
/** Select
|
|
2137
|
+
/** Select every reachable release the changelog does not already record. */
|
|
2112
2138
|
function missingReleases(releases, content = "") {
|
|
2113
2139
|
const { sections } = parseChangelog(content);
|
|
2114
2140
|
const known = new Set(sections.map((section) => section.version));
|
|
2115
2141
|
const recorded = sections.filter((section) => section.version !== "Unreleased").map((section) => section.version);
|
|
2116
2142
|
const unrecognized = recorded.filter((version) => semver.valid(version) === null);
|
|
2117
2143
|
if (unrecognized.length > 0) throw new Error(`The existing changelog has unrecognized release headings (${unrecognized.join(", ")}). Normalize its release headings before updating it.`);
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2120
|
-
return releases.filter((release) => release.version === "Unreleased" || !known.has(release.version) && (newest === void 0 || semver.gt(release.version, newest)));
|
|
2144
|
+
for (const version of recorded) if (releases.every((release) => release.version !== version)) throw new Error(`Cannot locate the Git release boundary for existing changelog version ${version}. Fetch its tags or configure tagPatterns.`);
|
|
2145
|
+
return releases.filter((release) => release.version === "Unreleased" || !known.has(release.version));
|
|
2121
2146
|
}
|
|
2122
2147
|
/** Apply an explicit release context only to the package being released. */
|
|
2123
2148
|
function releaseContext(options, pkg) {
|
|
@@ -2216,7 +2241,7 @@ function renderPackage(context, prepared, results, linksFor) {
|
|
|
2216
2241
|
revise
|
|
2217
2242
|
});
|
|
2218
2243
|
}
|
|
2219
|
-
const after = updateMarkdown(content, entries, exception, repository.github);
|
|
2244
|
+
const after = updateMarkdown(content, entries, exception, repository.github, releases.map((release) => release.version));
|
|
2220
2245
|
if (after !== file.content) plan.files.push({
|
|
2221
2246
|
after,
|
|
2222
2247
|
before: file.content,
|
|
@@ -2240,14 +2265,7 @@ function headingAnchor(heading) {
|
|
|
2240
2265
|
function missingPackageSummaries(summaries, content = "") {
|
|
2241
2266
|
const parsed = parseChangelog(content);
|
|
2242
2267
|
const known = new Set(parsed.sections.map((section) => section.version));
|
|
2243
|
-
return summaries.
|
|
2244
|
-
if (summary.version === "Unreleased" || known.has(`${summary.packageName}@${summary.version}`)) return false;
|
|
2245
|
-
return parsed.sections.every((section) => {
|
|
2246
|
-
const prefix = summary.packageName + "@";
|
|
2247
|
-
const version = section.version.startsWith(prefix) ? section.version.slice(prefix.length) : "";
|
|
2248
|
-
return semver.valid(version) === null || semver.lt(version, summary.version);
|
|
2249
|
-
});
|
|
2250
|
-
});
|
|
2268
|
+
return summaries.filter((summary) => summary.version !== "Unreleased" && !known.has(`${summary.packageName}@${summary.version}`));
|
|
2251
2269
|
}
|
|
2252
2270
|
|
|
2253
2271
|
// src/lib/workspace.ts
|
|
@@ -2320,10 +2338,11 @@ async function prepareIndependent(context, packages) {
|
|
|
2320
2338
|
* Write a section for each package release the root does not record yet, and
|
|
2321
2339
|
* keep the draft's links to packages with unreleased changes current.
|
|
2322
2340
|
*/
|
|
2323
|
-
function renderIndependent(context, prepared, results, summaries, exception) {
|
|
2341
|
+
async function renderIndependent(context, prepared, results, summaries, exception) {
|
|
2324
2342
|
const { plan, repository } = context;
|
|
2325
2343
|
const { content, draft, file, unreleased } = prepared;
|
|
2326
|
-
const
|
|
2344
|
+
const ordered = await sortReleases(repository, summaries.filter((summary) => summary.version !== "Unreleased"));
|
|
2345
|
+
const entries = Array.from(missingPackageSummaries(ordered, content), (summary) => ({
|
|
2327
2346
|
changes: results.get(summaryId(summary)) ?? [],
|
|
2328
2347
|
links: packageLink(repository, summary),
|
|
2329
2348
|
release: {
|
|
@@ -2343,7 +2362,7 @@ function renderIndependent(context, prepared, results, summaries, exception) {
|
|
|
2343
2362
|
release: unreleased.release,
|
|
2344
2363
|
revise
|
|
2345
2364
|
});
|
|
2346
|
-
const after = updateMarkdown(content, entries, exception, repository.github);
|
|
2365
|
+
const after = updateMarkdown(content, entries, exception, repository.github, ordered.map((summary) => `${summary.packageName}@${summary.version}`));
|
|
2347
2366
|
if (after !== file.content) plan.files.push({
|
|
2348
2367
|
after,
|
|
2349
2368
|
before: file.content,
|
|
@@ -2387,7 +2406,7 @@ async function generateWorkspace(context) {
|
|
|
2387
2406
|
...root,
|
|
2388
2407
|
exception: exception || root.exception
|
|
2389
2408
|
}, results, (release) => summaries.filter((summary) => summary.version === release.version && summary.ref === release.ref && !quiet.has(`${summary.changelogPath}@${summary.version}`)).flatMap((summary) => packageLink(repository, summary)));
|
|
2390
|
-
} else if (independent !== void 0) renderIndependent(context, independent, results, summaries, exception);
|
|
2409
|
+
} else if (independent !== void 0) await renderIndependent(context, independent, results, summaries, exception);
|
|
2391
2410
|
}
|
|
2392
2411
|
|
|
2393
2412
|
// src/lib/generate.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitschpatrol/chagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dist"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@clack/prompts": "^1.
|
|
48
|
+
"@clack/prompts": "^1.9.0",
|
|
49
49
|
"@types/node": "~24.13.6",
|
|
50
50
|
"cosmiconfig": "^10.0.1",
|
|
51
51
|
"cross-spawn": "^7.0.6",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"mdat-plugin-cli-help": "^3.4.2",
|
|
68
68
|
"publint": "^0.3.25",
|
|
69
69
|
"shx": "^0.4.0",
|
|
70
|
-
"tsdown": "^0.23.
|
|
70
|
+
"tsdown": "^0.23.7",
|
|
71
71
|
"typescript": "~6.0.3",
|
|
72
72
|
"vitest": "^5.1.0"
|
|
73
73
|
},
|
package/readme.md
CHANGED
|
@@ -287,6 +287,8 @@ Use `packages` and `exclude` directory globs to customize workspace discovery, a
|
|
|
287
287
|
|
|
288
288
|
### Implementation notes
|
|
289
289
|
|
|
290
|
+
Release order follows Git ancestry, with SemVer breaking ties between tags on the same commit. Fork releases such as `1.2.0-fork.2` can therefore follow upstream `1.2.0`. Updates fill missing reachable releases even when their versions compare lower than an existing heading, while preserving the text of recorded sections.
|
|
291
|
+
|
|
290
292
|
Chagent reads reachable version tags and commit history regardless of the project's language. Optional npm metadata supplies workspace discovery, local version-bump detection before tagging, and the conditional Node.js compatibility note. Repository tags remain authoritative even when a root `package.json` has an unrelated tooling version. Other projects use tagged releases, or an explicit `release` context through the library API. Without reliable released-version boundaries, new changes belong to `Unreleased`; version numbers are not invented. An existing release heading with no matching Git boundary requires its tags or a corrected tag pattern. Release headings can be level one or two, with optional links, dates, and a `v` prefix, including the Conventional Changelog format. Headings can name short versions the way tags do, so `## 1.2` and `## v1` record the `1.2.0` and `1.0.0` releases. Level three headings count as releases when they name a version, as standard-version writes patch releases. Chagent stops rather than risk duplicating history when a level one or two heading names a version in another form, or when an unclosed code fence would hide later headings. Broken `package.json` files in history are tolerated. Shallow clones must be completed before generation.
|
|
291
293
|
|
|
292
294
|
GitHub enrichment uses release dates and commit-associated PR information. PR lookups batch unique commits across all requested releases and packages; each release receives only its associated PRs. **GitHub release-note bodies are never generation inputs.** Changelog files, lockfiles, and `node_modules` are never source evidence. Existing changelog prose is supplied only to preserve edits, avoid duplicate entries, and show the writing style to match: each request carries the changelog's newest release section as a style reference, so new entries follow its voice, punctuation, and reference conventions.
|