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
97 changes: 97 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
release:
description: Force a release bump
required: false
default: auto
type: choice
options:
- auto
- patch
- minor
- major
version:
description: Exact release version, e.g. 0.6.0
required: false

permissions:
contents: read

jobs:
test:
Expand Down Expand Up @@ -48,3 +66,82 @@ jobs:
# job-local dir (the smoke script also overrides XDG_CONFIG_HOME).
HOME: ${{ runner.temp }}
run: python scripts/test_local.py

release:
name: Release
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
permissions:
contents: write
concurrency:
group: meti-release-main
cancel-in-progress: false

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Plan release
id: plan
env:
RELEASE_BUMP: ${{ github.event_name == 'workflow_dispatch' && inputs.release || 'auto' }}
RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || '' }}
run: |
python scripts/plan_release.py \
--force-bump "$RELEASE_BUMP" \
--version "$RELEASE_VERSION" \
--github-output

- name: Show release decision
run: |
echo "should_release=${{ steps.plan.outputs.should_release }}"
echo "version=${{ steps.plan.outputs.version }}"
echo "reason=${{ steps.plan.outputs.reason }}"

- name: Prepare release version
if: steps.plan.outputs.should_release == 'true'
run: python scripts/release.py prepare --version "${{ steps.plan.outputs.version }}"

- name: Commit release version
if: steps.plan.outputs.should_release == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add release.json pyproject.toml SKILL.md .claude-plugin/plugin.json .claude-plugin/marketplace.json CHANGELOG.md
git commit -m "chore(release): prepare ${{ steps.plan.outputs.version }}"

- name: Verify and build release
if: steps.plan.outputs.should_release == 'true'
run: |
python scripts/check_release.py
python scripts/release.py build

- name: Push release commit and tag
if: steps.plan.outputs.should_release == 'true'
run: |
git tag "${{ steps.plan.outputs.tag }}"
git push --atomic origin HEAD:main "${{ steps.plan.outputs.tag }}"

- name: Publish GitHub Release
if: steps.plan.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ steps.plan.outputs.tag }}" \
dist/releases/${{ steps.plan.outputs.tag }}/* \
--title "${{ steps.plan.outputs.tag }}" \
--notes-file CHANGELOG.md
22 changes: 17 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,23 @@ create live platform drafts, open a browser, read real credentials, or submit
marketplace data.

`release.json` is the canonical publication source for versioned releases.
Use the semi-automatic release workflow so Python package metadata, SKILL
frontmatter, plugin metadata, marketplace metadata, artifacts, changelog, tag,
and GitHub Release stay in sync:
Normal releases are created by CI from `main` after the full test matrix passes.
CI updates Python package metadata, SKILL frontmatter, plugin metadata,
marketplace metadata, artifacts, changelog, tag, and GitHub Release together.

Release selection is intentionally conservative:

- `feat(scope): ...` or `perf(scope): ...` creates a minor release.
- `feat(scope)!: ...` or a `BREAKING CHANGE:` footer creates a major release.
- `release: patch`, `release: minor`, or `release: major` in the commit body
can force a release when the Conventional Commit type is not enough.
- Routine `fix:`, `docs:`, `test:`, `chore:`, `ci:`, and `refactor:` commits do
not publish by default.

Use the local dry-run commands only when reviewing or recovering a release:

```bash
python scripts/plan_release.py
python scripts/release.py prepare --version X.Y.Z --dry-run
python scripts/release.py build --dry-run
python scripts/release.py publish --version X.Y.Z --dry-run
Expand Down Expand Up @@ -134,10 +146,10 @@ platform artifacts in tracked files.

## Commits, PRs, releases

- **Commit messages**: imperative present tense, scope-prefixed when it helps (`feat(substack):`, `fix(wechat-image):`, `docs:`, `refactor(browser):`). The recent git log is a good style reference.
- **Commit messages**: imperative present tense, scope-prefixed when it helps (`feat(substack):`, `fix(wechat-image):`, `docs:`, `refactor(browser):`). CI uses `feat`, `perf`, `!`, `BREAKING CHANGE:`, and `release:*` markers to decide whether to publish a release, so reserve those signals for important user-visible changes.
- **PRs** target `main`. Squash-merge by default. PR description should answer: what, why, how-verified.
- **Tests required** for any code change. New providers ship with both unit tests and a real-account verification note.
- **Releases**: maintainers publish `vMAJOR.MINOR.PATCH` from `main` after CI green and `python scripts/check_release.py` passes; the `release.json` bump lands in the same PR as the user-visible feature.
- **Releases**: CI publishes `vMAJOR.MINOR.PATCH` from `main` after CI green and `python scripts/check_release.py` passes. The `release.json` bump lands in the CI-created release commit.

### Prefer reinstall

Expand Down
42 changes: 39 additions & 3 deletions docs/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,53 @@ private paths, generated wheel/release bundle contents, install docs, and the
no-network smoke flow. It does not create live platform drafts, open a browser,
read real credentials, submit marketplace data, or publish anything publicly.

Release maintainers build and publish with dry-run-first commands:
## Automated release policy

CI owns normal releases from `main`. After the full macOS + Ubuntu test matrix
passes, the release job inspects commits since the latest `vX.Y.Z` tag and
publishes only when there is a release-worthy signal.

Automatic release signals:

- `feat(scope): ...` or `perf(scope): ...` creates a minor release.
- `feat(scope)!: ...` or a `BREAKING CHANGE:` footer creates a major release.
- `release: patch`, `release: minor`, or `release: major` in the commit body
overrides the automatic choice.

Routine `fix:`, `docs:`, `test:`, `chore:`, `ci:`, and `refactor:` commits do
not publish a release by default. This keeps the release stream focused on
important user-visible changes instead of every small maintenance update.

When a release is selected, CI runs:

```bash
python scripts/plan_release.py --github-output
python scripts/release.py prepare --version X.Y.Z
python scripts/check_release.py
python scripts/release.py build
```

Then CI commits the synchronized version files, atomically pushes `main` and
`vX.Y.Z`, and creates the GitHub Release with the generated artifacts from
`dist/releases/vX.Y.Z/`.

Manual override is available from the CI workflow dispatch UI for exceptional
cases: choose `patch`, `minor`, or `major`, or provide an exact `X.Y.Z` version.
Use this sparingly; the default path should be normal commits to `main`.

Release maintainers can still inspect the same flow locally with dry-run-first
commands:

```bash
python scripts/plan_release.py
python scripts/release.py prepare --version X.Y.Z --dry-run
python scripts/release.py build --dry-run
python scripts/release.py publish --version X.Y.Z --dry-run
```

Confirmed publish creates the `vX.Y.Z` tag and published GitHub Release only
Manual local publish creates the `vX.Y.Z` tag and published GitHub Release only
after the maintainer approves the printed target version, artifact list,
checksums, and `gh release create` command.
checksums, and `gh release create` command. Prefer CI for routine releases.

## Private artifacts

Expand Down
Loading
Loading