@sentry/junior-github 0.160.0 → 0.162.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/README.md +2 -0
- package/dist/annotations.d.ts +3 -0
- package/dist/index.js +526 -107
- package/dist/outcomes/profile-report.d.ts +11 -0
- package/dist/reply-markdown.d.ts +2 -0
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +0 -2
- package/skills/github-code/references/api-surface.md +1 -0
- package/skills/github-issues/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -29,3 +29,5 @@ The plugin owns its signed webhook route, deployment, pull request, and release
|
|
|
29
29
|
resource events, normalized pull request and issue outcome projections, and
|
|
30
30
|
dashboard operational report. Core only owns delivery from plugin-published
|
|
31
31
|
resource events into matching conversation subscriptions.
|
|
32
|
+
|
|
33
|
+
Person profiles call `hooks.profileReport` for Junior-owned GitHub work attributed through conversation actors.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ConversationAnnotation, ConversationSidebarAnnotation } from "@sentry/junior-plugin-api";
|
|
2
|
+
/** Return GitHub annotations for a conversation row, newest first. */
|
|
3
|
+
export declare function githubSidebarAnnotations(annotations: ConversationAnnotation[]): ConversationSidebarAnnotation[];
|
package/dist/index.js
CHANGED
|
@@ -3398,20 +3398,258 @@ function createGitHubWebhookRoute(args) {
|
|
|
3398
3398
|
};
|
|
3399
3399
|
}
|
|
3400
3400
|
|
|
3401
|
-
// src/outcomes/report.ts
|
|
3402
|
-
import { sql as sql5 } from "drizzle-orm";
|
|
3403
|
-
import { z as z16 } from "zod";
|
|
3404
|
-
|
|
3405
|
-
// src/outcomes/cost.ts
|
|
3401
|
+
// src/outcomes/profile-report.ts
|
|
3406
3402
|
import { sql as sql4 } from "drizzle-orm";
|
|
3407
3403
|
import { z as z15 } from "zod";
|
|
3408
3404
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3409
|
-
var
|
|
3405
|
+
var WINDOWS = [7, 30, 90];
|
|
3406
|
+
var pullRequestStatsSchema = z15.object({
|
|
3407
|
+
closed: z15.number().int().nonnegative(),
|
|
3408
|
+
created: z15.number().int().nonnegative(),
|
|
3410
3409
|
days: z15.number().int().positive(),
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3410
|
+
merged: z15.number().int().nonnegative()
|
|
3411
|
+
}).strict().transform((row) => {
|
|
3412
|
+
const terminal = row.merged + row.closed;
|
|
3413
|
+
return {
|
|
3414
|
+
...row,
|
|
3415
|
+
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3416
|
+
};
|
|
3417
|
+
});
|
|
3418
|
+
var issueStatsSchema = z15.object({
|
|
3419
|
+
created: z15.number().int().nonnegative(),
|
|
3420
|
+
days: z15.number().int().positive()
|
|
3421
|
+
}).strict();
|
|
3422
|
+
var daySchema = z15.object({
|
|
3423
|
+
created: z15.number().int().nonnegative(),
|
|
3424
|
+
date: z15.string().date()
|
|
3425
|
+
}).strict();
|
|
3426
|
+
function queryRows(result) {
|
|
3427
|
+
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
3428
|
+
throw new TypeError("GitHub profile report query did not return rows");
|
|
3429
|
+
}
|
|
3430
|
+
return result.rows;
|
|
3431
|
+
}
|
|
3432
|
+
function ownedByUserSql(conversationIds, userId) {
|
|
3433
|
+
return sql4`EXISTS (
|
|
3434
|
+
SELECT 1
|
|
3435
|
+
FROM unnest(${conversationIds}) AS linked(conversation_id)
|
|
3436
|
+
INNER JOIN junior_conversations AS conversations
|
|
3437
|
+
ON conversations.conversation_id = linked.conversation_id
|
|
3438
|
+
INNER JOIN junior_identities AS identities
|
|
3439
|
+
ON identities.id = conversations.actor_identity_id
|
|
3440
|
+
WHERE identities.user_id = ${userId}
|
|
3441
|
+
)`;
|
|
3442
|
+
}
|
|
3443
|
+
function formatPercent(value) {
|
|
3444
|
+
return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
|
|
3445
|
+
}
|
|
3446
|
+
function startOfUtcDay(timestampMs) {
|
|
3447
|
+
const date = new Date(timestampMs);
|
|
3448
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
3449
|
+
return date;
|
|
3450
|
+
}
|
|
3451
|
+
async function aggregatePullRequestWindows(args) {
|
|
3452
|
+
const starts = WINDOWS.map(
|
|
3453
|
+
(days) => [days, new Date(args.nowMs - days * DAY_MS)]
|
|
3454
|
+
);
|
|
3455
|
+
const oldestStart = starts.at(-1)[1];
|
|
3456
|
+
const table = juniorGitHubPullRequests;
|
|
3457
|
+
const owned = ownedByUserSql(sql4`${table.conversationIds}`, args.userId);
|
|
3458
|
+
const result = await args.db.execute(sql4`
|
|
3459
|
+
WITH windows(days, start_at) AS (
|
|
3460
|
+
VALUES
|
|
3461
|
+
(${starts[0][0]}::integer, ${starts[0][1]}::timestamptz),
|
|
3462
|
+
(${starts[1][0]}::integer, ${starts[1][1]}::timestamptz),
|
|
3463
|
+
(${starts[2][0]}::integer, ${starts[2][1]}::timestamptz)
|
|
3464
|
+
), recent_pull_requests AS MATERIALIZED (
|
|
3465
|
+
SELECT
|
|
3466
|
+
${table.pullRequestId},
|
|
3467
|
+
${table.state},
|
|
3468
|
+
${table.openedAt},
|
|
3469
|
+
${table.mergedAt},
|
|
3470
|
+
${table.closedAt}
|
|
3471
|
+
FROM ${table}
|
|
3472
|
+
WHERE (${table.openedAt} >= ${oldestStart}
|
|
3473
|
+
OR ${table.mergedAt} >= ${oldestStart}
|
|
3474
|
+
OR ${table.closedAt} >= ${oldestStart})
|
|
3475
|
+
AND ${owned}
|
|
3476
|
+
)
|
|
3477
|
+
SELECT
|
|
3478
|
+
windows.days AS "days",
|
|
3479
|
+
count(recent_pull_requests.pull_request_id)
|
|
3480
|
+
FILTER (WHERE recent_pull_requests.opened_at >= windows.start_at)::integer
|
|
3481
|
+
AS "created",
|
|
3482
|
+
count(recent_pull_requests.pull_request_id)
|
|
3483
|
+
FILTER (
|
|
3484
|
+
WHERE recent_pull_requests.state = 'merged'
|
|
3485
|
+
AND recent_pull_requests.merged_at >= windows.start_at
|
|
3486
|
+
)::integer AS "merged",
|
|
3487
|
+
count(recent_pull_requests.pull_request_id)
|
|
3488
|
+
FILTER (
|
|
3489
|
+
WHERE recent_pull_requests.state = 'closed_unmerged'
|
|
3490
|
+
AND recent_pull_requests.closed_at >= windows.start_at
|
|
3491
|
+
)::integer AS "closed"
|
|
3492
|
+
FROM windows
|
|
3493
|
+
LEFT JOIN recent_pull_requests ON true
|
|
3494
|
+
GROUP BY windows.days
|
|
3495
|
+
ORDER BY windows.days
|
|
3496
|
+
`);
|
|
3497
|
+
return z15.array(pullRequestStatsSchema).parse(queryRows(result));
|
|
3498
|
+
}
|
|
3499
|
+
async function aggregateIssueWindows(args) {
|
|
3500
|
+
const starts = WINDOWS.map(
|
|
3501
|
+
(days) => [days, new Date(args.nowMs - days * DAY_MS)]
|
|
3502
|
+
);
|
|
3503
|
+
const oldestStart = starts.at(-1)[1];
|
|
3504
|
+
const table = juniorGitHubIssues;
|
|
3505
|
+
const owned = ownedByUserSql(sql4`${table.conversationIds}`, args.userId);
|
|
3506
|
+
const result = await args.db.execute(sql4`
|
|
3507
|
+
WITH windows(days, start_at) AS (
|
|
3508
|
+
VALUES
|
|
3509
|
+
(${starts[0][0]}::integer, ${starts[0][1]}::timestamptz),
|
|
3510
|
+
(${starts[1][0]}::integer, ${starts[1][1]}::timestamptz),
|
|
3511
|
+
(${starts[2][0]}::integer, ${starts[2][1]}::timestamptz)
|
|
3512
|
+
), recent_issues AS MATERIALIZED (
|
|
3513
|
+
SELECT
|
|
3514
|
+
${table.issueId},
|
|
3515
|
+
${table.openedAt}
|
|
3516
|
+
FROM ${table}
|
|
3517
|
+
WHERE ${table.openedAt} >= ${oldestStart}
|
|
3518
|
+
AND ${owned}
|
|
3519
|
+
)
|
|
3520
|
+
SELECT
|
|
3521
|
+
windows.days AS "days",
|
|
3522
|
+
count(recent_issues.issue_id)
|
|
3523
|
+
FILTER (WHERE recent_issues.opened_at >= windows.start_at)::integer
|
|
3524
|
+
AS "created"
|
|
3525
|
+
FROM windows
|
|
3526
|
+
LEFT JOIN recent_issues ON true
|
|
3527
|
+
GROUP BY windows.days
|
|
3528
|
+
ORDER BY windows.days
|
|
3529
|
+
`);
|
|
3530
|
+
return z15.array(issueStatsSchema).parse(queryRows(result));
|
|
3531
|
+
}
|
|
3532
|
+
async function aggregateOpenedDays(args) {
|
|
3533
|
+
const end = new Date(args.nowMs);
|
|
3534
|
+
const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1) - 1) * DAY_MS);
|
|
3535
|
+
const table = args.table;
|
|
3536
|
+
const owned = ownedByUserSql(sql4`${table.conversationIds}`, args.userId);
|
|
3537
|
+
const result = await args.db.execute(sql4`
|
|
3538
|
+
WITH days AS (
|
|
3539
|
+
SELECT generate_series(
|
|
3540
|
+
date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
|
|
3541
|
+
date_trunc('day', ${end}::timestamptz AT TIME ZONE 'UTC'),
|
|
3542
|
+
interval '1 day'
|
|
3543
|
+
) AS day
|
|
3544
|
+
), daily AS (
|
|
3545
|
+
SELECT
|
|
3546
|
+
date_trunc('day', ${table.openedAt} AT TIME ZONE 'UTC') AS day,
|
|
3547
|
+
count(*)::integer AS created
|
|
3548
|
+
FROM ${table}
|
|
3549
|
+
WHERE ${table.openedAt} >= ${start}
|
|
3550
|
+
AND ${owned}
|
|
3551
|
+
GROUP BY date_trunc('day', ${table.openedAt} AT TIME ZONE 'UTC')
|
|
3552
|
+
)
|
|
3553
|
+
SELECT
|
|
3554
|
+
to_char(days.day, 'YYYY-MM-DD') AS "date",
|
|
3555
|
+
coalesce(daily.created, 0)::integer AS "created"
|
|
3556
|
+
FROM days
|
|
3557
|
+
LEFT JOIN daily ON daily.day = days.day
|
|
3558
|
+
ORDER BY days.day
|
|
3559
|
+
`);
|
|
3560
|
+
return z15.array(daySchema).parse(queryRows(result));
|
|
3561
|
+
}
|
|
3562
|
+
async function buildGitHubProfileReport(args) {
|
|
3563
|
+
const [windows, pullRequestDays, issueWindows, issueDays] = await Promise.all(
|
|
3564
|
+
[
|
|
3565
|
+
aggregatePullRequestWindows(args),
|
|
3566
|
+
aggregateOpenedDays({
|
|
3567
|
+
db: args.db,
|
|
3568
|
+
nowMs: args.nowMs,
|
|
3569
|
+
table: juniorGitHubPullRequests,
|
|
3570
|
+
userId: args.userId
|
|
3571
|
+
}),
|
|
3572
|
+
aggregateIssueWindows(args),
|
|
3573
|
+
aggregateOpenedDays({
|
|
3574
|
+
db: args.db,
|
|
3575
|
+
nowMs: args.nowMs,
|
|
3576
|
+
table: juniorGitHubIssues,
|
|
3577
|
+
userId: args.userId
|
|
3578
|
+
})
|
|
3579
|
+
]
|
|
3580
|
+
);
|
|
3581
|
+
const thirtyDays = windows.find((window) => window.days === 30);
|
|
3582
|
+
const issueThirtyDays = issueWindows.find((window) => window.days === 30);
|
|
3583
|
+
const hasActivity = windows.some((window) => window.created + window.merged + window.closed > 0) || issueWindows.some((window) => window.created > 0);
|
|
3584
|
+
if (!hasActivity) {
|
|
3585
|
+
return void 0;
|
|
3586
|
+
}
|
|
3587
|
+
return {
|
|
3588
|
+
generatedAt: new Date(args.nowMs).toISOString(),
|
|
3589
|
+
title: "GitHub",
|
|
3590
|
+
metrics: [
|
|
3591
|
+
{
|
|
3592
|
+
label: "PRs opened \xB7 30d",
|
|
3593
|
+
value: String(thirtyDays.created)
|
|
3594
|
+
},
|
|
3595
|
+
{
|
|
3596
|
+
label: "PRs merged \xB7 30d",
|
|
3597
|
+
value: String(thirtyDays.merged)
|
|
3598
|
+
},
|
|
3599
|
+
{
|
|
3600
|
+
label: "Issues opened \xB7 30d",
|
|
3601
|
+
value: String(issueThirtyDays.created)
|
|
3602
|
+
},
|
|
3603
|
+
{
|
|
3604
|
+
label: "PR merge rate \xB7 30d",
|
|
3605
|
+
value: formatPercent(thirtyDays.mergeRate)
|
|
3606
|
+
}
|
|
3607
|
+
],
|
|
3608
|
+
widgets: [
|
|
3609
|
+
{
|
|
3610
|
+
id: "pull-requests-created",
|
|
3611
|
+
type: "bar_chart",
|
|
3612
|
+
title: "Pull requests opened",
|
|
3613
|
+
description: "Junior-owned pull requests opened for this person per day",
|
|
3614
|
+
timeRangeDays: [...WINDOWS],
|
|
3615
|
+
series: [{ key: "created", label: "Opened" }],
|
|
3616
|
+
categories: pullRequestDays.map((stats) => ({
|
|
3617
|
+
id: stats.date,
|
|
3618
|
+
label: stats.date,
|
|
3619
|
+
values: { created: stats.created }
|
|
3620
|
+
}))
|
|
3621
|
+
},
|
|
3622
|
+
{
|
|
3623
|
+
id: "issues-created",
|
|
3624
|
+
type: "bar_chart",
|
|
3625
|
+
title: "Issues opened",
|
|
3626
|
+
description: "Junior-owned issues opened for this person per day",
|
|
3627
|
+
timeRangeDays: [...WINDOWS],
|
|
3628
|
+
series: [{ key: "created", label: "Opened" }],
|
|
3629
|
+
categories: issueDays.map((stats) => ({
|
|
3630
|
+
id: stats.date,
|
|
3631
|
+
label: stats.date,
|
|
3632
|
+
values: { created: stats.created }
|
|
3633
|
+
}))
|
|
3634
|
+
}
|
|
3635
|
+
]
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// src/outcomes/report.ts
|
|
3640
|
+
import { sql as sql6 } from "drizzle-orm";
|
|
3641
|
+
import { z as z17 } from "zod";
|
|
3642
|
+
|
|
3643
|
+
// src/outcomes/cost.ts
|
|
3644
|
+
import { sql as sql5 } from "drizzle-orm";
|
|
3645
|
+
import { z as z16 } from "zod";
|
|
3646
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
3647
|
+
var costWindowSchema = z16.object({
|
|
3648
|
+
days: z16.number().int().positive(),
|
|
3649
|
+
issueCostUsd: z16.number().nonnegative().nullable(),
|
|
3650
|
+
medianIssueCostUsd: z16.number().nonnegative().nullable(),
|
|
3651
|
+
medianPullRequestCostUsd: z16.number().nonnegative().nullable(),
|
|
3652
|
+
pullRequestCostUsd: z16.number().nonnegative().nullable()
|
|
3415
3653
|
}).strict().transform((row) => ({
|
|
3416
3654
|
days: row.days,
|
|
3417
3655
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
@@ -3419,12 +3657,12 @@ var costWindowSchema = z15.object({
|
|
|
3419
3657
|
medianPullRequestCostUsd: row.medianPullRequestCostUsd ?? void 0,
|
|
3420
3658
|
pullRequestCostUsd: row.pullRequestCostUsd ?? void 0
|
|
3421
3659
|
}));
|
|
3422
|
-
var repositoryCostSchema =
|
|
3423
|
-
issueCostUsd:
|
|
3424
|
-
medianIssueCostUsd:
|
|
3425
|
-
medianPullRequestCostUsd:
|
|
3426
|
-
pullRequestCostUsd:
|
|
3427
|
-
repository:
|
|
3660
|
+
var repositoryCostSchema = z16.object({
|
|
3661
|
+
issueCostUsd: z16.number().nonnegative().nullable(),
|
|
3662
|
+
medianIssueCostUsd: z16.number().nonnegative().nullable(),
|
|
3663
|
+
medianPullRequestCostUsd: z16.number().nonnegative().nullable(),
|
|
3664
|
+
pullRequestCostUsd: z16.number().nonnegative().nullable(),
|
|
3665
|
+
repository: z16.string().min(1)
|
|
3428
3666
|
}).strict().transform((row) => ({
|
|
3429
3667
|
issueCostUsd: row.issueCostUsd ?? void 0,
|
|
3430
3668
|
medianIssueCostUsd: row.medianIssueCostUsd ?? void 0,
|
|
@@ -3432,17 +3670,17 @@ var repositoryCostSchema = z15.object({
|
|
|
3432
3670
|
pullRequestCostUsd: row.pullRequestCostUsd ?? void 0,
|
|
3433
3671
|
repository: row.repository
|
|
3434
3672
|
}));
|
|
3435
|
-
function
|
|
3673
|
+
function queryRows2(result) {
|
|
3436
3674
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
3437
3675
|
throw new TypeError("GitHub cost query did not return rows");
|
|
3438
3676
|
}
|
|
3439
3677
|
return result.rows;
|
|
3440
3678
|
}
|
|
3441
3679
|
async function hasConversationUsageTable(db) {
|
|
3442
|
-
const result = await db.execute(
|
|
3680
|
+
const result = await db.execute(sql5`
|
|
3443
3681
|
SELECT to_regclass('public.junior_conversations') IS NOT NULL AS "present"
|
|
3444
3682
|
`);
|
|
3445
|
-
const row =
|
|
3683
|
+
const row = queryRows2(result)[0];
|
|
3446
3684
|
return typeof row === "object" && row !== null && "present" in row && row.present === true;
|
|
3447
3685
|
}
|
|
3448
3686
|
function emptyCostWindows(windows) {
|
|
@@ -3455,7 +3693,7 @@ function emptyCostWindows(windows) {
|
|
|
3455
3693
|
}));
|
|
3456
3694
|
}
|
|
3457
3695
|
function conversationTreeCostExpr() {
|
|
3458
|
-
return
|
|
3696
|
+
return sql5`
|
|
3459
3697
|
coalesce((
|
|
3460
3698
|
SELECT sum(
|
|
3461
3699
|
CASE
|
|
@@ -3486,7 +3724,7 @@ function conversationTreeCostExpr() {
|
|
|
3486
3724
|
function pullRequestConversationIdsExpr() {
|
|
3487
3725
|
const pullRequests = juniorGitHubPullRequests;
|
|
3488
3726
|
const issues = juniorGitHubIssues;
|
|
3489
|
-
return
|
|
3727
|
+
return sql5`
|
|
3490
3728
|
ARRAY(
|
|
3491
3729
|
SELECT DISTINCT unnest(
|
|
3492
3730
|
${pullRequests.conversationIds}
|
|
@@ -3508,7 +3746,7 @@ function pullRequestConversationIdsExpr() {
|
|
|
3508
3746
|
function issueConversationIdsExpr() {
|
|
3509
3747
|
const pullRequests = juniorGitHubPullRequests;
|
|
3510
3748
|
const issues = juniorGitHubIssues;
|
|
3511
|
-
return
|
|
3749
|
+
return sql5`
|
|
3512
3750
|
ARRAY(
|
|
3513
3751
|
SELECT DISTINCT unnest(
|
|
3514
3752
|
${issues.conversationIds}
|
|
@@ -3532,21 +3770,21 @@ async function aggregateGitHubCostWindows(args) {
|
|
|
3532
3770
|
return emptyCostWindows(args.windows);
|
|
3533
3771
|
}
|
|
3534
3772
|
const starts = args.windows.map(
|
|
3535
|
-
(days) => [days, new Date(args.nowMs - days *
|
|
3773
|
+
(days) => [days, new Date(args.nowMs - days * DAY_MS2)]
|
|
3536
3774
|
);
|
|
3537
3775
|
const oldestStart = starts.at(-1)[1];
|
|
3538
3776
|
const pullRequests = juniorGitHubPullRequests;
|
|
3539
3777
|
const issues = juniorGitHubIssues;
|
|
3540
|
-
const windowValues =
|
|
3778
|
+
const windowValues = sql5.join(
|
|
3541
3779
|
starts.map(
|
|
3542
|
-
([days, start]) =>
|
|
3780
|
+
([days, start]) => sql5`(${days}::integer, ${start}::timestamptz)`
|
|
3543
3781
|
),
|
|
3544
|
-
|
|
3782
|
+
sql5`, `
|
|
3545
3783
|
);
|
|
3546
3784
|
const conversationTreeCost = conversationTreeCostExpr();
|
|
3547
3785
|
const pullRequestConversationIds = pullRequestConversationIdsExpr();
|
|
3548
3786
|
const issueConversationIds = issueConversationIdsExpr();
|
|
3549
|
-
const result = await args.db.execute(
|
|
3787
|
+
const result = await args.db.execute(sql5`
|
|
3550
3788
|
WITH windows(days, start_at) AS (
|
|
3551
3789
|
VALUES ${windowValues}
|
|
3552
3790
|
), pull_request_entities AS (
|
|
@@ -3629,19 +3867,19 @@ async function aggregateGitHubCostWindows(args) {
|
|
|
3629
3867
|
INNER JOIN issue_window ON issue_window.days = pull_request_window.days
|
|
3630
3868
|
ORDER BY pull_request_window.days
|
|
3631
3869
|
`);
|
|
3632
|
-
return
|
|
3870
|
+
return z16.array(costWindowSchema).parse(queryRows2(result));
|
|
3633
3871
|
}
|
|
3634
3872
|
async function aggregateGitHubRepositoryCosts(args) {
|
|
3635
3873
|
if (!await hasConversationUsageTable(args.db)) {
|
|
3636
3874
|
return [];
|
|
3637
3875
|
}
|
|
3638
|
-
const start = new Date(args.nowMs - 30 *
|
|
3876
|
+
const start = new Date(args.nowMs - 30 * DAY_MS2);
|
|
3639
3877
|
const pullRequests = juniorGitHubPullRequests;
|
|
3640
3878
|
const issues = juniorGitHubIssues;
|
|
3641
3879
|
const conversationTreeCost = conversationTreeCostExpr();
|
|
3642
3880
|
const pullRequestConversationIds = pullRequestConversationIdsExpr();
|
|
3643
3881
|
const issueConversationIds = issueConversationIdsExpr();
|
|
3644
|
-
const result = await args.db.execute(
|
|
3882
|
+
const result = await args.db.execute(sql5`
|
|
3645
3883
|
WITH pull_request_entities AS (
|
|
3646
3884
|
SELECT
|
|
3647
3885
|
${pullRequests.repositoryFullName} AS repository,
|
|
@@ -3727,7 +3965,7 @@ async function aggregateGitHubRepositoryCosts(args) {
|
|
|
3727
3965
|
ON issue_totals.repository = repositories.repository
|
|
3728
3966
|
ORDER BY "repository" ASC
|
|
3729
3967
|
`);
|
|
3730
|
-
return
|
|
3968
|
+
return z16.array(repositoryCostSchema).parse(queryRows2(result));
|
|
3731
3969
|
}
|
|
3732
3970
|
function formatCostUsd(value) {
|
|
3733
3971
|
if (value === void 0) return "\u2014";
|
|
@@ -3740,14 +3978,14 @@ function formatCostUsd(value) {
|
|
|
3740
3978
|
}
|
|
3741
3979
|
|
|
3742
3980
|
// src/outcomes/report.ts
|
|
3743
|
-
var
|
|
3744
|
-
var
|
|
3745
|
-
var
|
|
3746
|
-
closed:
|
|
3747
|
-
created:
|
|
3748
|
-
days:
|
|
3749
|
-
medianMergeTimeMs:
|
|
3750
|
-
merged:
|
|
3981
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
3982
|
+
var WINDOWS2 = [7, 30, 90];
|
|
3983
|
+
var pullRequestStatsSchema2 = z17.object({
|
|
3984
|
+
closed: z17.number().int().nonnegative(),
|
|
3985
|
+
created: z17.number().int().nonnegative(),
|
|
3986
|
+
days: z17.number().int().positive(),
|
|
3987
|
+
medianMergeTimeMs: z17.number().nonnegative().nullable(),
|
|
3988
|
+
merged: z17.number().int().nonnegative()
|
|
3751
3989
|
}).strict().transform((row) => {
|
|
3752
3990
|
const terminal = row.merged + row.closed;
|
|
3753
3991
|
return {
|
|
@@ -3756,12 +3994,12 @@ var pullRequestStatsSchema = z16.object({
|
|
|
3756
3994
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3757
3995
|
};
|
|
3758
3996
|
});
|
|
3759
|
-
var pullRequestRepositoryStatsSchema =
|
|
3760
|
-
closed:
|
|
3761
|
-
created:
|
|
3762
|
-
juniorOnly:
|
|
3763
|
-
merged:
|
|
3764
|
-
repository:
|
|
3997
|
+
var pullRequestRepositoryStatsSchema = z17.object({
|
|
3998
|
+
closed: z17.number().int().nonnegative(),
|
|
3999
|
+
created: z17.number().int().nonnegative(),
|
|
4000
|
+
juniorOnly: z17.number().int().nonnegative(),
|
|
4001
|
+
merged: z17.number().int().nonnegative(),
|
|
4002
|
+
repository: z17.string().min(1)
|
|
3765
4003
|
}).strict().transform((row) => {
|
|
3766
4004
|
const terminal = row.merged + row.closed;
|
|
3767
4005
|
return {
|
|
@@ -3769,47 +4007,47 @@ var pullRequestRepositoryStatsSchema = z16.object({
|
|
|
3769
4007
|
mergeRate: terminal > 0 ? row.merged / terminal : void 0
|
|
3770
4008
|
};
|
|
3771
4009
|
});
|
|
3772
|
-
var
|
|
3773
|
-
closedCompleted:
|
|
3774
|
-
closedDuplicate:
|
|
3775
|
-
closedNotPlanned:
|
|
3776
|
-
closedUnknown:
|
|
3777
|
-
created:
|
|
3778
|
-
days:
|
|
3779
|
-
medianCloseTimeMs:
|
|
4010
|
+
var issueStatsSchema2 = z17.object({
|
|
4011
|
+
closedCompleted: z17.number().int().nonnegative(),
|
|
4012
|
+
closedDuplicate: z17.number().int().nonnegative(),
|
|
4013
|
+
closedNotPlanned: z17.number().int().nonnegative(),
|
|
4014
|
+
closedUnknown: z17.number().int().nonnegative(),
|
|
4015
|
+
created: z17.number().int().nonnegative(),
|
|
4016
|
+
days: z17.number().int().positive(),
|
|
4017
|
+
medianCloseTimeMs: z17.number().nonnegative().nullable()
|
|
3780
4018
|
}).strict().transform((row) => ({
|
|
3781
4019
|
...row,
|
|
3782
4020
|
medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
|
|
3783
4021
|
}));
|
|
3784
|
-
var pullRequestDaySchema =
|
|
3785
|
-
created:
|
|
3786
|
-
date:
|
|
4022
|
+
var pullRequestDaySchema = z17.object({
|
|
4023
|
+
created: z17.number().int().nonnegative(),
|
|
4024
|
+
date: z17.string().date()
|
|
3787
4025
|
}).strict();
|
|
3788
|
-
var issueDaySchema =
|
|
3789
|
-
created:
|
|
3790
|
-
date:
|
|
4026
|
+
var issueDaySchema = z17.object({
|
|
4027
|
+
created: z17.number().int().nonnegative(),
|
|
4028
|
+
date: z17.string().date()
|
|
3791
4029
|
}).strict();
|
|
3792
|
-
var issueRepositoryStatsSchema =
|
|
3793
|
-
closedCompleted:
|
|
3794
|
-
closedDuplicate:
|
|
3795
|
-
closedNotPlanned:
|
|
3796
|
-
closedUnknown:
|
|
3797
|
-
created:
|
|
3798
|
-
repository:
|
|
4030
|
+
var issueRepositoryStatsSchema = z17.object({
|
|
4031
|
+
closedCompleted: z17.number().int().nonnegative(),
|
|
4032
|
+
closedDuplicate: z17.number().int().nonnegative(),
|
|
4033
|
+
closedNotPlanned: z17.number().int().nonnegative(),
|
|
4034
|
+
closedUnknown: z17.number().int().nonnegative(),
|
|
4035
|
+
created: z17.number().int().nonnegative(),
|
|
4036
|
+
repository: z17.string().min(1)
|
|
3799
4037
|
}).strict();
|
|
3800
|
-
function
|
|
4038
|
+
function queryRows3(result) {
|
|
3801
4039
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
3802
4040
|
throw new TypeError("GitHub outcome query did not return rows");
|
|
3803
4041
|
}
|
|
3804
4042
|
return result.rows;
|
|
3805
4043
|
}
|
|
3806
|
-
async function
|
|
3807
|
-
const starts =
|
|
3808
|
-
(days) => [days, new Date(args.nowMs - days *
|
|
4044
|
+
async function aggregatePullRequestWindows2(args) {
|
|
4045
|
+
const starts = WINDOWS2.map(
|
|
4046
|
+
(days) => [days, new Date(args.nowMs - days * DAY_MS3)]
|
|
3809
4047
|
);
|
|
3810
4048
|
const oldestStart = starts.at(-1)[1];
|
|
3811
4049
|
const table = juniorGitHubPullRequests;
|
|
3812
|
-
const result = await args.db.execute(
|
|
4050
|
+
const result = await args.db.execute(sql6`
|
|
3813
4051
|
WITH windows(days, start_at) AS (
|
|
3814
4052
|
VALUES
|
|
3815
4053
|
(${starts[0][0]}::integer, ${starts[0][1]}::timestamptz),
|
|
@@ -3859,13 +4097,13 @@ async function aggregatePullRequestWindows(args) {
|
|
|
3859
4097
|
GROUP BY windows.days
|
|
3860
4098
|
ORDER BY windows.days
|
|
3861
4099
|
`);
|
|
3862
|
-
return
|
|
4100
|
+
return z17.array(pullRequestStatsSchema2).parse(queryRows3(result));
|
|
3863
4101
|
}
|
|
3864
4102
|
async function aggregatePullRequestDays(args) {
|
|
3865
4103
|
const end = new Date(args.nowMs);
|
|
3866
|
-
const start =
|
|
4104
|
+
const start = startOfUtcDay2(args.nowMs - (WINDOWS2.at(-1) - 1) * DAY_MS3);
|
|
3867
4105
|
const table = juniorGitHubPullRequests;
|
|
3868
|
-
const result = await args.db.execute(
|
|
4106
|
+
const result = await args.db.execute(sql6`
|
|
3869
4107
|
WITH days AS (
|
|
3870
4108
|
SELECT generate_series(
|
|
3871
4109
|
date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
|
|
@@ -3887,12 +4125,12 @@ async function aggregatePullRequestDays(args) {
|
|
|
3887
4125
|
LEFT JOIN daily ON daily.day = days.day
|
|
3888
4126
|
ORDER BY days.day
|
|
3889
4127
|
`);
|
|
3890
|
-
return
|
|
4128
|
+
return z17.array(pullRequestDaySchema).parse(queryRows3(result));
|
|
3891
4129
|
}
|
|
3892
4130
|
async function aggregatePullRequestRepositories(args) {
|
|
3893
|
-
const start = new Date(args.nowMs - 30 *
|
|
4131
|
+
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
3894
4132
|
const table = juniorGitHubPullRequests;
|
|
3895
|
-
const result = await args.db.execute(
|
|
4133
|
+
const result = await args.db.execute(sql6`
|
|
3896
4134
|
SELECT
|
|
3897
4135
|
${table.repositoryFullName} AS "repository",
|
|
3898
4136
|
count(*) FILTER (WHERE ${table.openedAt} >= ${start})::integer
|
|
@@ -3917,15 +4155,15 @@ async function aggregatePullRequestRepositories(args) {
|
|
|
3917
4155
|
ORDER BY "merged" DESC, "created" DESC, "repository" ASC
|
|
3918
4156
|
LIMIT 25
|
|
3919
4157
|
`);
|
|
3920
|
-
return
|
|
4158
|
+
return z17.array(pullRequestRepositoryStatsSchema).parse(queryRows3(result));
|
|
3921
4159
|
}
|
|
3922
|
-
async function
|
|
3923
|
-
const starts =
|
|
3924
|
-
(days) => [days, new Date(args.nowMs - days *
|
|
4160
|
+
async function aggregateIssueWindows2(args) {
|
|
4161
|
+
const starts = WINDOWS2.map(
|
|
4162
|
+
(days) => [days, new Date(args.nowMs - days * DAY_MS3)]
|
|
3925
4163
|
);
|
|
3926
4164
|
const oldestStart = starts.at(-1)[1];
|
|
3927
4165
|
const table = juniorGitHubIssues;
|
|
3928
|
-
const result = await args.db.execute(
|
|
4166
|
+
const result = await args.db.execute(sql6`
|
|
3929
4167
|
WITH windows(days, start_at) AS (
|
|
3930
4168
|
VALUES
|
|
3931
4169
|
(${starts[0][0]}::integer, ${starts[0][1]}::timestamptz),
|
|
@@ -3986,13 +4224,13 @@ async function aggregateIssueWindows(args) {
|
|
|
3986
4224
|
GROUP BY windows.days
|
|
3987
4225
|
ORDER BY windows.days
|
|
3988
4226
|
`);
|
|
3989
|
-
return
|
|
4227
|
+
return z17.array(issueStatsSchema2).parse(queryRows3(result));
|
|
3990
4228
|
}
|
|
3991
4229
|
async function aggregateIssueDays(args) {
|
|
3992
4230
|
const end = new Date(args.nowMs);
|
|
3993
|
-
const start =
|
|
4231
|
+
const start = startOfUtcDay2(args.nowMs - (WINDOWS2.at(-1) - 1) * DAY_MS3);
|
|
3994
4232
|
const table = juniorGitHubIssues;
|
|
3995
|
-
const result = await args.db.execute(
|
|
4233
|
+
const result = await args.db.execute(sql6`
|
|
3996
4234
|
WITH days AS (
|
|
3997
4235
|
SELECT generate_series(
|
|
3998
4236
|
date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
|
|
@@ -4014,12 +4252,12 @@ async function aggregateIssueDays(args) {
|
|
|
4014
4252
|
LEFT JOIN daily ON daily.day = days.day
|
|
4015
4253
|
ORDER BY days.day
|
|
4016
4254
|
`);
|
|
4017
|
-
return
|
|
4255
|
+
return z17.array(issueDaySchema).parse(queryRows3(result));
|
|
4018
4256
|
}
|
|
4019
4257
|
async function aggregateIssueRepositories(args) {
|
|
4020
|
-
const start = new Date(args.nowMs - 30 *
|
|
4258
|
+
const start = new Date(args.nowMs - 30 * DAY_MS3);
|
|
4021
4259
|
const table = juniorGitHubIssues;
|
|
4022
|
-
const result = await args.db.execute(
|
|
4260
|
+
const result = await args.db.execute(sql6`
|
|
4023
4261
|
SELECT
|
|
4024
4262
|
${table.repositoryFullName} AS "repository",
|
|
4025
4263
|
count(*) FILTER (WHERE ${table.openedAt} >= ${start})::integer
|
|
@@ -4051,9 +4289,9 @@ async function aggregateIssueRepositories(args) {
|
|
|
4051
4289
|
ORDER BY "created" DESC, "closedCompleted" DESC, "repository" ASC
|
|
4052
4290
|
LIMIT 25
|
|
4053
4291
|
`);
|
|
4054
|
-
return
|
|
4292
|
+
return z17.array(issueRepositoryStatsSchema).parse(queryRows3(result));
|
|
4055
4293
|
}
|
|
4056
|
-
function
|
|
4294
|
+
function formatPercent2(value) {
|
|
4057
4295
|
return value === void 0 ? "\u2014" : `${Math.round(value * 100)}%`;
|
|
4058
4296
|
}
|
|
4059
4297
|
function formatDuration(value) {
|
|
@@ -4063,7 +4301,7 @@ function formatDuration(value) {
|
|
|
4063
4301
|
if (hours < 24) return `${Math.round(hours * 10) / 10}h`;
|
|
4064
4302
|
return `${Math.round(hours / 24 * 10) / 10}d`;
|
|
4065
4303
|
}
|
|
4066
|
-
function
|
|
4304
|
+
function startOfUtcDay2(timestampMs) {
|
|
4067
4305
|
const date = new Date(timestampMs);
|
|
4068
4306
|
date.setUTCHours(0, 0, 0, 0);
|
|
4069
4307
|
return date;
|
|
@@ -4079,13 +4317,13 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4079
4317
|
costWindows,
|
|
4080
4318
|
repositoryCosts
|
|
4081
4319
|
] = await Promise.all([
|
|
4082
|
-
|
|
4320
|
+
aggregatePullRequestWindows2(args),
|
|
4083
4321
|
aggregatePullRequestDays(args),
|
|
4084
4322
|
aggregatePullRequestRepositories(args),
|
|
4085
|
-
|
|
4323
|
+
aggregateIssueWindows2(args),
|
|
4086
4324
|
aggregateIssueDays(args),
|
|
4087
4325
|
aggregateIssueRepositories(args),
|
|
4088
|
-
aggregateGitHubCostWindows({ ...args, windows:
|
|
4326
|
+
aggregateGitHubCostWindows({ ...args, windows: WINDOWS2 }),
|
|
4089
4327
|
aggregateGitHubRepositoryCosts(args)
|
|
4090
4328
|
]);
|
|
4091
4329
|
const thirtyDays = windows.find((window) => window.days === 30);
|
|
@@ -4100,7 +4338,7 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4100
4338
|
metrics: [
|
|
4101
4339
|
{
|
|
4102
4340
|
label: "PR closure merge rate \xB7 30d",
|
|
4103
|
-
value:
|
|
4341
|
+
value: formatPercent2(thirtyDays.mergeRate)
|
|
4104
4342
|
},
|
|
4105
4343
|
{
|
|
4106
4344
|
label: "Median PR merge time \xB7 merged in 30d",
|
|
@@ -4133,7 +4371,7 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4133
4371
|
type: "bar_chart",
|
|
4134
4372
|
title: "Pull requests created",
|
|
4135
4373
|
description: "Junior-owned pull requests opened per day",
|
|
4136
|
-
timeRangeDays: [...
|
|
4374
|
+
timeRangeDays: [...WINDOWS2],
|
|
4137
4375
|
series: [{ key: "created", label: "Created" }],
|
|
4138
4376
|
categories: pullRequestDays.map((stats) => ({
|
|
4139
4377
|
id: stats.date,
|
|
@@ -4146,7 +4384,7 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4146
4384
|
type: "bar_chart",
|
|
4147
4385
|
title: "Issues created",
|
|
4148
4386
|
description: "Junior-owned issues opened per day",
|
|
4149
|
-
timeRangeDays: [...
|
|
4387
|
+
timeRangeDays: [...WINDOWS2],
|
|
4150
4388
|
series: [{ key: "created", label: "Created" }],
|
|
4151
4389
|
categories: issueDays.map((stats) => ({
|
|
4152
4390
|
id: stats.date,
|
|
@@ -4176,7 +4414,7 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4176
4414
|
merged: String(stats.merged),
|
|
4177
4415
|
closed: String(stats.closed),
|
|
4178
4416
|
juniorOnly: String(stats.juniorOnly),
|
|
4179
|
-
mergeRate:
|
|
4417
|
+
mergeRate: formatPercent2(stats.mergeRate),
|
|
4180
4418
|
medianCost: formatCostUsd(
|
|
4181
4419
|
repositoryCostByName.get(repository)?.medianPullRequestCostUsd
|
|
4182
4420
|
)
|
|
@@ -4215,18 +4453,18 @@ async function buildGitHubOutcomeReport(args) {
|
|
|
4215
4453
|
}
|
|
4216
4454
|
|
|
4217
4455
|
// src/pull-request-outcomes/commit-composition.ts
|
|
4218
|
-
import { z as
|
|
4219
|
-
var canonicalCommitSchema =
|
|
4220
|
-
authorEmail:
|
|
4221
|
-
authorLogin:
|
|
4456
|
+
import { z as z18 } from "zod";
|
|
4457
|
+
var canonicalCommitSchema = z18.object({
|
|
4458
|
+
authorEmail: z18.string().nullable(),
|
|
4459
|
+
authorLogin: z18.string().nullable()
|
|
4222
4460
|
}).strict();
|
|
4223
|
-
var providerCommitSchema =
|
|
4224
|
-
author:
|
|
4225
|
-
commit:
|
|
4226
|
-
author:
|
|
4461
|
+
var providerCommitSchema = z18.object({
|
|
4462
|
+
author: z18.object({ login: z18.string() }).passthrough().nullable(),
|
|
4463
|
+
commit: z18.object({
|
|
4464
|
+
author: z18.object({ email: z18.string() }).passthrough().nullable()
|
|
4227
4465
|
}).passthrough()
|
|
4228
4466
|
}).passthrough();
|
|
4229
|
-
var commitPageSchema =
|
|
4467
|
+
var commitPageSchema = z18.array(providerCommitSchema).transform(
|
|
4230
4468
|
(commits) => commits.map(
|
|
4231
4469
|
(commit) => canonicalCommitSchema.parse({
|
|
4232
4470
|
authorEmail: commit.commit.author?.email ?? null,
|
|
@@ -4267,6 +4505,50 @@ async function classifyGitHubPullRequestCommitComposition(args) {
|
|
|
4267
4505
|
return foundCommit ? "junior_only" : void 0;
|
|
4268
4506
|
}
|
|
4269
4507
|
|
|
4508
|
+
// src/annotations.ts
|
|
4509
|
+
var STATUS_ICON = {
|
|
4510
|
+
warning: "triangle-alert",
|
|
4511
|
+
open: "circle-dot",
|
|
4512
|
+
draft: "circle-dashed",
|
|
4513
|
+
merged: "git-merge",
|
|
4514
|
+
closed: "circle-x"
|
|
4515
|
+
};
|
|
4516
|
+
function isPullRequestUrl(url) {
|
|
4517
|
+
try {
|
|
4518
|
+
return /^\/[^/]+\/[^/]+\/pull\/\d+(?:\/|$)/.test(new URL(url).pathname);
|
|
4519
|
+
} catch {
|
|
4520
|
+
return false;
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
function sidebarIconForStatus(status, url) {
|
|
4524
|
+
if (status === "open" && isPullRequestUrl(url)) return "git-pull-request";
|
|
4525
|
+
return STATUS_ICON[status];
|
|
4526
|
+
}
|
|
4527
|
+
function repositoryName(annotation) {
|
|
4528
|
+
try {
|
|
4529
|
+
const [, , repo] = new URL(annotation.url).pathname.split("/");
|
|
4530
|
+
return repo || void 0;
|
|
4531
|
+
} catch {
|
|
4532
|
+
return void 0;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
function githubSidebarAnnotations(annotations) {
|
|
4536
|
+
return annotations.flatMap((annotation) => {
|
|
4537
|
+
const status = annotation.status;
|
|
4538
|
+
const label = repositoryName(annotation);
|
|
4539
|
+
return status && label ? [
|
|
4540
|
+
{
|
|
4541
|
+
annotation: {
|
|
4542
|
+
icon: sidebarIconForStatus(status, annotation.url),
|
|
4543
|
+
key: annotation.key,
|
|
4544
|
+
label
|
|
4545
|
+
},
|
|
4546
|
+
updatedAt: annotation.updatedAt
|
|
4547
|
+
}
|
|
4548
|
+
] : [];
|
|
4549
|
+
}).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map(({ annotation }) => annotation);
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4270
4552
|
// src/webhooks/check-suite-enrichment.ts
|
|
4271
4553
|
function checkRunsFromResponse(value) {
|
|
4272
4554
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4514,6 +4796,121 @@ async function configureGit(ctx, key, value) {
|
|
|
4514
4796
|
}
|
|
4515
4797
|
}
|
|
4516
4798
|
|
|
4799
|
+
// src/reply-markdown.ts
|
|
4800
|
+
var GITHUB_OWNER_PATTERN = "[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?";
|
|
4801
|
+
var GITHUB_REPOSITORY_PATTERN = "[A-Za-z0-9._-]+";
|
|
4802
|
+
var GITHUB_ISSUE_REFERENCE_PATTERN = new RegExp(
|
|
4803
|
+
`^(${GITHUB_OWNER_PATTERN})\\/(${GITHUB_REPOSITORY_PATTERN})#(\\d+)\\b`
|
|
4804
|
+
);
|
|
4805
|
+
function isReferenceBoundary(char) {
|
|
4806
|
+
return char === void 0 || !/[A-Za-z0-9._-]/.test(char);
|
|
4807
|
+
}
|
|
4808
|
+
function readInlineCode(text2, start) {
|
|
4809
|
+
if (text2[start] !== "`") {
|
|
4810
|
+
return void 0;
|
|
4811
|
+
}
|
|
4812
|
+
let markerLength = 1;
|
|
4813
|
+
while (text2[start + markerLength] === "`") {
|
|
4814
|
+
markerLength++;
|
|
4815
|
+
}
|
|
4816
|
+
const marker = "`".repeat(markerLength);
|
|
4817
|
+
const end = text2.indexOf(marker, start + markerLength);
|
|
4818
|
+
return end === -1 ? void 0 : end + markerLength;
|
|
4819
|
+
}
|
|
4820
|
+
function readFenceLength(line) {
|
|
4821
|
+
const trimmed = line.trimStart();
|
|
4822
|
+
if (!trimmed.startsWith("```")) {
|
|
4823
|
+
return void 0;
|
|
4824
|
+
}
|
|
4825
|
+
let length = 0;
|
|
4826
|
+
while (trimmed[length] === "`") {
|
|
4827
|
+
length++;
|
|
4828
|
+
}
|
|
4829
|
+
return length >= 3 ? length : void 0;
|
|
4830
|
+
}
|
|
4831
|
+
function readMarkdownLink(text2, start) {
|
|
4832
|
+
if (text2[start] !== "[") {
|
|
4833
|
+
return void 0;
|
|
4834
|
+
}
|
|
4835
|
+
const labelEnd = text2.indexOf("](", start + 1);
|
|
4836
|
+
if (labelEnd === -1) {
|
|
4837
|
+
return void 0;
|
|
4838
|
+
}
|
|
4839
|
+
const label = text2.slice(start + 1, labelEnd);
|
|
4840
|
+
if (label.includes("[") || label.includes("]") || label.includes("\n")) {
|
|
4841
|
+
return void 0;
|
|
4842
|
+
}
|
|
4843
|
+
const destStart = labelEnd + 2;
|
|
4844
|
+
const closeParens = text2.indexOf(")", destStart);
|
|
4845
|
+
return closeParens === -1 ? void 0 : closeParens + 1;
|
|
4846
|
+
}
|
|
4847
|
+
function readAngleToken(text2, start) {
|
|
4848
|
+
if (text2[start] !== "<") {
|
|
4849
|
+
return void 0;
|
|
4850
|
+
}
|
|
4851
|
+
const end = text2.indexOf(">", start + 1);
|
|
4852
|
+
return end === -1 ? void 0 : end + 1;
|
|
4853
|
+
}
|
|
4854
|
+
function linkifyLine(line) {
|
|
4855
|
+
let output = "";
|
|
4856
|
+
let index2 = 0;
|
|
4857
|
+
while (index2 < line.length) {
|
|
4858
|
+
const codeEnd = readInlineCode(line, index2);
|
|
4859
|
+
if (codeEnd !== void 0) {
|
|
4860
|
+
output += line.slice(index2, codeEnd);
|
|
4861
|
+
index2 = codeEnd;
|
|
4862
|
+
continue;
|
|
4863
|
+
}
|
|
4864
|
+
const markdownLinkEnd = readMarkdownLink(line, index2);
|
|
4865
|
+
if (markdownLinkEnd !== void 0) {
|
|
4866
|
+
output += line.slice(index2, markdownLinkEnd);
|
|
4867
|
+
index2 = markdownLinkEnd;
|
|
4868
|
+
continue;
|
|
4869
|
+
}
|
|
4870
|
+
const angleTokenEnd = readAngleToken(line, index2);
|
|
4871
|
+
if (angleTokenEnd !== void 0) {
|
|
4872
|
+
output += line.slice(index2, angleTokenEnd);
|
|
4873
|
+
index2 = angleTokenEnd;
|
|
4874
|
+
continue;
|
|
4875
|
+
}
|
|
4876
|
+
if (line.startsWith("https://", index2) || line.startsWith("http://", index2)) {
|
|
4877
|
+
const match = /^https?:\/\/\S+/.exec(line.slice(index2));
|
|
4878
|
+
if (match) {
|
|
4879
|
+
output += match[0];
|
|
4880
|
+
index2 += match[0].length;
|
|
4881
|
+
continue;
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
if (isReferenceBoundary(index2 === 0 ? void 0 : line[index2 - 1])) {
|
|
4885
|
+
const match = GITHUB_ISSUE_REFERENCE_PATTERN.exec(line.slice(index2));
|
|
4886
|
+
if (match) {
|
|
4887
|
+
const [reference, owner, repository, number] = match;
|
|
4888
|
+
output += `[${reference}](https://github.com/${owner}/${repository}/issues/${number})`;
|
|
4889
|
+
index2 += reference.length;
|
|
4890
|
+
continue;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
output += line[index2];
|
|
4894
|
+
index2++;
|
|
4895
|
+
}
|
|
4896
|
+
return output;
|
|
4897
|
+
}
|
|
4898
|
+
function linkifyGitHubReferences(text2) {
|
|
4899
|
+
let openFenceLength = 0;
|
|
4900
|
+
return text2.split("\n").map((line) => {
|
|
4901
|
+
const fenceLength = readFenceLength(line);
|
|
4902
|
+
if (fenceLength !== void 0) {
|
|
4903
|
+
if (openFenceLength === 0) {
|
|
4904
|
+
openFenceLength = fenceLength;
|
|
4905
|
+
} else if (fenceLength >= openFenceLength) {
|
|
4906
|
+
openFenceLength = 0;
|
|
4907
|
+
}
|
|
4908
|
+
return line;
|
|
4909
|
+
}
|
|
4910
|
+
return openFenceLength > 0 ? line : linkifyLine(line);
|
|
4911
|
+
}).join("\n");
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4517
4914
|
// src/plugin.ts
|
|
4518
4915
|
function githubSmartHttpAccess(upstreamUrl) {
|
|
4519
4916
|
const pathname = upstreamUrl.pathname.toLowerCase();
|
|
@@ -4962,6 +5359,21 @@ function githubPlugin(options = {}) {
|
|
|
4962
5359
|
]
|
|
4963
5360
|
},
|
|
4964
5361
|
hooks: {
|
|
5362
|
+
conversationSidebar(ctx) {
|
|
5363
|
+
return {
|
|
5364
|
+
annotationsByConversationId: Object.fromEntries(
|
|
5365
|
+
ctx.conversationIds.flatMap((conversationId) => {
|
|
5366
|
+
const annotations = githubSidebarAnnotations(
|
|
5367
|
+
ctx.annotationsByConversationId[conversationId] ?? []
|
|
5368
|
+
);
|
|
5369
|
+
return annotations.length > 0 ? [[conversationId, annotations]] : [];
|
|
5370
|
+
})
|
|
5371
|
+
)
|
|
5372
|
+
};
|
|
5373
|
+
},
|
|
5374
|
+
formatMarkdown({ text: text2 }) {
|
|
5375
|
+
return linkifyGitHubReferences(text2);
|
|
5376
|
+
},
|
|
4965
5377
|
async unfinishedWork(ctx) {
|
|
4966
5378
|
const db = ctx.db;
|
|
4967
5379
|
const [
|
|
@@ -5031,6 +5443,13 @@ function githubPlugin(options = {}) {
|
|
|
5031
5443
|
nowMs: ctx.nowMs
|
|
5032
5444
|
});
|
|
5033
5445
|
},
|
|
5446
|
+
async profileReport(ctx) {
|
|
5447
|
+
return await buildGitHubProfileReport({
|
|
5448
|
+
db: ctx.db,
|
|
5449
|
+
nowMs: ctx.nowMs,
|
|
5450
|
+
userId: ctx.subject.id
|
|
5451
|
+
});
|
|
5452
|
+
},
|
|
5034
5453
|
tools(ctx) {
|
|
5035
5454
|
return createGitHubTools(ctx);
|
|
5036
5455
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PluginOperationalReportContent } from "@sentry/junior-plugin-api";
|
|
2
|
+
import type { GitHubDb } from "../db/database.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build one person-scoped GitHub report for Junior-owned work linked to the
|
|
5
|
+
* subject's conversations.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildGitHubProfileReport(args: {
|
|
8
|
+
db: GitHubDb;
|
|
9
|
+
nowMs: number;
|
|
10
|
+
userId: string;
|
|
11
|
+
}): Promise<PluginOperationalReportContent | undefined>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.162.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.162.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^25.9.1",
|
|
@@ -81,5 +81,3 @@ 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.
|
|
@@ -80,4 +80,5 @@ jr-rpc config set github.repo owner/repo
|
|
|
80
80
|
- 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.
|
|
81
81
|
- 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
82
|
- 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
|
+
- 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.
|
|
83
84
|
- Return actionable errors for access, permission, not-found, and validation failures.
|
|
@@ -76,7 +76,7 @@ Follow [references/research-rules.md](references/research-rules.md) for cross-ty
|
|
|
76
76
|
|
|
77
77
|
- The runtime adds the verified `Requested by` block. Do not add or rewrite requester attribution in model-authored body text.
|
|
78
78
|
- If the person who originally reported or observed the problem differs from the issue creator, capture that with durable body text such as `Reported by Alice.` or `Raised by Alice during incident triage.`
|
|
79
|
-
- Attach screenshots from the thread
|
|
79
|
+
- Attach screenshots from the thread when present. For GitHub Markdown, first publish each local image with `publishImage` (the image becomes public to anyone with the URL), then embed the returned URL in the issue body or comment. Do not use private Slack file links or conversation attachment URLs.
|
|
80
80
|
- Include code snippets, related issues, and related PRs only when they materially improve the issue.
|
|
81
81
|
|
|
82
82
|
### 4. Verify draft
|