Repository navigation
feat(cli): own PRs with quest iterate, ready, merge, and release - #185
Conversation
Quest-Claim: fad0dab25b515b9c
Each command takes a PR with one quest:<state> label and prints a hold token, the ID of the timeline event that added it. A token swaps the label or acts on it: ready --done hands a draft to reviewers, merge --land enables auto-merge bound to a head SHA with a merge commit by default, and release hands the PR back. The iterate, ready, merge, and complete skills take PRs before acting and skip PRs other sessions hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The implementation is complete and
(written by Claude Opus 5.5) |
Resolve conflicts with the guide rules (#187) and /quest-afk (#184): drop the helpers epic's links to the now-finished pr-states, afk, and guide-rules quests, and rename the PR state commands' token printer so it no longer shadows claims' `held`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
process.stdout.write returns a boolean, and print passed it through, so settle took `true` for the exit code and `quest iterate` and `release` crashed after printing on a real terminal. print and warn now return nothing, and bin.ts drops the write result to honor Output's contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56500a60b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async add(label: string): Promise<void> { | ||
| await this.github.request(`${this.issue}/labels`, { body: { labels: [label] } }); | ||
| } |
There was a problem hiding this comment.
Create state labels before assigning them
In a repository where quest:iterate, quest:ready, and quest:merge have not already been created, this endpoint cannot establish the hold: GitHub treats assigning labels and creating repository labels as separate operations (GitHub REST label documentation), while a repo-wide search finds no creation path or documented setup step here. Consequently the first state command in a normal repository fails or returns without the requested label, after which the timeline re-read errors; create missing labels first or explicitly require and validate setup.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Declining this one. GitHub's "add labels to an issue" endpoint creates a label that doesn't exist yet, with the default color and no description. That's what happened on this PR: quest:ready didn't exist in kixelated/quest until quest ready 185 <token> --done ran against it, and it now shows color ededed and an empty description. A first state command in a fresh repository therefore needs no setup step.
(written by Claude Opus 5.5)
Keep both the PR state commands' hold import and main's new github helpers (mergedHead, remoteUrl) in main.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`quest merge --land` swapped the caller's label for quest:merge before checking the head, so a stale SHA or a failed merge mutation lost the new token and left the PR held by nobody who could release it. Check the head before any label changes, and name the swapped-in hold's token in any error from the steps after a swap (land and ready --done). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main moved the hand-back heading into core's handedBack, shared with the Worker. Carry this PR's CRLF fix there and have the PR state commands call handedBack instead of a second copy of the regex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main finished the runner quest, so merge-job.md's Required section is now empty and removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's runner (#188) gives kix iterate jobs the iterate skill, which now takes and releases the PR with quest iterate and quest release. Runners hold no GitHub credentials and kix already owns the job's PR, so the system prompt tells the job to skip those steps, as it does for quest start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Landing this with a squash merge at 1a60dc0.
(written by Claude Opus 5.5) |
Finishes quest/m0/helpers/pr-states.md.
Summary
Before a session pushes to an existing PR, it now takes the PR, so two sessions never iterate on or merge one PR at once:
quest:iterate,quest:ready, orquest:merge. The hold token is the GraphQL ID of theLabeledEventthat added it.quest:*label is on the PR and this session has no token for it (this includes the command's own state); the PR is a draft its starter still holds (no## Decisions needed); the PR is merged or closed. The refusal names the holder's label, age, and token, so the user can decide whether to release a stale hold.--landchecks the head against the full SHA and callsenablePullRequestAutoMergewithexpectedHeadOid, which enters the merge queue when the base has one. When there is no queue and the PR is alreadyCLEAN/HAS_HOOKS/UNSTABLE, GitHub refuses auto-merge, so it callsmergePullRequestinstead. The method defaults toMERGE, and--squashor--rebasechanges it.quest:mergePR left the queue unmerged (a landing event comes after the label's add, and the PR has no auto-merge and no queue entry), the next command to read it removes the label.iterate,ready, andmergetake the PR before acting and release it at hand-back.mergelands withquest merge --landand picks--squash/--rebasefrom the repository's root instructions.completeskips PRs other sessions hold.HAND_BACKnow also matches CRLF bodies, which web edits produce. Before this,quest start/unblockedmissed a## Decisions neededheading saved from the web UI.quest rmdeleted the quest, and the prose links in the helpers README andmerge-job.mdnow point at this PR.Choices I made without asking (open to review):
--doneand--landneed it, and they swap first when it holds another state.releasealso works on merged or closed PRs, since removing a label pushes nothing. A label that is already off counts as released, with a warning.quest:mergelabel.quest waitandquest statusstay read-only.Decisions
Settled by the user on 2026-10-22, after this PR handed them back:
quest ready <pr> --donewithout a token finish an unlabeled starter draft, on trust.quest startprint a hold token tied to the claim.LabeledEventwhen a label already on the PR is added again, so two sessions that both add, say,quest:mergewithin one round trip share one token.--helpdocuments: it needs two sessions on one PR within milliseconds, and the skills dispatch one agent per PR.Iteration (/quest-iterate)
/quest-afk). Both sets of skill rules survive: the hold steps initerate,ready,merge, andcompletesit alongside main's reserved-decision, follow-up, and summary rules. The helpers epic no longer links the finishedafk,guide-rules, andpr-statesquests. A second merge picked up kixelated/quest#186 and kixelated/quest#191; onlymain.ts's imports conflicted. A third picked up kixelated/quest#182, which moved the hand-back heading into core'shandedBack(shared with the Worker); the CRLF fix now lives there, and the PR state commands callhandedBackinstead of keeping a second regex.heldintomain.ts, which collided with this PR's token printer; that is nowprintToken.quest iterate 185for real:printreturnedprocess.stdout.write's boolean, andsettletooktrueas the exit code, soiterate,ready,merge, andreleasecrashed after printing the token on a real terminal. Tests missed it because their stdout returns nothing.printandwarnnow return nothing,bin.tsdrops the write result, and a regression test drivesiterateandreleasethroughmainwith a stdout that returnstrue.--land.landnow checks the head before any label changes, and an error after a swap names the new token. Declined creating the labels first: GitHub's add-labels endpoint creates a missing label, as it did forquest:readyon this PR.Merge (/quest-merge)
quest runner), kixelated/quest#190, kixelated/quest#183, kixelated/quest#194, kixelated/quest#181, and kixelated/quest#189. Onlymerge-job.mdconflicted: main finished the runner quest and this PR finishespr-states, so itsRequiredsection is gone.quest iterateandquest release, as it already skipsquest start; marking ready already maps todeliver.Validation
test/hold.test.tscovers these cases against an in-memory PR: a refused draft its starter holds, a concurrent race for one PR, a second session taking the same state, a release with a stale token, a refused swap, landing on a moved head, a PR leaving the queue, a refused merged or closed PR, the default merge commit,--squashand--rebase, the merge queue,--done, and argument errors.isMergeQueueEnabled,LabeledEvent,AddedToMergeQueueEvent) on kixelated/quest and moq-dev/moq. On this PR,quest iterate,quest ready <token> --done(which swapped toquest:ready, dropped## Decisions needed, and marked it ready), andquest releaseran against GitHub.--landand the merge queue have still only run against the in-memory PR.just check,just test, andquest check --base origin/main --branch quest/m0/helpers/pr-statespass locally.Follow-ups
quest statusprintquest:*holds (label, age, token), socompletedoesn't need a separate label read.quest/m0/kix/merge-job.md: the kix bot labels through the service and records the token for the job.🤖 Generated with Claude Code
(written by Claude Opus 5.5)