@sentry/junior-github 0.162.0 → 0.164.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 +551 -233
- package/dist/sandbox-paths.d.ts +4 -0
- package/dist/tools/clone-repository.d.ts +2 -0
- package/dist/tools/resolve-pull-request-review-thread.d.ts +30 -0
- package/dist/tools.d.ts +1 -1
- package/dist/workspace-prepare.d.ts +3 -0
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +1 -1
- package/skills/github-code/references/api-surface.md +3 -1
- package/skills/github-code/references/troubleshooting-workarounds.md +1 -0
package/dist/index.js
CHANGED
|
@@ -730,16 +730,33 @@ import {
|
|
|
730
730
|
pluginToolOutputSchema
|
|
731
731
|
} from "@sentry/junior-plugin-api";
|
|
732
732
|
import { z } from "zod";
|
|
733
|
-
|
|
733
|
+
|
|
734
|
+
// src/sandbox-paths.ts
|
|
735
|
+
var RESERVED_SANDBOX_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
736
|
+
".junior",
|
|
737
|
+
"data",
|
|
738
|
+
"skills"
|
|
739
|
+
]);
|
|
740
|
+
function isReservedSandboxDirectory(path) {
|
|
741
|
+
return RESERVED_SANDBOX_DIRECTORIES.has(path.toLowerCase());
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/tools/clone-repository.ts
|
|
734
745
|
var inputSchema = z.object({
|
|
735
746
|
repo: z.string().describe('Repository in "owner/name" format.'),
|
|
736
|
-
directory: z.string().regex(/^[A-Za-z0-9._-]
|
|
737
|
-
|
|
738
|
-
|
|
747
|
+
directory: z.string().regex(/^(?:[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)$/).refine(
|
|
748
|
+
(value) => !value.split("/").some((part) => part === "." || part === ".."),
|
|
749
|
+
{
|
|
750
|
+
message: "Directory must be a relative path without . or .. segments."
|
|
751
|
+
}
|
|
752
|
+
).describe(
|
|
753
|
+
"Optional destination directory under the sandbox root. Defaults to repos/{name}."
|
|
754
|
+
).optional()
|
|
739
755
|
}).strict();
|
|
740
756
|
var cloneSchema = z.object({
|
|
741
757
|
path: z.string(),
|
|
742
|
-
repo: z.string()
|
|
758
|
+
repo: z.string(),
|
|
759
|
+
workspaces: z.array(z.string())
|
|
743
760
|
});
|
|
744
761
|
var outputSchema = pluginToolOutputSchema.extend({
|
|
745
762
|
target: z.literal("cloneRepository"),
|
|
@@ -753,7 +770,7 @@ function parseRepo(value) {
|
|
|
753
770
|
return { owner: parts[0], name: parts[1] };
|
|
754
771
|
}
|
|
755
772
|
function defaultDirectory(repoName) {
|
|
756
|
-
return
|
|
773
|
+
return `repos/${repoName}`;
|
|
757
774
|
}
|
|
758
775
|
function commandSignal(signal, timeoutMs) {
|
|
759
776
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
@@ -802,7 +819,27 @@ function createGitHubCloneRepositoryTool(ctx) {
|
|
|
802
819
|
async execute(input, options) {
|
|
803
820
|
const repo = parseRepo(input.repo);
|
|
804
821
|
const directory = input.directory ?? defaultDirectory(repo.name);
|
|
822
|
+
const rootSegment = directory.split("/")[0] ?? directory;
|
|
823
|
+
if (isReservedSandboxDirectory(rootSegment)) {
|
|
824
|
+
throw new PluginToolInputError(
|
|
825
|
+
`Directory conflicts with a reserved sandbox path: ${directory}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
805
828
|
const path = `${ctx.sandbox.root}/${directory}`;
|
|
829
|
+
const parentDirectory = directory.includes("/") ? directory.slice(0, directory.lastIndexOf("/")) : void 0;
|
|
830
|
+
if (parentDirectory) {
|
|
831
|
+
const mkdir = await ctx.sandbox.run({
|
|
832
|
+
cmd: "mkdir",
|
|
833
|
+
args: ["-p", "--", `${ctx.sandbox.root}/${parentDirectory}`],
|
|
834
|
+
cwd: ctx.sandbox.root,
|
|
835
|
+
signal: commandSignal(options.signal, 3e4)
|
|
836
|
+
});
|
|
837
|
+
if (mkdir.exitCode !== 0) {
|
|
838
|
+
throw new PluginToolInputError(
|
|
839
|
+
`Failed to create clone parent directory: ${parentDirectory}`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
806
843
|
const exists = await ctx.sandbox.run({
|
|
807
844
|
cmd: "bash",
|
|
808
845
|
args: ["-c", `test -e "$1"`, "bash", path],
|
|
@@ -837,7 +874,20 @@ function createGitHubCloneRepositoryTool(ctx) {
|
|
|
837
874
|
`GitHub repository clone failed: ${clone.stderr.trim() || `exit ${clone.exitCode}`}`
|
|
838
875
|
);
|
|
839
876
|
}
|
|
840
|
-
const
|
|
877
|
+
const repoId = `${repo.owner}/${repo.name}`;
|
|
878
|
+
let workspaces = [];
|
|
879
|
+
try {
|
|
880
|
+
workspaces = await ctx.workspaces.findByRepository({
|
|
881
|
+
provider: "github",
|
|
882
|
+
repo: repoId
|
|
883
|
+
});
|
|
884
|
+
} catch (error) {
|
|
885
|
+
ctx.log.error("github.clone.workspaces_lookup.failed", {
|
|
886
|
+
repo: repoId,
|
|
887
|
+
error: error instanceof Error ? error.message : String(error)
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
const data = { repo: repoId, path, workspaces };
|
|
841
891
|
return { target: "cloneRepository", ...data };
|
|
842
892
|
}
|
|
843
893
|
});
|
|
@@ -2550,8 +2600,200 @@ function createGitHubUpdatePullRequestTool(ctx) {
|
|
|
2550
2600
|
});
|
|
2551
2601
|
}
|
|
2552
2602
|
|
|
2603
|
+
// src/tools/resolve-pull-request-review-thread.ts
|
|
2604
|
+
import {
|
|
2605
|
+
definePluginTool as definePluginTool10,
|
|
2606
|
+
PluginToolInputError as PluginToolInputError11,
|
|
2607
|
+
pluginToolOutputSchema as pluginToolOutputSchema10
|
|
2608
|
+
} from "@sentry/junior-plugin-api";
|
|
2609
|
+
import { z as z10 } from "zod";
|
|
2610
|
+
|
|
2611
|
+
// src/webhooks/ownership.ts
|
|
2612
|
+
var GITHUB_NOREPLY_DOMAIN = "users.noreply.github.com";
|
|
2613
|
+
function botLoginFromEmail(value) {
|
|
2614
|
+
const email = value?.trim();
|
|
2615
|
+
if (!email) return void 0;
|
|
2616
|
+
const separator = email.lastIndexOf("@");
|
|
2617
|
+
if (separator <= 0) return void 0;
|
|
2618
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
2619
|
+
if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
|
|
2620
|
+
const localPart = email.slice(0, separator);
|
|
2621
|
+
const login = localPart.slice(localPart.indexOf("+") + 1).trim();
|
|
2622
|
+
return login.toLowerCase().endsWith("[bot]") ? login : void 0;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// src/tools/resolve-pull-request-review-thread.ts
|
|
2626
|
+
var inputSchema8 = z10.object({
|
|
2627
|
+
repo: z10.string().describe(
|
|
2628
|
+
'Repository in "owner/name" format. Required for repository-scoped credentials (GraphQL has no repo path).'
|
|
2629
|
+
),
|
|
2630
|
+
threadId: z10.string().trim().min(1).describe(
|
|
2631
|
+
"GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
|
|
2632
|
+
)
|
|
2633
|
+
}).strict();
|
|
2634
|
+
var outputSchema8 = pluginToolOutputSchema10.extend({
|
|
2635
|
+
target: z10.literal("resolvePullRequestReviewThread"),
|
|
2636
|
+
repo: z10.string(),
|
|
2637
|
+
number: z10.number(),
|
|
2638
|
+
threadId: z10.string(),
|
|
2639
|
+
resolved: z10.boolean()
|
|
2640
|
+
});
|
|
2641
|
+
function parseRepo10(value) {
|
|
2642
|
+
const parts = value.split("/").map((part) => part.trim());
|
|
2643
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
2644
|
+
throw new PluginToolInputError11('repo must use "owner/name" format');
|
|
2645
|
+
}
|
|
2646
|
+
return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
|
|
2647
|
+
}
|
|
2648
|
+
async function readJson7(response) {
|
|
2649
|
+
const text2 = await response.text();
|
|
2650
|
+
if (!text2) return void 0;
|
|
2651
|
+
try {
|
|
2652
|
+
return JSON.parse(text2);
|
|
2653
|
+
} catch {
|
|
2654
|
+
return text2;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
function githubError(payload) {
|
|
2658
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
2659
|
+
const message = payload.message;
|
|
2660
|
+
if (typeof message === "string" && message.trim()) return message.trim();
|
|
2661
|
+
}
|
|
2662
|
+
return "GitHub request failed";
|
|
2663
|
+
}
|
|
2664
|
+
function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
|
|
2665
|
+
return definePluginTool10({
|
|
2666
|
+
annotations: {
|
|
2667
|
+
destructiveHint: true,
|
|
2668
|
+
idempotentHint: true,
|
|
2669
|
+
openWorldHint: true,
|
|
2670
|
+
readOnlyHint: false
|
|
2671
|
+
},
|
|
2672
|
+
description: "Resolve a GitHub pull request review thread. Use this instead of shelling out to `gh api graphql` for resolveReviewThread (GraphQL-only; no REST or `gh pr` equivalent). Only works on pull requests Junior authored.",
|
|
2673
|
+
inputSchema: inputSchema8,
|
|
2674
|
+
outputSchema: outputSchema8,
|
|
2675
|
+
async execute(input) {
|
|
2676
|
+
const parsedInput = inputSchema8.safeParse(input);
|
|
2677
|
+
if (!parsedInput.success) {
|
|
2678
|
+
throw new PluginToolInputError11(
|
|
2679
|
+
"Invalid GitHub resolvePullRequestReviewThread input.",
|
|
2680
|
+
{ cause: parsedInput.error }
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
const repo = parseRepo10(parsedInput.data.repo);
|
|
2684
|
+
const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
|
|
2685
|
+
if (!botLogin) {
|
|
2686
|
+
throw new Error("GitHub App bot identity is not configured.");
|
|
2687
|
+
}
|
|
2688
|
+
const query = `query ReviewThreadOwnership($threadId: ID!) {
|
|
2689
|
+
node(id: $threadId) {
|
|
2690
|
+
... on PullRequestReviewThread {
|
|
2691
|
+
id
|
|
2692
|
+
isResolved
|
|
2693
|
+
pullRequest {
|
|
2694
|
+
number
|
|
2695
|
+
repository { nameWithOwner }
|
|
2696
|
+
author { login }
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}`;
|
|
2701
|
+
const lookupResponse = await ctx.egress.fetch({
|
|
2702
|
+
provider: "github",
|
|
2703
|
+
operation: "github.pull.review-thread.get",
|
|
2704
|
+
request: new Request("https://api.github.com/graphql", {
|
|
2705
|
+
method: "POST",
|
|
2706
|
+
headers: { "Content-Type": "application/json" },
|
|
2707
|
+
body: JSON.stringify({
|
|
2708
|
+
operationName: "ReviewThreadOwnership",
|
|
2709
|
+
query,
|
|
2710
|
+
variables: { threadId: parsedInput.data.threadId }
|
|
2711
|
+
})
|
|
2712
|
+
})
|
|
2713
|
+
});
|
|
2714
|
+
const lookupPayload = await readJson7(lookupResponse);
|
|
2715
|
+
if (!lookupResponse.ok) {
|
|
2716
|
+
throw new Error(
|
|
2717
|
+
`GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError(lookupPayload)}`
|
|
2718
|
+
);
|
|
2719
|
+
}
|
|
2720
|
+
const thread = z10.object({
|
|
2721
|
+
data: z10.object({
|
|
2722
|
+
node: z10.object({
|
|
2723
|
+
id: z10.string(),
|
|
2724
|
+
isResolved: z10.boolean(),
|
|
2725
|
+
pullRequest: z10.object({
|
|
2726
|
+
author: z10.object({ login: z10.string() }),
|
|
2727
|
+
number: z10.number(),
|
|
2728
|
+
repository: z10.object({ nameWithOwner: z10.string() })
|
|
2729
|
+
})
|
|
2730
|
+
}).nullable()
|
|
2731
|
+
})
|
|
2732
|
+
}).parse(lookupPayload).data.node;
|
|
2733
|
+
if (!thread) throw new Error("GitHub review thread was not found.");
|
|
2734
|
+
const pullRequest = thread.pullRequest;
|
|
2735
|
+
const ownsPullRequest = pullRequest.repository.nameWithOwner.toLowerCase() === repo.ref.toLowerCase() && pullRequest.author.login.toLowerCase() === botLogin;
|
|
2736
|
+
if (!ownsPullRequest) {
|
|
2737
|
+
throw new Error(
|
|
2738
|
+
"Junior can only resolve review threads on pull requests it authored."
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
if (thread.isResolved) {
|
|
2742
|
+
return {
|
|
2743
|
+
target: "resolvePullRequestReviewThread",
|
|
2744
|
+
repo: repo.ref,
|
|
2745
|
+
number: pullRequest.number,
|
|
2746
|
+
threadId: thread.id,
|
|
2747
|
+
resolved: true
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
const mutation = `mutation ResolveReviewThread($threadId: ID!) {
|
|
2751
|
+
resolveReviewThread(input: {threadId: $threadId}) {
|
|
2752
|
+
thread { id isResolved }
|
|
2753
|
+
}
|
|
2754
|
+
}`;
|
|
2755
|
+
const resolveResponse = await ctx.egress.fetch({
|
|
2756
|
+
provider: "github",
|
|
2757
|
+
operation: `github.pull.review-thread.resolve:${repo.ref.toLowerCase()}`,
|
|
2758
|
+
request: new Request("https://api.github.com/graphql", {
|
|
2759
|
+
method: "POST",
|
|
2760
|
+
headers: { "Content-Type": "application/json" },
|
|
2761
|
+
body: JSON.stringify({
|
|
2762
|
+
operationName: "ResolveReviewThread",
|
|
2763
|
+
query: mutation,
|
|
2764
|
+
variables: { threadId: thread.id }
|
|
2765
|
+
})
|
|
2766
|
+
})
|
|
2767
|
+
});
|
|
2768
|
+
const resolvePayload = await readJson7(resolveResponse);
|
|
2769
|
+
if (!resolveResponse.ok) {
|
|
2770
|
+
throw new Error(
|
|
2771
|
+
`GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError(resolvePayload)}`
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
const resolved = z10.object({
|
|
2775
|
+
data: z10.object({
|
|
2776
|
+
resolveReviewThread: z10.object({
|
|
2777
|
+
thread: z10.object({ id: z10.string(), isResolved: z10.boolean() })
|
|
2778
|
+
})
|
|
2779
|
+
})
|
|
2780
|
+
}).parse(resolvePayload).data.resolveReviewThread.thread;
|
|
2781
|
+
if (resolved.id !== thread.id || !resolved.isResolved) {
|
|
2782
|
+
throw new Error("GitHub did not resolve the requested review thread.");
|
|
2783
|
+
}
|
|
2784
|
+
return {
|
|
2785
|
+
target: "resolvePullRequestReviewThread",
|
|
2786
|
+
repo: repo.ref,
|
|
2787
|
+
number: pullRequest.number,
|
|
2788
|
+
threadId: resolved.id,
|
|
2789
|
+
resolved: true
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2553
2795
|
// src/tools.ts
|
|
2554
|
-
function createGitHubTools(ctx) {
|
|
2796
|
+
function createGitHubTools(ctx, botEmail) {
|
|
2555
2797
|
return {
|
|
2556
2798
|
cloneRepository: createGitHubCloneRepositoryTool(ctx),
|
|
2557
2799
|
createIssue: createGitHubIssueTool(ctx),
|
|
@@ -2560,6 +2802,7 @@ function createGitHubTools(ctx) {
|
|
|
2560
2802
|
getPullRequest: createGitHubGetPullRequestTool(ctx),
|
|
2561
2803
|
getRelease: createGitHubGetReleaseTool(ctx),
|
|
2562
2804
|
getRepository: createGitHubGetRepositoryTool(ctx),
|
|
2805
|
+
resolvePullRequestReviewThread: createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail),
|
|
2563
2806
|
updateIssue: createGitHubUpdateIssueTool(ctx),
|
|
2564
2807
|
updatePullRequest: createGitHubUpdatePullRequestTool(ctx)
|
|
2565
2808
|
};
|
|
@@ -2570,7 +2813,7 @@ import { createHmac, timingSafeEqual } from "crypto";
|
|
|
2570
2813
|
|
|
2571
2814
|
// src/issue-outcomes/store.ts
|
|
2572
2815
|
import { and, eq, lte, sql as sql2 } from "drizzle-orm";
|
|
2573
|
-
import { z as
|
|
2816
|
+
import { z as z12 } from "zod";
|
|
2574
2817
|
|
|
2575
2818
|
// src/db/schema.ts
|
|
2576
2819
|
import { sql } from "drizzle-orm";
|
|
@@ -2582,18 +2825,18 @@ import {
|
|
|
2582
2825
|
text,
|
|
2583
2826
|
timestamp
|
|
2584
2827
|
} from "drizzle-orm/pg-core";
|
|
2585
|
-
import { z as
|
|
2586
|
-
var githubPullRequestStateSchema =
|
|
2828
|
+
import { z as z11 } from "zod";
|
|
2829
|
+
var githubPullRequestStateSchema = z11.enum([
|
|
2587
2830
|
"closed_unmerged",
|
|
2588
2831
|
"merged",
|
|
2589
2832
|
"open"
|
|
2590
2833
|
]);
|
|
2591
|
-
var githubPullRequestCommitCompositionSchema =
|
|
2834
|
+
var githubPullRequestCommitCompositionSchema = z11.enum([
|
|
2592
2835
|
"junior_only",
|
|
2593
2836
|
"mixed"
|
|
2594
2837
|
]);
|
|
2595
|
-
var githubIssueStateSchema =
|
|
2596
|
-
var githubIssueStateReasonSchema =
|
|
2838
|
+
var githubIssueStateSchema = z11.enum(["closed", "open"]);
|
|
2839
|
+
var githubIssueStateReasonSchema = z11.enum([
|
|
2597
2840
|
"completed",
|
|
2598
2841
|
"duplicate",
|
|
2599
2842
|
"not_planned",
|
|
@@ -2663,21 +2906,21 @@ var juniorGitHubPullRequestIssues = pgTable(
|
|
|
2663
2906
|
);
|
|
2664
2907
|
|
|
2665
2908
|
// src/issue-outcomes/store.ts
|
|
2666
|
-
var githubIssueOutcomeInputSchema =
|
|
2667
|
-
candidateOwned:
|
|
2668
|
-
closedAt:
|
|
2669
|
-
issueId:
|
|
2670
|
-
number:
|
|
2671
|
-
openedAt:
|
|
2672
|
-
repositoryFullName:
|
|
2673
|
-
repositoryId:
|
|
2909
|
+
var githubIssueOutcomeInputSchema = z12.object({
|
|
2910
|
+
candidateOwned: z12.boolean(),
|
|
2911
|
+
closedAt: z12.date().optional(),
|
|
2912
|
+
issueId: z12.string().min(1),
|
|
2913
|
+
number: z12.number().int().positive(),
|
|
2914
|
+
openedAt: z12.date(),
|
|
2915
|
+
repositoryFullName: z12.string().min(1),
|
|
2916
|
+
repositoryId: z12.string().min(1),
|
|
2674
2917
|
state: githubIssueStateSchema,
|
|
2675
2918
|
stateReason: githubIssueStateReasonSchema.optional(),
|
|
2676
|
-
updatedAt:
|
|
2919
|
+
updatedAt: z12.date()
|
|
2677
2920
|
}).strict();
|
|
2678
|
-
var githubIssueConversationsInputSchema =
|
|
2679
|
-
conversationIds:
|
|
2680
|
-
issueId:
|
|
2921
|
+
var githubIssueConversationsInputSchema = z12.object({
|
|
2922
|
+
conversationIds: z12.array(z12.string().min(1)).min(1),
|
|
2923
|
+
issueId: z12.string().min(1)
|
|
2681
2924
|
}).strict();
|
|
2682
2925
|
function projectionValues(input) {
|
|
2683
2926
|
return {
|
|
@@ -2741,32 +2984,32 @@ async function recordGitHubIssueConversations(db, input) {
|
|
|
2741
2984
|
|
|
2742
2985
|
// src/pull-request-outcomes/store.ts
|
|
2743
2986
|
import { and as and2, eq as eq2, lte as lte2, ne, sql as sql3 } from "drizzle-orm";
|
|
2744
|
-
import { z as
|
|
2745
|
-
var githubPullRequestOutcomeInputSchema =
|
|
2746
|
-
candidateOwned:
|
|
2747
|
-
closedAt:
|
|
2987
|
+
import { z as z13 } from "zod";
|
|
2988
|
+
var githubPullRequestOutcomeInputSchema = z13.object({
|
|
2989
|
+
candidateOwned: z13.boolean(),
|
|
2990
|
+
closedAt: z13.date().optional(),
|
|
2748
2991
|
commitComposition: githubPullRequestCommitCompositionSchema.optional(),
|
|
2749
|
-
mergedAt:
|
|
2750
|
-
number:
|
|
2751
|
-
openedAt:
|
|
2752
|
-
pullRequestId:
|
|
2753
|
-
repositoryFullName:
|
|
2754
|
-
repositoryId:
|
|
2992
|
+
mergedAt: z13.date().optional(),
|
|
2993
|
+
number: z13.number().int().positive(),
|
|
2994
|
+
openedAt: z13.date(),
|
|
2995
|
+
pullRequestId: z13.string().min(1),
|
|
2996
|
+
repositoryFullName: z13.string().min(1),
|
|
2997
|
+
repositoryId: z13.string().min(1),
|
|
2755
2998
|
state: githubPullRequestStateSchema,
|
|
2756
|
-
updatedAt:
|
|
2999
|
+
updatedAt: z13.date()
|
|
2757
3000
|
}).strict();
|
|
2758
|
-
var githubPullRequestConversationsInputSchema =
|
|
2759
|
-
conversationIds:
|
|
2760
|
-
pullRequestId:
|
|
3001
|
+
var githubPullRequestConversationsInputSchema = z13.object({
|
|
3002
|
+
conversationIds: z13.array(z13.string().min(1)).min(1),
|
|
3003
|
+
pullRequestId: z13.string().min(1)
|
|
2761
3004
|
}).strict();
|
|
2762
|
-
var githubPullRequestLinkedIssuesInputSchema =
|
|
2763
|
-
linkedIssues:
|
|
2764
|
-
|
|
2765
|
-
number:
|
|
2766
|
-
repositoryFullName:
|
|
3005
|
+
var githubPullRequestLinkedIssuesInputSchema = z13.object({
|
|
3006
|
+
linkedIssues: z13.array(
|
|
3007
|
+
z13.object({
|
|
3008
|
+
number: z13.number().int().positive(),
|
|
3009
|
+
repositoryFullName: z13.string().min(1)
|
|
2767
3010
|
}).strict()
|
|
2768
3011
|
).min(1),
|
|
2769
|
-
pullRequestId:
|
|
3012
|
+
pullRequestId: z13.string().min(1)
|
|
2770
3013
|
}).strict();
|
|
2771
3014
|
function projectionValues2(input) {
|
|
2772
3015
|
return {
|
|
@@ -2931,55 +3174,39 @@ async function recordGitHubPullRequestLinkedIssues(db, input) {
|
|
|
2931
3174
|
}
|
|
2932
3175
|
|
|
2933
3176
|
// src/webhooks/issue-outcome.ts
|
|
2934
|
-
import { z as
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
const domain = email.slice(separator + 1).toLowerCase();
|
|
2944
|
-
if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
|
|
2945
|
-
const localPart = email.slice(0, separator);
|
|
2946
|
-
const login = localPart.slice(localPart.indexOf("+") + 1).trim();
|
|
2947
|
-
return login.toLowerCase().endsWith("[bot]") ? login : void 0;
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
// src/webhooks/issue-outcome.ts
|
|
2951
|
-
var canonicalIssueOutcomeSchema = z13.object({
|
|
2952
|
-
action: z13.enum(["opened", "closed", "reopened"]),
|
|
2953
|
-
issue: z13.object({
|
|
2954
|
-
body: z13.string().nullable().optional(),
|
|
2955
|
-
closed_at: z13.string().nullable().optional(),
|
|
2956
|
-
created_at: z13.string(),
|
|
2957
|
-
id: z13.number().int().positive(),
|
|
2958
|
-
number: z13.number().int().positive(),
|
|
3177
|
+
import { z as z14 } from "zod";
|
|
3178
|
+
var canonicalIssueOutcomeSchema = z14.object({
|
|
3179
|
+
action: z14.enum(["opened", "closed", "reopened"]),
|
|
3180
|
+
issue: z14.object({
|
|
3181
|
+
body: z14.string().nullable().optional(),
|
|
3182
|
+
closed_at: z14.string().nullable().optional(),
|
|
3183
|
+
created_at: z14.string(),
|
|
3184
|
+
id: z14.number().int().positive(),
|
|
3185
|
+
number: z14.number().int().positive(),
|
|
2959
3186
|
state_reason: githubIssueStateReasonSchema.nullable().optional(),
|
|
2960
|
-
updated_at:
|
|
2961
|
-
user:
|
|
3187
|
+
updated_at: z14.string(),
|
|
3188
|
+
user: z14.object({ login: z14.string().min(1) }).strict()
|
|
2962
3189
|
}).strict(),
|
|
2963
|
-
repository:
|
|
2964
|
-
full_name:
|
|
2965
|
-
id:
|
|
3190
|
+
repository: z14.object({
|
|
3191
|
+
full_name: z14.string().min(1),
|
|
3192
|
+
id: z14.number().int().positive()
|
|
2966
3193
|
}).strict()
|
|
2967
3194
|
}).strict();
|
|
2968
|
-
var issueOutcomeSchema =
|
|
2969
|
-
action:
|
|
2970
|
-
issue:
|
|
2971
|
-
body:
|
|
2972
|
-
closed_at:
|
|
2973
|
-
created_at:
|
|
2974
|
-
id:
|
|
2975
|
-
number:
|
|
3195
|
+
var issueOutcomeSchema = z14.object({
|
|
3196
|
+
action: z14.enum(["opened", "closed", "reopened"]),
|
|
3197
|
+
issue: z14.object({
|
|
3198
|
+
body: z14.string().nullable().optional(),
|
|
3199
|
+
closed_at: z14.string().nullable().optional(),
|
|
3200
|
+
created_at: z14.string(),
|
|
3201
|
+
id: z14.number().int().positive(),
|
|
3202
|
+
number: z14.number().int().positive(),
|
|
2976
3203
|
state_reason: githubIssueStateReasonSchema.nullable().optional(),
|
|
2977
|
-
updated_at:
|
|
2978
|
-
user:
|
|
3204
|
+
updated_at: z14.string(),
|
|
3205
|
+
user: z14.object({ login: z14.string().min(1) }).passthrough()
|
|
2979
3206
|
}).passthrough(),
|
|
2980
|
-
repository:
|
|
2981
|
-
full_name:
|
|
2982
|
-
id:
|
|
3207
|
+
repository: z14.object({
|
|
3208
|
+
full_name: z14.string().min(1),
|
|
3209
|
+
id: z14.number().int().positive()
|
|
2983
3210
|
}).passthrough()
|
|
2984
3211
|
}).passthrough().transform(
|
|
2985
3212
|
(provider) => canonicalIssueOutcomeSchema.parse({
|
|
@@ -3000,7 +3227,7 @@ var issueOutcomeSchema = z13.object({
|
|
|
3000
3227
|
}
|
|
3001
3228
|
})
|
|
3002
3229
|
);
|
|
3003
|
-
var issueLifecycleActionSchema =
|
|
3230
|
+
var issueLifecycleActionSchema = z14.object({ action: z14.string() }).passthrough();
|
|
3004
3231
|
function timestamp2(value) {
|
|
3005
3232
|
if (!value) return void 0;
|
|
3006
3233
|
const parsed = new Date(value);
|
|
@@ -3046,21 +3273,21 @@ function normalizeGitHubIssueOutcome(args) {
|
|
|
3046
3273
|
updatedAt
|
|
3047
3274
|
};
|
|
3048
3275
|
}
|
|
3049
|
-
var canonicalIssueConversationSchema =
|
|
3050
|
-
issue:
|
|
3051
|
-
body:
|
|
3052
|
-
id:
|
|
3053
|
-
user:
|
|
3276
|
+
var canonicalIssueConversationSchema = z14.object({
|
|
3277
|
+
issue: z14.object({
|
|
3278
|
+
body: z14.string().nullable().optional(),
|
|
3279
|
+
id: z14.number().int().positive(),
|
|
3280
|
+
user: z14.object({ login: z14.string().min(1) }).strict()
|
|
3054
3281
|
}).strict(),
|
|
3055
|
-
sender:
|
|
3282
|
+
sender: z14.object({ login: z14.string().min(1) }).strict().optional()
|
|
3056
3283
|
}).strict();
|
|
3057
|
-
var issueConversationSchema =
|
|
3058
|
-
issue:
|
|
3059
|
-
body:
|
|
3060
|
-
id:
|
|
3061
|
-
user:
|
|
3284
|
+
var issueConversationSchema = z14.object({
|
|
3285
|
+
issue: z14.object({
|
|
3286
|
+
body: z14.string().nullable().optional(),
|
|
3287
|
+
id: z14.number().int().positive(),
|
|
3288
|
+
user: z14.object({ login: z14.string().min(1) }).passthrough()
|
|
3062
3289
|
}).passthrough(),
|
|
3063
|
-
sender:
|
|
3290
|
+
sender: z14.object({ login: z14.string().min(1) }).passthrough().optional()
|
|
3064
3291
|
}).passthrough().transform(
|
|
3065
3292
|
(provider) => canonicalIssueConversationSchema.parse({
|
|
3066
3293
|
issue: {
|
|
@@ -3087,41 +3314,41 @@ function normalizeGitHubIssueConversations(args) {
|
|
|
3087
3314
|
}
|
|
3088
3315
|
|
|
3089
3316
|
// src/webhooks/pull-request-outcome.ts
|
|
3090
|
-
import { z as
|
|
3091
|
-
var canonicalPullRequestOutcomeSchema =
|
|
3092
|
-
action:
|
|
3093
|
-
pull_request:
|
|
3094
|
-
body:
|
|
3095
|
-
closed_at:
|
|
3096
|
-
created_at:
|
|
3097
|
-
id:
|
|
3098
|
-
merged:
|
|
3099
|
-
merged_at:
|
|
3100
|
-
number:
|
|
3101
|
-
updated_at:
|
|
3102
|
-
user:
|
|
3317
|
+
import { z as z15 } from "zod";
|
|
3318
|
+
var canonicalPullRequestOutcomeSchema = z15.object({
|
|
3319
|
+
action: z15.enum(["opened", "closed", "reopened"]),
|
|
3320
|
+
pull_request: z15.object({
|
|
3321
|
+
body: z15.string().nullable().optional(),
|
|
3322
|
+
closed_at: z15.string().nullable().optional(),
|
|
3323
|
+
created_at: z15.string(),
|
|
3324
|
+
id: z15.number().int().positive(),
|
|
3325
|
+
merged: z15.boolean(),
|
|
3326
|
+
merged_at: z15.string().nullable().optional(),
|
|
3327
|
+
number: z15.number().int().positive(),
|
|
3328
|
+
updated_at: z15.string(),
|
|
3329
|
+
user: z15.object({ login: z15.string().min(1) }).strict()
|
|
3103
3330
|
}).strict(),
|
|
3104
|
-
repository:
|
|
3105
|
-
full_name:
|
|
3106
|
-
id:
|
|
3331
|
+
repository: z15.object({
|
|
3332
|
+
full_name: z15.string().min(1),
|
|
3333
|
+
id: z15.number().int().positive()
|
|
3107
3334
|
}).strict()
|
|
3108
3335
|
}).strict();
|
|
3109
|
-
var pullRequestOutcomeSchema =
|
|
3110
|
-
action:
|
|
3111
|
-
pull_request:
|
|
3112
|
-
body:
|
|
3113
|
-
closed_at:
|
|
3114
|
-
created_at:
|
|
3115
|
-
id:
|
|
3116
|
-
merged:
|
|
3117
|
-
merged_at:
|
|
3118
|
-
number:
|
|
3119
|
-
updated_at:
|
|
3120
|
-
user:
|
|
3336
|
+
var pullRequestOutcomeSchema = z15.object({
|
|
3337
|
+
action: z15.enum(["opened", "closed", "reopened"]),
|
|
3338
|
+
pull_request: z15.object({
|
|
3339
|
+
body: z15.string().nullable().optional(),
|
|
3340
|
+
closed_at: z15.string().nullable().optional(),
|
|
3341
|
+
created_at: z15.string(),
|
|
3342
|
+
id: z15.number().int().positive(),
|
|
3343
|
+
merged: z15.boolean(),
|
|
3344
|
+
merged_at: z15.string().nullable().optional(),
|
|
3345
|
+
number: z15.number().int().positive(),
|
|
3346
|
+
updated_at: z15.string(),
|
|
3347
|
+
user: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3121
3348
|
}).passthrough(),
|
|
3122
|
-
repository:
|
|
3123
|
-
full_name:
|
|
3124
|
-
id:
|
|
3349
|
+
repository: z15.object({
|
|
3350
|
+
full_name: z15.string().min(1),
|
|
3351
|
+
id: z15.number().int().positive()
|
|
3125
3352
|
}).passthrough()
|
|
3126
3353
|
}).passthrough().transform(
|
|
3127
3354
|
(provider) => canonicalPullRequestOutcomeSchema.parse({
|
|
@@ -3143,24 +3370,24 @@ var pullRequestOutcomeSchema = z14.object({
|
|
|
3143
3370
|
}
|
|
3144
3371
|
})
|
|
3145
3372
|
);
|
|
3146
|
-
var pullRequestLifecycleActionSchema =
|
|
3147
|
-
var canonicalPullRequestConversationSchema =
|
|
3148
|
-
pull_request:
|
|
3149
|
-
body:
|
|
3150
|
-
id:
|
|
3151
|
-
user:
|
|
3373
|
+
var pullRequestLifecycleActionSchema = z15.object({ action: z15.string() }).passthrough();
|
|
3374
|
+
var canonicalPullRequestConversationSchema = z15.object({
|
|
3375
|
+
pull_request: z15.object({
|
|
3376
|
+
body: z15.string().nullable().optional(),
|
|
3377
|
+
id: z15.number().int().positive(),
|
|
3378
|
+
user: z15.object({ login: z15.string().min(1) }).strict()
|
|
3152
3379
|
}).strict(),
|
|
3153
|
-
repository:
|
|
3154
|
-
sender:
|
|
3380
|
+
repository: z15.object({ full_name: z15.string().min(1) }).strict(),
|
|
3381
|
+
sender: z15.object({ login: z15.string().min(1) }).strict()
|
|
3155
3382
|
}).strict();
|
|
3156
|
-
var pullRequestConversationSchema =
|
|
3157
|
-
pull_request:
|
|
3158
|
-
body:
|
|
3159
|
-
id:
|
|
3160
|
-
user:
|
|
3383
|
+
var pullRequestConversationSchema = z15.object({
|
|
3384
|
+
pull_request: z15.object({
|
|
3385
|
+
body: z15.string().nullable().optional(),
|
|
3386
|
+
id: z15.number().int().positive(),
|
|
3387
|
+
user: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3161
3388
|
}).passthrough(),
|
|
3162
|
-
repository:
|
|
3163
|
-
sender:
|
|
3389
|
+
repository: z15.object({ full_name: z15.string().min(1) }).passthrough(),
|
|
3390
|
+
sender: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3164
3391
|
}).passthrough().transform(
|
|
3165
3392
|
(provider) => canonicalPullRequestConversationSchema.parse({
|
|
3166
3393
|
pull_request: {
|
|
@@ -3400,14 +3627,14 @@ function createGitHubWebhookRoute(args) {
|
|
|
3400
3627
|
|
|
3401
3628
|
// src/outcomes/profile-report.ts
|
|
3402
3629
|
import { sql as sql4 } from "drizzle-orm";
|
|
3403
|
-
import { z as
|
|
3630
|
+
import { z as z16 } from "zod";
|
|
3404
3631
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3405
3632
|
var WINDOWS = [7, 30, 90];
|
|
3406
|
-
var pullRequestStatsSchema =
|
|
3407
|
-
closed:
|
|
3408
|
-
created:
|
|
3409
|
-
days:
|
|
3410
|
-
merged:
|
|
3633
|
+
var pullRequestStatsSchema = z16.object({
|
|
3634
|
+
closed: z16.number().int().nonnegative(),
|
|
3635
|
+
created: z16.number().int().nonnegative(),
|
|
3636
|
+
days: z16.number().int().positive(),
|
|
3637
|
+
merged: z16.number().int().nonnegative()
|
|
3411
3638
|
}).strict().transform((row) => {
|
|
3412
3639
|
const terminal = row.merged + row.closed;
|
|
3413
3640
|
return {
|
|
@@ -3415,13 +3642,13 @@ var pullRequestStatsSchema = z15.object({
|
|
|
3415
3642
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3416
3643
|
};
|
|
3417
3644
|
});
|
|
3418
|
-
var issueStatsSchema =
|
|
3419
|
-
created:
|
|
3420
|
-
days:
|
|
3645
|
+
var issueStatsSchema = z16.object({
|
|
3646
|
+
created: z16.number().int().nonnegative(),
|
|
3647
|
+
days: z16.number().int().positive()
|
|
3421
3648
|
}).strict();
|
|
3422
|
-
var daySchema =
|
|
3423
|
-
created:
|
|
3424
|
-
date:
|
|
3649
|
+
var daySchema = z16.object({
|
|
3650
|
+
created: z16.number().int().nonnegative(),
|
|
3651
|
+
date: z16.string().date()
|
|
3425
3652
|
}).strict();
|
|
3426
3653
|
function queryRows(result) {
|
|
3427
3654
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
@@ -3494,7 +3721,7 @@ async function aggregatePullRequestWindows(args) {
|
|
|
3494
3721
|
GROUP BY windows.days
|
|
3495
3722
|
ORDER BY windows.days
|
|
3496
3723
|
`);
|
|
3497
|
-
return
|
|
3724
|
+
return z16.array(pullRequestStatsSchema).parse(queryRows(result));
|
|
3498
3725
|
}
|
|
3499
3726
|
async function aggregateIssueWindows(args) {
|
|
3500
3727
|
const starts = WINDOWS.map(
|
|
@@ -3527,7 +3754,7 @@ async function aggregateIssueWindows(args) {
|
|
|
3527
3754
|
GROUP BY windows.days
|
|
3528
3755
|
ORDER BY windows.days
|
|
3529
3756
|
`);
|
|
3530
|
-
return
|
|
3757
|
+
return z16.array(issueStatsSchema).parse(queryRows(result));
|
|
3531
3758
|
}
|
|
3532
3759
|
async function aggregateOpenedDays(args) {
|
|
3533
3760
|
const end = new Date(args.nowMs);
|
|
@@ -3557,7 +3784,7 @@ async function aggregateOpenedDays(args) {
|
|
|
3557
3784
|
LEFT JOIN daily ON daily.day = days.day
|
|
3558
3785
|
ORDER BY days.day
|
|
3559
3786
|
`);
|
|
3560
|
-
return
|
|
3787
|
+
return z16.array(daySchema).parse(queryRows(result));
|
|
3561
3788
|
}
|
|
3562
3789
|
async function buildGitHubProfileReport(args) {
|
|
3563
3790
|
const [windows, pullRequestDays, issueWindows, issueDays] = await Promise.all(
|
|
@@ -3638,18 +3865,18 @@ async function buildGitHubProfileReport(args) {
|
|
|
3638
3865
|
|
|
3639
3866
|
// src/outcomes/report.ts
|
|
3640
3867
|
import { sql as sql6 } from "drizzle-orm";
|
|
3641
|
-
import { z as
|
|
3868
|
+
import { z as z18 } from "zod";
|
|
3642
3869
|
|
|
3643
3870
|
// src/outcomes/cost.ts
|
|
3644
3871
|
import { sql as sql5 } from "drizzle-orm";
|
|
3645
|
-
import { z as
|
|
3872
|
+
import { z as z17 } from "zod";
|
|
3646
3873
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
3647
|
-
var costWindowSchema =
|
|
3648
|
-
days:
|
|
3649
|
-
issueCostUsd:
|
|
3650
|
-
medianIssueCostUsd:
|
|
3651
|
-
medianPullRequestCostUsd:
|
|
3652
|
-
pullRequestCostUsd:
|
|
3874
|
+
var costWindowSchema = z17.object({
|
|
3875
|
+
days: z17.number().int().positive(),
|
|
3876
|
+
issueCostUsd: z17.number().nonnegative().nullable(),
|
|
3877
|
+
medianIssueCostUsd: z17.number().nonnegative().nullable(),
|
|
3878
|
+
medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3879
|
+
pullRequestCostUsd: z17.number().nonnegative().nullable()
|
|
3653
3880
|
}).strict().transform((row) => ({
|
|
3654
3881
|
days: row.days,
|
|
3655
3882
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
@@ -3657,12 +3884,12 @@ var costWindowSchema = z16.object({
|
|
|
3657
3884
|
medianPullRequestCostUsd: row.medianPullRequestCostUsd ?? void 0,
|
|
3658
3885
|
pullRequestCostUsd: row.pullRequestCostUsd ?? void 0
|
|
3659
3886
|
}));
|
|
3660
|
-
var repositoryCostSchema =
|
|
3661
|
-
issueCostUsd:
|
|
3662
|
-
medianIssueCostUsd:
|
|
3663
|
-
medianPullRequestCostUsd:
|
|
3664
|
-
pullRequestCostUsd:
|
|
3665
|
-
repository:
|
|
3887
|
+
var repositoryCostSchema = z17.object({
|
|
3888
|
+
issueCostUsd: z17.number().nonnegative().nullable(),
|
|
3889
|
+
medianIssueCostUsd: z17.number().nonnegative().nullable(),
|
|
3890
|
+
medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3891
|
+
pullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3892
|
+
repository: z17.string().min(1)
|
|
3666
3893
|
}).strict().transform((row) => ({
|
|
3667
3894
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
3668
3895
|
medianIssueCostUsd: row.medianIssueCostUsd ?? void 0,
|
|
@@ -3867,7 +4094,7 @@ async function aggregateGitHubCostWindows(args) {
|
|
|
3867
4094
|
INNER JOIN issue_window ON issue_window.days = pull_request_window.days
|
|
3868
4095
|
ORDER BY pull_request_window.days
|
|
3869
4096
|
`);
|
|
3870
|
-
return
|
|
4097
|
+
return z17.array(costWindowSchema).parse(queryRows2(result));
|
|
3871
4098
|
}
|
|
3872
4099
|
async function aggregateGitHubRepositoryCosts(args) {
|
|
3873
4100
|
if (!await hasConversationUsageTable(args.db)) {
|
|
@@ -3965,7 +4192,7 @@ async function aggregateGitHubRepositoryCosts(args) {
|
|
|
3965
4192
|
ON issue_totals.repository = repositories.repository
|
|
3966
4193
|
ORDER BY "repository" ASC
|
|
3967
4194
|
`);
|
|
3968
|
-
return
|
|
4195
|
+
return z17.array(repositoryCostSchema).parse(queryRows2(result));
|
|
3969
4196
|
}
|
|
3970
4197
|
function formatCostUsd(value) {
|
|
3971
4198
|
if (value === void 0) return "\u2014";
|
|
@@ -3980,12 +4207,12 @@ function formatCostUsd(value) {
|
|
|
3980
4207
|
// src/outcomes/report.ts
|
|
3981
4208
|
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
3982
4209
|
var WINDOWS2 = [7, 30, 90];
|
|
3983
|
-
var pullRequestStatsSchema2 =
|
|
3984
|
-
closed:
|
|
3985
|
-
created:
|
|
3986
|
-
days:
|
|
3987
|
-
medianMergeTimeMs:
|
|
3988
|
-
merged:
|
|
4210
|
+
var pullRequestStatsSchema2 = z18.object({
|
|
4211
|
+
closed: z18.number().int().nonnegative(),
|
|
4212
|
+
created: z18.number().int().nonnegative(),
|
|
4213
|
+
days: z18.number().int().positive(),
|
|
4214
|
+
medianMergeTimeMs: z18.number().nonnegative().nullable(),
|
|
4215
|
+
merged: z18.number().int().nonnegative()
|
|
3989
4216
|
}).strict().transform((row) => {
|
|
3990
4217
|
const terminal = row.merged + row.closed;
|
|
3991
4218
|
return {
|
|
@@ -3994,12 +4221,12 @@ var pullRequestStatsSchema2 = z17.object({
|
|
|
3994
4221
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3995
4222
|
};
|
|
3996
4223
|
});
|
|
3997
|
-
var pullRequestRepositoryStatsSchema =
|
|
3998
|
-
closed:
|
|
3999
|
-
created:
|
|
4000
|
-
juniorOnly:
|
|
4001
|
-
merged:
|
|
4002
|
-
repository:
|
|
4224
|
+
var pullRequestRepositoryStatsSchema = z18.object({
|
|
4225
|
+
closed: z18.number().int().nonnegative(),
|
|
4226
|
+
created: z18.number().int().nonnegative(),
|
|
4227
|
+
juniorOnly: z18.number().int().nonnegative(),
|
|
4228
|
+
merged: z18.number().int().nonnegative(),
|
|
4229
|
+
repository: z18.string().min(1)
|
|
4003
4230
|
}).strict().transform((row) => {
|
|
4004
4231
|
const terminal = row.merged + row.closed;
|
|
4005
4232
|
return {
|
|
@@ -4007,33 +4234,33 @@ var pullRequestRepositoryStatsSchema = z17.object({
|
|
|
4007
4234
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
4008
4235
|
};
|
|
4009
4236
|
});
|
|
4010
|
-
var issueStatsSchema2 =
|
|
4011
|
-
closedCompleted:
|
|
4012
|
-
closedDuplicate:
|
|
4013
|
-
closedNotPlanned:
|
|
4014
|
-
closedUnknown:
|
|
4015
|
-
created:
|
|
4016
|
-
days:
|
|
4017
|
-
medianCloseTimeMs:
|
|
4237
|
+
var issueStatsSchema2 = z18.object({
|
|
4238
|
+
closedCompleted: z18.number().int().nonnegative(),
|
|
4239
|
+
closedDuplicate: z18.number().int().nonnegative(),
|
|
4240
|
+
closedNotPlanned: z18.number().int().nonnegative(),
|
|
4241
|
+
closedUnknown: z18.number().int().nonnegative(),
|
|
4242
|
+
created: z18.number().int().nonnegative(),
|
|
4243
|
+
days: z18.number().int().positive(),
|
|
4244
|
+
medianCloseTimeMs: z18.number().nonnegative().nullable()
|
|
4018
4245
|
}).strict().transform((row) => ({
|
|
4019
4246
|
...row,
|
|
4020
4247
|
medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
|
|
4021
4248
|
}));
|
|
4022
|
-
var pullRequestDaySchema =
|
|
4023
|
-
created:
|
|
4024
|
-
date:
|
|
4249
|
+
var pullRequestDaySchema = z18.object({
|
|
4250
|
+
created: z18.number().int().nonnegative(),
|
|
4251
|
+
date: z18.string().date()
|
|
4025
4252
|
}).strict();
|
|
4026
|
-
var issueDaySchema =
|
|
4027
|
-
created:
|
|
4028
|
-
date:
|
|
4253
|
+
var issueDaySchema = z18.object({
|
|
4254
|
+
created: z18.number().int().nonnegative(),
|
|
4255
|
+
date: z18.string().date()
|
|
4029
4256
|
}).strict();
|
|
4030
|
-
var issueRepositoryStatsSchema =
|
|
4031
|
-
closedCompleted:
|
|
4032
|
-
closedDuplicate:
|
|
4033
|
-
closedNotPlanned:
|
|
4034
|
-
closedUnknown:
|
|
4035
|
-
created:
|
|
4036
|
-
repository:
|
|
4257
|
+
var issueRepositoryStatsSchema = z18.object({
|
|
4258
|
+
closedCompleted: z18.number().int().nonnegative(),
|
|
4259
|
+
closedDuplicate: z18.number().int().nonnegative(),
|
|
4260
|
+
closedNotPlanned: z18.number().int().nonnegative(),
|
|
4261
|
+
closedUnknown: z18.number().int().nonnegative(),
|
|
4262
|
+
created: z18.number().int().nonnegative(),
|
|
4263
|
+
repository: z18.string().min(1)
|
|
4037
4264
|
}).strict();
|
|
4038
4265
|
function queryRows3(result) {
|
|
4039
4266
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
@@ -4097,7 +4324,7 @@ async function aggregatePullRequestWindows2(args) {
|
|
|
4097
4324
|
GROUP BY windows.days
|
|
4098
4325
|
ORDER BY windows.days
|
|
4099
4326
|
`);
|
|
4100
|
-
return
|
|
4327
|
+
return z18.array(pullRequestStatsSchema2).parse(queryRows3(result));
|
|
4101
4328
|
}
|
|
4102
4329
|
async function aggregatePullRequestDays(args) {
|
|
4103
4330
|
const end = new Date(args.nowMs);
|
|
@@ -4125,7 +4352,7 @@ async function aggregatePullRequestDays(args) {
|
|
|
4125
4352
|
LEFT JOIN daily ON daily.day = days.day
|
|
4126
4353
|
ORDER BY days.day
|
|
4127
4354
|
`);
|
|
4128
|
-
return
|
|
4355
|
+
return z18.array(pullRequestDaySchema).parse(queryRows3(result));
|
|
4129
4356
|
}
|
|
4130
4357
|
async function aggregatePullRequestRepositories(args) {
|
|
4131
4358
|
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
@@ -4155,7 +4382,7 @@ async function aggregatePullRequestRepositories(args) {
|
|
|
4155
4382
|
ORDER BY "merged" DESC, "created" DESC, "repository" ASC
|
|
4156
4383
|
LIMIT 25
|
|
4157
4384
|
`);
|
|
4158
|
-
return
|
|
4385
|
+
return z18.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
|
|
4159
4386
|
}
|
|
4160
4387
|
async function aggregateIssueWindows2(args) {
|
|
4161
4388
|
const starts = WINDOWS2.map(
|
|
@@ -4224,7 +4451,7 @@ async function aggregateIssueWindows2(args) {
|
|
|
4224
4451
|
GROUP BY windows.days
|
|
4225
4452
|
ORDER BY windows.days
|
|
4226
4453
|
`);
|
|
4227
|
-
return
|
|
4454
|
+
return z18.array(issueStatsSchema2).parse(queryRows3(result));
|
|
4228
4455
|
}
|
|
4229
4456
|
async function aggregateIssueDays(args) {
|
|
4230
4457
|
const end = new Date(args.nowMs);
|
|
@@ -4252,7 +4479,7 @@ async function aggregateIssueDays(args) {
|
|
|
4252
4479
|
LEFT JOIN daily ON daily.day = days.day
|
|
4253
4480
|
ORDER BY days.day
|
|
4254
4481
|
`);
|
|
4255
|
-
return
|
|
4482
|
+
return z18.array(issueDaySchema).parse(queryRows3(result));
|
|
4256
4483
|
}
|
|
4257
4484
|
async function aggregateIssueRepositories(args) {
|
|
4258
4485
|
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
@@ -4289,7 +4516,7 @@ async function aggregateIssueRepositories(args) {
|
|
|
4289
4516
|
ORDER BY "created" DESC, "closedCompleted" DESC, "repository" ASC
|
|
4290
4517
|
LIMIT 25
|
|
4291
4518
|
`);
|
|
4292
|
-
return
|
|
4519
|
+
return z18.array(issueRepositoryStatsSchema).parse(queryRows3(result));
|
|
4293
4520
|
}
|
|
4294
4521
|
function formatPercent2(value) {
|
|
4295
4522
|
return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
|
|
@@ -4453,18 +4680,18 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4453
4680
|
}
|
|
4454
4681
|
|
|
4455
4682
|
// src/pull-request-outcomes/commit-composition.ts
|
|
4456
|
-
import { z as
|
|
4457
|
-
var canonicalCommitSchema =
|
|
4458
|
-
authorEmail:
|
|
4459
|
-
authorLogin:
|
|
4683
|
+
import { z as z19 } from "zod";
|
|
4684
|
+
var canonicalCommitSchema = z19.object({
|
|
4685
|
+
authorEmail: z19.string().nullable(),
|
|
4686
|
+
authorLogin: z19.string().nullable()
|
|
4460
4687
|
}).strict();
|
|
4461
|
-
var providerCommitSchema =
|
|
4462
|
-
author:
|
|
4463
|
-
commit:
|
|
4464
|
-
author:
|
|
4688
|
+
var providerCommitSchema = z19.object({
|
|
4689
|
+
author: z19.object({ login: z19.string() }).passthrough().nullable(),
|
|
4690
|
+
commit: z19.object({
|
|
4691
|
+
author: z19.object({ email: z19.string() }).passthrough().nullable()
|
|
4465
4692
|
}).passthrough()
|
|
4466
4693
|
}).passthrough();
|
|
4467
|
-
var commitPageSchema =
|
|
4694
|
+
var commitPageSchema = z19.array(providerCommitSchema).transform(
|
|
4468
4695
|
(commits) => commits.map(
|
|
4469
4696
|
(commit) => canonicalCommitSchema.parse({
|
|
4470
4697
|
authorEmail: commit.commit.author?.email ?? null,
|
|
@@ -4911,6 +5138,63 @@ function linkifyGitHubReferences(text2) {
|
|
|
4911
5138
|
}).join("\n");
|
|
4912
5139
|
}
|
|
4913
5140
|
|
|
5141
|
+
// src/workspace-prepare.ts
|
|
5142
|
+
async function prepareWorkspace(ctx) {
|
|
5143
|
+
const repos = ctx.repos.map((entry) => {
|
|
5144
|
+
const [owner, name, ...rest] = entry.repo.split("/");
|
|
5145
|
+
if (!owner || !name || rest.length > 0) {
|
|
5146
|
+
throw new Error(`Invalid GitHub repository: ${entry.repo}`);
|
|
5147
|
+
}
|
|
5148
|
+
const segments = entry.path.split("/");
|
|
5149
|
+
if (segments.length === 0 || segments.some(
|
|
5150
|
+
(part) => !part || part === "." || part === ".." || !/^[A-Za-z0-9._-]+$/.test(part)
|
|
5151
|
+
) || isReservedSandboxDirectory(segments[0])) {
|
|
5152
|
+
throw new Error(`Invalid workspace checkout path: ${entry.path}`);
|
|
5153
|
+
}
|
|
5154
|
+
return { owner, name, path: entry.path, repo: entry.repo };
|
|
5155
|
+
});
|
|
5156
|
+
const paths = /* @__PURE__ */ new Set();
|
|
5157
|
+
for (const entry of repos) {
|
|
5158
|
+
const key = entry.path.toLowerCase();
|
|
5159
|
+
if (paths.has(key)) {
|
|
5160
|
+
throw new Error(`Workspace checkout path collision: ${entry.path}`);
|
|
5161
|
+
}
|
|
5162
|
+
paths.add(key);
|
|
5163
|
+
}
|
|
5164
|
+
for (const { owner, name, path, repo } of repos) {
|
|
5165
|
+
const parent = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : void 0;
|
|
5166
|
+
if (parent) {
|
|
5167
|
+
const mkdir = await ctx.sandbox.run({
|
|
5168
|
+
cmd: "mkdir",
|
|
5169
|
+
args: ["-p", "--", parent],
|
|
5170
|
+
cwd: ctx.sandbox.root
|
|
5171
|
+
});
|
|
5172
|
+
if (mkdir.exitCode !== 0) {
|
|
5173
|
+
throw new Error(
|
|
5174
|
+
`GitHub workspace checkout parent failed for ${repo}: ${mkdir.stderr.trim() || `exit ${mkdir.exitCode}`}`
|
|
5175
|
+
);
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
const result = await ctx.sandbox.run({
|
|
5179
|
+
cmd: "git",
|
|
5180
|
+
args: [
|
|
5181
|
+
"clone",
|
|
5182
|
+
"--quiet",
|
|
5183
|
+
"--depth=1",
|
|
5184
|
+
"--",
|
|
5185
|
+
`https://github.com/${owner}/${name}.git`,
|
|
5186
|
+
path
|
|
5187
|
+
],
|
|
5188
|
+
cwd: ctx.sandbox.root
|
|
5189
|
+
});
|
|
5190
|
+
if (result.exitCode !== 0) {
|
|
5191
|
+
throw new Error(
|
|
5192
|
+
`GitHub workspace clone failed for ${repo}: ${result.stderr.trim() || `exit ${result.exitCode}`}`
|
|
5193
|
+
);
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
|
|
4914
5198
|
// src/plugin.ts
|
|
4915
5199
|
function githubSmartHttpAccess(upstreamUrl) {
|
|
4916
5200
|
const pathname = upstreamUrl.pathname.toLowerCase();
|
|
@@ -5142,6 +5426,28 @@ function githubApiWriteGrantName(method, upstreamUrl) {
|
|
|
5142
5426
|
}
|
|
5143
5427
|
return void 0;
|
|
5144
5428
|
}
|
|
5429
|
+
function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
|
|
5430
|
+
const prefix = "github.pull.review-thread.resolve:";
|
|
5431
|
+
if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation?.startsWith(prefix)) {
|
|
5432
|
+
return void 0;
|
|
5433
|
+
}
|
|
5434
|
+
const repository = operation.slice(prefix.length);
|
|
5435
|
+
if (!/^[^/]+\/[^/]+$/.test(repository)) return void 0;
|
|
5436
|
+
const parsed = parseGitHubGraphqlRequest(bodyText);
|
|
5437
|
+
if (parsed?.operationName !== "ResolveReviewThread" || !/\bmutation\s+ResolveReviewThread\b/.test(parsed.normalized) || !/\bresolveReviewThread\b/.test(parsed.normalized)) {
|
|
5438
|
+
return void 0;
|
|
5439
|
+
}
|
|
5440
|
+
return repository;
|
|
5441
|
+
}
|
|
5442
|
+
function repositoryLeaseScopeFromRef(repository) {
|
|
5443
|
+
const [owner, name] = repository.split("/");
|
|
5444
|
+
if (!owner || !name) {
|
|
5445
|
+
throw new EgressPolicyDenied2(
|
|
5446
|
+
"GitHub review thread resolution does not identify a target repository."
|
|
5447
|
+
);
|
|
5448
|
+
}
|
|
5449
|
+
return githubRepositoryLeaseScope({ owner, name });
|
|
5450
|
+
}
|
|
5145
5451
|
function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
|
|
5146
5452
|
if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
|
|
5147
5453
|
const parsed = parseGitHubGraphqlRequest(bodyText);
|
|
@@ -5233,6 +5539,20 @@ async function githubGrantForEgress(ctx) {
|
|
|
5233
5539
|
repositoryLeaseScope(upstreamUrl)
|
|
5234
5540
|
);
|
|
5235
5541
|
}
|
|
5542
|
+
const reviewThreadRepository = reviewThreadResolveRepository(
|
|
5543
|
+
ctx.request.operation,
|
|
5544
|
+
method,
|
|
5545
|
+
upstreamUrl,
|
|
5546
|
+
ctx.request.bodyText
|
|
5547
|
+
);
|
|
5548
|
+
if (reviewThreadRepository) {
|
|
5549
|
+
return grantForAccess(
|
|
5550
|
+
"write",
|
|
5551
|
+
"github.installation-write",
|
|
5552
|
+
"installation-write",
|
|
5553
|
+
repositoryLeaseScopeFromRef(reviewThreadRepository)
|
|
5554
|
+
);
|
|
5555
|
+
}
|
|
5236
5556
|
const graphqlAccess = githubGraphqlAccess(
|
|
5237
5557
|
method,
|
|
5238
5558
|
upstreamUrl,
|
|
@@ -5451,8 +5771,9 @@ function githubPlugin(options = {}) {
|
|
|
5451
5771
|
});
|
|
5452
5772
|
},
|
|
5453
5773
|
tools(ctx) {
|
|
5454
|
-
return createGitHubTools(ctx);
|
|
5774
|
+
return createGitHubTools(ctx, readEnv(botEmailEnv));
|
|
5455
5775
|
},
|
|
5776
|
+
workspacePrepare: prepareWorkspace,
|
|
5456
5777
|
async sandboxPrepare(ctx) {
|
|
5457
5778
|
const hooksPath = `${ctx.sandbox.juniorRoot}/git-hooks`;
|
|
5458
5779
|
await ctx.sandbox.writeFile({
|
|
@@ -5469,11 +5790,8 @@ function githubPlugin(options = {}) {
|
|
|
5469
5790
|
if (ctx.tool.name !== "bash") {
|
|
5470
5791
|
return;
|
|
5471
5792
|
}
|
|
5472
|
-
const botName =
|
|
5473
|
-
const botEmail =
|
|
5474
|
-
if (!botName || !botEmail) {
|
|
5475
|
-
return;
|
|
5476
|
-
}
|
|
5793
|
+
const botName = requireEnv(botNameEnv);
|
|
5794
|
+
const botEmail = requireEnv(botEmailEnv);
|
|
5477
5795
|
ctx.env.set("GIT_AUTHOR_NAME", botName);
|
|
5478
5796
|
ctx.env.set("GIT_AUTHOR_EMAIL", botEmail);
|
|
5479
5797
|
ctx.env.set("JUNIOR_GIT_AUTHOR_NAME", botName);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Sandbox root directories reserved for Junior runtime material. */
|
|
2
|
+
export declare const RESERVED_SANDBOX_DIRECTORIES: Set<string>;
|
|
3
|
+
/** True when a checkout path collides with a reserved sandbox root (case-insensitive). */
|
|
4
|
+
export declare function isReservedSandboxDirectory(path: string): boolean;
|
|
@@ -7,6 +7,7 @@ export declare function createGitHubCloneRepositoryTool(ctx: ToolRegistrationHoo
|
|
|
7
7
|
[x: string]: unknown;
|
|
8
8
|
path: string;
|
|
9
9
|
repo: string;
|
|
10
|
+
workspaces: string[];
|
|
10
11
|
target: "cloneRepository";
|
|
11
12
|
truncated?: boolean | undefined;
|
|
12
13
|
continuation?: {
|
|
@@ -17,6 +18,7 @@ export declare function createGitHubCloneRepositoryTool(ctx: ToolRegistrationHoo
|
|
|
17
18
|
[x: string]: unknown;
|
|
18
19
|
path: string;
|
|
19
20
|
repo: string;
|
|
21
|
+
workspaces: string[];
|
|
20
22
|
target: "cloneRepository";
|
|
21
23
|
truncated?: boolean | undefined;
|
|
22
24
|
continuation?: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
|
|
2
|
+
/** Resolve one review thread after GitHub proves it belongs to a Junior-authored PR. */
|
|
3
|
+
export declare function createGitHubResolvePullRequestReviewThreadTool(ctx: ToolRegistrationHookContext, botEmail: string | undefined): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
4
|
+
repo: string;
|
|
5
|
+
threadId: string;
|
|
6
|
+
}, {
|
|
7
|
+
[x: string]: unknown;
|
|
8
|
+
target: "resolvePullRequestReviewThread";
|
|
9
|
+
repo: string;
|
|
10
|
+
number: number;
|
|
11
|
+
threadId: string;
|
|
12
|
+
resolved: boolean;
|
|
13
|
+
truncated?: boolean | undefined;
|
|
14
|
+
continuation?: {
|
|
15
|
+
arguments: Record<string, unknown>;
|
|
16
|
+
reason?: string | undefined;
|
|
17
|
+
} | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
[x: string]: unknown;
|
|
20
|
+
target: "resolvePullRequestReviewThread";
|
|
21
|
+
repo: string;
|
|
22
|
+
number: number;
|
|
23
|
+
threadId: string;
|
|
24
|
+
resolved: boolean;
|
|
25
|
+
truncated?: boolean | undefined;
|
|
26
|
+
continuation?: {
|
|
27
|
+
arguments: Record<string, unknown>;
|
|
28
|
+
reason?: string | undefined;
|
|
29
|
+
} | undefined;
|
|
30
|
+
}>;
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PluginToolDefinition, ToolRegistrationHookContext } from "@sentry/junior-plugin-api";
|
|
2
2
|
/** Build the GitHub plugin's runtime tools from their per-tool modules. */
|
|
3
|
-
export declare function createGitHubTools(ctx: ToolRegistrationHookContext): Record<string, PluginToolDefinition>;
|
|
3
|
+
export declare function createGitHubTools(ctx: ToolRegistrationHookContext, botEmail?: string): Record<string, PluginToolDefinition>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.164.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.4.3",
|
|
34
|
-
"@sentry/junior-plugin-api": "0.
|
|
34
|
+
"@sentry/junior-plugin-api": "0.164.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.9.1",
|
|
@@ -5,7 +5,7 @@ description: Work with GitHub repositories, source code, branches, commits, pull
|
|
|
5
5
|
|
|
6
6
|
# GitHub Code Operations
|
|
7
7
|
|
|
8
|
-
Use `git` and `gh` for repository work. Use `github_createPullRequest`, not `gh pr create`, for new PRs. Use `github_updatePullRequest`, not raw `gh api`/`gh pr edit`, when changing PR title, body, base, or open/closed state.
|
|
8
|
+
Use `git` and `gh` for repository work. Use `github_createPullRequest`, not `gh pr create`, for new PRs. Use `github_updatePullRequest`, not raw `gh api`/`gh pr edit`, when changing PR title, body, base, or open/closed state. Use `github_resolvePullRequestReviewThread`, not raw `gh api graphql` `resolveReviewThread`, when resolving review threads on Junior-authored PRs.
|
|
9
9
|
|
|
10
10
|
## References
|
|
11
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GitHub API Surface — code & pull requests
|
|
2
2
|
|
|
3
|
-
PR creation uses Junior's `github_createPullRequest` tool. PR title, body, base, and open/closed state updates use `github_updatePullRequest` so Junior keeps requester attribution and the conversation footer. Other supported mutations use allowlisted REST endpoints through `gh api`; generic GraphQL-backed `gh pr` mutations are not supported.
|
|
3
|
+
PR creation uses Junior's `github_createPullRequest` tool. PR title, body, base, and open/closed state updates use `github_updatePullRequest` so Junior keeps requester attribution and the conversation footer. Review-thread resolve uses `github_resolvePullRequestReviewThread` because GitHub exposes only the GraphQL `resolveReviewThread` mutation (no REST endpoint and no first-class `gh pr` subcommand). Other supported mutations use allowlisted REST endpoints through `gh api`; generic GraphQL-backed `gh pr` mutations are not supported.
|
|
4
4
|
|
|
5
5
|
## Repo scoping
|
|
6
6
|
|
|
@@ -50,6 +50,7 @@ Treat explicit repo flags as command-targeting safety rails, not as a credential
|
|
|
50
50
|
| Submit pull request review | `gh api repos/owner/repo/pulls/NUMBER/reviews --method POST --input review.json` |
|
|
51
51
|
| Post inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments --method POST --input comment.json` |
|
|
52
52
|
| Reply to inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments/COMMENT_ID/replies --method POST --input reply.json` |
|
|
53
|
+
| Resolve review thread | `github_resolvePullRequestReviewThread({ repo: "owner/repo", threadId: "PRRT_..." })` (GraphQL `resolveReviewThread` substitute; Junior-authored PRs only) |
|
|
53
54
|
| View pull request | `gh pr view NUMBER --repo owner/repo [--json ...]` |
|
|
54
55
|
| List pull requests | `gh pr list --repo owner/repo [--state open \| closed \| merged]` |
|
|
55
56
|
| Diff pull request | `gh pr diff NUMBER --repo owner/repo` |
|
|
@@ -78,6 +79,7 @@ jr-rpc config set github.repo owner/repo
|
|
|
78
79
|
- Use `github_updatePullRequest` for title, body, base, or open/closed state changes. Do not raw-`PATCH` `/repos/.../pulls/NUMBER`; that path is denied so Junior can keep the conversation footer.
|
|
79
80
|
- Merge, fork creation, REST contents/Git database writes, and repository administration are outside the current write allowlist.
|
|
80
81
|
- Pull request reviews and inline review comments use the same repository-scoped `installation-write` credential as other bot-owned PR writes, so they post as Junior even on headless turns. Merge remains denied.
|
|
82
|
+
- Resolve review threads with `github_resolvePullRequestReviewThread`. That tool is the Junior equivalent of `gh api graphql` `resolveReviewThread`; raw GraphQL mutations stay denied, and the tool only succeeds on Junior-authored PRs.
|
|
81
83
|
- 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.
|
|
82
84
|
- PR comments, labels, and assignees use GitHub's issue endpoints; use the `github-issues` REST guidance for those operations. All allowlisted bot writes share the same repository-scoped `installation-write` credential.
|
|
83
85
|
- To embed a local image in a GitHub issue, pull request, review, or comment, call `publishImage` first. That tool returns a durable public URL. The published image is public to anyone on the internet who has the URL. Embed the URL with normal GitHub Markdown. Do not use private Slack file links or conversation attachment URLs.
|
|
@@ -19,6 +19,7 @@ Use this table to recover quickly while keeping operations deterministic.
|
|
|
19
19
|
| `github_createPullRequest` returns 422 for `head` | The head branch was not pushed or the explicit head ref is wrong. | Push the branch, then retry with explicit `repo`, `head`, and `base` values. |
|
|
20
20
|
| `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`. |
|
|
21
21
|
| `403` names `github_updatePullRequest` | Raw PR title/body/base/state PATCH was blocked so Junior can own the footer. | Retry with `github_updatePullRequest`; do not use `gh api .../pulls/NUMBER --method PATCH` or `gh pr edit`. |
|
|
22
|
+
| `GraphQL mutations are not enabled` / resolve thread denied | Raw `gh api graphql` `resolveReviewThread` is blocked. | Retry with `github_resolvePullRequestReviewThread({ repo, threadId })`. Only Junior-authored PRs are allowed. |
|
|
22
23
|
| `github_updatePullRequest` returns upstream 401/403 | The App installation or target repository does not permit the operation. | Use the structured upstream denial to verify installation scope and accepted permissions; do not request user OAuth for this bot-owned operation. |
|
|
23
24
|
| `git blame`, long log history, or old commits are missing after clone | Repo was cloned shallow by design. | Fetch the required ref and deepen it incrementally; use `--unshallow` only when bounded deepening is insufficient. |
|
|
24
25
|
| Rebase, merge-base, or `origin/BASE...HEAD` comparison fails or gives odd ancestry | Required ancestry or the remote-tracking base ref is absent from the shallow clone. | Fetch a bounded depth into `BASE:refs/remotes/origin/BASE`, deepen that base ref until the merge base exists, and compare or rebase against `origin/BASE`. Never force-push around incomplete history. |
|