@sentry/junior-github 0.239.0 → 0.240.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2123,11 +2123,30 @@ var inputSchema7 = z10.object({
2123
2123
  "Replacement pull request body. The runtime appends requester attribution and the conversation footer."
2124
2124
  ),
2125
2125
  base: z10.string().trim().min(1).optional().describe("Replacement base branch."),
2126
- state: z10.enum(["open", "closed"]).optional().describe("Replacement pull request state.")
2126
+ state: z10.enum(["open", "closed"]).optional().describe("Replacement pull request state."),
2127
+ draft: z10.boolean().optional().describe(
2128
+ "Set false to mark a draft pull request ready for review, or true to convert it back to a draft."
2129
+ )
2127
2130
  }).strict().refine(
2128
- ({ title, body, base, state }) => title !== void 0 || body !== void 0 || base !== void 0 || state !== void 0,
2131
+ ({ title, body, base, state, draft }) => title !== void 0 || body !== void 0 || base !== void 0 || state !== void 0 || draft !== void 0,
2129
2132
  { message: "At least one pull request field must be provided." }
2130
2133
  );
2134
+ var MARK_READY_FOR_REVIEW = {
2135
+ operationName: "MarkPullRequestReadyForReview",
2136
+ query: "mutation MarkPullRequestReadyForReview($id: ID!) { result: markPullRequestReadyForReview(input: {pullRequestId: $id}) { pullRequest { isDraft } } }"
2137
+ };
2138
+ var CONVERT_TO_DRAFT = {
2139
+ operationName: "ConvertPullRequestToDraft",
2140
+ query: "mutation ConvertPullRequestToDraft($id: ID!) { result: convertPullRequestToDraft(input: {pullRequestId: $id}) { pullRequest { isDraft } } }"
2141
+ };
2142
+ var draftMutationResponseSchema = z10.object({
2143
+ data: z10.object({
2144
+ result: z10.object({ pullRequest: z10.object({ isDraft: z10.boolean() }) })
2145
+ })
2146
+ });
2147
+ var graphqlErrorsSchema = z10.object({
2148
+ errors: z10.array(z10.object({ message: z10.string() })).min(1)
2149
+ });
2131
2150
  var pullRequestSchema2 = z10.object({
2132
2151
  base: z10.string(),
2133
2152
  body: z10.string().nullable(),
@@ -2173,6 +2192,34 @@ function githubApiErrorMessage4(payload) {
2173
2192
  if (typeof payload === "string" && payload.trim()) return payload.trim();
2174
2193
  return "GitHub request failed";
2175
2194
  }
2195
+ async function setPullRequestDraft(input) {
2196
+ const mutation = input.draft ? CONVERT_TO_DRAFT : MARK_READY_FOR_REVIEW;
2197
+ const response = await input.egress.fetch({
2198
+ provider: "github",
2199
+ operation: `github.pull.draft.update:${input.repo.toLowerCase()}`,
2200
+ request: new Request("https://api.github.com/graphql", {
2201
+ method: "POST",
2202
+ headers: { "Content-Type": "application/json" },
2203
+ body: JSON.stringify({
2204
+ operationName: mutation.operationName,
2205
+ query: mutation.query,
2206
+ variables: { id: input.nodeId }
2207
+ })
2208
+ })
2209
+ });
2210
+ const payload = await readJson6(response);
2211
+ const isDraft = draftMutationResponseSchema.safeParse(payload).data?.data.result.pullRequest.isDraft;
2212
+ if (!response.ok || isDraft !== input.draft) {
2213
+ throw new Error(
2214
+ `GitHub pull request draft update failed with HTTP ${response.status}: ${githubGraphqlErrorMessage(payload)}`
2215
+ );
2216
+ }
2217
+ return isDraft;
2218
+ }
2219
+ function githubGraphqlErrorMessage(payload) {
2220
+ const result = graphqlErrorsSchema.safeParse(payload);
2221
+ return result.success ? result.data.errors.map((error) => error.message).join("; ") : githubApiErrorMessage4(payload);
2222
+ }
2176
2223
  function createGitHubUpdatePullRequestTool(ctx) {
2177
2224
  return definePluginTool9({
2178
2225
  annotations: {
@@ -2181,7 +2228,7 @@ function createGitHubUpdatePullRequestTool(ctx) {
2181
2228
  openWorldHint: true,
2182
2229
  readOnlyHint: false
2183
2230
  },
2184
- description: "Update an existing GitHub pull request's title, body, base branch, or open/closed state. Use this instead of raw GitHub API calls when changing PR metadata.",
2231
+ description: "Update an existing GitHub pull request's title, body, base branch, open/closed state, or draft state. Use this instead of raw GitHub API calls when changing PR metadata or marking a PR ready for review.",
2185
2232
  inputSchema: inputSchema7,
2186
2233
  outputSchema: outputSchema7,
2187
2234
  async execute(input) {
@@ -2206,21 +2253,20 @@ function createGitHubUpdatePullRequestTool(ctx) {
2206
2253
  ...update.base !== void 0 ? { base: update.base } : void 0,
2207
2254
  ...update.state !== void 0 ? { state: update.state } : void 0
2208
2255
  };
2256
+ const pullUrl = `https://api.github.com/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}/pulls/${update.number}`;
2257
+ const hasRestUpdate = Object.keys(payload).length > 0;
2209
2258
  const response = await ctx.egress.fetch({
2210
2259
  provider: "github",
2211
- operation: "github.pull.update",
2212
- request: new Request(
2213
- `https://api.github.com/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}/pulls/${update.number}`,
2214
- {
2215
- method: "PATCH",
2216
- headers: {
2217
- Accept: "application/vnd.github+json",
2218
- "Content-Type": "application/json",
2219
- "X-GitHub-Api-Version": "2022-11-28"
2220
- },
2221
- body: JSON.stringify(payload)
2222
- }
2223
- )
2260
+ operation: hasRestUpdate ? "github.pull.update" : "github.pull.get",
2261
+ request: new Request(pullUrl, {
2262
+ method: hasRestUpdate ? "PATCH" : "GET",
2263
+ headers: {
2264
+ Accept: "application/vnd.github+json",
2265
+ "X-GitHub-Api-Version": "2022-11-28",
2266
+ ...hasRestUpdate ? { "Content-Type": "application/json" } : void 0
2267
+ },
2268
+ ...hasRestUpdate ? { body: JSON.stringify(payload) } : void 0
2269
+ })
2224
2270
  });
2225
2271
  const parsed = await readJson6(response);
2226
2272
  if (!response.ok) {
@@ -2234,10 +2280,24 @@ function createGitHubUpdatePullRequestTool(ctx) {
2234
2280
  draft: z10.boolean(),
2235
2281
  html_url: z10.string(),
2236
2282
  merged: z10.boolean(),
2283
+ node_id: z10.string().optional(),
2237
2284
  number: z10.number(),
2238
2285
  state: z10.string(),
2239
2286
  title: z10.string()
2240
2287
  }).parse(parsed);
2288
+ if (update.draft !== void 0 && update.draft !== providerResult.draft) {
2289
+ if (!providerResult.node_id) {
2290
+ throw new Error(
2291
+ "GitHub pull request response did not include node_id."
2292
+ );
2293
+ }
2294
+ providerResult.draft = await setPullRequestDraft({
2295
+ draft: update.draft,
2296
+ egress: ctx.egress,
2297
+ nodeId: providerResult.node_id,
2298
+ repo: repo.ref
2299
+ });
2300
+ }
2241
2301
  const subscribable = ctx.events.canSubscribe ? gitHubPullRequestSubscribable({
2242
2302
  number: providerResult.number,
2243
2303
  repo: repo.ref
@@ -2255,7 +2315,10 @@ function createGitHubUpdatePullRequestTool(ctx) {
2255
2315
  return {
2256
2316
  objectAnnotations: [
2257
2317
  githubObjectAnnotation({
2258
- ...githubObjectFacts("code_change", parsed),
2318
+ ...githubObjectFacts("code_change", {
2319
+ ...parsed,
2320
+ draft: providerResult.draft
2321
+ }),
2259
2322
  repo: repo.ref,
2260
2323
  number: providerResult.number,
2261
2324
  title: providerResult.title,
@@ -4512,9 +4575,6 @@ function githubApiWriteGrantName(method, upstreamUrl) {
4512
4575
  )) {
4513
4576
  return "installation-write";
4514
4577
  }
4515
- if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/ready_for_review$/.test(pathname)) {
4516
- return "installation-write";
4517
- }
4518
4578
  if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/requested_reviewers$/.test(pathname)) {
4519
4579
  return "installation-write";
4520
4580
  }
@@ -4536,18 +4596,36 @@ function githubApiWriteGrantName(method, upstreamUrl) {
4536
4596
  }
4537
4597
  return void 0;
4538
4598
  }
4539
- function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
4540
- const prefix = "github.pull.review-thread.resolve:";
4541
- if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation?.startsWith(prefix)) {
4542
- return void 0;
4599
+ var HOST_GRAPHQL_MUTATIONS = [
4600
+ {
4601
+ field: "resolveReviewThread",
4602
+ operationName: "ResolveReviewThread",
4603
+ operationPrefix: "github.pull.review-thread.resolve:"
4604
+ },
4605
+ {
4606
+ field: "markPullRequestReadyForReview",
4607
+ operationName: "MarkPullRequestReadyForReview",
4608
+ operationPrefix: "github.pull.draft.update:"
4609
+ },
4610
+ {
4611
+ field: "convertPullRequestToDraft",
4612
+ operationName: "ConvertPullRequestToDraft",
4613
+ operationPrefix: "github.pull.draft.update:"
4543
4614
  }
4544
- const repository = operation.slice(prefix.length);
4545
- if (!/^[^/]+\/[^/]+$/.test(repository)) return void 0;
4546
- const parsed = parseGitHubGraphqlRequest(bodyText);
4547
- if (parsed?.operationName !== "ResolveReviewThread" || !/\bmutation\s+ResolveReviewThread\b/.test(parsed.normalized) || !/\bresolveReviewThread\b/.test(parsed.normalized)) {
4615
+ ];
4616
+ function hostGraphqlMutationRepository(operation, method, upstreamUrl, bodyText) {
4617
+ if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation) {
4548
4618
  return void 0;
4549
4619
  }
4550
- return repository;
4620
+ const parsed = parseGitHubGraphqlRequest(bodyText);
4621
+ const mutation = HOST_GRAPHQL_MUTATIONS.find(
4622
+ (candidate) => operation.startsWith(candidate.operationPrefix) && parsed?.operationName === candidate.operationName && new RegExp(`\\bmutation\\s+${candidate.operationName}\\b`).test(
4623
+ parsed.normalized
4624
+ ) && new RegExp(`\\b${candidate.field}\\b`).test(parsed.normalized)
4625
+ );
4626
+ if (!mutation) return void 0;
4627
+ const repository = operation.slice(mutation.operationPrefix.length);
4628
+ return /^[^/]+\/[^/]+$/.test(repository) ? repository : void 0;
4551
4629
  }
4552
4630
  function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
4553
4631
  if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
@@ -4650,13 +4728,13 @@ async function githubGrantForEgress(ctx) {
4650
4728
  writeGrantName
4651
4729
  );
4652
4730
  }
4653
- const reviewThreadRepository = reviewThreadResolveRepository(
4731
+ const hostMutationRepository = hostGraphqlMutationRepository(
4654
4732
  ctx.request.operation,
4655
4733
  method,
4656
4734
  upstreamUrl,
4657
4735
  ctx.request.bodyText
4658
4736
  );
4659
- if (reviewThreadRepository) {
4737
+ if (hostMutationRepository) {
4660
4738
  return grantForAccess(
4661
4739
  "write",
4662
4740
  "github.installation-write",
@@ -26,6 +26,7 @@ export declare function createGitHubUpdatePullRequestTool(ctx: {
26
26
  body?: string | undefined;
27
27
  base?: string | undefined;
28
28
  state?: "closed" | "open" | undefined;
29
+ draft?: boolean | undefined;
29
30
  }, {
30
31
  base: string;
31
32
  body: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.239.0",
3
+ "version": "0.240.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "drizzle-orm": "^0.45.2",
33
33
  "zod": "^4.5.4",
34
- "@sentry/junior-plugin-api": "0.239.0"
34
+ "@sentry/junior-plugin-api": "0.240.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@oxlint/plugins": "1.79.0",
@@ -11,6 +11,7 @@ Use `git` and `gh` for repository work.
11
11
  | ------------------------------- | ------------------------------------------------------------------------ |
12
12
  | Create PR | `github_createPullRequest` (not `gh pr create`) |
13
13
  | Update PR title/body/base/state | `github_updatePullRequest` |
14
+ | Mark PR ready / draft | `github_updatePullRequest` with `draft: false` / `draft: true` |
14
15
  | Submit PR review | `github_submitPullRequestReview` |
15
16
  | Resolve review thread | `github_resolvePullRequestReviewThread` |
16
17
  | Clone missing repo | `github_cloneRepository`; on Workspace match error use `switchWorkspace` |
@@ -33,7 +34,7 @@ Use `git` and `gh` for repository work.
33
34
  - Push before `github_createPullRequest`. Never ask for a token for bot pushes.
34
35
  - When an error names a tool, use it. Change permissions only when GitHub rejected the request.
35
36
  - Stop for an unclear target, missing access, destructive work, or a GitHub permission failure.
36
- - Unless the user opts out, push completed work and open a draft PR.
37
+ - Unless the user opts out, push completed work and open a PR. Open it ready for review when the work is complete and the checks pass. Open a draft only when work, checks, or decisions remain.
37
38
  - Report repo, branch, PR, checks, and skipped checks.
38
39
 
39
40
  Do not install or repair the GitHub plugin from this skill.
@@ -1,6 +1,6 @@
1
1
  # GitHub commands and permissions
2
2
 
3
- Use the tools in `SKILL.md` to create or update a PR, submit a review, or resolve a review thread. Use supported REST endpoints for other writes. Do not use GraphQL mutations.
3
+ Use the tools in `SKILL.md` to create or update a PR, mark it ready for review or draft, submit a review, or resolve a review thread. Use supported REST endpoints for other writes. Do not send GraphQL mutations, for example with `gh api graphql`. The egress proxy denies them.
4
4
 
5
5
  ## Repo targeting
6
6
 
@@ -35,7 +35,7 @@ If the user omits `owner/repo`, run `jr-rpc config get github.repo`. Then pass `
35
35
  | Push | `git -C DIR push -u origin BRANCH` |
36
36
  | Workflow dispatch | `gh workflow run WORKFLOW --repo owner/repo --ref REF [-f key=value]` |
37
37
  | Rerun / cancel | `gh run rerun RUN_ID -R owner/repo [--failed]` / `gh run cancel RUN_ID -R owner/repo` |
38
- | Ready for review | `gh api repos/owner/repo/pulls/NUMBER/ready_for_review --method POST` |
38
+ | Mark PR ready / draft | `github_updatePullRequest` with `draft: false` / `draft: true` |
39
39
  | Request reviewers | `gh api repos/owner/repo/pulls/NUMBER/requested_reviewers --method POST --input reviewers.json` |
40
40
  | Submit review | `github_submitPullRequestReview` |
41
41
  | Set feedback status | `github_updatePullRequestFeedback` |
@@ -52,7 +52,7 @@ If the user omits `owner/repo`, run `jr-rpc config get github.repo`. Then pass `
52
52
  - A local commit does not call GitHub. A push uses installation credentials. Workflow changes also need `workflows.write`.
53
53
  - Deepen a shallow clone before work that needs old history. Do not force-push to work around missing history.
54
54
  - Push `head` and read the default `base` before `github_createPullRequest`.
55
- - Junior does not support merges, forks, repository administration, REST content or Git database writes, direct PR update or review writes, or GraphQL mutations.
55
+ - Junior does not support merges, forks, repository administration, REST content or Git database writes, direct PR update or review writes, auto-merge, or GraphQL mutations.
56
56
  - Reviews and inline comments use the App bot.
57
57
  - PR comments/labels/assignees use issue endpoints; load `github-issues` for those.
58
58
  - Embed local images with `publishImage` first (public URL). Do not use private Slack file links.
@@ -35,7 +35,7 @@ Open this when making repository edits, not for read-only inspection.
35
35
  2. Commit with repo conventions, else `<type>(<scope>): <Subject>` imperative present, no agent branding.
36
36
  3. Push with `git push -u origin BRANCH`.
37
37
  4. Resolve the actual default branch.
38
- 5. Update an existing PR for the branch with `github_updatePullRequest`, or create with `github_createPullRequest` (`draft: true` unless ready-for-review is required).
38
+ 5. Update an existing PR for the branch with `github_updatePullRequest`, or create with `github_createPullRequest` (`draft: true` only when work, checks, or decisions remain; otherwise ready for review).
39
39
  6. PR title matches the current dominant change, same conventional form as commits.
40
40
  7. PR body: short, plain English, what changed and why. Only context the diff cannot show. No empty sections, file lists, commit logs, or Checks/Verification/Test plan blocks. Put local check results in the user report only.
41
41
  8. After material follow-up commits, refresh title and body against the current diff.