pr-shepherd 0.30.0 → 0.31.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/.claude-plugin/plugin.json +1 -1
- package/bin/cli/fix-formatter-extra.mjs +25 -0
- package/bin/cli/fix-formatter.mjs +4 -20
- package/bin/cli/iterate-lean.mjs +3 -0
- package/bin/commands/iterate/fix-code.mjs +11 -6
- package/bin/commands/iterate/helpers.mjs +4 -13
- package/bin/commands/iterate/index.mjs +5 -5
- package/bin/commands/iterate/reruns.mjs +105 -0
- package/bin/config/load.mjs +7 -0
- package/bin/config.json +2 -1
- package/bin/github/batch-parsers.mjs +6 -6
- package/bin/github/gql/batch-pr.gql +4 -0
- package/bin/types/protected-run.mjs +1 -0
- package/package.json +2 -2
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { blockquote } from "./list-formatters.mjs";
|
|
2
|
+
export function renderCheckAnnotation(a) {
|
|
3
|
+
const loc = `${a.path}:${renderAnnotationRange(a)}`;
|
|
4
|
+
const link = a.blobUrl ? ` [↗](${a.blobUrl})` : "";
|
|
5
|
+
const title = a.title ? ` — ${a.title}` : "";
|
|
6
|
+
const lines = [`- \`${a.id}\`${link} \`${loc}\` [${a.level}]${title}`];
|
|
7
|
+
if (a.message.trim() !== "")
|
|
8
|
+
lines.push(blockquote(a.message));
|
|
9
|
+
if (a.rawDetails !== undefined && a.rawDetails.trim() !== "")
|
|
10
|
+
lines.push(blockquote(a.rawDetails));
|
|
11
|
+
return lines.join("\n");
|
|
12
|
+
}
|
|
13
|
+
export function renderProtectedRun(run) {
|
|
14
|
+
const label = run.workflowName
|
|
15
|
+
? `${run.workflowName} (${run.checkNames.join(", ")})`
|
|
16
|
+
: run.checkNames.join(", ");
|
|
17
|
+
return `- \`${run.runId}\` — \`${label}\` [matched: \`${run.matchedPattern}\`]`;
|
|
18
|
+
}
|
|
19
|
+
function renderAnnotationRange(a) {
|
|
20
|
+
if (a.startLine === null && a.endLine === null)
|
|
21
|
+
return "?";
|
|
22
|
+
const start = a.startLine ?? a.endLine;
|
|
23
|
+
const end = a.endLine ?? a.startLine;
|
|
24
|
+
return start === end ? String(start) : `${start}-${end}`;
|
|
25
|
+
}
|
|
@@ -3,6 +3,7 @@ import { joinSections } from "../util/markdown.mjs";
|
|
|
3
3
|
import { renderSuggestionBlock, renderLineRange } from "./suggestion-renderer.mjs";
|
|
4
4
|
import { renderThreadBullet, renderReviewBullet, renderThreadResolutionStatusTag, renderAuthor, buildFirstLookBullets, renderThreadConversation, blockquote, } from "./list-formatters.mjs";
|
|
5
5
|
import { numberInstructions } from "./iterate-instructions.mjs";
|
|
6
|
+
import { renderCheckAnnotation, renderProtectedRun } from "./fix-formatter-extra.mjs";
|
|
6
7
|
export function formatFixCodeResult(header, result) {
|
|
7
8
|
const sections = [header];
|
|
8
9
|
if (result.fix.threads.length > 0) {
|
|
@@ -116,6 +117,9 @@ export function formatFixCodeResult(header, result) {
|
|
|
116
117
|
sections.push("## In-progress runs");
|
|
117
118
|
sections.push(result.fix.inProgressRunIds.map((id) => `- \`${id}\``).join("\n"));
|
|
118
119
|
}
|
|
120
|
+
if (result.fix.protectedRuns.length > 0) {
|
|
121
|
+
sections.push("## Protected runs", result.fix.protectedRuns.map(renderProtectedRun).join("\n"));
|
|
122
|
+
}
|
|
119
123
|
if (result.cancelled.length > 0) {
|
|
120
124
|
sections.push("## Cancelled runs");
|
|
121
125
|
sections.push(result.cancelled.map((id) => `- \`${id}\``).join("\n"));
|
|
@@ -132,29 +136,9 @@ export function formatFixCodeResult(header, result) {
|
|
|
132
136
|
sections.push(numberInstructions(result.fix.instructions));
|
|
133
137
|
return joinSections(sections);
|
|
134
138
|
}
|
|
135
|
-
function renderCheckAnnotation(a) {
|
|
136
|
-
const loc = `${a.path}:${renderAnnotationRange(a)}`;
|
|
137
|
-
const link = a.blobUrl ? ` [↗](${a.blobUrl})` : "";
|
|
138
|
-
const title = a.title ? ` — ${a.title}` : "";
|
|
139
|
-
const lines = [`- \`${a.id}\`${link} \`${loc}\` [${a.level}]${title}`];
|
|
140
|
-
if (a.message.trim() !== "")
|
|
141
|
-
lines.push(blockquote(a.message));
|
|
142
|
-
if (a.rawDetails !== undefined && a.rawDetails.trim() !== "")
|
|
143
|
-
lines.push(blockquote(a.rawDetails));
|
|
144
|
-
return lines.join("\n");
|
|
145
|
-
}
|
|
146
139
|
function indentBlockquote(body, indent) {
|
|
147
140
|
return blockquote(body)
|
|
148
141
|
.split("\n")
|
|
149
142
|
.map((line) => `${indent}${line}`)
|
|
150
143
|
.join("\n");
|
|
151
144
|
}
|
|
152
|
-
function renderAnnotationRange(a) {
|
|
153
|
-
if (a.startLine === null && a.endLine === null)
|
|
154
|
-
return "?";
|
|
155
|
-
const start = a.startLine ?? a.endLine;
|
|
156
|
-
const end = a.endLine ?? a.startLine;
|
|
157
|
-
if (start === end)
|
|
158
|
-
return String(start);
|
|
159
|
-
return `${start}-${end}`;
|
|
160
|
-
}
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -114,6 +114,9 @@ export function projectIterateLean(result, opts) {
|
|
|
114
114
|
...(result.fix.inProgressRunIds.length > 0 && {
|
|
115
115
|
inProgressRunIds: result.fix.inProgressRunIds,
|
|
116
116
|
}),
|
|
117
|
+
...(result.fix.protectedRuns.length > 0 && {
|
|
118
|
+
protectedRuns: result.fix.protectedRuns,
|
|
119
|
+
}),
|
|
117
120
|
...(result.fix.checks.length > 0 && { checks: result.fix.checks }),
|
|
118
121
|
...(result.fix.changesRequestedReviews.length > 0 && {
|
|
119
122
|
changesRequestedReviews: result.fix.changesRequestedReviews,
|
|
@@ -7,7 +7,7 @@ import { checkEscalateTriggers, validateBaseBranch, buildEscalateSuggestion, bui
|
|
|
7
7
|
import { buildResolveCommand } from "./classify.mjs";
|
|
8
8
|
import { buildFixInstructions } from "./render.mjs";
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
|
-
import { tryCancelRun, buildInProgressRunIds } from "./helpers.mjs";
|
|
10
|
+
import { tryCancelRun, buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./helpers.mjs";
|
|
11
11
|
import { annotationMarkerBody } from "../check-annotations.mjs";
|
|
12
12
|
import { threadTranscriptBody } from "../../threads/transcript.mjs";
|
|
13
13
|
import { isHumanAuthor, isConfiguredBotAuthor } from "../../comments/authors.mjs";
|
|
@@ -29,6 +29,7 @@ function nextFixAttempts(stored, headSha, threads) {
|
|
|
29
29
|
export async function handleFixCode(ctx) {
|
|
30
30
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, ruleAutoResolveThreadIds, } = ctx;
|
|
31
31
|
const failingChecks = report.checks.failing;
|
|
32
|
+
const { protectedRunIds, protectedRuns } = buildRunProtection([...failingChecks, ...report.checks.inProgress], opts.neverCancelRuns);
|
|
32
33
|
const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
|
|
33
34
|
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, headSha, report.threads.actionable);
|
|
34
35
|
const botCrReviews = report.changesRequestedReviews.filter((r) => !isHumanAuthor(r) || isConfiguredBotAuthor(r, botUsernames));
|
|
@@ -78,10 +79,8 @@ export async function handleFixCode(ctx) {
|
|
|
78
79
|
await writeFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber }, { headSha, threadAttempts, threadBodyHashes });
|
|
79
80
|
let cancelled = [];
|
|
80
81
|
if (!opts.noAutoCancelActionable) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
];
|
|
84
|
-
const results = await Promise.all(uniqueRunIds.map((id) => tryCancelRun(id, repoOwner, repoName)));
|
|
82
|
+
const runIds = buildAutoCancelRunIdsWithOptions(report, { protectedRunIds });
|
|
83
|
+
const results = await Promise.all(runIds.map((id) => tryCancelRun(id, repoOwner, repoName)));
|
|
85
84
|
cancelled = results.filter((id) => id !== null);
|
|
86
85
|
}
|
|
87
86
|
const cancelledSet = new Set(cancelled);
|
|
@@ -100,7 +99,12 @@ export async function handleFixCode(ctx) {
|
|
|
100
99
|
hasConflicts ||
|
|
101
100
|
changesRequestedReviews.length > 0 ||
|
|
102
101
|
actionableComments.length > 0;
|
|
103
|
-
const inProgressRunIds = pushLikely
|
|
102
|
+
const inProgressRunIds = pushLikely
|
|
103
|
+
? buildInProgressRunIds(report, cancelledSet, {
|
|
104
|
+
suppressProtectedFreshReruns: false,
|
|
105
|
+
protectedRunIds,
|
|
106
|
+
})
|
|
107
|
+
: [];
|
|
104
108
|
const commentMinimizeIds = report.comments.minimizeIds ?? actionableComments.map((c) => c.id);
|
|
105
109
|
const allCommentIds = [...commentMinimizeIds, ...reviewSummaryIds];
|
|
106
110
|
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews, checks, prNumber, botUsernames, ruleAutoResolveThreadIds);
|
|
@@ -154,6 +158,7 @@ export async function handleFixCode(ctx) {
|
|
|
154
158
|
firstLookThreads,
|
|
155
159
|
firstLookComments,
|
|
156
160
|
inProgressRunIds,
|
|
161
|
+
protectedRuns,
|
|
157
162
|
},
|
|
158
163
|
cancelled,
|
|
159
164
|
};
|
|
@@ -2,13 +2,7 @@ import { execFile as execFileCb } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { rest } from "../../github/http.mjs";
|
|
4
4
|
const execFile = promisify(execFileCb);
|
|
5
|
-
export
|
|
6
|
-
return [
|
|
7
|
-
...new Set(report.checks.inProgress
|
|
8
|
-
.map((c) => c.runId)
|
|
9
|
-
.filter((id) => id !== null && !cancelledSet.has(id))),
|
|
10
|
-
];
|
|
11
|
-
}
|
|
5
|
+
export { buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./reruns.mjs";
|
|
12
6
|
export function buildSummary(report) {
|
|
13
7
|
return {
|
|
14
8
|
passing: report.checks.passing.length,
|
|
@@ -17,12 +11,7 @@ export function buildSummary(report) {
|
|
|
17
11
|
inProgress: report.checks.inProgress.length,
|
|
18
12
|
};
|
|
19
13
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Build the full list of CI checks relevant to PR readiness: triggered by a PR
|
|
22
|
-
* event (or StatusContext with null event), completed, and not skipped/neutral.
|
|
23
|
-
* Includes both passing and failing. Failing entries carry workflowName, jobName,
|
|
24
|
-
* failedStep, and summary.
|
|
25
|
-
*/
|
|
14
|
+
/** Build completed, non-skipped checks relevant to PR readiness. */
|
|
26
15
|
export function buildRelevantChecks(report) {
|
|
27
16
|
const excluded = new Set([null, "SKIPPED", "NEUTRAL"]);
|
|
28
17
|
const passing = report.checks.passing.flatMap((c) => {
|
|
@@ -35,6 +24,7 @@ export function buildRelevantChecks(report) {
|
|
|
35
24
|
conclusion,
|
|
36
25
|
runId: c.runId,
|
|
37
26
|
detailsUrl: c.detailsUrl || null,
|
|
27
|
+
...(c.workflowName !== undefined && { workflowName: c.workflowName }),
|
|
38
28
|
summary: c.summary,
|
|
39
29
|
},
|
|
40
30
|
];
|
|
@@ -66,6 +56,7 @@ export function buildActiveChecks(report) {
|
|
|
66
56
|
status: c.status,
|
|
67
57
|
runId: c.runId,
|
|
68
58
|
detailsUrl: c.detailsUrl || null,
|
|
59
|
+
...(c.workflowName !== undefined && { workflowName: c.workflowName }),
|
|
69
60
|
...(c.summary !== undefined && { summary: c.summary }),
|
|
70
61
|
}));
|
|
71
62
|
}
|
|
@@ -16,12 +16,12 @@ export async function runIterate(opts) {
|
|
|
16
16
|
const readyDelaySeconds = opts.readyDelaySeconds ?? config.watch.readyDelayMinutes * 60;
|
|
17
17
|
const stallTimeoutSeconds = opts.stallTimeoutSeconds ?? config.iterate.stallTimeoutMinutes * 60;
|
|
18
18
|
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
19
|
-
if (prNumber === null)
|
|
19
|
+
if (prNumber === null)
|
|
20
20
|
throw new Error("No open PR found for current branch. Pass a PR number explicitly.");
|
|
21
|
-
|
|
22
|
-
const optsWithPr = { ...opts, prNumber };
|
|
21
|
+
const neverCancelRuns = opts.neverCancelRuns ?? config.actions.neverCancelRuns;
|
|
23
22
|
const report = await runCheck({
|
|
24
|
-
...
|
|
23
|
+
...opts,
|
|
24
|
+
prNumber,
|
|
25
25
|
autoResolve: config.actions.autoResolveOutdated,
|
|
26
26
|
autoMinimizeSuppressed: config.actions.autoMinimizeSuppressed,
|
|
27
27
|
});
|
|
@@ -121,7 +121,7 @@ export async function runIterate(opts) {
|
|
|
121
121
|
return handleFixCode({
|
|
122
122
|
base,
|
|
123
123
|
report,
|
|
124
|
-
opts,
|
|
124
|
+
opts: { ...opts, prNumber, neverCancelRuns },
|
|
125
125
|
headSha,
|
|
126
126
|
stallKey,
|
|
127
127
|
prNumber,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import picomatch from "picomatch";
|
|
2
|
+
function matchesRerunCheck(failure, check) {
|
|
3
|
+
if (failure.runId !== null && check.runId !== null) {
|
|
4
|
+
return failure.runId === check.runId && failure.name === check.name;
|
|
5
|
+
}
|
|
6
|
+
return failure.runId === null && check.runId === null && failure.name === check.name;
|
|
7
|
+
}
|
|
8
|
+
function isProtectedByFreshRerun(failure, check) {
|
|
9
|
+
const attemptStartedAt = check.startedAtUnix ?? check.updatedAtUnix ?? check.createdAtUnix;
|
|
10
|
+
return (attemptStartedAt !== undefined &&
|
|
11
|
+
matchesRerunCheck(failure, check) &&
|
|
12
|
+
failure.completedAtUnix !== undefined &&
|
|
13
|
+
attemptStartedAt >= failure.completedAtUnix &&
|
|
14
|
+
(failure.startedAtUnix === undefined || failure.startedAtUnix < attemptStartedAt));
|
|
15
|
+
}
|
|
16
|
+
function hasProtectedFreshRerun(failure, checks) {
|
|
17
|
+
return checks.some((check) => isProtectedByFreshRerun(failure, check));
|
|
18
|
+
}
|
|
19
|
+
function isProtectedFreshRerun(check, failures) {
|
|
20
|
+
const matchingFailures = failures.filter((failure) => matchesRerunCheck(failure, check));
|
|
21
|
+
return (matchingFailures.length > 0 &&
|
|
22
|
+
matchingFailures.every((failure) => isProtectedByFreshRerun(failure, check)));
|
|
23
|
+
}
|
|
24
|
+
function protectedFreshRerunIds(report) {
|
|
25
|
+
return new Set(report.checks.inProgress
|
|
26
|
+
.filter((check) => isProtectedFreshRerun(check, report.checks.failing))
|
|
27
|
+
.map((check) => check.runId)
|
|
28
|
+
.filter((id) => id !== null));
|
|
29
|
+
}
|
|
30
|
+
export function buildAutoCancelRunIdsWithOptions(report, opts = {}) {
|
|
31
|
+
return [
|
|
32
|
+
...new Set(report.checks.failing
|
|
33
|
+
.filter((check) => !hasProtectedFreshRerun(check, report.checks.inProgress))
|
|
34
|
+
.map((check) => check.runId)
|
|
35
|
+
.filter((id) => id !== null && !(opts.protectedRunIds?.has(id) ?? false))),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
export function buildInProgressRunIds(report, cancelledSet, opts = {}) {
|
|
39
|
+
const protectedRunIds = opts.suppressProtectedFreshReruns === false ? new Set() : protectedFreshRerunIds(report);
|
|
40
|
+
return [
|
|
41
|
+
...new Set(report.checks.inProgress
|
|
42
|
+
.map((check) => check.runId)
|
|
43
|
+
.filter((id) => id !== null &&
|
|
44
|
+
!cancelledSet.has(id) &&
|
|
45
|
+
!protectedRunIds.has(id) &&
|
|
46
|
+
!(opts.protectedRunIds?.has(id) ?? false))),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
export function buildRunProtection(checks, patterns = []) {
|
|
50
|
+
if (patterns.length === 0)
|
|
51
|
+
return { protectedRunIds: new Set(), protectedRuns: [] };
|
|
52
|
+
const matchers = patterns.map((pattern) => ({
|
|
53
|
+
pattern,
|
|
54
|
+
isMatch: picomatch(pattern, { nocase: true }),
|
|
55
|
+
}));
|
|
56
|
+
const byRunId = new Map();
|
|
57
|
+
for (const check of checks) {
|
|
58
|
+
if (check.runId === null)
|
|
59
|
+
continue;
|
|
60
|
+
const match = findProtectionMatch(check, matchers);
|
|
61
|
+
if (match === null)
|
|
62
|
+
continue;
|
|
63
|
+
addProtectedRun(byRunId, check.runId, check, match);
|
|
64
|
+
}
|
|
65
|
+
const protectedRuns = [...byRunId.values()];
|
|
66
|
+
return {
|
|
67
|
+
protectedRunIds: new Set(protectedRuns.map((run) => run.runId)),
|
|
68
|
+
protectedRuns,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function addProtectedRun(byRunId, runId, check, matchedPattern) {
|
|
72
|
+
const existing = byRunId.get(runId);
|
|
73
|
+
if (existing) {
|
|
74
|
+
updateProtectedRun(existing, check);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
byRunId.set(runId, createProtectedRun(runId, check, matchedPattern));
|
|
78
|
+
}
|
|
79
|
+
function updateProtectedRun(run, check) {
|
|
80
|
+
if (!run.checkNames.includes(check.name))
|
|
81
|
+
run.checkNames.push(check.name);
|
|
82
|
+
if (run.workflowName === undefined && check.workflowName !== undefined) {
|
|
83
|
+
run.workflowName = check.workflowName;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function createProtectedRun(runId, check, matchedPattern) {
|
|
87
|
+
return {
|
|
88
|
+
runId,
|
|
89
|
+
matchedPattern,
|
|
90
|
+
checkNames: [check.name],
|
|
91
|
+
...(check.workflowName !== undefined && { workflowName: check.workflowName }),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function findProtectionMatch(check, matchers) {
|
|
95
|
+
const candidates = [
|
|
96
|
+
check.workflowName,
|
|
97
|
+
"jobName" in check ? check.jobName : undefined,
|
|
98
|
+
check.name,
|
|
99
|
+
].filter((value) => value !== undefined && value.trim() !== "");
|
|
100
|
+
for (const matcher of matchers) {
|
|
101
|
+
if (candidates.some((candidate) => matcher.isMatch(candidate)))
|
|
102
|
+
return matcher.pattern;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
package/bin/config/load.mjs
CHANGED
|
@@ -57,6 +57,12 @@ function parseIgnoreChecks(value) {
|
|
|
57
57
|
}
|
|
58
58
|
return value;
|
|
59
59
|
}
|
|
60
|
+
function parseNeverCancelRuns(value) {
|
|
61
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
62
|
+
throw new Error(`Invalid config: actions.neverCancelRuns must be an array of strings`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
60
66
|
const defaults = builtins;
|
|
61
67
|
const configCache = new Map();
|
|
62
68
|
export function loadConfig() {
|
|
@@ -74,6 +80,7 @@ export function loadConfig() {
|
|
|
74
80
|
const config = deepMerge(defaults, parsed);
|
|
75
81
|
config.botUsernames = parseBotUsernames(config.botUsernames);
|
|
76
82
|
config.ignoreChecks = parseIgnoreChecks(config.ignoreChecks);
|
|
83
|
+
config.actions.neverCancelRuns = parseNeverCancelRuns(config.actions.neverCancelRuns);
|
|
77
84
|
config.iterate.minimizeComments = parseMinimizeCommentsPolicy(config.iterate.minimizeComments);
|
|
78
85
|
configCache.set(cwd, config);
|
|
79
86
|
return config;
|
package/bin/config.json
CHANGED
|
@@ -82,16 +82,14 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
82
82
|
const checks = rawCheckNodes.flatMap((node) => {
|
|
83
83
|
if (node.__typename === "CheckRun") {
|
|
84
84
|
const event = node.checkSuite?.workflowRun?.event ?? null;
|
|
85
|
+
const workflowName = node.checkSuite?.workflowRun?.workflow?.name?.trim() || undefined;
|
|
85
86
|
const runId = extractRunId(node.detailsUrl);
|
|
86
87
|
const summary = extractCheckRunSummary(node.title, node.summary);
|
|
87
|
-
const rawCreatedAt = node.checkSuite
|
|
88
|
-
|
|
89
|
-
: undefined;
|
|
90
|
-
const rawUpdatedAt = node.checkSuite
|
|
91
|
-
? (node.checkSuite.workflowRun?.updatedAt ?? node.checkSuite.updatedAt)
|
|
92
|
-
: undefined;
|
|
88
|
+
const rawCreatedAt = node.checkSuite?.workflowRun?.createdAt ?? node.checkSuite?.createdAt;
|
|
89
|
+
const rawUpdatedAt = node.checkSuite?.workflowRun?.updatedAt ?? node.checkSuite?.updatedAt;
|
|
93
90
|
const createdAtUnix = rawCreatedAt ? parseCreatedAt(rawCreatedAt) : undefined;
|
|
94
91
|
const startedAtUnix = node.startedAt ? parseCreatedAt(node.startedAt) : undefined;
|
|
92
|
+
const completedAtUnix = node.completedAt ? parseCreatedAt(node.completedAt) : undefined;
|
|
95
93
|
const updatedAtUnix = rawUpdatedAt ? parseCreatedAt(rawUpdatedAt) : undefined;
|
|
96
94
|
return [
|
|
97
95
|
{
|
|
@@ -103,8 +101,10 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
103
101
|
detailsUrl: node.detailsUrl ?? "",
|
|
104
102
|
event,
|
|
105
103
|
runId,
|
|
104
|
+
...(workflowName !== undefined && { workflowName }),
|
|
106
105
|
...(createdAtUnix !== undefined && { createdAtUnix }),
|
|
107
106
|
...(startedAtUnix !== undefined && { startedAtUnix }),
|
|
107
|
+
...(completedAtUnix !== undefined && { completedAtUnix }),
|
|
108
108
|
...(updatedAtUnix !== undefined && { updatedAtUnix }),
|
|
109
109
|
...(summary !== undefined && { summary }),
|
|
110
110
|
},
|
|
@@ -192,6 +192,7 @@ query BatchPr(
|
|
|
192
192
|
status
|
|
193
193
|
conclusion
|
|
194
194
|
detailsUrl
|
|
195
|
+
completedAt
|
|
195
196
|
startedAt
|
|
196
197
|
title
|
|
197
198
|
summary
|
|
@@ -202,6 +203,9 @@ query BatchPr(
|
|
|
202
203
|
event
|
|
203
204
|
createdAt
|
|
204
205
|
updatedAt
|
|
206
|
+
workflow {
|
|
207
|
+
name
|
|
208
|
+
}
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-shepherd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Ong",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@vitest/coverage-v8": "^4.1.4",
|
|
39
39
|
"husky": "^9.1.7",
|
|
40
40
|
"knip": "^6.14.1",
|
|
41
|
-
"oxfmt": "^0.
|
|
41
|
+
"oxfmt": "^0.53.0",
|
|
42
42
|
"oxlint": "^1.60.0",
|
|
43
43
|
"typescript": "^6.0.3",
|
|
44
44
|
"vitest": "^4.1.4"
|