pr-shepherd 0.34.0 → 0.35.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 +3 -2
- package/.grok-plugin/marketplace.json +17 -0
- package/README.md +56 -39
- package/bin/api.d.mts +80 -0
- package/bin/api.mjs +236 -0
- package/bin/checks/classify.d.mts +41 -0
- package/bin/checks/startup-failures.d.mts +2 -0
- package/bin/checks/superseded.d.mts +21 -0
- package/bin/checks/triage.d.mts +4 -0
- package/bin/classify/apply.d.mts +18 -0
- package/bin/classify/apply.mjs +4 -0
- package/bin/classify/loader.d.mts +10 -0
- package/bin/classify/types.d.mts +35 -0
- package/bin/cli/args.d.mts +18 -0
- package/bin/cli/clean-formatter.d.mts +2 -0
- package/bin/cli/default-poll.d.mts +2 -0
- package/bin/cli/duration-flag.d.mts +2 -0
- package/bin/cli/duration.d.mts +13 -0
- package/bin/cli/fence.d.mts +1 -0
- package/bin/cli/fix-formatter-extra.d.mts +3 -0
- package/bin/cli/fix-formatter.d.mts +2 -0
- package/bin/cli/fix-formatter.mjs +6 -6
- package/bin/cli/formatters.d.mts +7 -0
- package/bin/cli/handlers.d.mts +4 -0
- package/bin/cli/handlers.mjs +11 -10
- package/bin/cli/help-command-pages.d.mts +231 -0
- package/bin/cli/help-command-pages.mjs +106 -1
- package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
- package/bin/cli/help-iterate-poll-pages.mjs +4 -2
- package/bin/cli/help-log-file-page.d.mts +1 -0
- package/bin/cli/help-top-page.d.mts +1 -0
- package/bin/cli/help-top-page.mjs +14 -15
- package/bin/cli/help.d.mts +236 -0
- package/bin/cli/help.mjs +17 -0
- package/bin/cli/iterate-emitter.d.mts +8 -0
- package/bin/cli/iterate-flags.d.mts +11 -0
- package/bin/cli/iterate-formatter.d.mts +19 -0
- package/bin/cli/iterate-instructions.d.mts +6 -0
- package/bin/cli/iterate-lean.d.mts +12 -0
- package/bin/cli/journal-formatter.d.mts +2 -0
- package/bin/cli/journal-formatter.mjs +15 -0
- package/bin/cli/journal-handler.d.mts +1 -0
- package/bin/cli/journal-handler.mjs +7 -20
- package/bin/cli/list-formatters.d.mts +76 -0
- package/bin/cli/list-formatters.mjs +9 -9
- package/bin/cli/mark-files-as-viewed-flags.d.mts +11 -0
- package/bin/cli/mark-files-as-viewed-formatter.d.mts +2 -0
- package/bin/cli/mutate-formatter.d.mts +2 -0
- package/bin/cli/poll-handler.d.mts +1 -0
- package/bin/cli/poll-handler.mjs +2 -2
- package/bin/cli/resolve-validators.d.mts +3 -0
- package/bin/cli/resolve-validators.mjs +3 -3
- package/bin/cli/runner.d.mts +7 -0
- package/bin/cli/suggestion-renderer.d.mts +3 -0
- package/bin/cli/validate-default-args.d.mts +6 -0
- package/bin/cli-parser.d.mts +2 -0
- package/bin/cli-parser.mjs +72 -16
- package/bin/commands/check-annotations.d.mts +5 -0
- package/bin/commands/check-status.d.mts +3 -0
- package/bin/commands/check-terminal-report.d.mts +5 -0
- package/bin/commands/check.d.mts +6 -0
- package/bin/commands/check.mjs +2 -1
- package/bin/commands/clean.d.mts +21 -0
- package/bin/commands/commit-suggestion-instruction.d.mts +8 -0
- package/bin/commands/commit-suggestion-instruction.mjs +3 -3
- package/bin/commands/commit-suggestion.d.mts +8 -0
- package/bin/commands/commit-suggestion.mjs +11 -10
- package/bin/commands/iterate/check-instructions.d.mts +16 -0
- package/bin/commands/iterate/check-instructions.mjs +3 -3
- package/bin/commands/iterate/classify.d.mts +18 -0
- package/bin/commands/iterate/classify.mjs +4 -4
- package/bin/commands/iterate/escalate.d.mts +31 -0
- package/bin/commands/iterate/escalate.mjs +7 -4
- package/bin/commands/iterate/fix-code.d.mts +25 -0
- package/bin/commands/iterate/helpers.d.mts +13 -0
- package/bin/commands/iterate/helpers.mjs +4 -1
- package/bin/commands/iterate/index.d.mts +2 -0
- package/bin/commands/iterate/index.mjs +5 -2
- package/bin/commands/iterate/render.d.mts +5 -0
- package/bin/commands/iterate/render.mjs +8 -6
- package/bin/commands/iterate/reruns.d.mts +20 -0
- package/bin/commands/iterate/stall.d.mts +6 -0
- package/bin/commands/journal/index.d.mts +14 -0
- package/bin/commands/journal/index.mjs +1 -0
- package/bin/commands/journal/transform.d.mts +22 -0
- package/bin/commands/log-file.d.mts +5 -0
- package/bin/commands/mark-files-as-viewed.d.mts +26 -0
- package/bin/commands/mark-files-as-viewed.mjs +1 -0
- package/bin/commands/poll.d.mts +10 -0
- package/bin/commands/poll.mjs +1 -0
- package/bin/commands/ready-delay.d.mts +29 -0
- package/bin/commands/ready-mergeability.d.mts +15 -0
- package/bin/commands/resolve-mutate.d.mts +4 -0
- package/bin/commands/resolve-mutate.mjs +1 -0
- package/bin/commands/resolve.d.mts +4 -0
- package/bin/commands/shepherd-journal.d.mts +7 -0
- package/bin/commands/shepherd-journal.mjs +2 -2
- package/bin/comments/authors.d.mts +14 -0
- package/bin/comments/marker.d.mts +2 -0
- package/bin/comments/minimize-policy.d.mts +4 -0
- package/bin/comments/pending-ops.d.mts +15 -0
- package/bin/comments/rate-limit.d.mts +18 -0
- package/bin/comments/resolve.d.mts +34 -0
- package/bin/comments/resolve.mjs +1 -0
- package/bin/comments/review-thread-markers.d.mts +8 -0
- package/bin/comments/review-visibility.d.mts +28 -0
- package/bin/comments/sha-poll.d.mts +2 -0
- package/bin/comments/thread-visibility.d.mts +11 -0
- package/bin/comments/visible-comments.d.mts +11 -0
- package/bin/config/load.d.mts +60 -0
- package/bin/config/load.mjs +72 -1
- package/bin/config.json +0 -2
- package/bin/execution-context.d.mts +9 -0
- package/bin/execution-context.mjs +19 -0
- package/bin/exit-codes.d.mts +51 -0
- package/bin/github/activity.d.mts +3 -0
- package/bin/github/activity.mjs +7 -0
- package/bin/github/batch-parser-helpers.d.mts +22 -0
- package/bin/github/batch-parsers.d.mts +3 -0
- package/bin/github/batch-parsers.mjs +6 -0
- package/bin/github/batch-raw-types.d.mts +207 -0
- package/bin/github/batch-response.d.mts +4 -0
- package/bin/github/batch.d.mts +22 -0
- package/bin/github/branch-protection.d.mts +3 -0
- package/bin/github/check-annotations.d.mts +2 -0
- package/bin/github/client.d.mts +46 -0
- package/bin/github/client.mjs +7 -2
- package/bin/github/errors.d.mts +25 -0
- package/bin/github/gql/batch-pr.gql +5 -0
- package/bin/github/gql/review-thread-comments.gql +1 -0
- package/bin/github/graphql-http.d.mts +19 -0
- package/bin/github/graphql-response.d.mts +7 -0
- package/bin/github/http-auth.d.mts +4 -0
- package/bin/github/http-request.d.mts +7 -0
- package/bin/github/http-utils.d.mts +11 -0
- package/bin/github/http.d.mts +5 -0
- package/bin/github/pagination.d.mts +45 -0
- package/bin/github/queries.d.mts +24 -0
- package/bin/github/rest-http.d.mts +2 -0
- package/bin/github/thread-comments.d.mts +2 -0
- package/bin/index.d.mts +10 -0
- package/bin/index.mjs +1 -1
- package/bin/log/log-file.d.mts +28 -0
- package/bin/log/session.d.mts +31 -0
- package/bin/log/setup.d.mts +6 -0
- package/bin/mcp/index.d.mts +5 -0
- package/bin/mcp/index.mjs +8 -0
- package/bin/mcp/server.d.mts +8 -0
- package/bin/mcp/server.mjs +157 -0
- package/bin/mcp-stdio.d.mts +2 -0
- package/bin/mcp-stdio.mjs +7 -0
- package/bin/merge-status/derive.d.mts +19 -0
- package/bin/reporters/agent.d.mts +23 -0
- package/bin/reporters/agent.mjs +3 -0
- package/bin/state/base.d.mts +1 -0
- package/bin/state/bot-cr-seen.d.mts +51 -0
- package/bin/state/bot-cr-seen.mjs +1 -1
- package/bin/state/fix-attempts.d.mts +27 -0
- package/bin/state/iterate-stall.d.mts +27 -0
- package/bin/state/seen-comments.d.mts +62 -0
- package/bin/suggestions/extract.d.mts +8 -0
- package/bin/suggestions/parse.d.mts +48 -0
- package/bin/suggestions/patch.d.mts +14 -0
- package/bin/threads/transcript.d.mts +14 -0
- package/bin/threads/transcript.mjs +4 -0
- package/bin/types/activity.d.mts +30 -0
- package/bin/types/agent-thread.d.mts +9 -0
- package/bin/types/check-annotations.d.mts +14 -0
- package/bin/types/check-classification.d.mts +19 -0
- package/bin/types/github.d.mts +139 -0
- package/bin/types/iterate.d.mts +157 -0
- package/bin/types/protected-run.d.mts +6 -0
- package/bin/types/report.d.mts +176 -0
- package/bin/types/review-thread.d.mts +12 -0
- package/bin/types.d.mts +9 -0
- package/bin/util/markdown.d.mts +1 -0
- package/bin/util/path-segment.d.mts +2 -0
- package/bin/util/sleep.d.mts +1 -0
- package/bin/util/worktree.d.mts +9 -0
- package/bin/util/worktree.mjs +4 -1
- package/package.json +51 -37
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
- package/plugins/pr-shepherd/.codex.mcp.json +8 -0
- package/plugins/pr-shepherd/.mcp.json +6 -0
- package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
- package/src/classify/types.mts +12 -0
package/bin/config/load.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import { readFileSync, statSync } from "node:fs";
|
|
2
3
|
import { join, dirname } from "node:path";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { parse } from "yaml";
|
|
5
6
|
import builtins from "../config.json" with { type: "json" };
|
|
7
|
+
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
6
8
|
const MINIMIZE_COMMENTS_POLICIES = ["all", "bots", "users", "none"];
|
|
7
9
|
const RC_FILENAME = ".pr-shepherdrc.yml";
|
|
8
10
|
function findRcFile(startDir) {
|
|
@@ -41,6 +43,10 @@ function isMinimizeCommentsPolicy(value) {
|
|
|
41
43
|
return MINIMIZE_COMMENTS_POLICIES.some((policy) => policy === value);
|
|
42
44
|
}
|
|
43
45
|
function parseMinimizeCommentsPolicy(value) {
|
|
46
|
+
if (value === "users") {
|
|
47
|
+
process.stderr.write('pr-shepherd: config: iterate.minimizeComments: "users" is deprecated and is now treated as "none".\n');
|
|
48
|
+
return "none";
|
|
49
|
+
}
|
|
44
50
|
if (isMinimizeCommentsPolicy(value))
|
|
45
51
|
return value;
|
|
46
52
|
throw new Error(`Invalid config: iterate.minimizeComments must be one of "all", "bots", "users", or "none", got ${JSON.stringify(value)}`);
|
|
@@ -63,10 +69,64 @@ function parseNeverCancelRuns(value) {
|
|
|
63
69
|
}
|
|
64
70
|
return value;
|
|
65
71
|
}
|
|
72
|
+
const KNOWN_CONFIG_KEYS = new Set([
|
|
73
|
+
"classify",
|
|
74
|
+
"botUsernames",
|
|
75
|
+
"ignoreChecks",
|
|
76
|
+
"iterate",
|
|
77
|
+
"watch",
|
|
78
|
+
"resolve",
|
|
79
|
+
"checks",
|
|
80
|
+
"mergeStatus",
|
|
81
|
+
"actions",
|
|
82
|
+
]);
|
|
83
|
+
const KNOWN_NESTED_KEYS = {
|
|
84
|
+
iterate: new Set([
|
|
85
|
+
"fixAttemptsPerThread",
|
|
86
|
+
"stallTimeoutMinutes",
|
|
87
|
+
"minimizeApprovals",
|
|
88
|
+
"minimizeComments",
|
|
89
|
+
"behindBaseHint",
|
|
90
|
+
]),
|
|
91
|
+
watch: new Set(["readyDelayMinutes"]),
|
|
92
|
+
resolve: new Set(["shaPoll"]),
|
|
93
|
+
checks: new Set(["ciTriggerEvents"]),
|
|
94
|
+
mergeStatus: new Set(["blockingReviewerLogins"]),
|
|
95
|
+
actions: new Set([
|
|
96
|
+
"autoMinimizeSuppressed",
|
|
97
|
+
"autoMarkReady",
|
|
98
|
+
"neverCancelRuns",
|
|
99
|
+
"autoResolveOutdated",
|
|
100
|
+
"commitSuggestions",
|
|
101
|
+
]),
|
|
102
|
+
};
|
|
103
|
+
function warnUnknownConfigKeys(config) {
|
|
104
|
+
for (const key of Object.keys(config)) {
|
|
105
|
+
if (!KNOWN_CONFIG_KEYS.has(key)) {
|
|
106
|
+
process.stderr.write(`pr-shepherd: config: unknown key "${key}" ignored.\n`);
|
|
107
|
+
delete config[key];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const value = config[key];
|
|
111
|
+
const nested = KNOWN_NESTED_KEYS[key];
|
|
112
|
+
if (nested === undefined ||
|
|
113
|
+
value === null ||
|
|
114
|
+
typeof value !== "object" ||
|
|
115
|
+
Array.isArray(value)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
for (const child of Object.keys(value)) {
|
|
119
|
+
if (!nested.has(child)) {
|
|
120
|
+
process.stderr.write(`pr-shepherd: config: unknown key "${key}.${child}" ignored.\n`);
|
|
121
|
+
delete value[child];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
66
126
|
const defaults = builtins;
|
|
67
127
|
const configCache = new Map();
|
|
68
128
|
export function loadConfig() {
|
|
69
|
-
const cwd =
|
|
129
|
+
const cwd = getEffectiveCwd();
|
|
70
130
|
if (configCache.has(cwd))
|
|
71
131
|
return configCache.get(cwd);
|
|
72
132
|
const rcPath = findRcFile(cwd);
|
|
@@ -77,6 +137,17 @@ export function loadConfig() {
|
|
|
77
137
|
try {
|
|
78
138
|
const raw = readFileSync(rcPath, "utf8");
|
|
79
139
|
const parsed = (parse(raw) ?? {});
|
|
140
|
+
const rawActions = parsed.actions;
|
|
141
|
+
if (rawActions !== null && typeof rawActions === "object" && !Array.isArray(rawActions)) {
|
|
142
|
+
const actions = rawActions;
|
|
143
|
+
for (const key of ["autoResolveOutdated", "commitSuggestions"]) {
|
|
144
|
+
if (key in actions) {
|
|
145
|
+
process.stderr.write(`pr-shepherd: config: actions.${key} is deprecated and ignored.\n`);
|
|
146
|
+
delete actions[key];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
warnUnknownConfigKeys(parsed);
|
|
80
151
|
const config = deepMerge(defaults, parsed);
|
|
81
152
|
config.botUsernames = parseBotUsernames(config.botUsernames);
|
|
82
153
|
config.ignoreChecks = parseIgnoreChecks(config.ignoreChecks);
|
package/bin/config.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs work with an optional working directory without changing Node's global
|
|
3
|
+
* process state. This keeps independent API/MCP requests safe to run together.
|
|
4
|
+
*/
|
|
5
|
+
export declare function runWithExecutionCwd<T>(cwd: string | undefined, work: () => T): T;
|
|
6
|
+
/** The caller-scoped working directory, when one was supplied. */
|
|
7
|
+
export declare function getExecutionCwd(): string | undefined;
|
|
8
|
+
/** The caller-scoped working directory, falling back to the CLI process cwd. */
|
|
9
|
+
export declare function getEffectiveCwd(): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const executionContext = new AsyncLocalStorage();
|
|
3
|
+
/**
|
|
4
|
+
* Runs work with an optional working directory without changing Node's global
|
|
5
|
+
* process state. This keeps independent API/MCP requests safe to run together.
|
|
6
|
+
*/
|
|
7
|
+
export function runWithExecutionCwd(cwd, work) {
|
|
8
|
+
if (cwd === undefined)
|
|
9
|
+
return work();
|
|
10
|
+
return executionContext.run({ cwd }, work);
|
|
11
|
+
}
|
|
12
|
+
/** The caller-scoped working directory, when one was supplied. */
|
|
13
|
+
export function getExecutionCwd() {
|
|
14
|
+
return executionContext.getStore()?.cwd;
|
|
15
|
+
}
|
|
16
|
+
/** The caller-scoped working directory, falling back to the CLI process cwd. */
|
|
17
|
+
export function getEffectiveCwd() {
|
|
18
|
+
return getExecutionCwd() ?? process.cwd();
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process exit codes for the pr-shepherd CLI.
|
|
3
|
+
*
|
|
4
|
+
* Three bands:
|
|
5
|
+
* 0 done — shepherd finished and the PR is in a good terminal state
|
|
6
|
+
* 10-19 shepherd RAN successfully; the code reports PR state
|
|
7
|
+
* 64-78 shepherd FAILED (BSD `sysexits.h` codes)
|
|
8
|
+
*
|
|
9
|
+
* Caller rule: `$? >= 64` means shepherd itself failed. `0` or `10-19` means it
|
|
10
|
+
* ran to completion and is reporting PR state. See docs/exit-codes.md.
|
|
11
|
+
*/
|
|
12
|
+
import type { IterateResult } from "./types.mts";
|
|
13
|
+
export declare const EXIT: Readonly<{
|
|
14
|
+
/** `cancel` + `merged` or `ready-delay-elapsed` — shepherd finished cleanly. */
|
|
15
|
+
readonly OK: 0;
|
|
16
|
+
/** Nothing to do yet; CI still in progress. */
|
|
17
|
+
readonly WAIT: 10;
|
|
18
|
+
/** Draft PR converted to ready for review. */
|
|
19
|
+
readonly MARK_READY: 11;
|
|
20
|
+
/** Agent work required. */
|
|
21
|
+
readonly FIX_CODE: 12;
|
|
22
|
+
/** Human attention required. */
|
|
23
|
+
readonly ESCALATE: 13;
|
|
24
|
+
/** `cancel` + `closed` — PR closed without merging. */
|
|
25
|
+
readonly CLOSED: 14;
|
|
26
|
+
/** Bad/unknown flag, unknown subcommand, missing required arg, invalid duration. */
|
|
27
|
+
readonly USAGE: 64;
|
|
28
|
+
/** Malformed caller data: bad `--require-sha`, `PRRC_*` IDs, bad repo string. */
|
|
29
|
+
readonly DATAERR: 65;
|
|
30
|
+
/** Input file/stdin could not be read. */
|
|
31
|
+
readonly NOINPUT: 66;
|
|
32
|
+
/** Precondition unmet: no open PR for branch, thread not eligible, unclassified 4xx. */
|
|
33
|
+
readonly UNAVAILABLE: 69;
|
|
34
|
+
/** Unexpected/unclassified internal error — the fallback. */
|
|
35
|
+
readonly SOFTWARE: 70;
|
|
36
|
+
/** Retryable GitHub failure: 429, 5xx, rate limit exhausted, `Retry-After` present. */
|
|
37
|
+
readonly TEMPFAIL: 75;
|
|
38
|
+
/** GitHub 401/403 — missing token or insufficient PAT scopes. */
|
|
39
|
+
readonly NOPERM: 77;
|
|
40
|
+
/** `.pr-shepherdrc.yml` validation failure. */
|
|
41
|
+
readonly CONFIG: 78;
|
|
42
|
+
}>;
|
|
43
|
+
/** An error that carries its own exit code, so the top-level handler doesn't have to guess. */
|
|
44
|
+
export declare class ShepherdError extends Error {
|
|
45
|
+
readonly exitCode: number;
|
|
46
|
+
constructor(message: string, exitCode: number, opts?: {
|
|
47
|
+
cause?: unknown;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export declare function iterateResultToExitCode(result: IterateResult): number;
|
|
51
|
+
export declare function errorToExitCode(err: unknown): number;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PrActivitySummary, PrComment, Review, ReviewThread } from "../types.mts";
|
|
2
|
+
import type { RawPr } from "./batch-raw-types.mts";
|
|
3
|
+
export declare function buildPrActivitySummary(raw: RawPr, comments: PrComment[], reviewThreads: ReviewThread[], reviewSummaries: Review[], changesRequestedReviews: Review[], approvedReviews: Review[]): PrActivitySummary;
|
package/bin/github/activity.mjs
CHANGED
|
@@ -8,6 +8,7 @@ function reviewActivityItems(reviews, latestCommitCommittedAtUnix, kind) {
|
|
|
8
8
|
id: r.id,
|
|
9
9
|
author: r.author,
|
|
10
10
|
authorType: r.authorType,
|
|
11
|
+
...(r.authorAssociation !== undefined && { authorAssociation: r.authorAssociation }),
|
|
11
12
|
body: r.body,
|
|
12
13
|
createdAtUnix: r.createdAtUnix ?? 0,
|
|
13
14
|
}));
|
|
@@ -26,6 +27,9 @@ export function buildPrActivitySummary(raw, comments, reviewThreads, reviewSumma
|
|
|
26
27
|
id: c.id,
|
|
27
28
|
author: c.author,
|
|
28
29
|
authorType: c.authorType,
|
|
30
|
+
...(c.authorAssociation !== undefined && {
|
|
31
|
+
authorAssociation: c.authorAssociation,
|
|
32
|
+
}),
|
|
29
33
|
body: c.body,
|
|
30
34
|
url: c.url,
|
|
31
35
|
createdAtUnix: c.createdAtUnix,
|
|
@@ -37,6 +41,9 @@ export function buildPrActivitySummary(raw, comments, reviewThreads, reviewSumma
|
|
|
37
41
|
id: c.id,
|
|
38
42
|
author: c.author,
|
|
39
43
|
authorType: c.authorType,
|
|
44
|
+
...(c.authorAssociation !== undefined && {
|
|
45
|
+
authorAssociation: c.authorAssociation,
|
|
46
|
+
}),
|
|
40
47
|
body: c.body,
|
|
41
48
|
url: c.url,
|
|
42
49
|
createdAtUnix: c.createdAtUnix,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthorType, CheckConclusion, CheckRun, CheckStatus, Review, ReviewThread } from "../types.mts";
|
|
2
|
+
import type { RawContextNode } from "./batch-raw-types.mts";
|
|
3
|
+
export declare function mapAuthorType(typeName: string | undefined | null, login?: string | undefined | null): AuthorType;
|
|
4
|
+
export declare function parseCreatedAt(iso: string): number;
|
|
5
|
+
/** Map a GraphQL CheckRun context node to a CheckRun. */
|
|
6
|
+
export declare function mapCheckRunNode(node: Extract<RawContextNode, {
|
|
7
|
+
__typename: "CheckRun";
|
|
8
|
+
}>): CheckRun;
|
|
9
|
+
export declare function latestApprovedLogins(latest: Array<{
|
|
10
|
+
login: string;
|
|
11
|
+
state: string;
|
|
12
|
+
}>): Set<string>;
|
|
13
|
+
/**
|
|
14
|
+
* A CR review is stale when its commit.oid differs from the PR head AND every
|
|
15
|
+
* associated review thread is resolved or outdated. Reviews with no associated
|
|
16
|
+
* threads are treated conservatively (not marked stale).
|
|
17
|
+
*/
|
|
18
|
+
export declare function isReviewStale(review: Review, headRefOid: string, reviewThreads: ReviewThread[]): boolean;
|
|
19
|
+
export declare function mapStatusContextState(state: string): {
|
|
20
|
+
status: CheckStatus;
|
|
21
|
+
conclusion: CheckConclusion;
|
|
22
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BatchPrData } from "../types.mts";
|
|
2
|
+
import type { RawPr, RawThread, RawComment, RawReview, RawReviewSummary, RawContextNode } from "./batch-raw-types.mts";
|
|
3
|
+
export declare function parseRawPr(raw: RawPr, rawThreadPages: RawThread[], rawCommentNodes: RawComment[], rawReviewNodes: RawReview[], rawReviewSummaryNodes: RawReviewSummary[], rawApprovedReviewNodes: RawReviewSummary[], rawCheckNodes: RawContextNode[]): BatchPrData;
|
|
@@ -6,6 +6,7 @@ function parseReviewNode(r) {
|
|
|
6
6
|
id: r.id,
|
|
7
7
|
author: r.author?.login ?? "unknown",
|
|
8
8
|
authorType: mapAuthorType(r.author?.__typename, r.author?.login),
|
|
9
|
+
...(r.authorAssociation !== undefined && { authorAssociation: r.authorAssociation }),
|
|
9
10
|
body: r.body,
|
|
10
11
|
createdAtUnix: r.createdAt ? parseCreatedAt(r.createdAt) : 0,
|
|
11
12
|
};
|
|
@@ -32,6 +33,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
32
33
|
...(c.pullRequestReview?.id ? { reviewId: c.pullRequestReview.id } : undefined),
|
|
33
34
|
author: c.author?.login ?? "unknown",
|
|
34
35
|
authorType: mapAuthorType(c.author?.__typename, c.author?.login),
|
|
36
|
+
...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
|
|
35
37
|
body: c.body,
|
|
36
38
|
url: c.url,
|
|
37
39
|
createdAtUnix: c.createdAt ? parseCreatedAt(c.createdAt) : 0,
|
|
@@ -47,6 +49,9 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
47
49
|
...(comment?.pullRequestReview?.id ? { reviewId: comment.pullRequestReview.id } : undefined),
|
|
48
50
|
author: comment?.author?.login ?? "unknown",
|
|
49
51
|
authorType: mapAuthorType(comment?.author?.__typename, comment?.author?.login),
|
|
52
|
+
...(comment?.authorAssociation !== undefined && {
|
|
53
|
+
authorAssociation: comment.authorAssociation,
|
|
54
|
+
}),
|
|
50
55
|
body: comment?.body ?? "",
|
|
51
56
|
url: comment?.url ?? "",
|
|
52
57
|
createdAtUnix: comment?.createdAt ? parseCreatedAt(comment.createdAt) : 0,
|
|
@@ -58,6 +63,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
58
63
|
isMinimized: c.isMinimized,
|
|
59
64
|
author: c.author?.login ?? "unknown",
|
|
60
65
|
authorType: mapAuthorType(c.author?.__typename, c.author?.login),
|
|
66
|
+
...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
|
|
61
67
|
body: c.body,
|
|
62
68
|
url: c.url,
|
|
63
69
|
createdAtUnix: c.createdAt ? parseCreatedAt(c.createdAt) : 0,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { CommentAuthorAssociation } from "../types/github.mts";
|
|
2
|
+
export interface RawBatchResponse {
|
|
3
|
+
repository: {
|
|
4
|
+
pullRequest: RawPr | null;
|
|
5
|
+
} | null;
|
|
6
|
+
}
|
|
7
|
+
export interface RawPr {
|
|
8
|
+
id: string;
|
|
9
|
+
number: number;
|
|
10
|
+
state: string;
|
|
11
|
+
isDraft: boolean;
|
|
12
|
+
mergeable: string;
|
|
13
|
+
mergeStateStatus: string;
|
|
14
|
+
reviewDecision: string | null;
|
|
15
|
+
headRefOid: string;
|
|
16
|
+
headRefName: string;
|
|
17
|
+
headRepository: {
|
|
18
|
+
nameWithOwner: string;
|
|
19
|
+
} | null;
|
|
20
|
+
baseRefName: string;
|
|
21
|
+
baseRef: {
|
|
22
|
+
branchProtectionRule: RawBranchProtectionRule | null;
|
|
23
|
+
} | null;
|
|
24
|
+
reviewRequests: {
|
|
25
|
+
nodes: Array<{
|
|
26
|
+
requestedReviewer: {
|
|
27
|
+
login?: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
} | null;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
latestReviews: {
|
|
33
|
+
nodes: Array<{
|
|
34
|
+
author: RawAuthor | null;
|
|
35
|
+
state: string;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
reviewThreads: {
|
|
39
|
+
pageInfo: {
|
|
40
|
+
hasPreviousPage: boolean;
|
|
41
|
+
startCursor: string | null;
|
|
42
|
+
};
|
|
43
|
+
nodes: RawThread[];
|
|
44
|
+
};
|
|
45
|
+
comments: {
|
|
46
|
+
pageInfo: {
|
|
47
|
+
hasPreviousPage: boolean;
|
|
48
|
+
startCursor: string | null;
|
|
49
|
+
};
|
|
50
|
+
nodes: RawComment[];
|
|
51
|
+
};
|
|
52
|
+
changesRequestedReviews: {
|
|
53
|
+
pageInfo: {
|
|
54
|
+
hasPreviousPage: boolean;
|
|
55
|
+
startCursor: string | null;
|
|
56
|
+
};
|
|
57
|
+
nodes: RawReview[];
|
|
58
|
+
};
|
|
59
|
+
reviewSummaries: {
|
|
60
|
+
pageInfo: {
|
|
61
|
+
hasPreviousPage: boolean;
|
|
62
|
+
startCursor: string | null;
|
|
63
|
+
};
|
|
64
|
+
nodes: RawReviewSummary[];
|
|
65
|
+
};
|
|
66
|
+
allReviews?: {
|
|
67
|
+
totalCount: number;
|
|
68
|
+
};
|
|
69
|
+
approvedReviews: {
|
|
70
|
+
pageInfo: {
|
|
71
|
+
hasPreviousPage: boolean;
|
|
72
|
+
startCursor: string | null;
|
|
73
|
+
};
|
|
74
|
+
nodes: RawReviewSummary[];
|
|
75
|
+
};
|
|
76
|
+
commits: {
|
|
77
|
+
totalCount?: number;
|
|
78
|
+
nodes: Array<{
|
|
79
|
+
commit: {
|
|
80
|
+
oid: string;
|
|
81
|
+
committedDate?: string;
|
|
82
|
+
statusCheckRollup: {
|
|
83
|
+
contexts: {
|
|
84
|
+
pageInfo: {
|
|
85
|
+
hasNextPage: boolean;
|
|
86
|
+
endCursor: string | null;
|
|
87
|
+
};
|
|
88
|
+
nodes: Array<RawContextNode | null>;
|
|
89
|
+
};
|
|
90
|
+
} | null;
|
|
91
|
+
};
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
interface RawAuthor {
|
|
96
|
+
__typename?: string;
|
|
97
|
+
login: string;
|
|
98
|
+
}
|
|
99
|
+
export interface RawThreadComment {
|
|
100
|
+
id: string;
|
|
101
|
+
isMinimized: boolean;
|
|
102
|
+
url: string;
|
|
103
|
+
authorAssociation?: CommentAuthorAssociation;
|
|
104
|
+
author: RawAuthor | null;
|
|
105
|
+
pullRequestReview?: {
|
|
106
|
+
id: string;
|
|
107
|
+
} | null;
|
|
108
|
+
body: string;
|
|
109
|
+
path: string | null;
|
|
110
|
+
line: number | null;
|
|
111
|
+
startLine: number | null;
|
|
112
|
+
createdAt?: string;
|
|
113
|
+
}
|
|
114
|
+
export interface RawThread {
|
|
115
|
+
id: string;
|
|
116
|
+
isResolved: boolean;
|
|
117
|
+
isOutdated: boolean;
|
|
118
|
+
path?: string | null;
|
|
119
|
+
line?: number | null;
|
|
120
|
+
startLine?: number | null;
|
|
121
|
+
comments: {
|
|
122
|
+
pageInfo?: {
|
|
123
|
+
hasNextPage: boolean;
|
|
124
|
+
endCursor: string | null;
|
|
125
|
+
};
|
|
126
|
+
nodes: RawThreadComment[];
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export interface RawReviewThreadCommentsResponse {
|
|
130
|
+
node: {
|
|
131
|
+
__typename?: string;
|
|
132
|
+
comments: {
|
|
133
|
+
pageInfo: {
|
|
134
|
+
hasNextPage: boolean;
|
|
135
|
+
endCursor: string | null;
|
|
136
|
+
};
|
|
137
|
+
nodes: RawThreadComment[];
|
|
138
|
+
} | null;
|
|
139
|
+
} | null;
|
|
140
|
+
}
|
|
141
|
+
export interface RawComment {
|
|
142
|
+
id: string;
|
|
143
|
+
isMinimized: boolean;
|
|
144
|
+
url: string;
|
|
145
|
+
authorAssociation?: CommentAuthorAssociation;
|
|
146
|
+
author: RawAuthor | null;
|
|
147
|
+
body: string;
|
|
148
|
+
createdAt?: string;
|
|
149
|
+
}
|
|
150
|
+
export interface RawReview {
|
|
151
|
+
id: string;
|
|
152
|
+
authorAssociation?: CommentAuthorAssociation;
|
|
153
|
+
author: RawAuthor | null;
|
|
154
|
+
body: string;
|
|
155
|
+
createdAt?: string;
|
|
156
|
+
commit?: {
|
|
157
|
+
oid: string;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export interface RawReviewSummary {
|
|
161
|
+
id: string;
|
|
162
|
+
isMinimized: boolean;
|
|
163
|
+
authorAssociation?: CommentAuthorAssociation;
|
|
164
|
+
author: RawAuthor | null;
|
|
165
|
+
body: string;
|
|
166
|
+
createdAt?: string;
|
|
167
|
+
}
|
|
168
|
+
interface RawBranchProtectionRule {
|
|
169
|
+
requiresApprovingReviews: boolean;
|
|
170
|
+
requiredApprovingReviewCount: number;
|
|
171
|
+
requiresConversationResolution: boolean;
|
|
172
|
+
requiresStatusChecks: boolean;
|
|
173
|
+
requiredStatusCheckContexts: string[] | null;
|
|
174
|
+
}
|
|
175
|
+
export type RawContextNode = {
|
|
176
|
+
__typename: "CheckRun";
|
|
177
|
+
id: string;
|
|
178
|
+
name: string;
|
|
179
|
+
status: string;
|
|
180
|
+
conclusion: string | null;
|
|
181
|
+
detailsUrl: string | null;
|
|
182
|
+
completedAt?: string | null;
|
|
183
|
+
startedAt?: string | null;
|
|
184
|
+
title: string | null;
|
|
185
|
+
summary: string | null;
|
|
186
|
+
checkSuite: {
|
|
187
|
+
createdAt?: string;
|
|
188
|
+
updatedAt?: string;
|
|
189
|
+
workflowRun: {
|
|
190
|
+
event: string;
|
|
191
|
+
createdAt?: string;
|
|
192
|
+
updatedAt?: string;
|
|
193
|
+
workflow?: {
|
|
194
|
+
name: string;
|
|
195
|
+
databaseId?: number | null;
|
|
196
|
+
} | null;
|
|
197
|
+
} | null;
|
|
198
|
+
} | null;
|
|
199
|
+
} | {
|
|
200
|
+
__typename: "StatusContext";
|
|
201
|
+
context: string;
|
|
202
|
+
state: string;
|
|
203
|
+
createdAt?: string;
|
|
204
|
+
targetUrl: string | null;
|
|
205
|
+
description: string | null;
|
|
206
|
+
};
|
|
207
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RepoInfo } from "./client.mts";
|
|
2
|
+
import type { RawBatchResponse, RawContextNode, RawPr } from "./batch-raw-types.mts";
|
|
3
|
+
export declare function requireRawPr(response: RawBatchResponse | null | undefined, pr: number, repo: RepoInfo): RawPr;
|
|
4
|
+
export declare function requireContextNodes(nodes: Array<RawContextNode | null>): RawContextNode[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RateLimitInfo, type RepoInfo } from "./client.mts";
|
|
2
|
+
import type { BatchPrData } from "../types.mts";
|
|
3
|
+
interface BatchResult {
|
|
4
|
+
data: BatchPrData;
|
|
5
|
+
rateLimit?: RateLimitInfo;
|
|
6
|
+
}
|
|
7
|
+
interface FetchPrBatchOptions {
|
|
8
|
+
/**
|
|
9
|
+
* When false (default), the first-page approvedReviews are returned but
|
|
10
|
+
* backward pagination is skipped. Iterate's approvals-minimize flow sets this
|
|
11
|
+
* to true only when the user opts in, so long-lived PRs with > 50 approvals
|
|
12
|
+
* don't pay extra GraphQL round-trips per iterate call for data no consumer
|
|
13
|
+
* currently uses. The first page is free — already inside the one batch
|
|
14
|
+
* request — so there's no need to conditionally omit the field itself.
|
|
15
|
+
*/
|
|
16
|
+
paginateApprovedReviews?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetch all PR data needed for a `shepherd check` in one (or a few, if paginating) GraphQL requests.
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchPrBatch(pr: number, repo: RepoInfo, opts?: FetchPrBatchOptions): Promise<BatchResult>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level GitHub client — wraps http.mts for application-level concerns.
|
|
3
|
+
*
|
|
4
|
+
* All GitHub I/O goes through native fetch (via http.mts); the `gh` CLI is no
|
|
5
|
+
* longer used for GitHub API calls, but may be invoked as an auth-token
|
|
6
|
+
* fallback via `gh auth token` when neither GH_TOKEN nor GITHUB_TOKEN is set.
|
|
7
|
+
*/
|
|
8
|
+
import type { MergeableState, MergeStateStatus } from "../types.mts";
|
|
9
|
+
export type { RateLimitInfo } from "./http.mts";
|
|
10
|
+
export { graphql, graphqlWithRateLimit } from "./http.mts";
|
|
11
|
+
export interface RepoInfo {
|
|
12
|
+
owner: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current repo's owner and name by parsing `git remote get-url origin`.
|
|
17
|
+
* Handles https://, git@, and ssh:// remote URL formats.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getRepoInfo(): Promise<RepoInfo>;
|
|
20
|
+
/**
|
|
21
|
+
* Derives the PR number for the current HEAD branch.
|
|
22
|
+
* Returns null if no open PR is found.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCurrentPrNumber(): Promise<number | null>;
|
|
25
|
+
/** Returns the PR number for a given branch, or null if no open PR is found. */
|
|
26
|
+
export declare function getPrNumberForBranch(branch: string, owner: string, repo: string): Promise<number | null>;
|
|
27
|
+
/** Returns the `headRefOid` (commit SHA) of the given PR as reported by GitHub. */
|
|
28
|
+
export declare function getPrHeadSha(pr: number, owner: string, name: string): Promise<string>;
|
|
29
|
+
/** Fetches the node ID and body text for a PR. GitHub returns null body for empty bodies — coerced to "". */
|
|
30
|
+
export declare function getPullRequestBody(pr: number, owner: string, name: string): Promise<{
|
|
31
|
+
nodeId: string;
|
|
32
|
+
body: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** Overwrites the PR body. */
|
|
35
|
+
export declare function updatePullRequestBody(pullRequestId: string, body: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Fetches `mergeable` and `mergeStateStatus` via the REST API.
|
|
38
|
+
*
|
|
39
|
+
* Used as a fallback when the GraphQL API returns `UNKNOWN` for these fields —
|
|
40
|
+
* a known GitHub quirk where GraphQL lags behind the REST layer.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getMergeableState(pr: number, owner: string, repo: string): Promise<{
|
|
43
|
+
mergeable: MergeableState;
|
|
44
|
+
mergeStateStatus: MergeStateStatus;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function getCurrentBranch(): Promise<string>;
|
package/bin/github/client.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { execFile as execFileCb } from "node:child_process";
|
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
10
|
import { graphql as httpGraphql, rest } from "./http.mjs";
|
|
11
11
|
import { PR_NUMBER_BY_BRANCH_QUERY, GET_PR_HEAD_SHA_QUERY, GET_PR_BODY_QUERY, UPDATE_PR_BODY_MUTATION, } from "./queries.mjs";
|
|
12
|
+
import { getExecutionCwd } from "../execution-context.mjs";
|
|
12
13
|
const execFile = promisify(execFileCb);
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// GraphQL — thin re-exports so callers don't need to import http.mts directly
|
|
@@ -19,7 +20,9 @@ export { graphql, graphqlWithRateLimit } from "./http.mjs";
|
|
|
19
20
|
* Handles https://, git@, and ssh:// remote URL formats.
|
|
20
21
|
*/
|
|
21
22
|
export async function getRepoInfo() {
|
|
22
|
-
const { stdout } = await execFile("git", ["remote", "get-url", "origin"]
|
|
23
|
+
const { stdout } = await execFile("git", ["remote", "get-url", "origin"], {
|
|
24
|
+
cwd: getExecutionCwd(),
|
|
25
|
+
});
|
|
23
26
|
const url = stdout.trim();
|
|
24
27
|
return parseRemoteUrl(url);
|
|
25
28
|
}
|
|
@@ -95,7 +98,9 @@ export async function getMergeableState(pr, owner, repo) {
|
|
|
95
98
|
// Internal helpers
|
|
96
99
|
// ---------------------------------------------------------------------------
|
|
97
100
|
export async function getCurrentBranch() {
|
|
98
|
-
const { stdout } = await execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"]
|
|
101
|
+
const { stdout } = await execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
102
|
+
cwd: getExecutionCwd(),
|
|
103
|
+
});
|
|
99
104
|
return stdout.trim();
|
|
100
105
|
}
|
|
101
106
|
function parseRemoteUrl(url) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ShepherdError } from "../exit-codes.mts";
|
|
2
|
+
import type { RateLimitInfo } from "./http.mts";
|
|
3
|
+
export interface GitHubGraphQlError {
|
|
4
|
+
message: string;
|
|
5
|
+
path?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare class GitHubRequestError extends ShepherdError {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly rateLimit?: RateLimitInfo;
|
|
10
|
+
readonly retryAfterSeconds?: number;
|
|
11
|
+
readonly graphqlErrors?: GitHubGraphQlError[];
|
|
12
|
+
constructor(message: string, opts: {
|
|
13
|
+
status: number;
|
|
14
|
+
rateLimit?: RateLimitInfo;
|
|
15
|
+
retryAfterSeconds?: number;
|
|
16
|
+
graphqlErrors?: GitHubGraphQlError[];
|
|
17
|
+
/**
|
|
18
|
+
* Bypasses status-based classification entirely — for callers that already
|
|
19
|
+
* know the failure kind better than the HTTP status can express (e.g. a
|
|
20
|
+
* response that failed to parse at all, which is an internal failure, not
|
|
21
|
+
* an availability or permission problem).
|
|
22
|
+
*/
|
|
23
|
+
exitCodeOverride?: number;
|
|
24
|
+
});
|
|
25
|
+
}
|