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
269 changes: 13 additions & 256 deletions .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build Binaries
name: Publish Approved Distributions

on:
push:
Expand All @@ -22,207 +22,8 @@ concurrency:
cancel-in-progress: false

jobs:
# Keep the public GitHub Release publication last. Binary assets are staged in
# a draft release first; cleanup removes the draft if later publishing fails.
build:
runs-on: ubuntu-latest
permissions:
contents: read
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
SOURCE_REF: ${{ github.event.inputs.source_ref || github.event.inputs.tag || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.SOURCE_REF }}
persist-credentials: false

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- name: Hydrate release model data
run: npm run hydrate:model-data

- name: Create source archive
run: |
set -euo pipefail

VERSION="${RELEASE_TAG#v}"
mkdir -p release-assets
./scripts/create-source-archive.sh \
--version "${VERSION}" \
--ref HEAD \
--out "release-assets/diffex-${VERSION}-source.tar.gz"

- name: Build binaries from source archive
run: |
set -euo pipefail

VERSION="${RELEASE_TAG#v}"
build_root="$(mktemp -d)"
trap 'rm -rf "${build_root}"' EXIT
tar -xzf "release-assets/diffex-${VERSION}-source.tar.gz" -C "${build_root}"
"${build_root}/diffex-${VERSION}/scripts/build-binaries.sh" \
--offline-model-data \
--out "${GITHUB_WORKSPACE}/packages/coding-agent/binaries"

- name: Prepare GitHub release payload
run: |
set -euo pipefail

mkdir -p release-assets

VERSION="${RELEASE_TAG}"
VERSION="${VERSION#v}" # Remove 'v' prefix
node scripts/release-notes.mjs extract --version "${VERSION}" --tag "${RELEASE_TAG}" --out release-assets/RELEASE_NOTES.md
node scripts/generate-coding-agent-install-lock.mjs --check
cp packages/coding-agent/install-lock/package.json release-assets/diffex-coding-agent-install-package.json
cp packages/coding-agent/install-lock/package-lock.json release-assets/diffex-coding-agent-install-package-lock.json

cd packages/coding-agent/binaries

binary_assets=(
diffex-darwin-arm64.tar.gz
diffex-darwin-x64.tar.gz
diffex-linux-x64.tar.gz
diffex-linux-arm64.tar.gz
diffex-windows-x64.zip
diffex-windows-arm64.zip
)

for asset in "${binary_assets[@]}"; do
test -f "${asset}"
done

cp "${binary_assets[@]}" "${GITHUB_WORKSPACE}/release-assets/"

cd "${GITHUB_WORKSPACE}/release-assets"
source_asset="diffex-${VERSION}-source.tar.gz"
release_assets=(
"${source_asset}"
diffex-darwin-arm64.tar.gz
diffex-darwin-x64.tar.gz
diffex-linux-x64.tar.gz
diffex-linux-arm64.tar.gz
diffex-windows-x64.zip
diffex-windows-arm64.zip
diffex-coding-agent-install-package.json
diffex-coding-agent-install-package-lock.json
)
sha256sum "${release_assets[@]}" > SHA256SUMS

- name: Upload GitHub release payload
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-assets-${{ env.RELEASE_TAG }}
path: release-assets/*
if-no-files-found: error
retention-days: 14

stage-github-release:
runs-on: ubuntu-latest
needs: build
permissions:
actions: read
contents: write
env:
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Download GitHub release payload
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-assets-${{ env.RELEASE_TAG }}
path: release-assets

- name: Validate GitHub release payload
run: |
set -euo pipefail

cd release-assets

VERSION="${RELEASE_TAG#v}"
source_asset="diffex-${VERSION}-source.tar.gz"
expected_assets=(
"${source_asset}"
diffex-darwin-arm64.tar.gz
diffex-darwin-x64.tar.gz
diffex-linux-x64.tar.gz
diffex-linux-arm64.tar.gz
diffex-windows-x64.zip
diffex-windows-arm64.zip
diffex-coding-agent-install-package.json
diffex-coding-agent-install-package-lock.json
SHA256SUMS
RELEASE_NOTES.md
)

for asset in "${expected_assets[@]}"; do
test -f "${asset}"
done

sha256sum -c SHA256SUMS

- name: Create draft GitHub Release and upload assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

cd release-assets

VERSION="${RELEASE_TAG#v}"
source_asset="diffex-${VERSION}-source.tar.gz"
release_assets=(
"${source_asset}"
diffex-darwin-arm64.tar.gz
diffex-darwin-x64.tar.gz
diffex-linux-x64.tar.gz
diffex-linux-arm64.tar.gz
diffex-windows-x64.zip
diffex-windows-arm64.zip
diffex-coding-agent-install-package.json
diffex-coding-agent-install-package-lock.json
SHA256SUMS
)

existing_release="$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || true)"
if [[ "${existing_release}" == "false" ]]; then
echo "::error::GitHub Release ${RELEASE_TAG} is already published. Refusing to mutate a public release."
exit 1
fi
if [[ "${existing_release}" == "true" ]]; then
gh release delete "${RELEASE_TAG}" --yes
fi

gh release create "${RELEASE_TAG}" \
--verify-tag \
--draft \
--title "${RELEASE_TAG}" \
--notes-file RELEASE_NOTES.md \
"${release_assets[@]}"

expected_asset_names="$(printf '%s\n' "${release_assets[@]}" | sort)"
actual_asset_names="$(gh release view "${RELEASE_TAG}" --json assets --jq '.assets[].name' | sort)"

if [[ "${actual_asset_names}" != "${expected_asset_names}" ]]; then
echo "::error::Draft GitHub Release asset set does not match expected files."
diff -u <(printf '%s\n' "${expected_asset_names}") <(printf '%s\n' "${actual_asset_names}") || true
exit 1
fi

publish-npm:
runs-on: ubuntu-latest
needs: stage-github-release
environment: npm-publish
permissions:
contents: read
Expand Down Expand Up @@ -270,6 +71,18 @@ jobs:
- name: Publish npm packages
run: node scripts/publish.mjs

- name: Retain private payload inventory and debug maps
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: private-npm-evidence-${{ env.RELEASE_TAG }}
path: |
.artifacts/npm-payload-inventory.json
.artifacts/private-debug/
if-no-files-found: warn
include-hidden-files: true
retention-days: 14

announce-diffex-release:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down Expand Up @@ -309,59 +122,3 @@ jobs:
--version "${RELEASE_TAG#v}" \
--bucket diffex-artifacts \
--endpoint "$R2_ENDPOINT"

publish-github-release:
runs-on: ubuntu-latest
needs:
- stage-github-release
- publish-npm
- announce-diffex-release
permissions:
contents: write
env:
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Publish staged GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

existing_release="$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || true)"
if [[ "${existing_release}" == "" ]]; then
echo "::error::Draft GitHub Release ${RELEASE_TAG} does not exist."
exit 1
fi
if [[ "${existing_release}" == "false" ]]; then
echo "::error::GitHub Release ${RELEASE_TAG} is already published."
exit 1
fi

gh release edit "${RELEASE_TAG}" --draft=false

cleanup-draft-github-release:
runs-on: ubuntu-latest
needs:
- build
- stage-github-release
- publish-npm
- announce-diffex-release
- publish-github-release
if: ${{ always() && needs.stage-github-release.result != 'skipped' && (needs.stage-github-release.result != 'success' || needs.publish-npm.result != 'success' || needs.announce-diffex-release.result != 'success' || needs.publish-github-release.result != 'success') }}
permissions:
contents: write
env:
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Delete draft GitHub Release after failure
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

existing_release="$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || true)"
if [[ "${existing_release}" == "true" ]]; then
gh release delete "${RELEASE_TAG}" --yes
fi
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ plans/
.diffex/hf-sessions/
.diffex/hf-sessions-backup/
collect.sh

# Generated distribution notices owned by scripts/prepare-distribution.mjs
packages/**/LICENSE
packages/**/NOTICE
packages/**/THIRD-PARTY-NOTICES.txt
packages/**/distribution-components.json
packages/**/distribution-files.json
packages/coding-agent/release/
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ Use `./diffex-test.sh` to run the Diffex-branded coding agent directly from this
| [`packages/coding-agent`](packages/coding-agent) | Interactive coding-agent CLI and extension system. |
| [`packages/evals`](packages/evals) | Evaluation utilities and scenarios. |

## Distribution policy

The source repository remains private.
The approved release channel is the eleven public npm packages, including `@diffexai/diffex`.
Standalone archives, Python, secondary package registries, and interactive CLI containers are deferred.
Public packages include readable JavaScript and declarations, with Apache-2.0 metadata and complete applicable upstream notices.
They exclude source maps, TypeScript examples, and native source build files.
Delivered implementation code remains inspectable; private source hosting and compilation do not guarantee confidentiality.
See the [distribution review](docs/release/distribution-review.md) for exact scope and release prerequisites.

## Local Setup

Diffex requires Node.js 22.19 or newer and npm.
Expand Down
30 changes: 28 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Releasing Diffex

Diffex publishes its public workspaces in lockstep and uses GitHub Actions trusted publishing for normal npm releases.
The release tag also builds binaries, stages the GitHub release, announces the verified release, and publishes the staged GitHub release.
The release tag publishes the approved npm packages and then announces the verified npm release.
Standalone archives and other installation channels are deferred by the [approved distribution policy](docs/release/distribution-policy.json).

## Prerequisites

Expand All @@ -11,6 +12,31 @@ The release tag also builds binaries, stages the GitHub release, announces the v
- Use npm 11.15.0 or newer when configuring trusted publishers.
- Confirm every public package has repository metadata pointing to `diffexai/diffex`.

## Approved payloads

The eleven public packages are derived from `scripts/release-packages.mjs`.
Every package declares Apache-2.0 and ships the full root license, including the complete MIT notice for Pi-derived portions.
The generated `NOTICE`, `THIRD-PARTY-NOTICES.txt`, and `distribution-components.json` identify applicable portions and bundled components.
Readable runtime JavaScript, declarations, runtime JSON/SQL data, HTML/CSS templates, and documentation are approved.
Examples and native source build files are excluded; evolution and harness-trace runtime code is explicitly public when vendored in the CLI.
Source maps move into ignored private debug storage, and final tarball inspection rejects embedded maps and prohibited files.
The publisher checks every exact tarball before registry access and publishes those checked bytes.
The same disclosure gate applies to bootstrap publication.

All nine authorized project changelogs must be regenerated through the owner's `/cl` audit after the one-time reset.
Empty or structurally invalid changelogs block packing.
The pre-reset contents are retained at `ae811d134dd63b0a688aea10ea0368c0935e7e48`; never repeat that reset for later releases.

The full source archive remains a private build input and is never a public release asset.
The distribution manifest identifies the private source by its full commit string.
The CLI tarball carries `release/distribution-manifest.json`, `release/install-package.json`, and `release/install-package-lock.json`.
Each package carries `distribution-files.json` with SHA-256 hashes for its approved runtime files.
The complete tarball's npm `dist.integrity` is the registry verification value.
Versioned anonymous downloads use `https://registry.npmjs.org/<PACKAGE>/-/<UNSCOPED_NAME>-<VERSION>.tgz`.
For the CLI at version 0.2.0, that URL is `https://registry.npmjs.org/@diffexai/diffex/-/diffex-0.2.0.tgz`; it becomes available only after separately authorized publication.
No GitHub release publication or separate asset-repository credential is used.
Npm trust remains bound to the private source repository and the existing workflow filename.

## First npm publication

npm requires a package to exist before a trusted publisher can be configured.
Expand Down Expand Up @@ -69,4 +95,4 @@ It is not a dry-run command.
Review the target version and obtain approval for the remote push before running it.

The pushed tag triggers `.github/workflows/build-binaries.yml`.
Do not announce the release until that workflow publishes npm packages, the verified release announcement, and the GitHub release successfully.
Do not announce the release until the workflow verifies npm publication and writes the release announcement successfully.
Loading
Loading