chrismdp / issue-to-pull-request
An issue becomes a pull request
workflow.yaml
workflow.yaml is AWML 0.4, as it was pushed. The registry draws, checks and fits it converted to AWML 0.8.
# An issue the maintainer labels build becomes a change on main. The
# repository is public, so everything here sits in one scope: anyone may read the issues, the
# threads and the branches. What matters is who may act. Only people with write access can add a
# label, so the label is the decision. Claude drafts with a read-only token, and the repository's
# own token opens the pull request once the checks pass. A code rule then decides whether the
# change is straightforward, by rules the maintainer wrote down. If it is, the repository's
# own token merges it. If not, the maintainer reviews and merges it from their own account, which is
# their yes. Answering new issues with options is left out: it reads the maintainer's private
# notes, so it stays an interactive session.
#
# The actions implementation runs it on GitHub Actions. GitHub decides whether a label
# starts anything, before any runner exists. Claude works in one job. The checks run in a second
# job that can only read, so Claude's code never runs where a token can push. A third job, which
# runs nothing from the change, pushes the branch, opens the pull request, applies the rule and
# merges. None of these jobs shares another's access, so Claude can't write the verdict the merge
# relies on. Claude's session sits inside the first job's block, since it shares that job's access.
awml: "0.4"
title: A decision on an issue becomes a change on main
scopes:
- { id: public, label: "public: anyone who reads the repository", processing: any }
parts:
- { id: comment, kind: trigger, label: "The build label is added to an issue", wait: PT1H }
- { id: token, kind: connector, label: "The repository, through its own Actions token", in: public }
- { id: who, kind: code, op: sort, label: "Sort by rule: is the label build?", in: public }
- { id: bring, kind: code, op: bring-in, label: "Bring in the issue and the comments of the repository's own people", in: public }
- { id: repo, kind: context, label: "The spec, guide, code and AGENTS.md", in: public }
- { id: build, kind: ai, op: draft, label: "Write the change test first", in: public }
- { id: patch, kind: context, label: "The change, as a patch, with Claude's note on it", in: public }
- { id: gate, kind: code, op: check, label: "Run the tests, vet, the examples and wasm parity", in: public }
- { id: open, kind: code, op: do, label: "Push a branch and open a pull request that closes the issue", in: public }
- { id: pr, kind: outside, label: "Open the pull request: Allow", in: public }
- { id: rules, kind: context, label: "The build rules: what needs a review", in: public }
- { id: judge, kind: code, op: sort, label: "Sort by rule: is the change straightforward?", in: public }
- { id: automerge, kind: code, op: do, label: "Merge the pull request, saying in its message why it was straightforward", in: public }
- { id: mainauto, kind: outside, label: "Merge a straightforward change into main: Allow", in: public }
- { id: account, kind: connector, label: "The repository, through the maintainer's account", in: public }
- { id: merge, kind: person, op: do, label: "Review the pull request and merge it", in: public }
- { id: main, kind: outside, label: "Merge a change that needed review into main: Ask", in: public }
- { id: strangers, kind: comment, label: "Anyone can comment, but the bring-in step passes on only the comments of the owner, members and collaborators, so strangers' text never reaches the draft step. AWML can't mark this yet (awml #5)" }
- { id: held, kind: comment, label: "Only people with write access can add a label, and GitHub holds that, not a prompt, so a stranger's comment starts nothing. The draft step's token can only read, so it can't push (awml #26)" }
- { id: verdict, kind: comment, label: "The check runs where the draft step can't reach, and its verdict is in its own record (awml #13, #20)" }
- { id: terms, kind: comment, label: "Allow, because a merge is undone with git revert, everything in it is already public on its branch, and the maintainer would not be surprised by a change the rule finds straightforward. One the rule sends to review is where they would be, so that merge is Ask. Installs of the plugin pick up either only at a release, which stays the maintainer's yes, though a new install takes main" }
- { id: escalate, kind: comment, label: "Claude's note can send a change to review but can't skip one. The rule reads the patch itself: what it touches, its size and whether it deletes a test (awml #20, #26)" }
- { id: nostart, kind: comment, label: "A merge by the Actions token doesn't start CI or the rebase workflow, so the merge step starts the rebase workflow itself" }
lines:
- { from: comment, to: who, kind: start, label: "the label" }
- { from: who, to: bring, kind: start, label: "[build]" }
- { from: token, to: bring, kind: data, label: "the issue's thread" }
- { from: bring, to: build, kind: data, label: "the issue and its people's comments" }
- { from: repo, to: build, kind: data }
- { from: build, to: patch, kind: data }
- { from: patch, to: gate, kind: data }
- { from: gate, to: open, kind: start, label: "[all pass]" }
- { from: patch, to: open, kind: data }
- { from: open, to: pr, kind: data }
- { from: pr, to: token, kind: data }
- { from: open, to: judge, kind: start, label: "[opened]" }
- { from: patch, to: judge, kind: data }
- { from: rules, to: judge, kind: data }
- { from: judge, to: automerge, kind: start, label: "[straightforward]" }
- { from: judge, to: merge, kind: start, label: "[needs a review]" }
- { from: automerge, to: mainauto, kind: data }
- { from: mainauto, to: token, kind: data }
- { from: account, to: merge, kind: data, label: "the pull request" }
- { from: merge, to: main, kind: data }
- { from: main, to: account, kind: data }
- { from: strangers, to: bring, kind: tie }
- { from: held, to: comment, kind: tie }
- { from: verdict, to: gate, kind: tie }
- { from: terms, to: mainauto, kind: tie }
- { from: escalate, to: judge, kind: tie }
- { from: nostart, to: automerge, kind: tie }
implementations:
actions:
label: GitHub Actions
blocks:
- { id: github, label: "GitHub: issues, branches, pull requests", parts: [comment, token, who, repo, patch, pr, rules, mainauto, account, main] }
- { id: draftjob, label: "Actions job: draft, with a read-only token", platform: actions-code, parts: [bring] }
- { id: agentjob, label: "Claude Code, claude-opus-5-5, on the maintainer's subscription", platform: actions-agent, in: draftjob, parts: [build] }
- { id: checkjob, label: "Actions job: checks, with a read-only token", platform: actions-code, parts: [gate] }
- { id: codejob, label: "Actions job: branch, pull request and merge, running nothing from the change", platform: actions-code, parts: [open, judge, automerge] }
- { id: browser, label: "The maintainer's browser", parts: [merge] }