agent-orchestrator/.github
suraj_markup 15158aa7b2
refactor(release): split publish into two-repo model — GitHub release public, npm publish private (#1815)
* refactor(release): split publish into two-repo model

Org compliance forbids npm publish credentials in public repositories.
Move the npm publish step out of this repo and into a private
ComposioHQ/ao-publisher repo, triggered via repository_dispatch.

Public repo (this one) now only:
- Bumps versions via changesets/action (no `publish:` argument)
- Creates git tags via `pnpm changeset tag`
- Creates the GitHub release (stable or prerelease)
- Dispatches `publish-npm-stable` / `publish-npm-nightly` to ao-publisher

The only secret needed here is PUBLISHER_DISPATCH_TOKEN, a fine-grained
PAT scoped to ao-publisher with `repository_dispatch:write`. NPM_TOKEN
lives in ao-publisher's secret store and never enters this repo.

Removed from both workflows: NODE_AUTH_TOKEN env, NPM_CONFIG_PROVENANCE
env, and `registry-url` on setup-node — none are needed when no npm
publish happens here. Canary also gains a skip-if-unchanged guard so
cron ticks during quiet stretches don't republish identical SHAs.

CONTRIBUTING.md "Release Setup" → "Release Architecture": documents the
two-repo split, secret layout, rotation procedure, and the stable vs.
nightly flows.

Requires PUBLISHER_DISPATCH_TOKEN secret + ao-publisher repo setup by
maintainers (out of scope for this PR — see PR description).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release): address Greptile review findings on two-repo split

Three review findings on PR #1815:

1. (P1) canary.yml missing git commit before tagging
   `pnpm changeset version --snapshot` modifies package.json but does
   not commit. `pnpm changeset tag` would then tag the pre-snapshot
   HEAD, so ao-publisher would check out un-bumped versions.

   Add a "Commit snapshot version bumps" step with `git diff --cached
   --quiet || git commit` (defensive: skip if nothing to commit) and
   the `[skip ci]` marker. The commit is never pushed to main — only
   the tags are pushed and the orphan commit travels with them.

2. (P2) release.yml `--target main` race condition
   If a commit lands on main between `git push --follow-tags` and
   `gh release create --target main`, the release commitish drifts
   and auto-generated notes pull in unrelated commits.

   Create an explicit `vX.Y.Z` git tag pointing at the version-bump
   commit, push it, and drop `--target`. `gh release create` then
   resolves the commitish from the existing tag — no race window.

3. (P2) canary.yml skip-guard suppresses post-stable nightlies
   `gh release list --limit 1` returns the most recent release by
   date, which could be a stable from `release.yml`. An explicit
   `workflow_dispatch` nightly right after a stable cut would be
   suppressed.

   Filter to prereleases only:
     gh release list --json tagName,isPrerelease \
       --jq '[.[] | select(.isPrerelease)][0].tagName // empty'

   If no prerelease exists yet, the jq returns empty and the guard
   falls through naturally (LAST_SHA empty → condition false →
   nightly proceeds).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: retrigger checks (event dropped on previous push)

* fix(release): make release.yml idempotent and dispatch reachable on re-run

The previous design used a single `released` output (based on `after >
before` tag count) to gate both `Create GitHub release` and
`Dispatch npm publish`. On a re-run, all tags are already on the
remote and `fetch-depth: 0` brings them down, so `pnpm changeset tag`
adds nothing, `after == before`, `released=false`, and both steps are
skipped — breaking the "re-run recovers cleanly" claim, especially
the common case where the first run failed only at dispatch because
`PUBLISHER_DISPATCH_TOKEN` was missing.

Refactor into a `Determine release state` step that emits three
independent signals:

- `is_release_commit` — version-bump signal. Detected by comparing
  `packages/ao/package.json` version against its value in HEAD^.
  A Version Packages merge changes this; a regular commit does not.
  This is the filter that prevents the dispatch from firing on every
  commit to main (which all have `hasChangesets == 'false'`).
- `tag_on_remote` — whether the `vX.Y.Z` tag exists at origin.
- `release_exists` — whether the matching GitHub release exists.

Each downstream step is gated on its own piece of state:

- Tag push: `is_release_commit && !tag_on_remote`
- Release create: `is_release_commit && !release_exists`
- Dispatch: `is_release_commit` (always fires on a release commit)

The dispatch fires unconditionally on a release commit, even when
tag and release already exist on the remote. That guarantees recovery
from the most common failure mode (first run succeeded everywhere
except dispatch). The publisher must be idempotent against already-
published versions for this to be safe — `pnpm changeset publish`
already has this property since it skips packages whose current
version is already on the registry.

Update CONTRIBUTING.md → "Release Architecture":
- Add "Idempotency contract for ao-publisher" section spelling out the
  no-op-on-already-published requirement.
- Add "Recovery" section explaining that re-running the failed
  workflow is the canonical recovery path, plus a manual `gh api`
  fallback for cases where re-running isn't practical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(canary): skip-guard anchors on tag parent, not the orphan snapshot

The previous P1 fix added a "Commit snapshot version bumps" step before
`pnpm changeset tag`, which moved the snapshot tag onto an orphan
commit (the version-bump commit) rather than main HEAD. The
skip-if-unchanged guard still compared `git rev-list -n 1 "$LAST_TAG"`
against `GITHUB_SHA`, but `LAST_TAG` now resolves to the orphan
snapshot commit's SHA, never the main SHA. The two could never be
equal → `skip=true` was unreachable → every cron tick republished
regardless of whether main had advanced.

Use `${LAST_TAG}^` to anchor on the snapshot commit's first parent —
which is the main HEAD at the time the previous nightly ran — and
compare that against the current `GITHUB_SHA`. Now the guard fires
correctly when main hasn't advanced since the last nightly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(release): single umbrella tag per release, drop per-package tags

`pnpm changeset tag` creates one tag per publishable package (~27 here)
on every release. At 5 nightlies/week × 52 weeks × 27 packages that's
roughly 7 000 tags/year just from canary — pure decoration since
`ao-publisher` only consumes the umbrella `vX.Y.Z` tag. The per-
package tags also cause partial-recovery conflicts: `git push --tags`
on a re-run trips over tags that were pushed by the prior run.

Drop the `pnpm changeset tag` call from both workflows and replace
`git push origin --tags` with `git push origin "v$version"`. Push
exactly one umbrella tag per release.

CONTRIBUTING.md → "Release Architecture" updated:
- Flow diagram replaces "changeset tag → push" with "push vX.Y.Z tag"
- New paragraph spells out the single-tag-per-release policy and why
  we skip `pnpm changeset tag`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(canary): workflow_dispatch bypasses skip-guard for recovery

When a nightly pushes the tag but fails at `gh release create` or
dispatch (e.g. `PUBLISHER_DISPATCH_TOKEN` missing), a re-run via
`workflow_dispatch` was silently skipped: `LAST_TAG` resolves to the
just-pushed nightly, `${LAST_TAG}^` equals `GITHUB_SHA`, and the
binary skip guard fires before any of the downstream steps we want
to retry.

`release.yml` handles the equivalent case by gating each step on
independent state. Canary has a single binary gate, so it needs a
different escape hatch: `workflow_dispatch` always proceeds. The
human trigger is itself the signal that we want to run regardless
of whether the source SHA looks unchanged. Cron-driven runs keep
the SHA-equality dedup so quiet stretches don't republish the same
SHA on every tick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(release): replace ao-publisher dispatch with AO cron poll model

Remove the ao-publisher dispatch steps and PUBLISHER_DISPATCH_TOKEN from
both release.yml and canary.yml. npm publishing is now handled by an AO
cron job on a private server that polls GitHub releases and publishes
when a new tag is ahead of the current npm version.

Changes:
- release.yml: remove dispatch step, keep tag + GitHub release
- canary.yml: remove dispatch step, keep tag + GitHub prerelease
- CONTRIBUTING.md: rewrite Release Architecture for two-stage model
  (public CI → GitHub release, private cron → npm publish)

* fix(release): address review — remove env/id-token, scope git add, fix wording

- Remove environment: release from both workflows (no npm publish here,
  could block on required reviewers)
- Remove id-token: write permission (unused, no OIDC needed)
- Scope git add in canary to package.json and .changeset/ only (avoid
  staging build artifacts)
- Fix 'orphan commit' wording → 'snapshot commit' (not actually orphan)
- Fix nightly version format 0.0.0-* → X.Y.Z-nightly-<sha>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: i-trytoohard[bot] <1484917231245856808@users.noreply.github.com>
2026-05-13 03:29:04 +05:30
..
scripts fix: remove dead has_coverage output variable (#913) 2026-04-05 17:37:29 +05:30
workflows refactor(release): split publish into two-repo model — GitHub release public, npm publish private (#1815) 2026-05-13 03:29:04 +05:30
copilot-instructions.md feat(windows): complete Windows support (#1025) 2026-05-09 00:10:53 +05:30