@sentry/junior-github 0.158.0 → 0.160.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 +83 -81
- package/dist/issue-outcomes/store.d.ts +6 -1
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +2 -0
package/dist/index.js
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
// src/plugin.ts
|
|
21
21
|
import {
|
|
22
22
|
defineJuniorPlugin,
|
|
23
|
-
EgressPolicyDenied as EgressPolicyDenied2
|
|
23
|
+
EgressPolicyDenied as EgressPolicyDenied2,
|
|
24
|
+
enforceEgressPolicy
|
|
24
25
|
} from "@sentry/junior-plugin-api";
|
|
25
26
|
|
|
26
27
|
// src/permissions.ts
|
|
@@ -2694,19 +2695,30 @@ async function recordGitHubIssueOutcome(db, input) {
|
|
|
2694
2695
|
const outcome = githubIssueOutcomeInputSchema.parse(input);
|
|
2695
2696
|
const values = projectionValues(outcome);
|
|
2696
2697
|
if (!outcome.candidateOwned) {
|
|
2697
|
-
await db.update(juniorGitHubIssues).set(values).where(
|
|
2698
|
+
const updated = await db.update(juniorGitHubIssues).set(values).where(
|
|
2698
2699
|
and(
|
|
2699
2700
|
eq(juniorGitHubIssues.issueId, outcome.issueId),
|
|
2700
2701
|
lte(juniorGitHubIssues.updatedAt, outcome.updatedAt)
|
|
2701
2702
|
)
|
|
2702
|
-
)
|
|
2703
|
-
|
|
2703
|
+
).returning({
|
|
2704
|
+
conversationIds: juniorGitHubIssues.conversationIds
|
|
2705
|
+
});
|
|
2706
|
+
return {
|
|
2707
|
+
applied: updated.length > 0,
|
|
2708
|
+
conversationIds: updated[0]?.conversationIds ?? []
|
|
2709
|
+
};
|
|
2704
2710
|
}
|
|
2705
|
-
await db.insert(juniorGitHubIssues).values({ issueId: outcome.issueId, ...values }).onConflictDoUpdate({
|
|
2711
|
+
const inserted = await db.insert(juniorGitHubIssues).values({ issueId: outcome.issueId, ...values }).onConflictDoUpdate({
|
|
2706
2712
|
target: juniorGitHubIssues.issueId,
|
|
2707
2713
|
set: values,
|
|
2708
2714
|
where: lte(juniorGitHubIssues.updatedAt, outcome.updatedAt)
|
|
2715
|
+
}).returning({
|
|
2716
|
+
conversationIds: juniorGitHubIssues.conversationIds
|
|
2709
2717
|
});
|
|
2718
|
+
return {
|
|
2719
|
+
applied: inserted.length > 0,
|
|
2720
|
+
conversationIds: inserted[0]?.conversationIds ?? []
|
|
2721
|
+
};
|
|
2710
2722
|
}
|
|
2711
2723
|
async function recordGitHubIssueConversations(db, input) {
|
|
2712
2724
|
const association = githubIssueConversationsInputSchema.parse(input);
|
|
@@ -3341,7 +3353,23 @@ function createGitHubWebhookRoute(args) {
|
|
|
3341
3353
|
}
|
|
3342
3354
|
}
|
|
3343
3355
|
if (issueOutcome) {
|
|
3344
|
-
await recordGitHubIssueOutcome(
|
|
3356
|
+
const recordedOutcome = await recordGitHubIssueOutcome(
|
|
3357
|
+
args.db,
|
|
3358
|
+
issueOutcome
|
|
3359
|
+
);
|
|
3360
|
+
if (recordedOutcome.applied && issueOutcome.state === "closed") {
|
|
3361
|
+
await Promise.all(
|
|
3362
|
+
recordedOutcome.conversationIds.map(
|
|
3363
|
+
(conversationId) => args.annotations.forConversation(conversationId).upsert({
|
|
3364
|
+
kind: "resource_link",
|
|
3365
|
+
key: `${issueOutcome.repositoryFullName.toLowerCase()}#${issueOutcome.number}`,
|
|
3366
|
+
label: `${issueOutcome.repositoryFullName}#${issueOutcome.number}`,
|
|
3367
|
+
url: `https://github.com/${issueOutcome.repositoryFullName}/issues/${issueOutcome.number}`,
|
|
3368
|
+
status: "closed"
|
|
3369
|
+
})
|
|
3370
|
+
)
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3345
3373
|
}
|
|
3346
3374
|
const recordedIssueConversations = issueConversations ? await recordGitHubIssueConversations(args.db, issueConversations) : false;
|
|
3347
3375
|
const recordedPullRequestConversations = pullRequestConversations ? await recordGitHubPullRequestConversations(
|
|
@@ -4638,6 +4666,36 @@ function shouldInspectGitHubGraphqlResponse(ctx) {
|
|
|
4638
4666
|
const contentType = ctx.response.headers.get("content-type");
|
|
4639
4667
|
return contentType ? /\bjson\b/i.test(contentType) : false;
|
|
4640
4668
|
}
|
|
4669
|
+
var GITHUB_BODY_WRITES = [
|
|
4670
|
+
{
|
|
4671
|
+
denialMessage: `GitHub issue creation must use the github_createIssue tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
|
|
4672
|
+
graphqlField: "createIssue",
|
|
4673
|
+
method: "POST",
|
|
4674
|
+
operation: "github.issue.create",
|
|
4675
|
+
restPath: /^\/repos\/[^/]+\/[^/]+\/issues$/
|
|
4676
|
+
},
|
|
4677
|
+
{
|
|
4678
|
+
denialMessage: `GitHub pull request creation must use the github_createPullRequest tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
|
|
4679
|
+
graphqlField: "createPullRequest",
|
|
4680
|
+
method: "POST",
|
|
4681
|
+
operation: "github.pull.create",
|
|
4682
|
+
restPath: /^\/repos\/[^/]+\/[^/]+\/pulls$/
|
|
4683
|
+
},
|
|
4684
|
+
{
|
|
4685
|
+
denialMessage: `GitHub issue updates must use the github_updateIssue tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
|
|
4686
|
+
graphqlField: "updateIssue",
|
|
4687
|
+
method: "PATCH",
|
|
4688
|
+
operation: "github.issue.update",
|
|
4689
|
+
restPath: /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/
|
|
4690
|
+
},
|
|
4691
|
+
{
|
|
4692
|
+
denialMessage: `GitHub pull request updates must use the github_updatePullRequest tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`,
|
|
4693
|
+
graphqlField: "updatePullRequest",
|
|
4694
|
+
method: "PATCH",
|
|
4695
|
+
operation: "github.pull.update",
|
|
4696
|
+
restPath: /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/
|
|
4697
|
+
}
|
|
4698
|
+
];
|
|
4641
4699
|
function githubApiWriteGrantName(method, upstreamUrl) {
|
|
4642
4700
|
const pathname = upstreamUrl.pathname.toLowerCase();
|
|
4643
4701
|
if (!isGitHubApiUrl2(upstreamUrl)) {
|
|
@@ -4653,26 +4711,19 @@ function githubApiWriteGrantName(method, upstreamUrl) {
|
|
|
4653
4711
|
) || /^\/repos\/[^/]+\/[^/]+\/actions\/jobs\/[^/]+\/rerun$/.test(pathname))) {
|
|
4654
4712
|
return "installation-write";
|
|
4655
4713
|
}
|
|
4656
|
-
if (
|
|
4714
|
+
if (GITHUB_BODY_WRITES.some(
|
|
4715
|
+
(write) => write.method === method && write.restPath.test(pathname)
|
|
4716
|
+
)) {
|
|
4657
4717
|
return "installation-write";
|
|
4658
4718
|
}
|
|
4659
4719
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/comments$/.test(pathname)) {
|
|
4660
4720
|
return "installation-write";
|
|
4661
4721
|
}
|
|
4662
|
-
if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+$/.test(pathname)) {
|
|
4663
|
-
return "installation-write";
|
|
4664
|
-
}
|
|
4665
4722
|
if ((method === "POST" || method === "DELETE") && /^\/repos\/[^/]+\/[^/]+\/issues\/[^/]+\/(labels|assignees)(?:\/[^/]+)?$/.test(
|
|
4666
4723
|
pathname
|
|
4667
4724
|
)) {
|
|
4668
4725
|
return "installation-write";
|
|
4669
4726
|
}
|
|
4670
|
-
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(pathname)) {
|
|
4671
|
-
return "installation-write";
|
|
4672
|
-
}
|
|
4673
|
-
if (method === "PATCH" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+$/.test(pathname)) {
|
|
4674
|
-
return "installation-write";
|
|
4675
|
-
}
|
|
4676
4727
|
if (method === "POST" && /^\/repos\/[^/]+\/[^/]+\/pulls\/[^/]+\/ready_for_review$/.test(pathname)) {
|
|
4677
4728
|
return "installation-write";
|
|
4678
4729
|
}
|
|
@@ -4694,17 +4745,6 @@ function githubApiWriteGrantName(method, upstreamUrl) {
|
|
|
4694
4745
|
}
|
|
4695
4746
|
return void 0;
|
|
4696
4747
|
}
|
|
4697
|
-
function isGitHubIssueCreateRestRequest(method, upstreamUrl) {
|
|
4698
|
-
return method === "POST" && isGitHubApiUrl2(upstreamUrl) && /^\/repos\/[^/]+\/[^/]+\/issues$/.test(upstreamUrl.pathname.toLowerCase());
|
|
4699
|
-
}
|
|
4700
|
-
function isGitHubPullCreateRestRequest(method, upstreamUrl) {
|
|
4701
|
-
return method === "POST" && isGitHubApiUrl2(upstreamUrl) && /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(upstreamUrl.pathname.toLowerCase());
|
|
4702
|
-
}
|
|
4703
|
-
function isGitHubResourceUpdateRestRequest(method, upstreamUrl, resource) {
|
|
4704
|
-
return method === "PATCH" && isGitHubApiUrl2(upstreamUrl) && new RegExp(`^/repos/[^/]+/[^/]+/${resource}/[^/]+$`).test(
|
|
4705
|
-
upstreamUrl.pathname.toLowerCase()
|
|
4706
|
-
);
|
|
4707
|
-
}
|
|
4708
4748
|
function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
|
|
4709
4749
|
if (method !== "POST" || !isGitHubGraphqlUrl(upstreamUrl)) return false;
|
|
4710
4750
|
const parsed = parseGitHubGraphqlRequest(bodyText);
|
|
@@ -4716,64 +4756,26 @@ function isGitHubGraphqlMutation(method, upstreamUrl, bodyText, field) {
|
|
|
4716
4756
|
`\\bmutation\\s+${escapeRegExp2(parsed.operationName)}\\b`
|
|
4717
4757
|
).test(parsed.normalized);
|
|
4718
4758
|
}
|
|
4719
|
-
function
|
|
4759
|
+
function applyGitHubEgressPolicy(input) {
|
|
4720
4760
|
assertGitHubPullRequestApprovalDenied({
|
|
4721
4761
|
...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
|
|
4722
4762
|
method: input.method,
|
|
4723
4763
|
upstreamUrl: input.upstreamUrl
|
|
4724
4764
|
});
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
)
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
}
|
|
4739
|
-
if (isGitHubPullCreateRestRequest(input.method, input.upstreamUrl) || isGitHubGraphqlMutation(
|
|
4740
|
-
input.method,
|
|
4741
|
-
input.upstreamUrl,
|
|
4742
|
-
input.bodyText,
|
|
4743
|
-
"createPullRequest"
|
|
4744
|
-
)) {
|
|
4745
|
-
throw new EgressPolicyDenied2(
|
|
4746
|
-
`GitHub pull request creation must use the github_createPullRequest tool so Junior can own idempotency and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
|
|
4747
|
-
);
|
|
4748
|
-
}
|
|
4749
|
-
if (isGitHubResourceUpdateRestRequest(
|
|
4750
|
-
input.method,
|
|
4751
|
-
input.upstreamUrl,
|
|
4752
|
-
"issues"
|
|
4753
|
-
) || isGitHubGraphqlMutation(
|
|
4754
|
-
input.method,
|
|
4755
|
-
input.upstreamUrl,
|
|
4756
|
-
input.bodyText,
|
|
4757
|
-
"updateIssue"
|
|
4758
|
-
)) {
|
|
4759
|
-
throw new EgressPolicyDenied2(
|
|
4760
|
-
`GitHub issue updates must use the github_updateIssue tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
|
|
4761
|
-
);
|
|
4762
|
-
}
|
|
4763
|
-
if (isGitHubResourceUpdateRestRequest(
|
|
4764
|
-
input.method,
|
|
4765
|
-
input.upstreamUrl,
|
|
4766
|
-
"pulls"
|
|
4767
|
-
) || isGitHubGraphqlMutation(
|
|
4768
|
-
input.method,
|
|
4769
|
-
input.upstreamUrl,
|
|
4770
|
-
input.bodyText,
|
|
4771
|
-
"updatePullRequest"
|
|
4772
|
-
)) {
|
|
4773
|
-
throw new EgressPolicyDenied2(
|
|
4774
|
-
`GitHub pull request updates must use the github_updatePullRequest tool so Junior can own requester attribution and the conversation footer. ${CREATE_TOOL_ROUTING_GUIDANCE}`
|
|
4775
|
-
);
|
|
4776
|
-
}
|
|
4765
|
+
const pathname = input.upstreamUrl.pathname.toLowerCase();
|
|
4766
|
+
const write = GITHUB_BODY_WRITES.find(
|
|
4767
|
+
(candidate) => candidate.method === input.method && isGitHubApiUrl2(input.upstreamUrl) && candidate.restPath.test(pathname) || isGitHubGraphqlMutation(
|
|
4768
|
+
input.method,
|
|
4769
|
+
input.upstreamUrl,
|
|
4770
|
+
input.bodyText,
|
|
4771
|
+
candidate.graphqlField
|
|
4772
|
+
)
|
|
4773
|
+
);
|
|
4774
|
+
if (!write) return;
|
|
4775
|
+
enforceEgressPolicy({
|
|
4776
|
+
allowed: input.operation === write.operation,
|
|
4777
|
+
denialMessage: write.denialMessage
|
|
4778
|
+
});
|
|
4777
4779
|
}
|
|
4778
4780
|
function grantForAccess(access, reason, name, leaseScope) {
|
|
4779
4781
|
return {
|
|
@@ -4796,7 +4798,7 @@ function repositoryLeaseScope(upstreamUrl) {
|
|
|
4796
4798
|
async function githubGrantForEgress(ctx) {
|
|
4797
4799
|
const method = ctx.request.method.toUpperCase();
|
|
4798
4800
|
const upstreamUrl = new URL(ctx.request.url);
|
|
4799
|
-
|
|
4801
|
+
applyGitHubEgressPolicy({
|
|
4800
4802
|
...ctx.request.bodyText !== void 0 ? { bodyText: ctx.request.bodyText } : {},
|
|
4801
4803
|
method,
|
|
4802
4804
|
...ctx.request.operation ? { operation: ctx.request.operation } : {},
|
|
@@ -30,8 +30,13 @@ export type GitHubIssueConversationsInput = z.output<typeof githubIssueConversat
|
|
|
30
30
|
* Record the newest lifecycle projection for one Junior-owned issue.
|
|
31
31
|
* Ownership-qualified opening or closing events insert; later events update
|
|
32
32
|
* existing rows, and older provider timestamps cannot regress them.
|
|
33
|
+
* Returns the written row state so follow-up annotation updates stay scoped to
|
|
34
|
+
* associated conversations.
|
|
33
35
|
*/
|
|
34
|
-
export declare function recordGitHubIssueOutcome(db: GitHubDb, input: GitHubIssueOutcomeInput): Promise<
|
|
36
|
+
export declare function recordGitHubIssueOutcome(db: GitHubDb, input: GitHubIssueOutcomeInput): Promise<{
|
|
37
|
+
applied: boolean;
|
|
38
|
+
conversationIds: string[];
|
|
39
|
+
}>;
|
|
35
40
|
/** Append native conversation ids to an existing Junior-owned issue projection. */
|
|
36
41
|
export declare function recordGitHubIssueConversations(db: GitHubDb, input: GitHubIssueConversationsInput): Promise<boolean>;
|
|
37
42
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.160.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.160.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.9.1",
|
|
@@ -81,3 +81,5 @@ If PR creation or update is blocked, report the exact failed command/tool call a
|
|
|
81
81
|
When PR creation returns a subscribable resource hint, subscribe to suggested review/CI events. Report only actionable feedback addressed, build failures fixed, fully green/ready state, or merge.
|
|
82
82
|
|
|
83
83
|
Return: repo, branch, PR URL/number, checks and results, pre-existing failures, and anything not run with the reason.
|
|
84
|
+
|
|
85
|
+
When you mention a pull request in a user-facing reply, always include a direct link. Prefer the full PR URL or a Markdown link such as `[#123](https://github.com/owner/repo/pull/123)`. `owner/repo#number` is also fine. Do not leave bare `PR #123` text without a URL or repo.
|