fix(release): always bump minor for releases
This commit is contained in:
parent
7005166f58
commit
be783efaba
|
|
@ -61,7 +61,8 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
|
|||
- Breaking changes or migration steps:
|
||||
<!-- For ! / BREAKING CHANGE: show a previously valid call/configuration,
|
||||
its new behavior and migration, and why compatibility/deprecation cannot
|
||||
preserve it. Compatible fixes/additions should use patch/minor semantics.
|
||||
preserve it. Every release increments minor, including breaking changes;
|
||||
keep breaking markers and migration notes so callers can assess compatibility.
|
||||
See docs/releasing.md. -->
|
||||
- Real Hopper/Ghidra, browser, or OS coverage:
|
||||
- Package or release metadata impact:
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Development requires Node.js 24.18.0 and npm 11.16.0 (`.nvmrc` and `packageManag
|
|||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Use Conventional Commit subjects because Release Please derives versions and changelogs from them. Examples: `feat: add historical source import`, `fix(process): stop timers after exit`, and `docs: update architecture`. Use `!` or a `BREAKING CHANGE:` footer for breaking changes. Pull request titles must follow the same format because squash merges use the title as the release commit. Pull requests should describe contract or behavior changes, list verification commands, link issues, and include sanitized MCP examples when schemas change. State whether real Hopper/Ghidra verification was performed. Never commit binaries, Hopper or Ghidra project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts (e.g. `.codex/`).
|
||||
Use Conventional Commit subjects because Release Please derives changelogs from them. Release Please uses `always-bump-minor`: every release increments minor, including breaking changes. Examples: `feat: add historical source import`, `fix(process): stop timers after exit`, and `docs: update architecture`. Use `!` or a `BREAKING CHANGE:` footer for breaking changes. Pull request titles must follow the same format because squash merges use the title as the release commit. Pull requests should describe contract or behavior changes, list verification commands, link issues, and include sanitized MCP examples when schemas change. State whether real Hopper/Ghidra verification was performed. Never commit binaries, Hopper or Ghidra project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts (e.g. `.codex/`).
|
||||
|
||||
<!-- BEGIN:turborepo-agent-rules -->
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ accepting changes without changing the candidate being tested.
|
|||
|
||||
## 1. Select the source
|
||||
|
||||
Choose the next version from the unreleased Conventional Commits, including
|
||||
Use the next minor version proposed by Release Please, including releases with
|
||||
breaking changes. Record the full source SHA and create `release/VERSION` at
|
||||
that commit. The version is a maintainer choice; Release Please's proposed
|
||||
version must agree before publication.
|
||||
that commit. The selected version and Release Please's proposed version must
|
||||
agree before publication.
|
||||
|
||||
Use the automatic main proposal to review the suggested version and notes.
|
||||
Keep that proposal unmerged: select the application commit from main, create
|
||||
|
|
@ -28,19 +28,21 @@ registry metadata or changelog disagrees with that version before creating a tag
|
|||
|
||||
### Compatibility and version selection
|
||||
|
||||
REA uses the default SemVer strategy: compatible fixes increment patch;
|
||||
compatible capabilities increment minor; incompatible public behavior increments
|
||||
major. Adding tools, internal refactoring and correcting behavior outside the
|
||||
supported contract do not by themselves require a major release.
|
||||
REA uses Release Please's `always-bump-minor` strategy: every release increments
|
||||
minor and resets patch, including releases with breaking changes. For example,
|
||||
the next release after 6.0.0 is 6.1.0. Version numbers use the SemVer format,
|
||||
but a minor increment does not promise backward compatibility. Review the
|
||||
changelog's breaking-change section and migration notes before upgrading.
|
||||
|
||||
The supported public surface includes documented CLI commands/options, MCP tool
|
||||
names and input/result contracts, saved evidence formats and supported runtime
|
||||
requirements. Before adding `!` or a `BREAKING CHANGE` footer, a PR must identify
|
||||
a previously valid call or configuration that will fail or change meaning,
|
||||
explain why compatibility cannot be preserved, and give its migration. Prefer
|
||||
optional additions, compatibility adapters and a documented deprecation period;
|
||||
group deliberate removals into a planned major release. Maintainers review this
|
||||
impact before merging; the bot parses markers and cannot infer compatibility.
|
||||
optional additions, compatibility adapters and a documented deprecation period.
|
||||
Keep breaking markers so the bot includes migration notes without incrementing
|
||||
major. Maintainers review this impact before merging; the bot parses markers
|
||||
and cannot infer compatibility.
|
||||
|
||||
For example, adding an optional inspection tool is a minor change. Rejecting a
|
||||
relative MCP path previously accepted by the public contract is breaking;
|
||||
|
|
@ -90,9 +92,11 @@ and the PR titles in GitHub's default merge messages. The report preserves the
|
|||
original merge subject alongside the extracted Conventional Commit title.
|
||||
This matters after merging a side-branch release back into a newer main:
|
||||
Release Please's chronological history cutoff can omit unreleased mainline
|
||||
commits. Unreleased breaking markers require a major increment and references
|
||||
in the new release's breaking-change migration section. Other omitted entries
|
||||
are reported for review. Historical notes from an older release cannot satisfy
|
||||
commits. Under `always-bump-minor`, unreleased breaking markers allow a minor
|
||||
increment and still require references in the new release's breaking-change
|
||||
migration section. Checkpoints using the default strategy retain the major
|
||||
increment requirement. Other omitted entries are reported for review.
|
||||
Historical notes from an older release cannot satisfy
|
||||
the new release's migration check. This audits declared markers and references;
|
||||
it does not prove API compatibility or the quality of a migration explanation.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"release-type": "node",
|
||||
"versioning": "always-bump-minor",
|
||||
"include-v-in-tag": false,
|
||||
"changelog-sections": [
|
||||
{ "type": "feat", "section": "Features", "hidden": false },
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const shaSchema = z.string().regex(/^[a-f0-9]{40}$/u);
|
|||
const versionSchema = z.object({ version: z.string() });
|
||||
const manifestSchema = z.object({ ".": z.string() });
|
||||
const sectionsSchema = z.object({
|
||||
versioning: z.string().optional(),
|
||||
"changelog-sections": z.array(
|
||||
z.object({ type: z.string(), hidden: z.boolean().optional() }),
|
||||
),
|
||||
|
|
@ -297,6 +298,7 @@ export async function inspectReleaseCheckpoint(git, options) {
|
|||
).filter((commit) => commit.breaking || visibleTypes.has(commit.type));
|
||||
const breaking = commits.filter((commit) => commit.breaking);
|
||||
if (
|
||||
configuration.versioning !== "always-bump-minor" &&
|
||||
breaking.length > 0 &&
|
||||
major(baselineVersion) > 0 &&
|
||||
major(expectedVersion) <= major(baselineVersion)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,12 @@ async function git(
|
|||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async function metadata(directory: string, version: string, notes: string) {
|
||||
async function metadata(
|
||||
directory: string,
|
||||
version: string,
|
||||
notes: string,
|
||||
versioning?: string,
|
||||
) {
|
||||
await mkdir(join(directory, "docs"), { recursive: true });
|
||||
await mkdir(join(directory, "src"), { recursive: true });
|
||||
const files = {
|
||||
|
|
@ -51,6 +56,7 @@ async function metadata(directory: string, version: string, notes: string) {
|
|||
packages: [{ identifier: "rea-agents", version }],
|
||||
},
|
||||
"release-please-config.json": {
|
||||
...(versioning === undefined ? {} : { versioning }),
|
||||
"changelog-sections": [
|
||||
{ type: "feat", hidden: false },
|
||||
{ type: "fix", hidden: false },
|
||||
|
|
@ -74,15 +80,15 @@ async function metadata(directory: string, version: string, notes: string) {
|
|||
async function fixture(
|
||||
subject = "fix(contracts)!: require absolute paths (#948)",
|
||||
footer = "",
|
||||
options: { mergePullRequest?: boolean } = {},
|
||||
options: { mergePullRequest?: boolean; versioning?: string } = {},
|
||||
) {
|
||||
const directory = await createTestTempDirectory("rea-release-ancestry-");
|
||||
await git(directory, ["init", "--initial-branch=main"]);
|
||||
await metadata(directory, "4.0.0", "Initial release");
|
||||
await metadata(directory, "4.0.0", "Initial release", options.versioning);
|
||||
await git(directory, ["add", "."]);
|
||||
await git(directory, ["commit", "-m", "chore: seed"], "2026-01-01T00:00:00Z");
|
||||
await git(directory, ["switch", "-c", "release/5.0.0"]);
|
||||
await metadata(directory, "5.0.0", "Previous release");
|
||||
await metadata(directory, "5.0.0", "Previous release", options.versioning);
|
||||
await git(directory, ["add", "."]);
|
||||
await git(
|
||||
directory,
|
||||
|
|
@ -130,7 +136,13 @@ async function fixture(
|
|||
"chore: sync published baseline",
|
||||
]);
|
||||
const sourceSha = await git(directory, ["rev-parse", "HEAD"]);
|
||||
return { directory, sourceSha, changedSha, releasedSha };
|
||||
return {
|
||||
directory,
|
||||
sourceSha,
|
||||
changedSha,
|
||||
releasedSha,
|
||||
versioning: options.versioning,
|
||||
};
|
||||
}
|
||||
|
||||
type Fixture = Awaited<ReturnType<typeof fixture>>;
|
||||
|
|
@ -140,7 +152,7 @@ async function candidate(
|
|||
version: string,
|
||||
notes = "### ⚠ BREAKING CHANGES\n\n* MCP callers must use absolute paths; CLI paths remain compatible. See #948.",
|
||||
) {
|
||||
await metadata(f.directory, version, notes);
|
||||
await metadata(f.directory, version, notes, f.versioning);
|
||||
await git(f.directory, ["add", "."]);
|
||||
await git(
|
||||
f.directory,
|
||||
|
|
@ -238,6 +250,51 @@ it("rejects a requested minor that omits the breaking ancestry", async () => {
|
|||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ subject: "fix(contracts)!: require absolute paths (#948)", footer: "" },
|
||||
{
|
||||
subject: "fix(contracts): change path inputs (#948)",
|
||||
footer: "BREAKING CHANGE: relative MCP paths are rejected",
|
||||
},
|
||||
])(
|
||||
"accepts a breaking minor under always-bump-minor: $subject",
|
||||
async ({ subject, footer }) => {
|
||||
const f = await fixture(subject, footer, {
|
||||
versioning: "always-bump-minor",
|
||||
});
|
||||
await expect(
|
||||
verify(f, "5.1.0", "prepare", "source"),
|
||||
).resolves.toMatchObject({
|
||||
stderr: "",
|
||||
});
|
||||
const candidateSha = await candidate(f, "5.1.0");
|
||||
const report = reportSchema.parse(
|
||||
JSON.parse((await verify(f, "5.1.0")).stdout),
|
||||
);
|
||||
expect(report.expectedVersion).toBe("5.1.0");
|
||||
expect(report.missingNotes).toEqual([]);
|
||||
expect(report.commits).toContainEqual({
|
||||
sha: f.changedSha,
|
||||
subject,
|
||||
breaking: true,
|
||||
});
|
||||
await expect(
|
||||
verify({ ...f, sourceSha: candidateSha }, "5.1.0", "publish", "source"),
|
||||
).resolves.toMatchObject({ stderr: "" });
|
||||
},
|
||||
);
|
||||
|
||||
it("still requires migration notes for breaking minor releases", async () => {
|
||||
const f = await fixture(undefined, "", { versioning: "always-bump-minor" });
|
||||
await candidate(f, "5.1.0", "### Bug Fixes\n\nSee #948.");
|
||||
await expect(verify(f, "5.1.0")).rejects.toMatchObject({
|
||||
code: 1,
|
||||
stderr: expect.stringContaining(
|
||||
"migration notes omit unreleased breaking changes",
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a minor when a GitHub merge commit carries its bang marker in the PR title", async () => {
|
||||
const f = await fixture("fix(contracts)!: require absolute paths", "", {
|
||||
mergePullRequest: true,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ describe("release configuration", () => {
|
|||
const configuration = await readJson("release-please-config.json");
|
||||
|
||||
expect(configuration).toMatchObject({
|
||||
versioning: "always-bump-minor",
|
||||
packages: {
|
||||
".": {
|
||||
"extra-files": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue