@sentry/junior-github 0.163.0 → 0.165.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 +461 -232
- 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/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
|
@@ -755,7 +755,8 @@ var inputSchema = z.object({
|
|
|
755
755
|
}).strict();
|
|
756
756
|
var cloneSchema = z.object({
|
|
757
757
|
path: z.string(),
|
|
758
|
-
repo: z.string()
|
|
758
|
+
repo: z.string(),
|
|
759
|
+
workspaces: z.array(z.string())
|
|
759
760
|
});
|
|
760
761
|
var outputSchema = pluginToolOutputSchema.extend({
|
|
761
762
|
target: z.literal("cloneRepository"),
|
|
@@ -873,7 +874,20 @@ function createGitHubCloneRepositoryTool(ctx) {
|
|
|
873
874
|
`GitHub repository clone failed: ${clone.stderr.trim() || `exit ${clone.exitCode}`}`
|
|
874
875
|
);
|
|
875
876
|
}
|
|
876
|
-
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 };
|
|
877
891
|
return { target: "cloneRepository", ...data };
|
|
878
892
|
}
|
|
879
893
|
});
|
|
@@ -2008,10 +2022,13 @@ function createGitHubGetPullRequestTool(ctx) {
|
|
|
2008
2022
|
)
|
|
2009
2023
|
});
|
|
2010
2024
|
const parsed = await readJson2(response);
|
|
2011
|
-
if (!response.ok)
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2025
|
+
if (!response.ok) {
|
|
2026
|
+
const message = `GitHub pull request lookup failed with HTTP ${response.status}`;
|
|
2027
|
+
if (response.status === 404) {
|
|
2028
|
+
throw new PluginToolInputError6(message);
|
|
2029
|
+
}
|
|
2030
|
+
throw new Error(message);
|
|
2031
|
+
}
|
|
2015
2032
|
const providerResult = z5.object({
|
|
2016
2033
|
base: z5.object({ ref: z5.string() }),
|
|
2017
2034
|
draft: z5.boolean(),
|
|
@@ -2586,8 +2603,202 @@ function createGitHubUpdatePullRequestTool(ctx) {
|
|
|
2586
2603
|
});
|
|
2587
2604
|
}
|
|
2588
2605
|
|
|
2606
|
+
// src/tools/resolve-pull-request-review-thread.ts
|
|
2607
|
+
import {
|
|
2608
|
+
definePluginTool as definePluginTool10,
|
|
2609
|
+
PluginToolInputError as PluginToolInputError11,
|
|
2610
|
+
pluginToolOutputSchema as pluginToolOutputSchema10
|
|
2611
|
+
} from "@sentry/junior-plugin-api";
|
|
2612
|
+
import { z as z10 } from "zod";
|
|
2613
|
+
|
|
2614
|
+
// src/webhooks/ownership.ts
|
|
2615
|
+
var GITHUB_NOREPLY_DOMAIN = "users.noreply.github.com";
|
|
2616
|
+
function botLoginFromEmail(value) {
|
|
2617
|
+
const email = value?.trim();
|
|
2618
|
+
if (!email) return void 0;
|
|
2619
|
+
const separator = email.lastIndexOf("@");
|
|
2620
|
+
if (separator <= 0) return void 0;
|
|
2621
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
2622
|
+
if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
|
|
2623
|
+
const localPart = email.slice(0, separator);
|
|
2624
|
+
const login = localPart.slice(localPart.indexOf("+") + 1).trim();
|
|
2625
|
+
return login.toLowerCase().endsWith("[bot]") ? login : void 0;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/tools/resolve-pull-request-review-thread.ts
|
|
2629
|
+
var inputSchema8 = z10.object({
|
|
2630
|
+
repo: z10.string().describe(
|
|
2631
|
+
'Repository in "owner/name" format. Required for repository-scoped credentials (GraphQL has no repo path).'
|
|
2632
|
+
),
|
|
2633
|
+
threadId: z10.string().trim().min(1).describe(
|
|
2634
|
+
"GitHub pull request review thread node ID (the same `threadId` / `id` variable used by `gh api graphql` resolveReviewThread)."
|
|
2635
|
+
)
|
|
2636
|
+
}).strict();
|
|
2637
|
+
var outputSchema8 = pluginToolOutputSchema10.extend({
|
|
2638
|
+
target: z10.literal("resolvePullRequestReviewThread"),
|
|
2639
|
+
repo: z10.string(),
|
|
2640
|
+
number: z10.number(),
|
|
2641
|
+
threadId: z10.string(),
|
|
2642
|
+
resolved: z10.boolean()
|
|
2643
|
+
});
|
|
2644
|
+
function parseRepo10(value) {
|
|
2645
|
+
const parts = value.split("/").map((part) => part.trim());
|
|
2646
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
2647
|
+
throw new PluginToolInputError11('repo must use "owner/name" format');
|
|
2648
|
+
}
|
|
2649
|
+
return { owner: parts[0], name: parts[1], ref: `${parts[0]}/${parts[1]}` };
|
|
2650
|
+
}
|
|
2651
|
+
async function readJson7(response) {
|
|
2652
|
+
const text2 = await response.text();
|
|
2653
|
+
if (!text2) return void 0;
|
|
2654
|
+
try {
|
|
2655
|
+
return JSON.parse(text2);
|
|
2656
|
+
} catch {
|
|
2657
|
+
return text2;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function githubError(payload) {
|
|
2661
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
2662
|
+
const message = payload.message;
|
|
2663
|
+
if (typeof message === "string" && message.trim()) return message.trim();
|
|
2664
|
+
}
|
|
2665
|
+
return "GitHub request failed";
|
|
2666
|
+
}
|
|
2667
|
+
function createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail) {
|
|
2668
|
+
return definePluginTool10({
|
|
2669
|
+
annotations: {
|
|
2670
|
+
destructiveHint: true,
|
|
2671
|
+
idempotentHint: true,
|
|
2672
|
+
openWorldHint: true,
|
|
2673
|
+
readOnlyHint: false
|
|
2674
|
+
},
|
|
2675
|
+
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.",
|
|
2676
|
+
inputSchema: inputSchema8,
|
|
2677
|
+
outputSchema: outputSchema8,
|
|
2678
|
+
async execute(input) {
|
|
2679
|
+
const parsedInput = inputSchema8.safeParse(input);
|
|
2680
|
+
if (!parsedInput.success) {
|
|
2681
|
+
throw new PluginToolInputError11(
|
|
2682
|
+
"Invalid GitHub resolvePullRequestReviewThread input.",
|
|
2683
|
+
{ cause: parsedInput.error }
|
|
2684
|
+
);
|
|
2685
|
+
}
|
|
2686
|
+
const repo = parseRepo10(parsedInput.data.repo);
|
|
2687
|
+
const botLogin = botLoginFromEmail(botEmail)?.toLowerCase();
|
|
2688
|
+
if (!botLogin) {
|
|
2689
|
+
throw new Error("GitHub App bot identity is not configured.");
|
|
2690
|
+
}
|
|
2691
|
+
const query = `query ReviewThreadOwnership($threadId: ID!) {
|
|
2692
|
+
node(id: $threadId) {
|
|
2693
|
+
... on PullRequestReviewThread {
|
|
2694
|
+
id
|
|
2695
|
+
isResolved
|
|
2696
|
+
pullRequest {
|
|
2697
|
+
number
|
|
2698
|
+
repository { nameWithOwner }
|
|
2699
|
+
author { login }
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}`;
|
|
2704
|
+
const lookupResponse = await ctx.egress.fetch({
|
|
2705
|
+
provider: "github",
|
|
2706
|
+
operation: "github.pull.review-thread.get",
|
|
2707
|
+
request: new Request("https://api.github.com/graphql", {
|
|
2708
|
+
method: "POST",
|
|
2709
|
+
headers: { "Content-Type": "application/json" },
|
|
2710
|
+
body: JSON.stringify({
|
|
2711
|
+
operationName: "ReviewThreadOwnership",
|
|
2712
|
+
query,
|
|
2713
|
+
variables: { threadId: parsedInput.data.threadId }
|
|
2714
|
+
})
|
|
2715
|
+
})
|
|
2716
|
+
});
|
|
2717
|
+
const lookupPayload = await readJson7(lookupResponse);
|
|
2718
|
+
if (!lookupResponse.ok) {
|
|
2719
|
+
throw new Error(
|
|
2720
|
+
`GitHub review thread lookup failed with HTTP ${lookupResponse.status}: ${githubError(lookupPayload)}`
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
const thread = z10.object({
|
|
2724
|
+
data: z10.object({
|
|
2725
|
+
node: z10.object({
|
|
2726
|
+
id: z10.string(),
|
|
2727
|
+
isResolved: z10.boolean(),
|
|
2728
|
+
pullRequest: z10.object({
|
|
2729
|
+
author: z10.object({ login: z10.string() }),
|
|
2730
|
+
number: z10.number(),
|
|
2731
|
+
repository: z10.object({ nameWithOwner: z10.string() })
|
|
2732
|
+
})
|
|
2733
|
+
}).nullable()
|
|
2734
|
+
})
|
|
2735
|
+
}).parse(lookupPayload).data.node;
|
|
2736
|
+
if (!thread) {
|
|
2737
|
+
throw new PluginToolInputError11("GitHub review thread was not found.");
|
|
2738
|
+
}
|
|
2739
|
+
const pullRequest = thread.pullRequest;
|
|
2740
|
+
const ownsPullRequest = pullRequest.repository.nameWithOwner.toLowerCase() === repo.ref.toLowerCase() && pullRequest.author.login.toLowerCase() === botLogin;
|
|
2741
|
+
if (!ownsPullRequest) {
|
|
2742
|
+
throw new PluginToolInputError11(
|
|
2743
|
+
"Junior can only resolve review threads on pull requests it authored."
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
if (thread.isResolved) {
|
|
2747
|
+
return {
|
|
2748
|
+
target: "resolvePullRequestReviewThread",
|
|
2749
|
+
repo: repo.ref,
|
|
2750
|
+
number: pullRequest.number,
|
|
2751
|
+
threadId: thread.id,
|
|
2752
|
+
resolved: true
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
const mutation = `mutation ResolveReviewThread($threadId: ID!) {
|
|
2756
|
+
resolveReviewThread(input: {threadId: $threadId}) {
|
|
2757
|
+
thread { id isResolved }
|
|
2758
|
+
}
|
|
2759
|
+
}`;
|
|
2760
|
+
const resolveResponse = await ctx.egress.fetch({
|
|
2761
|
+
provider: "github",
|
|
2762
|
+
operation: `github.pull.review-thread.resolve:${repo.ref.toLowerCase()}`,
|
|
2763
|
+
request: new Request("https://api.github.com/graphql", {
|
|
2764
|
+
method: "POST",
|
|
2765
|
+
headers: { "Content-Type": "application/json" },
|
|
2766
|
+
body: JSON.stringify({
|
|
2767
|
+
operationName: "ResolveReviewThread",
|
|
2768
|
+
query: mutation,
|
|
2769
|
+
variables: { threadId: thread.id }
|
|
2770
|
+
})
|
|
2771
|
+
})
|
|
2772
|
+
});
|
|
2773
|
+
const resolvePayload = await readJson7(resolveResponse);
|
|
2774
|
+
if (!resolveResponse.ok) {
|
|
2775
|
+
throw new Error(
|
|
2776
|
+
`GitHub review thread resolution failed with HTTP ${resolveResponse.status}: ${githubError(resolvePayload)}`
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
const resolved = z10.object({
|
|
2780
|
+
data: z10.object({
|
|
2781
|
+
resolveReviewThread: z10.object({
|
|
2782
|
+
thread: z10.object({ id: z10.string(), isResolved: z10.boolean() })
|
|
2783
|
+
})
|
|
2784
|
+
})
|
|
2785
|
+
}).parse(resolvePayload).data.resolveReviewThread.thread;
|
|
2786
|
+
if (resolved.id !== thread.id || !resolved.isResolved) {
|
|
2787
|
+
throw new Error("GitHub did not resolve the requested review thread.");
|
|
2788
|
+
}
|
|
2789
|
+
return {
|
|
2790
|
+
target: "resolvePullRequestReviewThread",
|
|
2791
|
+
repo: repo.ref,
|
|
2792
|
+
number: pullRequest.number,
|
|
2793
|
+
threadId: resolved.id,
|
|
2794
|
+
resolved: true
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2589
2800
|
// src/tools.ts
|
|
2590
|
-
function createGitHubTools(ctx) {
|
|
2801
|
+
function createGitHubTools(ctx, botEmail) {
|
|
2591
2802
|
return {
|
|
2592
2803
|
cloneRepository: createGitHubCloneRepositoryTool(ctx),
|
|
2593
2804
|
createIssue: createGitHubIssueTool(ctx),
|
|
@@ -2596,6 +2807,7 @@ function createGitHubTools(ctx) {
|
|
|
2596
2807
|
getPullRequest: createGitHubGetPullRequestTool(ctx),
|
|
2597
2808
|
getRelease: createGitHubGetReleaseTool(ctx),
|
|
2598
2809
|
getRepository: createGitHubGetRepositoryTool(ctx),
|
|
2810
|
+
resolvePullRequestReviewThread: createGitHubResolvePullRequestReviewThreadTool(ctx, botEmail),
|
|
2599
2811
|
updateIssue: createGitHubUpdateIssueTool(ctx),
|
|
2600
2812
|
updatePullRequest: createGitHubUpdatePullRequestTool(ctx)
|
|
2601
2813
|
};
|
|
@@ -2606,7 +2818,7 @@ import { createHmac, timingSafeEqual } from "crypto";
|
|
|
2606
2818
|
|
|
2607
2819
|
// src/issue-outcomes/store.ts
|
|
2608
2820
|
import { and, eq, lte, sql as sql2 } from "drizzle-orm";
|
|
2609
|
-
import { z as
|
|
2821
|
+
import { z as z12 } from "zod";
|
|
2610
2822
|
|
|
2611
2823
|
// src/db/schema.ts
|
|
2612
2824
|
import { sql } from "drizzle-orm";
|
|
@@ -2618,18 +2830,18 @@ import {
|
|
|
2618
2830
|
text,
|
|
2619
2831
|
timestamp
|
|
2620
2832
|
} from "drizzle-orm/pg-core";
|
|
2621
|
-
import { z as
|
|
2622
|
-
var githubPullRequestStateSchema =
|
|
2833
|
+
import { z as z11 } from "zod";
|
|
2834
|
+
var githubPullRequestStateSchema = z11.enum([
|
|
2623
2835
|
"closed_unmerged",
|
|
2624
2836
|
"merged",
|
|
2625
2837
|
"open"
|
|
2626
2838
|
]);
|
|
2627
|
-
var githubPullRequestCommitCompositionSchema =
|
|
2839
|
+
var githubPullRequestCommitCompositionSchema = z11.enum([
|
|
2628
2840
|
"junior_only",
|
|
2629
2841
|
"mixed"
|
|
2630
2842
|
]);
|
|
2631
|
-
var githubIssueStateSchema =
|
|
2632
|
-
var githubIssueStateReasonSchema =
|
|
2843
|
+
var githubIssueStateSchema = z11.enum(["closed", "open"]);
|
|
2844
|
+
var githubIssueStateReasonSchema = z11.enum([
|
|
2633
2845
|
"completed",
|
|
2634
2846
|
"duplicate",
|
|
2635
2847
|
"not_planned",
|
|
@@ -2699,21 +2911,21 @@ var juniorGitHubPullRequestIssues = pgTable(
|
|
|
2699
2911
|
);
|
|
2700
2912
|
|
|
2701
2913
|
// src/issue-outcomes/store.ts
|
|
2702
|
-
var githubIssueOutcomeInputSchema =
|
|
2703
|
-
candidateOwned:
|
|
2704
|
-
closedAt:
|
|
2705
|
-
issueId:
|
|
2706
|
-
number:
|
|
2707
|
-
openedAt:
|
|
2708
|
-
repositoryFullName:
|
|
2709
|
-
repositoryId:
|
|
2914
|
+
var githubIssueOutcomeInputSchema = z12.object({
|
|
2915
|
+
candidateOwned: z12.boolean(),
|
|
2916
|
+
closedAt: z12.date().optional(),
|
|
2917
|
+
issueId: z12.string().min(1),
|
|
2918
|
+
number: z12.number().int().positive(),
|
|
2919
|
+
openedAt: z12.date(),
|
|
2920
|
+
repositoryFullName: z12.string().min(1),
|
|
2921
|
+
repositoryId: z12.string().min(1),
|
|
2710
2922
|
state: githubIssueStateSchema,
|
|
2711
2923
|
stateReason: githubIssueStateReasonSchema.optional(),
|
|
2712
|
-
updatedAt:
|
|
2924
|
+
updatedAt: z12.date()
|
|
2713
2925
|
}).strict();
|
|
2714
|
-
var githubIssueConversationsInputSchema =
|
|
2715
|
-
conversationIds:
|
|
2716
|
-
issueId:
|
|
2926
|
+
var githubIssueConversationsInputSchema = z12.object({
|
|
2927
|
+
conversationIds: z12.array(z12.string().min(1)).min(1),
|
|
2928
|
+
issueId: z12.string().min(1)
|
|
2717
2929
|
}).strict();
|
|
2718
2930
|
function projectionValues(input) {
|
|
2719
2931
|
return {
|
|
@@ -2777,32 +2989,32 @@ async function recordGitHubIssueConversations(db, input) {
|
|
|
2777
2989
|
|
|
2778
2990
|
// src/pull-request-outcomes/store.ts
|
|
2779
2991
|
import { and as and2, eq as eq2, lte as lte2, ne, sql as sql3 } from "drizzle-orm";
|
|
2780
|
-
import { z as
|
|
2781
|
-
var githubPullRequestOutcomeInputSchema =
|
|
2782
|
-
candidateOwned:
|
|
2783
|
-
closedAt:
|
|
2992
|
+
import { z as z13 } from "zod";
|
|
2993
|
+
var githubPullRequestOutcomeInputSchema = z13.object({
|
|
2994
|
+
candidateOwned: z13.boolean(),
|
|
2995
|
+
closedAt: z13.date().optional(),
|
|
2784
2996
|
commitComposition: githubPullRequestCommitCompositionSchema.optional(),
|
|
2785
|
-
mergedAt:
|
|
2786
|
-
number:
|
|
2787
|
-
openedAt:
|
|
2788
|
-
pullRequestId:
|
|
2789
|
-
repositoryFullName:
|
|
2790
|
-
repositoryId:
|
|
2997
|
+
mergedAt: z13.date().optional(),
|
|
2998
|
+
number: z13.number().int().positive(),
|
|
2999
|
+
openedAt: z13.date(),
|
|
3000
|
+
pullRequestId: z13.string().min(1),
|
|
3001
|
+
repositoryFullName: z13.string().min(1),
|
|
3002
|
+
repositoryId: z13.string().min(1),
|
|
2791
3003
|
state: githubPullRequestStateSchema,
|
|
2792
|
-
updatedAt:
|
|
3004
|
+
updatedAt: z13.date()
|
|
2793
3005
|
}).strict();
|
|
2794
|
-
var githubPullRequestConversationsInputSchema =
|
|
2795
|
-
conversationIds:
|
|
2796
|
-
pullRequestId:
|
|
3006
|
+
var githubPullRequestConversationsInputSchema = z13.object({
|
|
3007
|
+
conversationIds: z13.array(z13.string().min(1)).min(1),
|
|
3008
|
+
pullRequestId: z13.string().min(1)
|
|
2797
3009
|
}).strict();
|
|
2798
|
-
var githubPullRequestLinkedIssuesInputSchema =
|
|
2799
|
-
linkedIssues:
|
|
2800
|
-
|
|
2801
|
-
number:
|
|
2802
|
-
repositoryFullName:
|
|
3010
|
+
var githubPullRequestLinkedIssuesInputSchema = z13.object({
|
|
3011
|
+
linkedIssues: z13.array(
|
|
3012
|
+
z13.object({
|
|
3013
|
+
number: z13.number().int().positive(),
|
|
3014
|
+
repositoryFullName: z13.string().min(1)
|
|
2803
3015
|
}).strict()
|
|
2804
3016
|
).min(1),
|
|
2805
|
-
pullRequestId:
|
|
3017
|
+
pullRequestId: z13.string().min(1)
|
|
2806
3018
|
}).strict();
|
|
2807
3019
|
function projectionValues2(input) {
|
|
2808
3020
|
return {
|
|
@@ -2967,55 +3179,39 @@ async function recordGitHubPullRequestLinkedIssues(db, input) {
|
|
|
2967
3179
|
}
|
|
2968
3180
|
|
|
2969
3181
|
// src/webhooks/issue-outcome.ts
|
|
2970
|
-
import { z as
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
const domain = email.slice(separator + 1).toLowerCase();
|
|
2980
|
-
if (domain !== GITHUB_NOREPLY_DOMAIN) return void 0;
|
|
2981
|
-
const localPart = email.slice(0, separator);
|
|
2982
|
-
const login = localPart.slice(localPart.indexOf("+") + 1).trim();
|
|
2983
|
-
return login.toLowerCase().endsWith("[bot]") ? login : void 0;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
// src/webhooks/issue-outcome.ts
|
|
2987
|
-
var canonicalIssueOutcomeSchema = z13.object({
|
|
2988
|
-
action: z13.enum(["opened", "closed", "reopened"]),
|
|
2989
|
-
issue: z13.object({
|
|
2990
|
-
body: z13.string().nullable().optional(),
|
|
2991
|
-
closed_at: z13.string().nullable().optional(),
|
|
2992
|
-
created_at: z13.string(),
|
|
2993
|
-
id: z13.number().int().positive(),
|
|
2994
|
-
number: z13.number().int().positive(),
|
|
3182
|
+
import { z as z14 } from "zod";
|
|
3183
|
+
var canonicalIssueOutcomeSchema = z14.object({
|
|
3184
|
+
action: z14.enum(["opened", "closed", "reopened"]),
|
|
3185
|
+
issue: z14.object({
|
|
3186
|
+
body: z14.string().nullable().optional(),
|
|
3187
|
+
closed_at: z14.string().nullable().optional(),
|
|
3188
|
+
created_at: z14.string(),
|
|
3189
|
+
id: z14.number().int().positive(),
|
|
3190
|
+
number: z14.number().int().positive(),
|
|
2995
3191
|
state_reason: githubIssueStateReasonSchema.nullable().optional(),
|
|
2996
|
-
updated_at:
|
|
2997
|
-
user:
|
|
3192
|
+
updated_at: z14.string(),
|
|
3193
|
+
user: z14.object({ login: z14.string().min(1) }).strict()
|
|
2998
3194
|
}).strict(),
|
|
2999
|
-
repository:
|
|
3000
|
-
full_name:
|
|
3001
|
-
id:
|
|
3195
|
+
repository: z14.object({
|
|
3196
|
+
full_name: z14.string().min(1),
|
|
3197
|
+
id: z14.number().int().positive()
|
|
3002
3198
|
}).strict()
|
|
3003
3199
|
}).strict();
|
|
3004
|
-
var issueOutcomeSchema =
|
|
3005
|
-
action:
|
|
3006
|
-
issue:
|
|
3007
|
-
body:
|
|
3008
|
-
closed_at:
|
|
3009
|
-
created_at:
|
|
3010
|
-
id:
|
|
3011
|
-
number:
|
|
3200
|
+
var issueOutcomeSchema = z14.object({
|
|
3201
|
+
action: z14.enum(["opened", "closed", "reopened"]),
|
|
3202
|
+
issue: z14.object({
|
|
3203
|
+
body: z14.string().nullable().optional(),
|
|
3204
|
+
closed_at: z14.string().nullable().optional(),
|
|
3205
|
+
created_at: z14.string(),
|
|
3206
|
+
id: z14.number().int().positive(),
|
|
3207
|
+
number: z14.number().int().positive(),
|
|
3012
3208
|
state_reason: githubIssueStateReasonSchema.nullable().optional(),
|
|
3013
|
-
updated_at:
|
|
3014
|
-
user:
|
|
3209
|
+
updated_at: z14.string(),
|
|
3210
|
+
user: z14.object({ login: z14.string().min(1) }).passthrough()
|
|
3015
3211
|
}).passthrough(),
|
|
3016
|
-
repository:
|
|
3017
|
-
full_name:
|
|
3018
|
-
id:
|
|
3212
|
+
repository: z14.object({
|
|
3213
|
+
full_name: z14.string().min(1),
|
|
3214
|
+
id: z14.number().int().positive()
|
|
3019
3215
|
}).passthrough()
|
|
3020
3216
|
}).passthrough().transform(
|
|
3021
3217
|
(provider) => canonicalIssueOutcomeSchema.parse({
|
|
@@ -3036,7 +3232,7 @@ var issueOutcomeSchema = z13.object({
|
|
|
3036
3232
|
}
|
|
3037
3233
|
})
|
|
3038
3234
|
);
|
|
3039
|
-
var issueLifecycleActionSchema =
|
|
3235
|
+
var issueLifecycleActionSchema = z14.object({ action: z14.string() }).passthrough();
|
|
3040
3236
|
function timestamp2(value) {
|
|
3041
3237
|
if (!value) return void 0;
|
|
3042
3238
|
const parsed = new Date(value);
|
|
@@ -3082,21 +3278,21 @@ function normalizeGitHubIssueOutcome(args) {
|
|
|
3082
3278
|
updatedAt
|
|
3083
3279
|
};
|
|
3084
3280
|
}
|
|
3085
|
-
var canonicalIssueConversationSchema =
|
|
3086
|
-
issue:
|
|
3087
|
-
body:
|
|
3088
|
-
id:
|
|
3089
|
-
user:
|
|
3281
|
+
var canonicalIssueConversationSchema = z14.object({
|
|
3282
|
+
issue: z14.object({
|
|
3283
|
+
body: z14.string().nullable().optional(),
|
|
3284
|
+
id: z14.number().int().positive(),
|
|
3285
|
+
user: z14.object({ login: z14.string().min(1) }).strict()
|
|
3090
3286
|
}).strict(),
|
|
3091
|
-
sender:
|
|
3287
|
+
sender: z14.object({ login: z14.string().min(1) }).strict().optional()
|
|
3092
3288
|
}).strict();
|
|
3093
|
-
var issueConversationSchema =
|
|
3094
|
-
issue:
|
|
3095
|
-
body:
|
|
3096
|
-
id:
|
|
3097
|
-
user:
|
|
3289
|
+
var issueConversationSchema = z14.object({
|
|
3290
|
+
issue: z14.object({
|
|
3291
|
+
body: z14.string().nullable().optional(),
|
|
3292
|
+
id: z14.number().int().positive(),
|
|
3293
|
+
user: z14.object({ login: z14.string().min(1) }).passthrough()
|
|
3098
3294
|
}).passthrough(),
|
|
3099
|
-
sender:
|
|
3295
|
+
sender: z14.object({ login: z14.string().min(1) }).passthrough().optional()
|
|
3100
3296
|
}).passthrough().transform(
|
|
3101
3297
|
(provider) => canonicalIssueConversationSchema.parse({
|
|
3102
3298
|
issue: {
|
|
@@ -3123,41 +3319,41 @@ function normalizeGitHubIssueConversations(args) {
|
|
|
3123
3319
|
}
|
|
3124
3320
|
|
|
3125
3321
|
// src/webhooks/pull-request-outcome.ts
|
|
3126
|
-
import { z as
|
|
3127
|
-
var canonicalPullRequestOutcomeSchema =
|
|
3128
|
-
action:
|
|
3129
|
-
pull_request:
|
|
3130
|
-
body:
|
|
3131
|
-
closed_at:
|
|
3132
|
-
created_at:
|
|
3133
|
-
id:
|
|
3134
|
-
merged:
|
|
3135
|
-
merged_at:
|
|
3136
|
-
number:
|
|
3137
|
-
updated_at:
|
|
3138
|
-
user:
|
|
3322
|
+
import { z as z15 } from "zod";
|
|
3323
|
+
var canonicalPullRequestOutcomeSchema = z15.object({
|
|
3324
|
+
action: z15.enum(["opened", "closed", "reopened"]),
|
|
3325
|
+
pull_request: z15.object({
|
|
3326
|
+
body: z15.string().nullable().optional(),
|
|
3327
|
+
closed_at: z15.string().nullable().optional(),
|
|
3328
|
+
created_at: z15.string(),
|
|
3329
|
+
id: z15.number().int().positive(),
|
|
3330
|
+
merged: z15.boolean(),
|
|
3331
|
+
merged_at: z15.string().nullable().optional(),
|
|
3332
|
+
number: z15.number().int().positive(),
|
|
3333
|
+
updated_at: z15.string(),
|
|
3334
|
+
user: z15.object({ login: z15.string().min(1) }).strict()
|
|
3139
3335
|
}).strict(),
|
|
3140
|
-
repository:
|
|
3141
|
-
full_name:
|
|
3142
|
-
id:
|
|
3336
|
+
repository: z15.object({
|
|
3337
|
+
full_name: z15.string().min(1),
|
|
3338
|
+
id: z15.number().int().positive()
|
|
3143
3339
|
}).strict()
|
|
3144
3340
|
}).strict();
|
|
3145
|
-
var pullRequestOutcomeSchema =
|
|
3146
|
-
action:
|
|
3147
|
-
pull_request:
|
|
3148
|
-
body:
|
|
3149
|
-
closed_at:
|
|
3150
|
-
created_at:
|
|
3151
|
-
id:
|
|
3152
|
-
merged:
|
|
3153
|
-
merged_at:
|
|
3154
|
-
number:
|
|
3155
|
-
updated_at:
|
|
3156
|
-
user:
|
|
3341
|
+
var pullRequestOutcomeSchema = z15.object({
|
|
3342
|
+
action: z15.enum(["opened", "closed", "reopened"]),
|
|
3343
|
+
pull_request: z15.object({
|
|
3344
|
+
body: z15.string().nullable().optional(),
|
|
3345
|
+
closed_at: z15.string().nullable().optional(),
|
|
3346
|
+
created_at: z15.string(),
|
|
3347
|
+
id: z15.number().int().positive(),
|
|
3348
|
+
merged: z15.boolean(),
|
|
3349
|
+
merged_at: z15.string().nullable().optional(),
|
|
3350
|
+
number: z15.number().int().positive(),
|
|
3351
|
+
updated_at: z15.string(),
|
|
3352
|
+
user: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3157
3353
|
}).passthrough(),
|
|
3158
|
-
repository:
|
|
3159
|
-
full_name:
|
|
3160
|
-
id:
|
|
3354
|
+
repository: z15.object({
|
|
3355
|
+
full_name: z15.string().min(1),
|
|
3356
|
+
id: z15.number().int().positive()
|
|
3161
3357
|
}).passthrough()
|
|
3162
3358
|
}).passthrough().transform(
|
|
3163
3359
|
(provider) => canonicalPullRequestOutcomeSchema.parse({
|
|
@@ -3179,24 +3375,24 @@ var pullRequestOutcomeSchema = z14.object({
|
|
|
3179
3375
|
}
|
|
3180
3376
|
})
|
|
3181
3377
|
);
|
|
3182
|
-
var pullRequestLifecycleActionSchema =
|
|
3183
|
-
var canonicalPullRequestConversationSchema =
|
|
3184
|
-
pull_request:
|
|
3185
|
-
body:
|
|
3186
|
-
id:
|
|
3187
|
-
user:
|
|
3378
|
+
var pullRequestLifecycleActionSchema = z15.object({ action: z15.string() }).passthrough();
|
|
3379
|
+
var canonicalPullRequestConversationSchema = z15.object({
|
|
3380
|
+
pull_request: z15.object({
|
|
3381
|
+
body: z15.string().nullable().optional(),
|
|
3382
|
+
id: z15.number().int().positive(),
|
|
3383
|
+
user: z15.object({ login: z15.string().min(1) }).strict()
|
|
3188
3384
|
}).strict(),
|
|
3189
|
-
repository:
|
|
3190
|
-
sender:
|
|
3385
|
+
repository: z15.object({ full_name: z15.string().min(1) }).strict(),
|
|
3386
|
+
sender: z15.object({ login: z15.string().min(1) }).strict()
|
|
3191
3387
|
}).strict();
|
|
3192
|
-
var pullRequestConversationSchema =
|
|
3193
|
-
pull_request:
|
|
3194
|
-
body:
|
|
3195
|
-
id:
|
|
3196
|
-
user:
|
|
3388
|
+
var pullRequestConversationSchema = z15.object({
|
|
3389
|
+
pull_request: z15.object({
|
|
3390
|
+
body: z15.string().nullable().optional(),
|
|
3391
|
+
id: z15.number().int().positive(),
|
|
3392
|
+
user: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3197
3393
|
}).passthrough(),
|
|
3198
|
-
repository:
|
|
3199
|
-
sender:
|
|
3394
|
+
repository: z15.object({ full_name: z15.string().min(1) }).passthrough(),
|
|
3395
|
+
sender: z15.object({ login: z15.string().min(1) }).passthrough()
|
|
3200
3396
|
}).passthrough().transform(
|
|
3201
3397
|
(provider) => canonicalPullRequestConversationSchema.parse({
|
|
3202
3398
|
pull_request: {
|
|
@@ -3436,14 +3632,14 @@ function createGitHubWebhookRoute(args) {
|
|
|
3436
3632
|
|
|
3437
3633
|
// src/outcomes/profile-report.ts
|
|
3438
3634
|
import { sql as sql4 } from "drizzle-orm";
|
|
3439
|
-
import { z as
|
|
3635
|
+
import { z as z16 } from "zod";
|
|
3440
3636
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3441
3637
|
var WINDOWS = [7, 30, 90];
|
|
3442
|
-
var pullRequestStatsSchema =
|
|
3443
|
-
closed:
|
|
3444
|
-
created:
|
|
3445
|
-
days:
|
|
3446
|
-
merged:
|
|
3638
|
+
var pullRequestStatsSchema = z16.object({
|
|
3639
|
+
closed: z16.number().int().nonnegative(),
|
|
3640
|
+
created: z16.number().int().nonnegative(),
|
|
3641
|
+
days: z16.number().int().positive(),
|
|
3642
|
+
merged: z16.number().int().nonnegative()
|
|
3447
3643
|
}).strict().transform((row) => {
|
|
3448
3644
|
const terminal = row.merged + row.closed;
|
|
3449
3645
|
return {
|
|
@@ -3451,13 +3647,13 @@ var pullRequestStatsSchema = z15.object({
|
|
|
3451
3647
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3452
3648
|
};
|
|
3453
3649
|
});
|
|
3454
|
-
var issueStatsSchema =
|
|
3455
|
-
created:
|
|
3456
|
-
days:
|
|
3650
|
+
var issueStatsSchema = z16.object({
|
|
3651
|
+
created: z16.number().int().nonnegative(),
|
|
3652
|
+
days: z16.number().int().positive()
|
|
3457
3653
|
}).strict();
|
|
3458
|
-
var daySchema =
|
|
3459
|
-
created:
|
|
3460
|
-
date:
|
|
3654
|
+
var daySchema = z16.object({
|
|
3655
|
+
created: z16.number().int().nonnegative(),
|
|
3656
|
+
date: z16.string().date()
|
|
3461
3657
|
}).strict();
|
|
3462
3658
|
function queryRows(result) {
|
|
3463
3659
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
@@ -3530,7 +3726,7 @@ async function aggregatePullRequestWindows(args) {
|
|
|
3530
3726
|
GROUP BY windows.days
|
|
3531
3727
|
ORDER BY windows.days
|
|
3532
3728
|
`);
|
|
3533
|
-
return
|
|
3729
|
+
return z16.array(pullRequestStatsSchema).parse(queryRows(result));
|
|
3534
3730
|
}
|
|
3535
3731
|
async function aggregateIssueWindows(args) {
|
|
3536
3732
|
const starts = WINDOWS.map(
|
|
@@ -3563,7 +3759,7 @@ async function aggregateIssueWindows(args) {
|
|
|
3563
3759
|
GROUP BY windows.days
|
|
3564
3760
|
ORDER BY windows.days
|
|
3565
3761
|
`);
|
|
3566
|
-
return
|
|
3762
|
+
return z16.array(issueStatsSchema).parse(queryRows(result));
|
|
3567
3763
|
}
|
|
3568
3764
|
async function aggregateOpenedDays(args) {
|
|
3569
3765
|
const end = new Date(args.nowMs);
|
|
@@ -3593,7 +3789,7 @@ async function aggregateOpenedDays(args) {
|
|
|
3593
3789
|
LEFT JOIN daily ON daily.day = days.day
|
|
3594
3790
|
ORDER BY days.day
|
|
3595
3791
|
`);
|
|
3596
|
-
return
|
|
3792
|
+
return z16.array(daySchema).parse(queryRows(result));
|
|
3597
3793
|
}
|
|
3598
3794
|
async function buildGitHubProfileReport(args) {
|
|
3599
3795
|
const [windows, pullRequestDays, issueWindows, issueDays] = await Promise.all(
|
|
@@ -3674,18 +3870,18 @@ async function buildGitHubProfileReport(args) {
|
|
|
3674
3870
|
|
|
3675
3871
|
// src/outcomes/report.ts
|
|
3676
3872
|
import { sql as sql6 } from "drizzle-orm";
|
|
3677
|
-
import { z as
|
|
3873
|
+
import { z as z18 } from "zod";
|
|
3678
3874
|
|
|
3679
3875
|
// src/outcomes/cost.ts
|
|
3680
3876
|
import { sql as sql5 } from "drizzle-orm";
|
|
3681
|
-
import { z as
|
|
3877
|
+
import { z as z17 } from "zod";
|
|
3682
3878
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
3683
|
-
var costWindowSchema =
|
|
3684
|
-
days:
|
|
3685
|
-
issueCostUsd:
|
|
3686
|
-
medianIssueCostUsd:
|
|
3687
|
-
medianPullRequestCostUsd:
|
|
3688
|
-
pullRequestCostUsd:
|
|
3879
|
+
var costWindowSchema = z17.object({
|
|
3880
|
+
days: z17.number().int().positive(),
|
|
3881
|
+
issueCostUsd: z17.number().nonnegative().nullable(),
|
|
3882
|
+
medianIssueCostUsd: z17.number().nonnegative().nullable(),
|
|
3883
|
+
medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3884
|
+
pullRequestCostUsd: z17.number().nonnegative().nullable()
|
|
3689
3885
|
}).strict().transform((row) => ({
|
|
3690
3886
|
days: row.days,
|
|
3691
3887
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
@@ -3693,12 +3889,12 @@ var costWindowSchema = z16.object({
|
|
|
3693
3889
|
medianPullRequestCostUsd: row.medianPullRequestCostUsd ?? void 0,
|
|
3694
3890
|
pullRequestCostUsd: row.pullRequestCostUsd ?? void 0
|
|
3695
3891
|
}));
|
|
3696
|
-
var repositoryCostSchema =
|
|
3697
|
-
issueCostUsd:
|
|
3698
|
-
medianIssueCostUsd:
|
|
3699
|
-
medianPullRequestCostUsd:
|
|
3700
|
-
pullRequestCostUsd:
|
|
3701
|
-
repository:
|
|
3892
|
+
var repositoryCostSchema = z17.object({
|
|
3893
|
+
issueCostUsd: z17.number().nonnegative().nullable(),
|
|
3894
|
+
medianIssueCostUsd: z17.number().nonnegative().nullable(),
|
|
3895
|
+
medianPullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3896
|
+
pullRequestCostUsd: z17.number().nonnegative().nullable(),
|
|
3897
|
+
repository: z17.string().min(1)
|
|
3702
3898
|
}).strict().transform((row) => ({
|
|
3703
3899
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
3704
3900
|
medianIssueCostUsd: row.medianIssueCostUsd ?? void 0,
|
|
@@ -3903,7 +4099,7 @@ async function aggregateGitHubCostWindows(args) {
|
|
|
3903
4099
|
INNER JOIN issue_window ON issue_window.days = pull_request_window.days
|
|
3904
4100
|
ORDER BY pull_request_window.days
|
|
3905
4101
|
`);
|
|
3906
|
-
return
|
|
4102
|
+
return z17.array(costWindowSchema).parse(queryRows2(result));
|
|
3907
4103
|
}
|
|
3908
4104
|
async function aggregateGitHubRepositoryCosts(args) {
|
|
3909
4105
|
if (!await hasConversationUsageTable(args.db)) {
|
|
@@ -4001,7 +4197,7 @@ async function aggregateGitHubRepositoryCosts(args) {
|
|
|
4001
4197
|
ON issue_totals.repository = repositories.repository
|
|
4002
4198
|
ORDER BY "repository" ASC
|
|
4003
4199
|
`);
|
|
4004
|
-
return
|
|
4200
|
+
return z17.array(repositoryCostSchema).parse(queryRows2(result));
|
|
4005
4201
|
}
|
|
4006
4202
|
function formatCostUsd(value) {
|
|
4007
4203
|
if (value === void 0) return "\u2014";
|
|
@@ -4016,12 +4212,12 @@ function formatCostUsd(value) {
|
|
|
4016
4212
|
// src/outcomes/report.ts
|
|
4017
4213
|
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
4018
4214
|
var WINDOWS2 = [7, 30, 90];
|
|
4019
|
-
var pullRequestStatsSchema2 =
|
|
4020
|
-
closed:
|
|
4021
|
-
created:
|
|
4022
|
-
days:
|
|
4023
|
-
medianMergeTimeMs:
|
|
4024
|
-
merged:
|
|
4215
|
+
var pullRequestStatsSchema2 = z18.object({
|
|
4216
|
+
closed: z18.number().int().nonnegative(),
|
|
4217
|
+
created: z18.number().int().nonnegative(),
|
|
4218
|
+
days: z18.number().int().positive(),
|
|
4219
|
+
medianMergeTimeMs: z18.number().nonnegative().nullable(),
|
|
4220
|
+
merged: z18.number().int().nonnegative()
|
|
4025
4221
|
}).strict().transform((row) => {
|
|
4026
4222
|
const terminal = row.merged + row.closed;
|
|
4027
4223
|
return {
|
|
@@ -4030,12 +4226,12 @@ var pullRequestStatsSchema2 = z17.object({
|
|
|
4030
4226
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
4031
4227
|
};
|
|
4032
4228
|
});
|
|
4033
|
-
var pullRequestRepositoryStatsSchema =
|
|
4034
|
-
closed:
|
|
4035
|
-
created:
|
|
4036
|
-
juniorOnly:
|
|
4037
|
-
merged:
|
|
4038
|
-
repository:
|
|
4229
|
+
var pullRequestRepositoryStatsSchema = z18.object({
|
|
4230
|
+
closed: z18.number().int().nonnegative(),
|
|
4231
|
+
created: z18.number().int().nonnegative(),
|
|
4232
|
+
juniorOnly: z18.number().int().nonnegative(),
|
|
4233
|
+
merged: z18.number().int().nonnegative(),
|
|
4234
|
+
repository: z18.string().min(1)
|
|
4039
4235
|
}).strict().transform((row) => {
|
|
4040
4236
|
const terminal = row.merged + row.closed;
|
|
4041
4237
|
return {
|
|
@@ -4043,33 +4239,33 @@ var pullRequestRepositoryStatsSchema = z17.object({
|
|
|
4043
4239
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
4044
4240
|
};
|
|
4045
4241
|
});
|
|
4046
|
-
var issueStatsSchema2 =
|
|
4047
|
-
closedCompleted:
|
|
4048
|
-
closedDuplicate:
|
|
4049
|
-
closedNotPlanned:
|
|
4050
|
-
closedUnknown:
|
|
4051
|
-
created:
|
|
4052
|
-
days:
|
|
4053
|
-
medianCloseTimeMs:
|
|
4242
|
+
var issueStatsSchema2 = z18.object({
|
|
4243
|
+
closedCompleted: z18.number().int().nonnegative(),
|
|
4244
|
+
closedDuplicate: z18.number().int().nonnegative(),
|
|
4245
|
+
closedNotPlanned: z18.number().int().nonnegative(),
|
|
4246
|
+
closedUnknown: z18.number().int().nonnegative(),
|
|
4247
|
+
created: z18.number().int().nonnegative(),
|
|
4248
|
+
days: z18.number().int().positive(),
|
|
4249
|
+
medianCloseTimeMs: z18.number().nonnegative().nullable()
|
|
4054
4250
|
}).strict().transform((row) => ({
|
|
4055
4251
|
...row,
|
|
4056
4252
|
medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
|
|
4057
4253
|
}));
|
|
4058
|
-
var pullRequestDaySchema =
|
|
4059
|
-
created:
|
|
4060
|
-
date:
|
|
4254
|
+
var pullRequestDaySchema = z18.object({
|
|
4255
|
+
created: z18.number().int().nonnegative(),
|
|
4256
|
+
date: z18.string().date()
|
|
4061
4257
|
}).strict();
|
|
4062
|
-
var issueDaySchema =
|
|
4063
|
-
created:
|
|
4064
|
-
date:
|
|
4258
|
+
var issueDaySchema = z18.object({
|
|
4259
|
+
created: z18.number().int().nonnegative(),
|
|
4260
|
+
date: z18.string().date()
|
|
4065
4261
|
}).strict();
|
|
4066
|
-
var issueRepositoryStatsSchema =
|
|
4067
|
-
closedCompleted:
|
|
4068
|
-
closedDuplicate:
|
|
4069
|
-
closedNotPlanned:
|
|
4070
|
-
closedUnknown:
|
|
4071
|
-
created:
|
|
4072
|
-
repository:
|
|
4262
|
+
var issueRepositoryStatsSchema = z18.object({
|
|
4263
|
+
closedCompleted: z18.number().int().nonnegative(),
|
|
4264
|
+
closedDuplicate: z18.number().int().nonnegative(),
|
|
4265
|
+
closedNotPlanned: z18.number().int().nonnegative(),
|
|
4266
|
+
closedUnknown: z18.number().int().nonnegative(),
|
|
4267
|
+
created: z18.number().int().nonnegative(),
|
|
4268
|
+
repository: z18.string().min(1)
|
|
4073
4269
|
}).strict();
|
|
4074
4270
|
function queryRows3(result) {
|
|
4075
4271
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
@@ -4133,7 +4329,7 @@ async function aggregatePullRequestWindows2(args) {
|
|
|
4133
4329
|
GROUP BY windows.days
|
|
4134
4330
|
ORDER BY windows.days
|
|
4135
4331
|
`);
|
|
4136
|
-
return
|
|
4332
|
+
return z18.array(pullRequestStatsSchema2).parse(queryRows3(result));
|
|
4137
4333
|
}
|
|
4138
4334
|
async function aggregatePullRequestDays(args) {
|
|
4139
4335
|
const end = new Date(args.nowMs);
|
|
@@ -4161,7 +4357,7 @@ async function aggregatePullRequestDays(args) {
|
|
|
4161
4357
|
LEFT JOIN daily ON daily.day = days.day
|
|
4162
4358
|
ORDER BY days.day
|
|
4163
4359
|
`);
|
|
4164
|
-
return
|
|
4360
|
+
return z18.array(pullRequestDaySchema).parse(queryRows3(result));
|
|
4165
4361
|
}
|
|
4166
4362
|
async function aggregatePullRequestRepositories(args) {
|
|
4167
4363
|
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
@@ -4191,7 +4387,7 @@ async function aggregatePullRequestRepositories(args) {
|
|
|
4191
4387
|
ORDER BY "merged" DESC, "created" DESC, "repository" ASC
|
|
4192
4388
|
LIMIT 25
|
|
4193
4389
|
`);
|
|
4194
|
-
return
|
|
4390
|
+
return z18.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
|
|
4195
4391
|
}
|
|
4196
4392
|
async function aggregateIssueWindows2(args) {
|
|
4197
4393
|
const starts = WINDOWS2.map(
|
|
@@ -4260,7 +4456,7 @@ async function aggregateIssueWindows2(args) {
|
|
|
4260
4456
|
GROUP BY windows.days
|
|
4261
4457
|
ORDER BY windows.days
|
|
4262
4458
|
`);
|
|
4263
|
-
return
|
|
4459
|
+
return z18.array(issueStatsSchema2).parse(queryRows3(result));
|
|
4264
4460
|
}
|
|
4265
4461
|
async function aggregateIssueDays(args) {
|
|
4266
4462
|
const end = new Date(args.nowMs);
|
|
@@ -4288,7 +4484,7 @@ async function aggregateIssueDays(args) {
|
|
|
4288
4484
|
LEFT JOIN daily ON daily.day = days.day
|
|
4289
4485
|
ORDER BY days.day
|
|
4290
4486
|
`);
|
|
4291
|
-
return
|
|
4487
|
+
return z18.array(issueDaySchema).parse(queryRows3(result));
|
|
4292
4488
|
}
|
|
4293
4489
|
async function aggregateIssueRepositories(args) {
|
|
4294
4490
|
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
@@ -4325,7 +4521,7 @@ async function aggregateIssueRepositories(args) {
|
|
|
4325
4521
|
ORDER BY "created" DESC, "closedCompleted" DESC, "repository" ASC
|
|
4326
4522
|
LIMIT 25
|
|
4327
4523
|
`);
|
|
4328
|
-
return
|
|
4524
|
+
return z18.array(issueRepositoryStatsSchema).parse(queryRows3(result));
|
|
4329
4525
|
}
|
|
4330
4526
|
function formatPercent2(value) {
|
|
4331
4527
|
return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
|
|
@@ -4489,18 +4685,18 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4489
4685
|
}
|
|
4490
4686
|
|
|
4491
4687
|
// src/pull-request-outcomes/commit-composition.ts
|
|
4492
|
-
import { z as
|
|
4493
|
-
var canonicalCommitSchema =
|
|
4494
|
-
authorEmail:
|
|
4495
|
-
authorLogin:
|
|
4688
|
+
import { z as z19 } from "zod";
|
|
4689
|
+
var canonicalCommitSchema = z19.object({
|
|
4690
|
+
authorEmail: z19.string().nullable(),
|
|
4691
|
+
authorLogin: z19.string().nullable()
|
|
4496
4692
|
}).strict();
|
|
4497
|
-
var providerCommitSchema =
|
|
4498
|
-
author:
|
|
4499
|
-
commit:
|
|
4500
|
-
author:
|
|
4693
|
+
var providerCommitSchema = z19.object({
|
|
4694
|
+
author: z19.object({ login: z19.string() }).passthrough().nullable(),
|
|
4695
|
+
commit: z19.object({
|
|
4696
|
+
author: z19.object({ email: z19.string() }).passthrough().nullable()
|
|
4501
4697
|
}).passthrough()
|
|
4502
4698
|
}).passthrough();
|
|
4503
|
-
var commitPageSchema =
|
|
4699
|
+
var commitPageSchema = z19.array(providerCommitSchema).transform(
|
|
4504
4700
|
(commits) => commits.map(
|
|
4505
4701
|
(commit) => canonicalCommitSchema.parse({
|
|
4506
4702
|
authorEmail: commit.commit.author?.email ?? null,
|
|
@@ -5235,6 +5431,28 @@ function githubApiWriteGrantName(method, upstreamUrl) {
|
|
|
5235
5431
|
}
|
|
5236
5432
|
return void 0;
|
|
5237
5433
|
}
|
|
5434
|
+
function reviewThreadResolveRepository(operation, method, upstreamUrl, bodyText) {
|
|
5435
|
+
const prefix = "github.pull.review-thread.resolve:";
|
|
5436
|
+
if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl) || !operation?.startsWith(prefix)) {
|
|
5437
|
+
return void 0;
|
|
5438
|
+
}
|
|
5439
|
+
const repository = operation.slice(prefix.length);
|
|
5440
|
+
if (!/^[^/]+\/[^/]+$/.test(repository)) return void 0;
|
|
5441
|
+
const parsed = parseGitHubGraphqlRequest(bodyText);
|
|
5442
|
+
if (parsed?.operationName !== "ResolveReviewThread" || !/\bmutation\s+ResolveReviewThread\b/.test(parsed.normalized) || !/\bresolveReviewThread\b/.test(parsed.normalized)) {
|
|
5443
|
+
return void 0;
|
|
5444
|
+
}
|
|
5445
|
+
return repository;
|
|
5446
|
+
}
|
|
5447
|
+
function repositoryLeaseScopeFromRef(repository) {
|
|
5448
|
+
const [owner, name] = repository.split("/");
|
|
5449
|
+
if (!owner || !name) {
|
|
5450
|
+
throw new EgressPolicyDenied2(
|
|
5451
|
+
"GitHub review thread resolution does not identify a target repository."
|
|
5452
|
+
);
|
|
5453
|
+
}
|
|
5454
|
+
return githubRepositoryLeaseScope({ owner, name });
|
|
5455
|
+
}
|
|
5238
5456
|
function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
|
|
5239
5457
|
if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
|
|
5240
5458
|
const parsed = parseGitHubGraphqlRequest(bodyText);
|
|
@@ -5326,6 +5544,20 @@ async function githubGrantForEgress(ctx) {
|
|
|
5326
5544
|
repositoryLeaseScope(upstreamUrl)
|
|
5327
5545
|
);
|
|
5328
5546
|
}
|
|
5547
|
+
const reviewThreadRepository = reviewThreadResolveRepository(
|
|
5548
|
+
ctx.request.operation,
|
|
5549
|
+
method,
|
|
5550
|
+
upstreamUrl,
|
|
5551
|
+
ctx.request.bodyText
|
|
5552
|
+
);
|
|
5553
|
+
if (reviewThreadRepository) {
|
|
5554
|
+
return grantForAccess(
|
|
5555
|
+
"write",
|
|
5556
|
+
"github.installation-write",
|
|
5557
|
+
"installation-write",
|
|
5558
|
+
repositoryLeaseScopeFromRef(reviewThreadRepository)
|
|
5559
|
+
);
|
|
5560
|
+
}
|
|
5329
5561
|
const graphqlAccess = githubGraphqlAccess(
|
|
5330
5562
|
method,
|
|
5331
5563
|
upstreamUrl,
|
|
@@ -5544,7 +5776,7 @@ function githubPlugin(options = {}) {
|
|
|
5544
5776
|
});
|
|
5545
5777
|
},
|
|
5546
5778
|
tools(ctx) {
|
|
5547
|
-
return createGitHubTools(ctx);
|
|
5779
|
+
return createGitHubTools(ctx, readEnv(botEmailEnv));
|
|
5548
5780
|
},
|
|
5549
5781
|
workspacePrepare: prepareWorkspace,
|
|
5550
5782
|
async sandboxPrepare(ctx) {
|
|
@@ -5563,11 +5795,8 @@ function githubPlugin(options = {}) {
|
|
|
5563
5795
|
if (ctx.tool.name !== "bash") {
|
|
5564
5796
|
return;
|
|
5565
5797
|
}
|
|
5566
|
-
const botName =
|
|
5567
|
-
const botEmail =
|
|
5568
|
-
if (!botName || !botEmail) {
|
|
5569
|
-
return;
|
|
5570
|
-
}
|
|
5798
|
+
const botName = requireEnv(botNameEnv);
|
|
5799
|
+
const botEmail = requireEnv(botEmailEnv);
|
|
5571
5800
|
ctx.env.set("GIT_AUTHOR_NAME", botName);
|
|
5572
5801
|
ctx.env.set("GIT_AUTHOR_EMAIL", botEmail);
|
|
5573
5802
|
ctx.env.set("JUNIOR_GIT_AUTHOR_NAME", botName);
|
|
@@ -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.165.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.165.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. |
|