From 4c5b2fc0de46b2502733c20b40ab07b89a929b6d Mon Sep 17 00:00:00 2001 From: Brian Estrada Date: Sun, 11 Oct 2026 15:35:15 +0200 Subject: [PATCH 1/3] feat: publish diffcop to npm --- .github/workflows/release.yml | 32 +++++++++++++++++++++++++------- AGENTS.md | 6 +++--- README.md | 12 +++++------- bun.lock | 2 -- package.json | 26 +++++++++++++++++++++----- 5 files changed, 54 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c9ce0b..ac17a97 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,8 @@ -# Cuts a release from main: tags the current commit and creates the GitHub Release with notes -# from git-cliff. Run it from the Actions tab. -# It creates the tag through the Releases API rather than pushing, because main's ruleset blocks bot pushes, -# so no release commit is made and package.json's version isn't bumped. +# Cuts a release from main: publishes to npm, then tags the commit and creates the GitHub Release +# with notes from git-cliff. Run it from the Actions tab. +# npm trusted publishing (OIDC) matches this workflow by file name, so keep it `release.yml`. +# main's ruleset blocks bot pushes, so there's no release commit: the tag comes from the Releases API, +# and package.json's version is bumped only on the runner, for the publish. name: Release on: @@ -15,6 +16,8 @@ on: permissions: # Create the tag and the GitHub Release. contents: write + # OIDC token for npm trusted publishing and provenance. + id-token: write concurrency: group: release @@ -36,15 +39,20 @@ jobs: with: bun-version: 1.4.3 - - run: bun install --frozen-lockfile + # Trusted publishing needs npm 11.5.1 or later, which current LTS Node ships. + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org - - run: bun run check + - run: bun install --frozen-lockfile - name: Pick the version id: version env: REQUESTED: ${{ inputs.version }} run: | + # The latest tag, not package.json: its version stays put on main. latest="$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2> /dev/null || true)" if [ -n "$REQUESTED" ]; then version="${REQUESTED#v}" @@ -67,7 +75,17 @@ jobs: fi echo "version=$version" >> "$GITHUB_OUTPUT" - - name: Create the release + - name: Bump package.json + env: + VERSION: ${{ steps.version.outputs.version }} + run: bun pm version "$VERSION" --no-git-tag-version + + - run: bun run check + + # Before the tag, so a failed publish (e.g. a trusted publisher mismatch) can just be re-run. + - run: npm publish + + - name: Create the GitHub Release env: VERSION: ${{ steps.version.outputs.version }} GH_TOKEN: ${{ github.token }} diff --git a/AGENTS.md b/AGENTS.md index 74fb8df..11ab148 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ AGENTS.md strategy: this file is for coding agents, and it's loaded into every s ## Rules -- Use Bun for everything (`bun`, `bun run`, `bunx`), not node, npm, ts-node, or dotenv. Bun loads `.env` itself. +- Use Bun for everything (`bun`, `bun run`, `bunx`), not node, npm, ts-node, or dotenv. Bun loads `.env` itself. The one exception is `npm publish` in the release workflow. - Prefer Bun APIs over npm packages: `` Bun.$`git ...` ``, `Bun.file`, `Bun.serve`. - Parse data from outside the process (GitHub API, event payloads, harness metadata) with a Zod schema, and get its type from `z.infer`. GitHub responses go through `schema.safeParse` and `unwrapResponse`. - Keep the reviewer agent in `src/agent/claude-code/claude-code-agent.ts` read-only (`read`, `glob`, `grep`, `permissionMode: "allow-reads"`). Never give it write or shell tools. @@ -38,8 +38,8 @@ Before writing or keeping a comment or JSDoc block, walk this: ## Releasing - Release only when asked. Preview with `bunx git-cliff --bumped-version` and `bun run release-notes`. -- Run `gh workflow run release.yml --ref main` (add `-f version=X.Y.Z` to override), then `gh run watch`. It tags the commit and creates the GitHub Release. Never tag or create releases by hand. -- Notes come from commit subjects (PR titles after a squash merge): `feat`, `fix`, `perf`, `docs` and breaking changes; the rest is skipped. Use those types only for changes the action's users notice. +- Run `gh workflow run release.yml --ref main` (add `-f version=X.Y.Z` to override), then `gh run watch`. It publishes to npm, tags the commit and creates the GitHub Release. Never tag, create releases or run `npm publish` by hand. +- Notes come from commit subjects (PR titles after a squash merge): `feat`, `fix`, `perf`, `docs` and breaking changes; the rest is skipped. Use those types only for changes the CLI's or action's users notice. ## PRs diff --git a/README.md b/README.md index 69f743b..6c370bf 100644 --- a/README.md +++ b/README.md @@ -34,15 +34,13 @@ for your subscription and set it as `CLAUDE_CODE_OAUTH_TOKEN`. To pay per token `ANTHROPIC_API_KEY`. ```bash -git clone https://github.com/BrianEstrada/diffcop.git -cd diffcop -bun install -bun link # puts `diffcop` on your PATH (~/.bun/bin) - -cd ../your-repo -diffcop review "review this code" +cd your-repo +bunx diffcop review "review this code" ``` +To keep `diffcop` on your PATH, install it with `bun add --global diffcop`. It runs on Bun, so +`npx diffcop` works only where Bun is installed. + ## Usage ```bash diff --git a/bun.lock b/bun.lock index 95cce0a..1458344 100644 --- a/bun.lock +++ b/bun.lock @@ -28,8 +28,6 @@ "oxlint": "1.87.0", "oxlint-plugin-deslopify": "0.5.0", "oxlint-tsgolint": "7.0.2004", - }, - "peerDependencies": { "typescript": "7.0.2", }, }, diff --git a/package.json b/package.json index 7a2fd57..47b9301 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,29 @@ { "name": "diffcop", "version": "0.1.0", - "private": true, + "description": "An opinionated, local-first AI code reviewer that runs on the coding agent you already use.", + "keywords": [ + "ai", + "claude-code", + "cli", + "code-review" + ], + "homepage": "https://github.com/BrianEstrada/diffcop#readme", + "bugs": "https://github.com/BrianEstrada/diffcop/issues", "license": "MIT", + "author": "Brian Estrada", + "repository": { + "type": "git", + "url": "git+https://github.com/BrianEstrada/diffcop.git" + }, "bin": { - "diffcop": "./src/cli.ts" + "diffcop": "src/cli.ts" }, + "files": [ + "src", + "!src/**/*.test.ts", + "tsconfig.json" + ], "type": "module", "imports": { "#package.json": "./package.json" @@ -43,9 +61,7 @@ "oxfmt": "0.72.0", "oxlint": "1.87.0", "oxlint-plugin-deslopify": "0.5.0", - "oxlint-tsgolint": "7.0.2004" - }, - "peerDependencies": { + "oxlint-tsgolint": "7.0.2004", "typescript": "7.0.2" } } From 627faed6acd6aae141fdb55b41984dae702cd59b Mon Sep 17 00:00:00 2001 From: Brian Estrada Date: Sun, 11 Oct 2026 15:38:55 +0200 Subject: [PATCH 2/3] ci(release): pick the version with the shared git-cliff-version action --- .github/workflows/release.yml | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac17a97..ae339a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,33 +47,10 @@ jobs: - run: bun install --frozen-lockfile - - name: Pick the version - id: version - env: - REQUESTED: ${{ inputs.version }} - run: | - # The latest tag, not package.json: its version stays put on main. - latest="$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2> /dev/null || true)" - if [ -n "$REQUESTED" ]; then - version="${REQUESTED#v}" - else - version="$(bunx git-cliff --bumped-version)" - version="${version#v}" - fi - if ! echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "\"$version\" isn't a version like 1.2.3" >&2 - exit 1 - fi - # git-cliff returns the latest version when every commit since it is skipped (deps, chores). - if [ "v$version" = "$latest" ]; then - echo "Nothing to release: no feat, fix, perf, docs or breaking commits since $latest" >&2 - exit 1 - fi - if git rev-parse -q --verify "refs/tags/v$version" > /dev/null; then - echo "Tag v$version already exists" >&2 - exit 1 - fi - echo "version=$version" >> "$GITHUB_OUTPUT" + - id: version + uses: BrianEstrada/.github/git-cliff-version@4a23053ced2816db78af5015fd62b75aff614ecf # main + with: + version: ${{ inputs.version }} - name: Bump package.json env: From 1fa2af9270d1b76f1a5d1d1b79b138e88cc582c8 Mon Sep 17 00:00:00 2001 From: Brian Estrada Date: Sun, 11 Oct 2026 15:51:03 +0200 Subject: [PATCH 3/3] ci(release): release with the shared bun-npm-release workflow --- .github/workflows/release.yml | 62 +++++++---------------------------- AGENTS.md | 2 +- CHANGELOG.md | 13 ++++++++ cliff.toml | 11 ++++++- package.json | 1 + 5 files changed, 36 insertions(+), 53 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae339a5..d43ca73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,21 +1,19 @@ -# Cuts a release from main: publishes to npm, then tags the commit and creates the GitHub Release -# with notes from git-cliff. Run it from the Actions tab. +# Cuts a release from main with the shared bun-npm-release workflow: bumps the version, updates +# CHANGELOG.md, commits and tags it, publishes to npm and creates the GitHub Release. +# Run it from the Actions tab. # npm trusted publishing (OIDC) matches this workflow by file name, so keep it `release.yml`. -# main's ruleset blocks bot pushes, so there's no release commit: the tag comes from the Releases API, -# and package.json's version is bumped only on the runner, for the publish. name: Release on: workflow_dispatch: inputs: version: - description: Version to release, like 0.2.0. Leave empty to work it out from the commits. + description: Version to release, like 1.2.3. Leave empty to work it out from the commits. required: false type: string permissions: - # Create the tag and the GitHub Release. - contents: write + contents: read # OIDC token for npm trusted publishing and provenance. id-token: write @@ -25,47 +23,9 @@ concurrency: jobs: release: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - # git-cliff reads every commit and tag since the last release. - fetch-depth: 0 - persist-credentials: false - - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.4.3 - - # Trusted publishing needs npm 11.5.1 or later, which current LTS Node ships. - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 - with: - node-version: lts/* - registry-url: https://registry.npmjs.org - - - run: bun install --frozen-lockfile - - - id: version - uses: BrianEstrada/.github/git-cliff-version@4a23053ced2816db78af5015fd62b75aff614ecf # main - with: - version: ${{ inputs.version }} - - - name: Bump package.json - env: - VERSION: ${{ steps.version.outputs.version }} - run: bun pm version "$VERSION" --no-git-tag-version - - - run: bun run check - - # Before the tag, so a failed publish (e.g. a trusted publisher mismatch) can just be re-run. - - run: npm publish - - - name: Create the GitHub Release - env: - VERSION: ${{ steps.version.outputs.version }} - GH_TOKEN: ${{ github.token }} - run: | - bunx git-cliff --unreleased --tag "v$VERSION" --strip all > "$RUNNER_TEMP/notes.md" - gh release create "v$VERSION" --target "$GITHUB_SHA" --title "v$VERSION" --notes-file "$RUNNER_TEMP/notes.md" + uses: BrianEstrada/.github/.github/workflows/bun-npm-release.yml@a3365073142d6188b8e60334052183ae289b4ba3 # main + with: + version: ${{ inputs.version }} + app-client-id: ${{ vars.RELEASE_APP_CLIENT_ID }} + secrets: + app-private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} diff --git a/AGENTS.md b/AGENTS.md index 11ab148..4ce18b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ Before writing or keeping a comment or JSDoc block, walk this: ## Releasing - Release only when asked. Preview with `bunx git-cliff --bumped-version` and `bun run release-notes`. -- Run `gh workflow run release.yml --ref main` (add `-f version=X.Y.Z` to override), then `gh run watch`. It publishes to npm, tags the commit and creates the GitHub Release. Never tag, create releases or run `npm publish` by hand. +- Run `gh workflow run release.yml --ref main` (add `-f version=X.Y.Z` to override), then `gh run watch`. The shared `bun-npm-release` workflow bumps `package.json`, prepends to `CHANGELOG.md`, commits `chore(release): vX.Y.Z`, tags, publishes to npm and creates the GitHub Release. Never do any of those steps by hand: don't edit `version`, run `bun run changelog`, tag, or run `npm publish`. - Notes come from commit subjects (PR titles after a squash merge): `feat`, `fix`, `perf`, `docs` and breaking changes; the rest is skipped. Use those types only for changes the CLI's or action's users notice. ## PRs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..88302c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project are documented here. Generated by [git-cliff](https://git-cliff.org) from the commit history. + + + +## [0.1.0](https://github.com/BrianEstrada/diffcop/releases/tag/v0.1.0) - 2026-10-11 + +First release. + +### Documentation + +- Add diffcop branding ([#4](https://github.com/BrianEstrada/diffcop/pull/4)) diff --git a/cliff.toml b/cliff.toml index 13c1e4f..6e400e4 100644 --- a/cliff.toml +++ b/cliff.toml @@ -1,6 +1,15 @@ -# git-cliff config: builds release notes from Conventional Commits. https://git-cliff.org/docs/configuration +# git-cliff config: builds CHANGELOG.md from Conventional Commits. https://git-cliff.org/docs/configuration [changelog] +header = """ +# Changelog + +All notable changes to this project are documented here. Generated by [git-cliff](https://git-cliff.org) from the commit history. + + +""" +# `--prepend` swaps everything above this marker for the new header. +header_marker = "" body = """ {% if version -%} ## [{{ version | trim_start_matches(pat="v") }}]\ diff --git a/package.json b/package.json index 47b9301..c807199 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "lint": "oxlint", "lint:fix": "oxlint --fix", "check": "bun run typecheck && bun run lint && bun run fmt:check && bun test --pass-with-no-tests", + "changelog": "if grep -qF \"## [$npm_package_version]\" CHANGELOG.md; then echo \"CHANGELOG.md already has $npm_package_version\"; exit 1; fi; git-cliff --unreleased --tag v$npm_package_version --prepend CHANGELOG.md && oxfmt CHANGELOG.md", "release-notes": "git-cliff --unreleased --strip all" }, "dependencies": {