@sentry/junior-github 0.88.0 → 0.90.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/SETUP.md CHANGED
@@ -153,13 +153,14 @@ For PR creation in automation, push explicitly and pass that branch as `head`:
153
153
 
154
154
  ```bash
155
155
  git -C repo push -u origin BRANCH
156
+ gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name
156
157
  ```
157
158
 
158
159
  ```ts
159
160
  github_createPullRequest({
160
161
  repo: "owner/repo",
161
162
  head: "BRANCH",
162
- base: "main",
163
+ base: "<DEFAULT_BRANCH>",
163
164
  title: "Example PR",
164
165
  body: "Example body",
165
166
  draft: true,
package/dist/index.js CHANGED
@@ -72,11 +72,14 @@ function permissionCapabilities(permissions) {
72
72
 
73
73
  // src/tools/create-issue.ts
74
74
  import {
75
+ definePluginTool,
75
76
  EgressAuthRequired,
76
- PluginToolInputError as PluginToolInputError2
77
+ PluginToolInputError as PluginToolInputError2,
78
+ pluginToolResultSchema
77
79
  } from "@sentry/junior-plugin-api";
78
80
  import { Type } from "@sinclair/typebox";
79
81
  import { Value } from "@sinclair/typebox/value";
82
+ import { z } from "zod";
80
83
 
81
84
  // src/tools/footer.ts
82
85
  import { PluginToolInputError } from "@sentry/junior-plugin-api";
@@ -179,6 +182,12 @@ var createIssueInputSchema = Type.Object(
179
182
  },
180
183
  { additionalProperties: false }
181
184
  );
185
+ var createIssueToolInputSchema = z.object({
186
+ repo: z.string().describe('Repository in "owner/name" format.'),
187
+ title: z.string().describe("Issue title."),
188
+ body: z.string().describe("Issue body. Junior appends the conversation footer.").optional(),
189
+ labels: z.array(z.string()).describe("Labels to apply to the issue.").optional()
190
+ }).strict();
182
191
  var createIssueStateSchema = Type.Union([
183
192
  Type.Object(
184
193
  {
@@ -197,6 +206,27 @@ var createIssueStateSchema = Type.Union([
197
206
  { additionalProperties: false }
198
207
  )
199
208
  ]);
209
+ var gitHubIssueDataSchema = z.object({
210
+ number: z.number(),
211
+ url: z.string()
212
+ });
213
+ var gitHubIssueOutputSchema = pluginToolResultSchema.extend({
214
+ ok: z.literal(true),
215
+ status: z.literal("success"),
216
+ target: z.literal("createIssue"),
217
+ data: gitHubIssueDataSchema,
218
+ number: z.number(),
219
+ url: z.string()
220
+ });
221
+ function gitHubIssueToolResult(result) {
222
+ return {
223
+ ok: true,
224
+ status: "success",
225
+ target: "createIssue",
226
+ data: result,
227
+ ...result
228
+ };
229
+ }
200
230
  function parseCreateIssueInput(input) {
201
231
  try {
202
232
  return Value.Parse(createIssueInputSchema, input);
@@ -320,9 +350,10 @@ async function createGitHubIssue(ctx, request) {
320
350
  };
321
351
  }
322
352
  function createGitHubIssueTool(ctx) {
323
- return {
353
+ return definePluginTool({
324
354
  description: "Create a GitHub issue with a runtime-owned Junior conversation footer. Use this instead of shelling out to gh issue create when creating issues.",
325
- inputSchema: createIssueInputSchema,
355
+ inputSchema: createIssueToolInputSchema,
356
+ outputSchema: gitHubIssueOutputSchema,
326
357
  async execute(input, options) {
327
358
  const parsedInput = parseCreateIssueInput(input);
328
359
  const conversationId = nonEmptyString2(
@@ -337,10 +368,10 @@ function createGitHubIssueTool(ctx) {
337
368
  async () => {
338
369
  const state = createIssueState(await ctx.state.get(key));
339
370
  if (state?.status === "completed") {
340
- return {
371
+ return gitHubIssueToolResult({
341
372
  number: state.number,
342
373
  url: state.url
343
- };
374
+ });
344
375
  }
345
376
  if (state?.status === "pending") {
346
377
  throw new Error(
@@ -372,7 +403,7 @@ function createGitHubIssueTool(ctx) {
372
403
  { cause: error }
373
404
  );
374
405
  }
375
- return result;
406
+ return gitHubIssueToolResult(result);
376
407
  } catch (error) {
377
408
  if (isEgressAuthRequired(error) || isDefinitiveGitHubIssueCreateRejection(error)) {
378
409
  await ctx.state.delete(key);
@@ -382,16 +413,19 @@ function createGitHubIssueTool(ctx) {
382
413
  }
383
414
  );
384
415
  }
385
- };
416
+ });
386
417
  }
387
418
 
388
419
  // src/tools/create-pull-request.ts
389
420
  import {
421
+ definePluginTool as definePluginTool2,
390
422
  EgressAuthRequired as EgressAuthRequired2,
391
- PluginToolInputError as PluginToolInputError3
423
+ PluginToolInputError as PluginToolInputError3,
424
+ pluginToolResultSchema as pluginToolResultSchema2
392
425
  } from "@sentry/junior-plugin-api";
393
426
  import { Type as Type2 } from "@sinclair/typebox";
394
427
  import { Value as Value2 } from "@sinclair/typebox/value";
428
+ import { z as z2 } from "zod";
395
429
  var GITHUB_PULL_REQUEST_CREATE_IDEMPOTENCY_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
396
430
  var GITHUB_PULL_REQUEST_CREATE_LOCK_TTL_MS = 6e4;
397
431
  var GitHubPullRequestCreateRejectedError = class extends Error {
@@ -429,6 +463,14 @@ var createPullRequestInputSchema = Type2.Object(
429
463
  },
430
464
  { additionalProperties: false }
431
465
  );
466
+ var createPullRequestToolInputSchema = z2.object({
467
+ repo: z2.string().describe('Repository in "owner/name" format.'),
468
+ title: z2.string().describe("Pull request title."),
469
+ head: z2.string().describe("Head branch or owner:branch ref."),
470
+ base: z2.string().describe("Base branch."),
471
+ body: z2.string().describe("Pull request body. Junior appends the conversation footer.").optional(),
472
+ draft: z2.boolean().describe("Whether to open the pull request as a draft.").optional()
473
+ }).strict();
432
474
  var createPullRequestStateSchema = Type2.Union([
433
475
  Type2.Object(
434
476
  {
@@ -449,6 +491,28 @@ var createPullRequestStateSchema = Type2.Union([
449
491
  { additionalProperties: false }
450
492
  )
451
493
  ]);
494
+ var subscribableResourceSchema = z2.object({
495
+ provider: z2.string(),
496
+ type: z2.string(),
497
+ resourceRef: z2.string(),
498
+ label: z2.string(),
499
+ supportedEvents: z2.array(z2.string()),
500
+ suggestedEvents: z2.array(z2.string()).optional()
501
+ }).strict();
502
+ var gitHubPullRequestDataSchema = z2.object({
503
+ number: z2.number(),
504
+ url: z2.string(),
505
+ subscribable: subscribableResourceSchema.optional()
506
+ });
507
+ var gitHubPullRequestOutputSchema = pluginToolResultSchema2.extend({
508
+ ok: z2.literal(true),
509
+ status: z2.literal("success"),
510
+ target: z2.literal("createPullRequest"),
511
+ data: gitHubPullRequestDataSchema,
512
+ number: z2.number(),
513
+ url: z2.string(),
514
+ subscribable: subscribableResourceSchema.optional()
515
+ });
452
516
  function parseCreatePullRequestInput(input) {
453
517
  try {
454
518
  return Value2.Parse(createPullRequestInputSchema, input);
@@ -490,7 +554,8 @@ function githubApiErrorMessage2(payload) {
490
554
  if (payload && typeof payload === "object" && !Array.isArray(payload)) {
491
555
  const message = payload.message;
492
556
  if (typeof message === "string") {
493
- return message;
557
+ const details = githubApiErrorDetails(payload);
558
+ return details ? `${message}: ${details}` : message;
494
559
  }
495
560
  }
496
561
  if (typeof payload === "string" && payload.trim()) {
@@ -498,6 +563,23 @@ function githubApiErrorMessage2(payload) {
498
563
  }
499
564
  return "GitHub request failed";
500
565
  }
566
+ function githubApiErrorDetails(payload) {
567
+ const errors = payload.errors;
568
+ if (!Array.isArray(errors)) {
569
+ return void 0;
570
+ }
571
+ const details = errors.map((error) => {
572
+ if (!error || typeof error !== "object" || Array.isArray(error)) {
573
+ return void 0;
574
+ }
575
+ const fields = ["resource", "field", "code", "message"].map((name) => {
576
+ const value = error[name];
577
+ return typeof value === "string" && value.trim() ? `${name}=${value.trim()}` : void 0;
578
+ }).filter((value) => value !== void 0);
579
+ return fields.length ? fields.join(" ") : void 0;
580
+ }).filter((value) => value !== void 0);
581
+ return details.length ? details.join("; ") : void 0;
582
+ }
501
583
  function createPullRequestState(value) {
502
584
  if (value === void 0) {
503
585
  return void 0;
@@ -614,10 +696,21 @@ function gitHubPullRequestToolResult(input, result) {
614
696
  subscribable: gitHubPullRequestSubscribable(input, result)
615
697
  };
616
698
  }
617
- function createGitHubPullRequestTool(ctx) {
699
+ function gitHubPullRequestStructuredResult(input, result) {
700
+ const data = gitHubPullRequestToolResult(input, result);
618
701
  return {
702
+ ok: true,
703
+ status: "success",
704
+ target: "createPullRequest",
705
+ data,
706
+ ...data
707
+ };
708
+ }
709
+ function createGitHubPullRequestTool(ctx) {
710
+ return definePluginTool2({
619
711
  description: "Create a GitHub pull request with a runtime-owned Junior conversation footer. Use this instead of shelling out to gh pr create when creating pull requests.",
620
- inputSchema: createPullRequestInputSchema,
712
+ inputSchema: createPullRequestToolInputSchema,
713
+ outputSchema: gitHubPullRequestOutputSchema,
621
714
  async execute(input, options) {
622
715
  const parsedInput = parseCreatePullRequestInput(input);
623
716
  const conversationId = nonEmptyString3(
@@ -632,10 +725,13 @@ function createGitHubPullRequestTool(ctx) {
632
725
  async () => {
633
726
  const state = createPullRequestState(await ctx.state.get(key));
634
727
  if (state?.status === "completed") {
635
- return gitHubPullRequestToolResult(state.input ?? parsedInput, {
636
- number: state.number,
637
- url: state.url
638
- });
728
+ return gitHubPullRequestStructuredResult(
729
+ state.input ?? parsedInput,
730
+ {
731
+ number: state.number,
732
+ url: state.url
733
+ }
734
+ );
639
735
  }
640
736
  if (state?.status === "pending") {
641
737
  throw new Error(
@@ -671,7 +767,7 @@ function createGitHubPullRequestTool(ctx) {
671
767
  { cause: error }
672
768
  );
673
769
  }
674
- return gitHubPullRequestToolResult(parsedInput, result);
770
+ return gitHubPullRequestStructuredResult(parsedInput, result);
675
771
  } catch (error) {
676
772
  if (isEgressAuthRequired2(error) || isDefinitiveGitHubPullRequestCreateRejection(error)) {
677
773
  await ctx.state.delete(key);
@@ -681,7 +777,7 @@ function createGitHubPullRequestTool(ctx) {
681
777
  }
682
778
  );
683
779
  }
684
- };
780
+ });
685
781
  }
686
782
 
687
783
  // src/tools.ts
@@ -1,12 +1,29 @@
1
- import { type PluginToolDefinition, type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
2
- import { type Static } from "@sinclair/typebox";
3
- declare const createIssueInputSchema: import("@sinclair/typebox").TObject<{
4
- repo: import("@sinclair/typebox").TString;
5
- title: import("@sinclair/typebox").TString;
6
- body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
7
- labels: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
8
- }>;
9
- type CreateGitHubIssueInput = Static<typeof createIssueInputSchema>;
1
+ import { type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
10
2
  /** Own issue creation so provider writes use host egress and the footer stays deterministic. */
11
- export declare function createGitHubIssueTool(ctx: ToolRegistrationHookContext): PluginToolDefinition<CreateGitHubIssueInput>;
12
- export {};
3
+ export declare function createGitHubIssueTool(ctx: ToolRegistrationHookContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
4
+ repo: string;
5
+ title: string;
6
+ body?: string | undefined;
7
+ labels?: string[] | undefined;
8
+ }, {
9
+ [x: string]: unknown;
10
+ ok: true;
11
+ status: "success";
12
+ target: "createIssue";
13
+ data: {
14
+ number: number;
15
+ url: string;
16
+ };
17
+ number: number;
18
+ url: string;
19
+ truncated?: boolean | undefined;
20
+ continuation?: {
21
+ arguments: Record<string, unknown>;
22
+ reason?: string | undefined;
23
+ } | undefined;
24
+ error?: string | {
25
+ kind: string;
26
+ message: string;
27
+ retryable?: boolean | undefined;
28
+ } | undefined;
29
+ }>;
@@ -1,14 +1,47 @@
1
- import { type PluginToolDefinition, type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
2
- import { type Static } from "@sinclair/typebox";
3
- declare const createPullRequestInputSchema: import("@sinclair/typebox").TObject<{
4
- repo: import("@sinclair/typebox").TString;
5
- title: import("@sinclair/typebox").TString;
6
- head: import("@sinclair/typebox").TString;
7
- base: import("@sinclair/typebox").TString;
8
- body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
- draft: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
10
- }>;
11
- type CreateGitHubPullRequestInput = Static<typeof createPullRequestInputSchema>;
1
+ import { type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
12
2
  /** Own PR creation so provider writes use host egress and the footer stays deterministic. */
13
- export declare function createGitHubPullRequestTool(ctx: ToolRegistrationHookContext): PluginToolDefinition<CreateGitHubPullRequestInput>;
14
- export {};
3
+ export declare function createGitHubPullRequestTool(ctx: ToolRegistrationHookContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
4
+ repo: string;
5
+ title: string;
6
+ head: string;
7
+ base: string;
8
+ body?: string | undefined;
9
+ draft?: boolean | undefined;
10
+ }, {
11
+ [x: string]: unknown;
12
+ ok: true;
13
+ status: "success";
14
+ target: "createPullRequest";
15
+ data: {
16
+ number: number;
17
+ url: string;
18
+ subscribable?: {
19
+ provider: string;
20
+ type: string;
21
+ resourceRef: string;
22
+ label: string;
23
+ supportedEvents: string[];
24
+ suggestedEvents?: string[] | undefined;
25
+ } | undefined;
26
+ };
27
+ number: number;
28
+ url: string;
29
+ truncated?: boolean | undefined;
30
+ continuation?: {
31
+ arguments: Record<string, unknown>;
32
+ reason?: string | undefined;
33
+ } | undefined;
34
+ error?: string | {
35
+ kind: string;
36
+ message: string;
37
+ retryable?: boolean | undefined;
38
+ } | undefined;
39
+ subscribable?: {
40
+ provider: string;
41
+ type: string;
42
+ resourceRef: string;
43
+ label: string;
44
+ supportedEvents: string[];
45
+ suggestedEvents?: string[] | undefined;
46
+ } | undefined;
47
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.88.0",
3
+ "version": "0.90.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,7 +24,8 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@sinclair/typebox": "^0.34.49",
27
- "@sentry/junior-plugin-api": "0.88.0"
27
+ "zod": "^4.4.3",
28
+ "@sentry/junior-plugin-api": "0.90.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^25.9.1",
@@ -109,7 +109,8 @@ Before creating:
109
109
 
110
110
  1. Changes committed on a non-default branch.
111
111
  2. Push the branch explicitly: `git push -u origin BRANCH`.
112
- 3. Create with explicit targeting: `github_createPullRequest({ repo: "owner/repo", head: "BRANCH", base: "main", title: "...", body: "...", draft: true })`.
112
+ 3. Resolve the actual default branch: `gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name`.
113
+ 4. Create with explicit targeting, using that default branch as `base`: `github_createPullRequest({ repo: "owner/repo", head: "BRANCH", base: "<DEFAULT_BRANCH>", title: "...", body: "...", draft: true })`.
113
114
 
114
115
  Defaults:
115
116
 
@@ -32,6 +32,7 @@ Treat explicit repo flags as command-targeting safety rails, not as a credential
32
32
  | Check worktree state | `git -C DIRECTORY status --short --branch` |
33
33
  | View commit log against base | `git -C DIRECTORY log BASE..HEAD --oneline` |
34
34
  | Diff against base | `git -C DIRECTORY diff BASE...HEAD` |
35
+ | Resolve default branch | `gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name` |
35
36
  | Create branch | `git -C DIRECTORY checkout -b BRANCH` |
36
37
  | Stage and commit | `git -C DIRECTORY add -A && git -C DIRECTORY commit -m "message"` |
37
38
  | Push branch before PR creation | `git -C DIRECTORY push -u origin BRANCH` |
@@ -64,7 +65,7 @@ jr-rpc config set github.repo owner/repo
64
65
  - A local `git commit` does not call GitHub. Pushing that commit does: `git push` requires `github.contents.write` on the target repo and requester write access.
65
66
  - REST Git commit construction also requires `github.contents.write`: `POST /git/blobs`, `POST /git/trees`, `POST /git/commits`, `POST /git/refs`, and `PATCH /git/refs/{ref}`.
66
67
  - If the commit changes workflow files under `.github/workflows`, expect `github.workflows.write` in addition to contents write.
67
- - Before `github_createPullRequest`, push the head branch explicitly. That push requires GitHub write access to the remote.
68
+ - Before `github_createPullRequest`, push the head branch explicitly and resolve the target repo's default branch for `base`. That push requires GitHub write access to the remote.
68
69
  - Do not use fork creation as the normal PR path. GitHub requires Administration write plus Contents read for `POST /repos/{owner}/{repo}/forks`, and the app must be installed on both source and destination accounts.
69
70
  - If the explicit `git push` fails with 401/403 or another access/permission error, verify the repo context and retry once. If it still fails, load troubleshooting guidance and report the exact command failure.
70
71
  - `gh pr edit` is not a single-permission command: title/body/base/reviewer changes need pull request write permission; label, assignee, and milestone changes need issue write permission (use the `github-issues` skill); project flags are outside the current GitHub App permission guidance.
@@ -16,6 +16,7 @@ Use this table to recover quickly while keeping operations deterministic.
16
16
  | 403 Forbidden | App lacks required permission on repo or install scope is too narrow. | Verify the repo context, then confirm GitHub App permissions and installation scope. |
17
17
  | `gh auth status` shows `Token scopes: none` | Expected for GitHub App user-to-server tokens. | Do not treat this as read-only proof. Use the failed command, `permission_denied.acceptedPermissions`, and GitHub App permissions instead. |
18
18
  | `github_createPullRequest` fails with auth/permission errors | The branch was not pushed first, or repo context is wrong. | Push the branch explicitly first, then retry `github_createPullRequest` with explicit `repo`, `head`, and `base` values. |
19
+ | `github_createPullRequest` fails with 422 validation on `base` | The `base` branch does not exist in the target repo. | Resolve the default branch with `gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name`, then retry with that value as `base`. |
19
20
  | `git blame`, long log history, or old commits are missing after clone | Repo was cloned shallow by design. | Deepen incrementally with `git -C DIRECTORY fetch --depth=N origin`, or `git -C DIRECTORY fetch --unshallow` when full history is required. |
20
21
  | `sandbox setup failed (dnf install gh failed ...)` | `gh` package not available from the plugin runtime dependency bootstrap. | Report the plugin runtime bootstrap failure; do not try to repair package installation from the skill workflow. |
21
22