Skip to content

fix(release): correctly promote and demote releases - #132

Merged
kunchenguid merged 4 commits into
mainfrom
fm/ghaxi-release-edit-promote-r1
Aug 30, 2026
Merged

fix(release): correctly promote and demote releases#132
kunchenguid merged 4 commits into
mainfrom
fm/ghaxi-release-edit-promote-r1

Conversation

@kunchenguid

Copy link
Copy Markdown
Owner

Intent

Fix a real agent-ergonomics correctness bug: gh-axi release edit <tag> --prerelease=false --latest silently ACCEPTS the command (prints the tag) but does NOT apply the change - the release stayed prerelease=true and was not promoted to latest. Separately, gh-axi api appeared GET-only so there was no gh-axi path to promote a release.

  1. Make gh-axi release edit correctly pass through --prerelease=false (the UNSET form, promoting a prerelease to a full release) and --latest[=true|false] (mark/unmark repo latest) so promoting/demoting a release actually works. Handle the boolean-with-value forms correctly (--prerelease=false, --latest, --latest=false) - a common cause is a bare-boolean flag parser that treats --prerelease as always-true and drops the =false, or that omits latest entirely. Verify the change ROUND-TRIPS against the GitHub API: after the edit, the API read-back shows prerelease=false and the release is the repo's latest.

  2. gh-axi api write support - pick one and justify it: EITHER add a write path to gh-axi api (--method/-X PATCH/POST/PUT/DELETE plus a --field/-f/--raw-field body path, mirroring gh api), OR clearly DOCUMENT that gh-axi api is read-only (GET) by design and point users to release edit (and typed subcommands) for writes - and make it FAIL LOUDLY with that guidance if a user passes a write method/body, never silently drop it. Prefer whichever is more consistent with gh-axi's existing surface and AXI ergonomics (agents must never get a silent no-op). Decision made during implementation: keep the existing write path (POST/PUT/PATCH/DELETE, -X, --field, --input already shipped); documenting read-only would regress that surface. Do not remove or weaken writes.

Tests must be executable behavioral coverage that drives the real code path (CLI end-to-end against a mocked/faked gh or GitHub API layer), asserting OBSERVABLE behavior, not source text/snapshots:

  • After release edit --prerelease=false --latest, the release reads prerelease=false AND is the repo latest (round-trip).
  • --latest=false / --prerelease (set-true) also behave correctly (no regressions to the working forms).
  • For the api decision: writes kept, so a PATCH/POST round-trips and never silently no-ops as GET.

Do not weaken any auth handling - gh-axi delegates auth to gh; keep that. Keep the change tight and well-tested. Public npm publish / release go-no-go is out of scope.

What Changed

  • Forward boolean-valued --prerelease, --draft, and --latest release edit flags, including explicit =false values, while rejecting conflicting repetitions.
  • Add stateful CLI integration coverage for release promotion and demotion, prerelease restoration, and existing API PATCH write round-trips.

Risk Assessment

✅ Low: The change is tightly scoped, correctly forwards release boolean value forms, and adds stateful CLI-level round-trip coverage for release edits and API writes.

Testing

Targeted command tests and public CLI end-to-end checks against a stateful fake GitHub backend confirmed persisted release promotion/demotion, boolean flag behavior, and API PATCH read-back. A reviewer-visible CLI transcript records the complete flow.

Evidence: Stateful public CLI release and API round-trip transcript

Source: Stateful public CLI release and API round-trip transcript

Stateful fake GitHub round-trip through the public gh-axi CLI

$ gh-axi api /repos/octo/repo/releases/tags/v1.0.0
id: 1
tag_name: v1.0.0
name: Version 1 prerelease
prerelease: true
draft: false

$ gh-axi api /repos/octo/repo/releases/latest
id: 2
tag_name: v0.9.0
name: Version 0.9
prerelease: false
draft: false

$ gh-axi release edit v1.0.0 --prerelease=false --latest
edit: ok
tag: v1.0.0
help[1]:
  Run `gh-axi release view v1.0.0 -R octo/repo` to see updated release

$ gh-axi api /repos/octo/repo/releases/tags/v1.0.0
id: 1
tag_name: v1.0.0
name: Version 1 prerelease
prerelease: false
draft: false

$ gh-axi api /repos/octo/repo/releases/latest
id: 1
tag_name: v1.0.0
name: Version 1 prerelease
prerelease: false
draft: false

$ gh-axi release edit v1.0.0 --latest=false
edit: ok
tag: v1.0.0
help[1]:
  Run `gh-axi release view v1.0.0 -R octo/repo` to see updated release

$ gh-axi api /repos/octo/repo/releases/latest
id: 2
tag_name: v0.9.0
name: Version 0.9
prerelease: false
draft: false

$ gh-axi release edit v1.0.0 --prerelease
edit: ok
tag: v1.0.0
help[1]:
  Run `gh-axi release view v1.0.0 -R octo/repo` to see updated release

$ gh-axi api /repos/octo/repo/releases/tags/v1.0.0
id: 1
tag_name: v1.0.0
name: Version 1 prerelease
prerelease: true
draft: false

$ gh-axi api PATCH /repos/octo/repo/releases/1 --field name=Version\ 1\ stable\ via\ API --field prerelease=false
id: 1
tag_name: v1.0.0
name: Version 1 stable via API
prerelease: false
draft: false

$ gh-axi api /repos/octo/repo/releases/1
id: 1
tag_name: v1.0.0
name: Version 1 stable via API
prerelease: false
draft: false

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed (2) ✅
  • 🚨 test/commands/release.test.ts:411 - Intent requires "CLI end-to-end against a mocked/faked gh or GitHub API layer" and an API read-back proving prerelease=false and that the release is repo latest. These tests call releaseCommand directly and use an in-process argv interpreter with a single latest boolean, so they neither execute the CLI/gh boundary nor read back /releases/latest. Likewise, test/commands/api.test.ts:64 returns the desired PATCH response unconditionally rather than performing a stateful write/read round-trip. Add executable CLI coverage using a fake gh/API whose persisted state is queried after each write.
  • ⚠️ src/commands/release.ts:121 - appendOptionalValueBoolFlag forwards the first --flag=value occurrence and returns, leaving any bare or additional valued occurrence silently ignored. For release edit v1 --latest=false --latest, it sends only --latest=false, despite the final valid form requesting true. Reject repeated forms as conflicting, or consume and forward them with defined ordering, rather than accepting and dropping one.

🔧 Fix: Reject conflicting repeated release boolean flags
1 error still open:

  • 🚨 test/commands/release.test.ts:411 - Required "CLI end-to-end against a mocked/faked gh or GitHub API layer" coverage is still absent. These tests invoke releaseCommand directly and mutate an in-process state object rather than executing the CLI and reading /releases/latest; test/commands/api.test.ts similarly returns a predetermined PATCH response without a stateful write/read round-trip. Add CLI-level tests backed by a stateful fake gh/API, then read back the edited release and latest endpoint to verify the persisted result.

🔧 Fix: Add stateful CLI release and API round-trips
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • pnpm exec vitest run test/integration/release-api-roundtrip.integration.test.ts
  • pnpm exec vitest run test/commands/release.test.ts -t 'edit|boolean-with-value'
  • Public CLI stateful-fake round-trip: initial reads, release edit v1.0.0 --prerelease=false --latest, --latest=false, bare --prerelease, API PATCH, and subsequent API GET read-backs
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

kunchenguid and others added 4 commits August 30, 2026 00:51
Bare-boolean parsing dropped the unset form so promoting a prerelease
silently no-op'd, and --latest was rejected as unknown. Forward both
equals-forms to gh so the GitHub API actually applies the change.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps

greptile-apps Bot commented Aug 30, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge, with the release boolean forwarding behavior covered through observable stateful CLI round trips.

The changed parser retains bare and explicit boolean values, rejects conflicting repetitions before invoking gh, and forwards the resulting release-edit arguments through the existing authenticated execution boundary; no actionable regression remains.

Reviews (1): Last reviewed commit: "no-mistakes(document): Consolidate relea..." | Re-trigger Greptile

@kunchenguid
kunchenguid merged commit 22b6151 into main Aug 30, 2026
4 checks passed
@kunchenguid
kunchenguid deleted the fm/ghaxi-release-edit-promote-r1 branch August 30, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant