pr-shepherd 0.32.1 → 0.32.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
3
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
4
- "version": "0.32.1",
4
+ "version": "0.32.2",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
@@ -7,8 +7,14 @@
7
7
  * to PR readiness.
8
8
  * 2. Drop checks with `conclusion == SKIPPED` or `conclusion == NEUTRAL` from the
9
9
  * pass/fail tally. Report them as "skipped" for transparency but don't block on them.
10
+ * 3. Reclassify `CANCELLED` checks as "superseded" (non-blocking) when a newer run of
11
+ * the same workflow exists on the same commit — this is GitHub's concurrency-group
12
+ * eviction behavior, not a real failure. GitHub branch protection itself resolves
13
+ * required status checks by latest-run-per-name and merges past these; mirroring
14
+ * that here keeps shepherd's verdict aligned with what GitHub will actually allow.
10
15
  */
11
16
  import { loadConfig } from "../config/load.mjs";
17
+ import { buildSupersededIndices } from "./superseded.mjs";
12
18
  import picomatch from "picomatch";
13
19
  /**
14
20
  * Classify a list of raw check runs into shepherd categories.
@@ -22,9 +28,19 @@ export function classifyChecks(checks) {
22
28
  const isIgnored = buildMatcher(config.ignoreChecks ?? []);
23
29
  const isProtected = buildMatcher(config.actions.neverCancelRuns ?? []);
24
30
  const protectedRunIds = buildProtectedRunIds(checks, isProtected);
25
- return checks.map((c) => isIgnored(c.name) && !isProtectedCheck(c, protectedRunIds)
26
- ? { ...c, category: "ignored" }
27
- : classify(c, relevantEvents));
31
+ const supersededIndices = buildSupersededIndices(checks);
32
+ return checks.map((c, index) => {
33
+ if (isIgnored(c.name) && !isProtectedCheck(c, protectedRunIds)) {
34
+ return { ...c, category: "ignored" };
35
+ }
36
+ const classified = classify(c, relevantEvents);
37
+ // Only ever override a "failing" verdict (i.e. conclusion === CANCELLED, guaranteed by
38
+ // buildSupersededIndices below) — never touch filtered/skipped/passed classifications.
39
+ if (classified.category === "failing" && supersededIndices.has(index)) {
40
+ return { ...classified, category: "superseded" };
41
+ }
42
+ return classified;
43
+ });
28
44
  }
29
45
  function buildMatcher(patterns) {
30
46
  if (patterns.length === 0)
@@ -74,7 +90,10 @@ function classify(check, relevantEvents) {
74
90
  }
75
91
  /** Compute a high-level CI verdict from a list of classified checks. */
76
92
  export function getCiVerdict(classified) {
77
- const relevant = classified.filter((c) => c.category !== "filtered" && c.category !== "skipped" && c.category !== "ignored");
93
+ const relevant = classified.filter((c) => c.category !== "filtered" &&
94
+ c.category !== "skipped" &&
95
+ c.category !== "ignored" &&
96
+ c.category !== "superseded");
78
97
  const anyInProgress = relevant.some((c) => c.category === "in_progress");
79
98
  const anyFailing = relevant.some((c) => c.category === "failing");
80
99
  // When there are no relevant checks (e.g. docs-only PR where all checks are filtered/skipped),
@@ -83,5 +102,14 @@ export function getCiVerdict(classified) {
83
102
  const hasChecks = relevant.length > 0;
84
103
  const filteredNames = classified.filter((c) => c.category === "filtered").map((c) => c.name);
85
104
  const ignoredNames = Array.from(new Set(classified.filter((c) => c.category === "ignored").map((c) => c.name)));
86
- return { allPassed, hasChecks, anyInProgress, anyFailing, filteredNames, ignoredNames };
105
+ const supersededNames = Array.from(new Set(classified.filter((c) => c.category === "superseded").map((c) => c.name)));
106
+ return {
107
+ allPassed,
108
+ hasChecks,
109
+ anyInProgress,
110
+ anyFailing,
111
+ filteredNames,
112
+ ignoredNames,
113
+ supersededNames,
114
+ };
87
115
  }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Detects check runs that are `CANCELLED` because a newer run of the *same workflow*
3
+ * superseded them on the same commit (concurrency-group eviction), rather than a genuine
4
+ * cancellation. Split out of classify.mts to stay under the file-length cap.
5
+ */
6
+ /** Grouping key for a check's workflow: numeric `workflowId`, falling back to `workflowName`. */
7
+ function workflowKeyOf(check) {
8
+ return check.workflowId ?? check.workflowName;
9
+ }
10
+ /**
11
+ * Grouping key is `workflowId ?? workflowName` — the numeric GitHub Actions workflow database
12
+ * ID when available, falling back to the display name. Checks with neither a workflow identity
13
+ * nor a numeric `runId` (status contexts, startup-failure synthetics) never participate: they
14
+ * can neither be marked superseded nor count as evidence of a newer run.
15
+ *
16
+ * A check is superseded iff its own conclusion is `CANCELLED` and some other check sharing its
17
+ * workflow key has a strictly greater `runId`. The newest run for a workflow is therefore never
18
+ * superseded, even if it is itself cancelled — that case stays "failing" so the agent can decide
19
+ * whether to rerun it.
20
+ *
21
+ * @returns Indices into `checks` (not object identities, since check-run objects are not
22
+ * deduplicated by reference elsewhere) that should be reclassified as "superseded".
23
+ */
24
+ export function buildSupersededIndices(checks) {
25
+ const runIdByIndex = new Map();
26
+ const maxRunIdByWorkflow = new Map();
27
+ checks.forEach((check, index) => {
28
+ const workflowKey = workflowKeyOf(check);
29
+ if (workflowKey === undefined || check.runId === null)
30
+ return;
31
+ const runIdNum = Number(check.runId);
32
+ if (!Number.isFinite(runIdNum))
33
+ return;
34
+ runIdByIndex.set(index, runIdNum);
35
+ const currentMax = maxRunIdByWorkflow.get(workflowKey);
36
+ if (currentMax === undefined || runIdNum > currentMax) {
37
+ maxRunIdByWorkflow.set(workflowKey, runIdNum);
38
+ }
39
+ });
40
+ const superseded = new Set();
41
+ checks.forEach((check, index) => {
42
+ if (check.conclusion !== "CANCELLED")
43
+ return;
44
+ const runIdNum = runIdByIndex.get(index);
45
+ if (runIdNum === undefined)
46
+ return;
47
+ // workflowKeyOf(check) is guaranteed defined here, with a corresponding entry in
48
+ // maxRunIdByWorkflow: runIdByIndex is only ever populated in the loop above alongside a
49
+ // maxRunIdByWorkflow entry for that same workflow key (at minimum, this check's own
50
+ // runIdNum) — the two maps are always updated together for a given index. A defensive
51
+ // undefined-check here would therefore guard a branch no input can ever exercise, which
52
+ // would silently fail this repo's 100%-coverage requirement instead of catching a real bug.
53
+ const maxRunId = maxRunIdByWorkflow.get(workflowKeyOf(check));
54
+ if (maxRunId > runIdNum) {
55
+ superseded.add(index);
56
+ }
57
+ });
58
+ return superseded;
59
+ }
@@ -58,7 +58,7 @@ export function formatIterateResult(result, opts) {
58
58
  else if (result.mergeStatus === "CONFLICTS" && result.baseBranch) {
59
59
  verboseBranch = ` · **branch** conflicts with \`origin/${result.baseBranch}\``;
60
60
  }
61
- summaryLine = `**summary** ${result.summary.passing} passing, ${result.summary.skipped} skipped, ${result.summary.filtered} filtered, ${result.summary.inProgress} inProgress · **remainingSeconds** ${result.remainingSeconds} · **blockingBotReviewInProgress** ${result.blockingBotReviewInProgress} · **isDraft** ${result.isDraft} · **shouldCancel** ${result.shouldCancel}${verboseBranch}`;
61
+ summaryLine = `**summary** ${result.summary.passing} passing, ${result.summary.skipped} skipped, ${result.summary.filtered} filtered, ${result.summary.inProgress} inProgress, ${result.summary.superseded} superseded · **remainingSeconds** ${result.remainingSeconds} · **blockingBotReviewInProgress** ${result.blockingBotReviewInProgress} · **isDraft** ${result.isDraft} · **shouldCancel** ${result.shouldCancel}${verboseBranch}`;
62
62
  }
63
63
  else {
64
64
  const counts = [`${result.summary.passing} passing`];
@@ -68,6 +68,8 @@ export function formatIterateResult(result, opts) {
68
68
  counts.push(`${result.summary.filtered} filtered`);
69
69
  if (result.summary.inProgress > 0)
70
70
  counts.push(`${result.summary.inProgress} inProgress`);
71
+ if (result.summary.superseded > 0)
72
+ counts.push(`${result.summary.superseded} superseded`);
71
73
  const segs = [`**summary** ${counts.join(", ")}`];
72
74
  if (result.status === "READY" && result.remainingSeconds > 0) {
73
75
  segs.push(`**remainingSeconds** ${result.remainingSeconds}`);
@@ -116,6 +118,10 @@ export function formatIterateResult(result, opts) {
116
118
  const names = result.ignoredNames.map((n) => "`" + n + "`").join(", ");
117
119
  headerLines.push(`**ignored** ${names}`);
118
120
  }
121
+ if (result.supersededNames && result.supersededNames.length > 0) {
122
+ const names = result.supersededNames.map((n) => "`" + n + "`").join(", ");
123
+ headerLines.push(`**superseded** ${names}`);
124
+ }
119
125
  const activityLine = formatActivityLine(result);
120
126
  if (activityLine)
121
127
  headerLines.push(activityLine);
@@ -141,6 +147,10 @@ export function formatIterateResult(result, opts) {
141
147
  const ignoredStr = result.ignoredNames.map((n) => "`" + n + "`").join(", ");
142
148
  cancelHeaderLines.push(`**ignored** ${ignoredStr}`);
143
149
  }
150
+ if (result.supersededNames && result.supersededNames.length > 0) {
151
+ const supersededStr = result.supersededNames.map((n) => "`" + n + "`").join(", ");
152
+ cancelHeaderLines.push(`**superseded** ${supersededStr}`);
153
+ }
144
154
  if (activityLine)
145
155
  cancelHeaderLines.push(activityLine);
146
156
  return joinSections([
@@ -33,6 +33,7 @@ export function projectIterateLean(result, opts) {
33
33
  ...(result.summary.skipped > 0 && { skipped: result.summary.skipped }),
34
34
  ...(result.summary.filtered > 0 && { filtered: result.summary.filtered }),
35
35
  ...(result.summary.inProgress > 0 && { inProgress: result.summary.inProgress }),
36
+ ...(result.summary.superseded > 0 && { superseded: result.summary.superseded }),
36
37
  },
37
38
  ...(result.status === "READY" &&
38
39
  result.remainingSeconds > 0 && {
@@ -58,6 +59,9 @@ export function projectIterateLean(result, opts) {
58
59
  ...((result.ignoredNames?.length ?? 0) > 0 && {
59
60
  ignoredNames: result.ignoredNames,
60
61
  }),
62
+ ...((result.supersededNames?.length ?? 0) > 0 && {
63
+ supersededNames: result.supersededNames,
64
+ }),
61
65
  };
62
66
  switch (result.action) {
63
67
  case "wait":
@@ -127,6 +127,7 @@ export async function runCheck(opts) {
127
127
  filteredNames: verdict.filteredNames,
128
128
  blockedByFilteredCheck,
129
129
  ...(verdict.ignoredNames.length > 0 && { ignoredNames: verdict.ignoredNames }),
130
+ ...(verdict.supersededNames.length > 0 && { supersededNames: verdict.supersededNames }),
130
131
  },
131
132
  threads: {
132
133
  actionable: threadVisibility.activeThreads,
@@ -21,7 +21,7 @@ export function buildFailingCheckInstructions(checks) {
21
21
  parts.push("read any included log excerpt first; fetch the full log with `gh run view <runId> --log-failed` when the excerpt is insufficient; decide whether to rerun with `gh run rerun <runId> --failed` for transient infrastructure failures (network timeout, OOM kill, runner crash), or apply a code fix for real test/build failures; if GitHub omits workflow-evaluation details from API/log output, open the run URL in the GitHub UI");
22
22
  }
23
23
  if (hasCancelled) {
24
- parts.push("for `[conclusion: CANCELLED]` entries: rerun with `gh run rerun <runId>` if the cancellation looks unintended (not superseded by a newer push or concurrency-group eviction); otherwise treat as resolved — do NOT confuse with IDs under `## Cancelled runs`");
24
+ parts.push("for `[conclusion: CANCELLED]` entries: these are not concurrency-superseded (superseded CANCELLED checks are excluded from this section and reported under `**superseded**` instead) — rerun with `gh run rerun <runId>` unless you are already pushing new commits this tick for other reasons, in which case the fresh run naturally supersedes it; do not silently treat a required CANCELLED check as resolved — do NOT confuse with IDs under `## Cancelled runs`");
25
25
  }
26
26
  if (hasStartupFailure) {
27
27
  parts.push("for `[conclusion: STARTUP_FAILURE]` entries: inspect with `gh run view <runId>` and rerun with `gh run rerun <runId>` if the workflow should be retried");
@@ -9,6 +9,43 @@ export function buildSummary(report) {
9
9
  skipped: report.checks.skipped.length,
10
10
  filtered: report.checks.filtered.length,
11
11
  inProgress: report.checks.inProgress.length,
12
+ superseded: report.checks.supersededNames?.length ?? 0,
13
+ };
14
+ }
15
+ /** Non-blocking check-name lists (ignored/superseded), omitted from the result when empty. */
16
+ export function buildSuppressedCheckFields(report) {
17
+ return {
18
+ ...(report.checks.ignoredNames?.length ? { ignoredNames: report.checks.ignoredNames } : {}),
19
+ ...(report.checks.supersededNames?.length
20
+ ? { supersededNames: report.checks.supersededNames }
21
+ : {}),
22
+ };
23
+ }
24
+ /** Build the `cancel` result for a merged/closed PR — caller has already updated ready-delay/stall state. */
25
+ export function buildTerminalCancelResult(report) {
26
+ const state = report.mergeStatus.state;
27
+ return {
28
+ pr: report.pr,
29
+ repo: report.repo,
30
+ status: report.status,
31
+ mergeStateStatus: report.mergeStatus.mergeStateStatus,
32
+ mergeStatus: report.mergeStatus.status,
33
+ reviewDecision: report.mergeStatus.reviewDecision,
34
+ blockingBotReviewInProgress: report.mergeStatus.blockingBotReviewInProgress,
35
+ isDraft: report.mergeStatus.isDraft,
36
+ shouldCancel: true,
37
+ remainingSeconds: 0,
38
+ state,
39
+ summary: buildSummary(report),
40
+ baseBranch: report.baseBranch,
41
+ branchProtection: report.branchProtection,
42
+ checks: buildRelevantChecks(report),
43
+ inProgressChecks: buildActiveChecks(report),
44
+ ...buildSuppressedCheckFields(report),
45
+ activity: report.activity,
46
+ action: "cancel",
47
+ reason: state === "MERGED" ? "merged" : "closed",
48
+ log: `CANCEL: PR #${report.pr} is ${state.toLowerCase()} — stopping`,
12
49
  };
13
50
  }
14
51
  /** Build completed, non-skipped checks relevant to PR readiness. */
@@ -4,7 +4,7 @@ import { getCurrentPrNumber } from "../../github/client.mjs";
4
4
  import { graphql } from "../../github/http.mjs";
5
5
  import { MARK_PR_READY_MUTATION } from "../../github/queries.mjs";
6
6
  import { loadConfig } from "../../config/load.mjs";
7
- import { getCurrentHeadSha, buildSummary, buildRelevantChecks, buildActiveChecks, buildWaitLog, } from "./helpers.mjs";
7
+ import { getCurrentHeadSha, buildSummary, buildRelevantChecks, buildActiveChecks, buildWaitLog, buildSuppressedCheckFields, buildTerminalCancelResult, } from "./helpers.mjs";
8
8
  import { classifyReviewSummaries } from "./classify.mjs";
9
9
  import { applyStallGuard } from "./stall.mjs";
10
10
  import { clearStallState } from "../../state/iterate-stall.mjs";
@@ -31,32 +31,9 @@ export async function runIterate(opts) {
31
31
  }
32
32
  const stallKey = { owner: repoOwner, repo: repoName, pr: prNumber };
33
33
  if (report.mergeStatus.state !== "OPEN") {
34
- const state = report.mergeStatus.state.toLowerCase();
35
34
  await updateReadyDelay(report.pr, false, readyDelaySeconds, repoOwner, repoName);
36
35
  await clearStallState(stallKey);
37
- return {
38
- pr: report.pr,
39
- repo: report.repo,
40
- status: report.status,
41
- mergeStateStatus: report.mergeStatus.mergeStateStatus,
42
- mergeStatus: report.mergeStatus.status,
43
- reviewDecision: report.mergeStatus.reviewDecision,
44
- blockingBotReviewInProgress: report.mergeStatus.blockingBotReviewInProgress,
45
- isDraft: report.mergeStatus.isDraft,
46
- shouldCancel: true,
47
- remainingSeconds: 0,
48
- state: report.mergeStatus.state,
49
- summary: buildSummary(report),
50
- baseBranch: report.baseBranch,
51
- branchProtection: report.branchProtection,
52
- checks: buildRelevantChecks(report),
53
- inProgressChecks: buildActiveChecks(report),
54
- ...(report.checks.ignoredNames?.length ? { ignoredNames: report.checks.ignoredNames } : {}),
55
- activity: report.activity,
56
- action: "cancel",
57
- reason: report.mergeStatus.state === "MERGED" ? "merged" : "closed",
58
- log: `CANCEL: PR #${report.pr} is ${state} — stopping`,
59
- };
36
+ return buildTerminalCancelResult(report);
60
37
  }
61
38
  const { minimizeIds: reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, } = classifyReviewSummaries({
62
39
  firstLook: report.firstLookSummaries,
@@ -95,7 +72,7 @@ export async function runIterate(opts) {
95
72
  branchProtection: report.branchProtection,
96
73
  checks: buildRelevantChecks(report),
97
74
  inProgressChecks: buildActiveChecks(report),
98
- ...(report.checks.ignoredNames?.length ? { ignoredNames: report.checks.ignoredNames } : {}),
75
+ ...buildSuppressedCheckFields(report),
99
76
  activity: report.activity,
100
77
  };
101
78
  if (readyState.shouldCancel) {
@@ -6,13 +6,48 @@ export function parseCreatedAt(iso) {
6
6
  const ms = new Date(iso).getTime();
7
7
  return Number.isFinite(ms) ? Math.floor(ms / 1000) : 0;
8
8
  }
9
- export function extractRunId(url) {
9
+ function extractRunId(url) {
10
10
  if (!url)
11
11
  return null;
12
12
  const m = /\/runs\/(\d+)/.exec(url);
13
13
  return m ? (m[1] ?? null) : null;
14
14
  }
15
- export function extractCheckRunSummary(title, summary) {
15
+ /** Stringify a GraphQL Workflow.databaseId, when present, for use as a check-grouping key. */
16
+ function resolveWorkflowId(databaseId) {
17
+ return databaseId !== null && databaseId !== undefined ? String(databaseId) : undefined;
18
+ }
19
+ /** Map a GraphQL CheckRun context node to a CheckRun. */
20
+ export function mapCheckRunNode(node) {
21
+ const event = node.checkSuite?.workflowRun?.event ?? null;
22
+ const workflowName = node.checkSuite?.workflowRun?.workflow?.name?.trim() || undefined;
23
+ const workflowId = resolveWorkflowId(node.checkSuite?.workflowRun?.workflow?.databaseId);
24
+ const runId = extractRunId(node.detailsUrl);
25
+ const summary = extractCheckRunSummary(node.title, node.summary);
26
+ const rawCreatedAt = node.checkSuite?.workflowRun?.createdAt ?? node.checkSuite?.createdAt;
27
+ const rawUpdatedAt = node.checkSuite?.workflowRun?.updatedAt ?? node.checkSuite?.updatedAt;
28
+ const createdAtUnix = rawCreatedAt ? parseCreatedAt(rawCreatedAt) : undefined;
29
+ const startedAtUnix = node.startedAt ? parseCreatedAt(node.startedAt) : undefined;
30
+ const completedAtUnix = node.completedAt ? parseCreatedAt(node.completedAt) : undefined;
31
+ const updatedAtUnix = rawUpdatedAt ? parseCreatedAt(rawUpdatedAt) : undefined;
32
+ return {
33
+ id: node.id,
34
+ name: node.name,
35
+ status: node.status,
36
+ conclusion: node.conclusion,
37
+ source: "check_run",
38
+ detailsUrl: node.detailsUrl ?? "",
39
+ event,
40
+ runId,
41
+ ...(workflowName !== undefined && { workflowName }),
42
+ ...(workflowId !== undefined && { workflowId }),
43
+ ...(createdAtUnix !== undefined && { createdAtUnix }),
44
+ ...(startedAtUnix !== undefined && { startedAtUnix }),
45
+ ...(completedAtUnix !== undefined && { completedAtUnix }),
46
+ ...(updatedAtUnix !== undefined && { updatedAtUnix }),
47
+ ...(summary !== undefined && { summary }),
48
+ };
49
+ }
50
+ function extractCheckRunSummary(title, summary) {
16
51
  const t = title?.trim();
17
52
  if (t)
18
53
  return t;
@@ -1,4 +1,4 @@
1
- import { mapAuthorType, parseCreatedAt, extractRunId, extractCheckRunSummary, mapStatusContextState, latestApprovedLogins, isReviewStale, } from "./batch-parser-helpers.mjs";
1
+ import { mapAuthorType, parseCreatedAt, mapStatusContextState, latestApprovedLogins, isReviewStale, mapCheckRunNode, } from "./batch-parser-helpers.mjs";
2
2
  import { buildPrActivitySummary } from "./activity.mjs";
3
3
  import { parseBranchProtection } from "./branch-protection.mjs";
4
4
  function parseReviewNode(r) {
@@ -81,34 +81,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
81
81
  .map((r) => parseReviewNode(r));
82
82
  const checks = rawCheckNodes.flatMap((node) => {
83
83
  if (node.__typename === "CheckRun") {
84
- const event = node.checkSuite?.workflowRun?.event ?? null;
85
- const workflowName = node.checkSuite?.workflowRun?.workflow?.name?.trim() || undefined;
86
- const runId = extractRunId(node.detailsUrl);
87
- const summary = extractCheckRunSummary(node.title, node.summary);
88
- const rawCreatedAt = node.checkSuite?.workflowRun?.createdAt ?? node.checkSuite?.createdAt;
89
- const rawUpdatedAt = node.checkSuite?.workflowRun?.updatedAt ?? node.checkSuite?.updatedAt;
90
- const createdAtUnix = rawCreatedAt ? parseCreatedAt(rawCreatedAt) : undefined;
91
- const startedAtUnix = node.startedAt ? parseCreatedAt(node.startedAt) : undefined;
92
- const completedAtUnix = node.completedAt ? parseCreatedAt(node.completedAt) : undefined;
93
- const updatedAtUnix = rawUpdatedAt ? parseCreatedAt(rawUpdatedAt) : undefined;
94
- return [
95
- {
96
- id: node.id,
97
- name: node.name,
98
- status: node.status,
99
- conclusion: node.conclusion,
100
- source: "check_run",
101
- detailsUrl: node.detailsUrl ?? "",
102
- event,
103
- runId,
104
- ...(workflowName !== undefined && { workflowName }),
105
- ...(createdAtUnix !== undefined && { createdAtUnix }),
106
- ...(startedAtUnix !== undefined && { startedAtUnix }),
107
- ...(completedAtUnix !== undefined && { completedAtUnix }),
108
- ...(updatedAtUnix !== undefined && { updatedAtUnix }),
109
- ...(summary !== undefined && { summary }),
110
- },
111
- ];
84
+ return [mapCheckRunNode(node)];
112
85
  }
113
86
  if (node.__typename === "StatusContext") {
114
87
  const { status, conclusion } = mapStatusContextState(node.state);
@@ -205,6 +205,7 @@ query BatchPr(
205
205
  updatedAt
206
206
  workflow {
207
207
  name
208
+ databaseId
208
209
  }
209
210
  }
210
211
  }
@@ -0,0 +1,2 @@
1
+ // Check-run classification types, split out of github.mts to stay under the file-length cap.
2
+ export {};
@@ -1,2 +1,4 @@
1
1
  // GitHub primitives, check runs, review threads, and batch PR data types.
2
+ // Check classification types (CheckCategory/ClassifiedCheck/TriagedCheck) live in
3
+ // check-classification.mts to stay under the file-length cap.
2
4
  export {};
package/bin/types.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  /** Shared type definitions for the shepherd CLI. */
2
2
  export * from "./types/github.mjs";
3
+ export * from "./types/check-classification.mjs";
3
4
  export * from "./types/activity.mjs";
4
5
  export * from "./types/review-thread.mjs";
5
6
  export * from "./types/agent-thread.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.32.1",
3
+ "version": "0.32.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.32.1",
3
+ "version": "0.32.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",