Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions .skills/cli-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>.md` (archived per release)
Expand All @@ -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 @<user> (#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<last>..HEAD -- README.md` first — it's the best single view of user-facing changes.
- Read commit messages in bulk (`git log --oneline v<last>..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)
Expand Down Expand Up @@ -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
Expand Down
66 changes: 64 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>.md` — archived per-release snapshots. After a release
publishes, rename `next.md` to `v<version>.md` so the next cycle starts
blank.

`apps/cli/src/release.ts` reads `v<tag>.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
### <user-facing story>
bullets of concrete user value

## Fixes

## Breaking changes
### <specific surface>
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 @<user>` 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.
5 changes: 0 additions & 5 deletions apps/cli/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion apps/cloud/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion apps/desktop/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion apps/local/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion apps/marketing/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions examples/all-plugins/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion examples/promise-sdk/CHANGELOG.md

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 .",
Expand Down
4 changes: 0 additions & 4 deletions packages/core/api/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/cli/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/config/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/execution/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/sdk/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/storage-core/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/storage-drizzle/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/core/storage-file/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/core/storage-postgres/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/hosts/mcp/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/kernel/core/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/kernel/ir/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/kernel/runtime-deno-subprocess/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/kernel/runtime-dynamic-worker/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/kernel/runtime-quickjs/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/file-secrets/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/google-discovery/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/graphql/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/keychain/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/mcp/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/onepassword/CHANGELOG.md

This file was deleted.

4 changes: 0 additions & 4 deletions packages/plugins/openapi/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/plugins/workos-vault/CHANGELOG.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/react/CHANGELOG.md

This file was deleted.

97 changes: 97 additions & 0 deletions scripts/check-release-notes.ts
Original file line number Diff line number Diff line change
@@ -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 @<handle>` 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 <path/to/file.md> # 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);
}
Loading