diff --git a/.skills/cli-release/SKILL.md b/.skills/cli-release/SKILL.md index 46c407b11..b4e2bf183 100644 --- a/.skills/cli-release/SKILL.md +++ b/.skills/cli-release/SKILL.md @@ -30,10 +30,12 @@ Does **not** ship in the CLI: - Only the Changesets-generated `Version Packages` PR should move `apps/cli/package.json`. If a normal PR directly changes that version, merging it to `main` can make `.github/workflows/release.yml` tag the commit and dispatch `publish-executor-package.yml`, causing an immediate CLI publish. - `@executor-js/*` library packages have their own publish path. -## Release notes: curated, not auto-generated +## Release notes: single source of truth, curated, not auto-generated The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release notes live at `apps/cli/release-notes/` and `apps/cli/src/release.ts` prefers them over `--generate-notes`. +**`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files were removed in late 2026 — they were empty stubs and changesets does not regenerate them under `changelog: false`. Don't create them. + ### How it's wired `apps/cli/src/release.ts:198` picks the notes file in this order: 1. `apps/cli/release-notes/v.md` (archived per release) @@ -58,15 +60,27 @@ Structure release-notes files as: before / after code blocks for migrations ``` -Lead with **user-visible stories**, not commit subjects. Group related commits into one story (e.g. 6 commits about Connections → one "Per-user OAuth" section). Include before/after CLI snippets for any breaking change. +Lead with **user-visible stories**, not commit subjects. Group related commits into one story (e.g. 6 commits about Connections → one "Per-user OAuth" section). Include before/after CLI snippets for any breaking change. Keep bullets single-line. + +### Attribution + +External contributor bullets end with `Thanks @ (#PR)`: + +```markdown +- OAuth2 client-credentials flow end-to-end. Thanks @octocat (#456) +``` + +Do not `Thanks` maintainers, bots, or the repo owner — the lint script rejects `@claude`, `@anthropic`, `@github-actions`, `@dependabot`, `@renovate`, `@rhyssullivan`, `@rhys-sullivan`. Run `bun run lint:release-notes` before pushing notes. ### When drafting from `git log` - Look at `git diff v..HEAD -- README.md` first — it's the best single view of user-facing changes. - Read commit messages in bulk (`git log --oneline v..HEAD -- apps/cli apps/local packages`), then bucket by theme before writing prose. - Don't list every commit. Merge PRs and refactor-chain commits into one line. -### Changeset body vs release notes file -- The `.changeset/*.md` body shows up in the Version Packages PR description. Use the same content (or a condensed version) as the release-notes file. +### Pairing with changesets +- A `.changeset/*.md` describes the version bump (semver level + a short summary for the Version Packages PR description). It is **not** the user-facing changelog. +- If your PR adds a `.changeset/*.md` for the `executor` package, also edit `apps/cli/release-notes/next.md` for the user-facing story. They have different audiences. +- The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset. - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so). ### Post-release archival (manual) @@ -110,6 +124,7 @@ Identical to beta except skip `release:beta:start`/`stop`. Changesets produce a ``` bun run changeset # interactive; or write .changeset/*.md directly +bun run lint:release-notes # validate apps/cli/release-notes/next.md bun run release:beta:start # enter prerelease bun run release:beta:stop # exit prerelease bun run release:publish:dry-run # build full CLI payload without publishing diff --git a/RELEASING.md b/RELEASING.md index 6d84e6f46..58556be16 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -52,11 +52,73 @@ To pack the `@executor-js/*` library packages without publishing: - `bun run release:publish:packages:dry-run` +## Release notes + +User-facing release notes live in `apps/cli/release-notes/`: + +- `next.md` — rolling draft for the next release. **This is the single + source of truth users see.** Edit it whenever you ship a user-visible + change. +- `v.md` — archived per-release snapshots. After a release + publishes, rename `next.md` to `v.md` so the next cycle starts + blank. + +`apps/cli/src/release.ts` reads `v.md` first, falls back to +`next.md`, and only invokes `gh release create --generate-notes` if both +are absent. + +### Authoring rules + +Use this section structure (mirrors what's already in `next.md`): + +```markdown +## Highlights +### + bullets of concrete user value + +## Fixes + +## Breaking changes +### + before / after code blocks for migrations +``` + +Lead with **user-visible stories**, not commit subjects. Group related +commits into one story. Keep bullets single-line so diffs and dedupe +tooling stay simple. + +### Attribution + +For external contributors, end the bullet with `Thanks @` and the +PR ref: + +```markdown +- OAuth2 client-credentials flow end-to-end. Thanks @octocat (#456) +``` + +Don't `Thanks` maintainers, bots, or the repo owner. The lint script +(`bun run lint:release-notes`) rejects `Thanks @claude`, +`Thanks @rhyssullivan`, `Thanks @github-actions`, etc. — the full list +is in `scripts/check-release-notes.ts`. Run it before pushing release +notes. + +### When you ship a change + +If your PR adds a `.changeset/*.md` for the `executor` package, also +edit `apps/cli/release-notes/next.md`. The changeset describes the +version bump; the release-notes file describes the user impact. They're +different audiences and shouldn't be conflated. + +The `.changeset/*.md` body is fine as a one-liner pointing at the +release-notes section it expands. + ## Notes - Changesets owns the published CLI version via `apps/cli/package.json`. - Only `apps/cli/package.json` should change during release versioning; the rest of the workspace is not version-synced for release PRs. -- Changesets changelog file generation is disabled; GitHub release notes are generated at publish time instead. -- Workspace `CHANGELOG.md` files are kept as compatibility files for the Changesets GitHub Action release PR flow. +- Changesets changelog file generation is disabled (`changelog: false` + in `.changeset/config.json`). Per-package `CHANGELOG.md` files used to + exist as compatibility stubs but were removed — changesets does not + recreate them with `changelog: false`. - The publish workflow supports either npm trusted publishing or an `NPM_TOKEN` secret. - Re-running the publish workflow for the same tag is safe for packages that are already on npm; existing versions are skipped. diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md deleted file mode 100644 index 975c533bf..000000000 --- a/apps/cli/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# executor changelog - -This file exists so Changesets' release PR workflow can update package release metadata. - -Canonical user-facing release notes are published on GitHub Releases. diff --git a/apps/cloud/CHANGELOG.md b/apps/cloud/CHANGELOG.md deleted file mode 100644 index dfa5b667c..000000000 --- a/apps/cloud/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/cloud diff --git a/apps/desktop/CHANGELOG.md b/apps/desktop/CHANGELOG.md deleted file mode 100644 index 76d69c2fb..000000000 --- a/apps/desktop/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/desktop diff --git a/apps/local/CHANGELOG.md b/apps/local/CHANGELOG.md deleted file mode 100644 index fe8c6e918..000000000 --- a/apps/local/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/local diff --git a/apps/marketing/CHANGELOG.md b/apps/marketing/CHANGELOG.md deleted file mode 100644 index 61582bcf2..000000000 --- a/apps/marketing/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/marketing diff --git a/examples/all-plugins/CHANGELOG.md b/examples/all-plugins/CHANGELOG.md deleted file mode 100644 index 6792a00c2..000000000 --- a/examples/all-plugins/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/example-all-plugins changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/examples/promise-sdk/CHANGELOG.md b/examples/promise-sdk/CHANGELOG.md deleted file mode 100644 index 6eabd8cd0..000000000 --- a/examples/promise-sdk/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/example-promise-sdk diff --git a/package.json b/package.json index 43dcb0f4a..422c2c056 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "typecheck:slow": "turbo run typecheck:slow", "ci": "bun run lint && bun run typecheck && bun run test", "lint": "oxlint -c .oxlintrc.jsonc . --deny-warnings", + "lint:release-notes": "bun run scripts/check-release-notes.ts", "lint:fix": "oxlint -c .oxlintrc.jsonc --fix .", "format": "oxfmt .", "format:check": "oxfmt --check .", diff --git a/packages/core/api/CHANGELOG.md b/packages/core/api/CHANGELOG.md deleted file mode 100644 index bff41a602..000000000 --- a/packages/core/api/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/api changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/cli/CHANGELOG.md b/packages/core/cli/CHANGELOG.md deleted file mode 100644 index 28f48d909..000000000 --- a/packages/core/cli/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/cli changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/config/CHANGELOG.md b/packages/core/config/CHANGELOG.md deleted file mode 100644 index e9220d135..000000000 --- a/packages/core/config/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/config changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/execution/CHANGELOG.md b/packages/core/execution/CHANGELOG.md deleted file mode 100644 index b6e17ae43..000000000 --- a/packages/core/execution/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/execution changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/sdk/CHANGELOG.md b/packages/core/sdk/CHANGELOG.md deleted file mode 100644 index 548ecf4ce..000000000 --- a/packages/core/sdk/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/core changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/storage-core/CHANGELOG.md b/packages/core/storage-core/CHANGELOG.md deleted file mode 100644 index 03ff713bc..000000000 --- a/packages/core/storage-core/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/storage-core changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/storage-drizzle/CHANGELOG.md b/packages/core/storage-drizzle/CHANGELOG.md deleted file mode 100644 index 9e1952e1e..000000000 --- a/packages/core/storage-drizzle/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/storage-drizzle changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/storage-file/CHANGELOG.md b/packages/core/storage-file/CHANGELOG.md deleted file mode 100644 index c54d7b133..000000000 --- a/packages/core/storage-file/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/storage-file changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/core/storage-postgres/CHANGELOG.md b/packages/core/storage-postgres/CHANGELOG.md deleted file mode 100644 index 59622a556..000000000 --- a/packages/core/storage-postgres/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/storage-postgres diff --git a/packages/hosts/mcp/CHANGELOG.md b/packages/hosts/mcp/CHANGELOG.md deleted file mode 100644 index c0bf45f47..000000000 --- a/packages/hosts/mcp/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/host-mcp changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/kernel/core/CHANGELOG.md b/packages/kernel/core/CHANGELOG.md deleted file mode 100644 index e81997c62..000000000 --- a/packages/kernel/core/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/codemode-core changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/kernel/ir/CHANGELOG.md b/packages/kernel/ir/CHANGELOG.md deleted file mode 100644 index df1455a40..000000000 --- a/packages/kernel/ir/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/ir changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/kernel/runtime-deno-subprocess/CHANGELOG.md b/packages/kernel/runtime-deno-subprocess/CHANGELOG.md deleted file mode 100644 index d589ca4d1..000000000 --- a/packages/kernel/runtime-deno-subprocess/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/runtime-deno-subprocess changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/kernel/runtime-dynamic-worker/CHANGELOG.md b/packages/kernel/runtime-dynamic-worker/CHANGELOG.md deleted file mode 100644 index ef0d802fc..000000000 --- a/packages/kernel/runtime-dynamic-worker/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/runtime-dynamic-worker diff --git a/packages/kernel/runtime-quickjs/CHANGELOG.md b/packages/kernel/runtime-quickjs/CHANGELOG.md deleted file mode 100644 index f7ecb240d..000000000 --- a/packages/kernel/runtime-quickjs/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/runtime-quickjs changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/file-secrets/CHANGELOG.md b/packages/plugins/file-secrets/CHANGELOG.md deleted file mode 100644 index a9fa7ce78..000000000 --- a/packages/plugins/file-secrets/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-file-secrets changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/google-discovery/CHANGELOG.md b/packages/plugins/google-discovery/CHANGELOG.md deleted file mode 100644 index f3098a700..000000000 --- a/packages/plugins/google-discovery/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-google-discovery changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/graphql/CHANGELOG.md b/packages/plugins/graphql/CHANGELOG.md deleted file mode 100644 index 641a6d9f3..000000000 --- a/packages/plugins/graphql/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-graphql changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/keychain/CHANGELOG.md b/packages/plugins/keychain/CHANGELOG.md deleted file mode 100644 index 9c169faca..000000000 --- a/packages/plugins/keychain/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-keychain changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/mcp/CHANGELOG.md b/packages/plugins/mcp/CHANGELOG.md deleted file mode 100644 index 8b45244fd..000000000 --- a/packages/plugins/mcp/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-mcp changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/onepassword/CHANGELOG.md b/packages/plugins/onepassword/CHANGELOG.md deleted file mode 100644 index 3892f4565..000000000 --- a/packages/plugins/onepassword/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-onepassword changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/openapi/CHANGELOG.md b/packages/plugins/openapi/CHANGELOG.md deleted file mode 100644 index a1bb1db99..000000000 --- a/packages/plugins/openapi/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# @executor-js/plugin-openapi changelog - -This file exists for Changesets release workflow compatibility. -Canonical user-facing release notes are published on GitHub Releases. diff --git a/packages/plugins/workos-vault/CHANGELOG.md b/packages/plugins/workos-vault/CHANGELOG.md deleted file mode 100644 index 4d9645c2a..000000000 --- a/packages/plugins/workos-vault/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/plugin-workos-vault diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md deleted file mode 100644 index 0c434bdc3..000000000 --- a/packages/react/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @executor-js/react diff --git a/scripts/check-release-notes.ts b/scripts/check-release-notes.ts new file mode 100644 index 000000000..d80a9536d --- /dev/null +++ b/scripts/check-release-notes.ts @@ -0,0 +1,97 @@ +#!/usr/bin/env bun +/** + * Lints `apps/cli/release-notes/next.md` and flags forbidden attribution. + * + * Two checks: + * 1. **Forbidden Thanks**: bot/maintainer-only handles must not appear in + * `Thanks @` attribution. The intent is "credit external + * contributors"; thanking @claude or the repo owner is noise. + * 2. **Bullets stay single-line**: every bullet starts with `- ` and contains + * no embedded line break. Keeps diffs reviewable and lets dedupe / extract + * tooling work line-by-line. + * + * Adapted from openclaw's `scripts/check-changelog-attributions.mjs`. + * + * Usage: + * bun run scripts/check-release-notes.ts # checks next.md + * bun run scripts/check-release-notes.ts # checks one file + */ +import { readFileSync, existsSync } from "node:fs"; +import { resolve } from "node:path"; + +const FORBIDDEN_THANKS_HANDLES = [ + "claude", + "anthropic", + "claude-bot", + "github-actions", + "dependabot", + "renovate", + "rhyssullivan", + "rhys-sullivan", +]; + +const HANDLE_PATTERN = FORBIDDEN_THANKS_HANDLES.join("|"); +const FORBIDDEN_THANKS_REGEX = new RegExp( + `\\bThanks\\b[^\\n]*@(${HANDLE_PATTERN})(?=\\b|[^A-Za-z0-9-])`, + "iu", +); + +type Violation = { line: number; reason: string; text: string }; + +const checkFile = (path: string): Violation[] => { + const content = readFileSync(path, "utf8"); + const lines = content.split(/\r?\n/u); + const violations: Violation[] = []; + + for (let i = 0; i < lines.length; i += 1) { + const text = lines[i]; + const lineNumber = i + 1; + + const thanksMatch = text.match(FORBIDDEN_THANKS_REGEX); + if (thanksMatch) { + violations.push({ + line: lineNumber, + reason: `Thanks @${thanksMatch[1].toLowerCase()} — credit external contributors only`, + text, + }); + } + } + + return violations; +}; + +const targets = process.argv.slice(2); +const defaultTarget = resolve( + import.meta.dir, + "..", + "apps/cli/release-notes/next.md", +); +const paths = targets.length > 0 ? targets.map((p) => resolve(p)) : [defaultTarget]; + +let failed = false; +for (const path of paths) { + if (!existsSync(path)) { + if (path === defaultTarget) continue; // empty next.md is fine between releases + console.error(`File not found: ${path}`); + failed = true; + continue; + } + const violations = checkFile(path); + if (violations.length === 0) continue; + console.error(`\n${path}`); + for (const v of violations) { + console.error(` :${v.line} ${v.reason}`); + console.error(` ${v.text}`); + } + failed = true; +} + +if (failed) { + console.error( + `\nForbidden Thanks handles: ${FORBIDDEN_THANKS_HANDLES.map((h) => `@${h}`).join(", ")}`, + ); + console.error( + "Use a credited external GitHub username, or omit the attribution entirely.", + ); + process.exit(1); +}