pr-shepherd 0.46.7 → 0.47.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/api.d.mts +1 -1
- package/bin/commands/check-fingerprint.d.mts +8 -0
- package/bin/commands/check-fingerprint.mjs +69 -0
- package/bin/commands/check.d.mts +1 -0
- package/bin/commands/check.mjs +23 -3
- package/bin/commands/iterate/base.mjs +1 -0
- package/bin/commands/iterate/mark-ready.mjs +8 -0
- package/bin/commands/poll-progress.d.mts +11 -0
- package/bin/commands/poll-progress.mjs +52 -0
- package/bin/commands/poll-quota.d.mts +9 -0
- package/bin/commands/poll-quota.mjs +36 -0
- package/bin/commands/poll.mjs +64 -65
- package/bin/config/load.mjs +11 -1
- package/bin/github/batch-raw-rules.d.mts +4 -1
- package/bin/github/batch-raw-types.d.mts +14 -0
- package/bin/github/batch.d.mts +2 -0
- package/bin/github/batch.mjs +2 -0
- package/bin/github/fingerprint-fields.d.mts +58 -0
- package/bin/github/fingerprint-fields.mjs +39 -0
- package/bin/github/fingerprint.d.mts +35 -0
- package/bin/github/fingerprint.mjs +67 -0
- package/bin/github/gql/batch-pr.gql +25 -120
- package/bin/github/gql/commit-check-suites.gql +19 -0
- package/bin/github/gql/pr-fingerprint.gql +75 -0
- package/bin/github/gql/pr-merge-policy.gql +49 -0
- package/bin/github/merge-queue-checks.mjs +57 -30
- package/bin/github/queries.d.mts +2 -0
- package/bin/github/queries.mjs +4 -1
- package/bin/merge-status/requirements-format.mjs +1 -1
- package/bin/state/pr-fingerprint.d.mts +20 -0
- package/bin/state/pr-fingerprint.mjs +90 -0
- package/bin/types/iterate.d.mts +9 -0
- package/bin/types/report.d.mts +2 -0
- package/package.json +2 -2
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
package/bin/github/queries.mjs
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
import { readFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
const gql = (name) => readFileSync(join(import.meta.dirname, "gql", name), "utf8");
|
|
12
|
+
const withSharedFragments = (query) => `${gql("pr-merge-policy.gql")}\n${gql("commit-check-suites.gql")}\n${query}`;
|
|
12
13
|
/** The primary batch query that fetches CI + comments + merge status in one round-trip. */
|
|
13
|
-
export const BATCH_PR_QUERY = gql("batch-pr.gql");
|
|
14
|
+
export const BATCH_PR_QUERY = withSharedFragments(gql("batch-pr.gql"));
|
|
14
15
|
/** Slim @include follow-up for outstanding batch-query connections. */
|
|
15
16
|
export const BATCH_PR_PAGE_QUERY = gql("batch-pr-page.gql");
|
|
17
|
+
/** Cheap PR fingerprint used to skip an unchanged BatchPr snapshot. */
|
|
18
|
+
export const PR_FINGERPRINT_QUERY = withSharedFragments(gql("pr-fingerprint.gql"));
|
|
16
19
|
/** PR head fields plus a single review thread for `commit-suggestion`. */
|
|
17
20
|
export const SUGGESTION_THREADS_QUERY = gql("suggestion-threads.gql");
|
|
18
21
|
/** Fetches additional comments for a single review thread when its nested connection paginates. */
|
|
@@ -33,7 +33,7 @@ export function formatMergeRequirementLines(req) {
|
|
|
33
33
|
lines.push(formatMergeQueue(req.mergeQueue));
|
|
34
34
|
if (req.stack) {
|
|
35
35
|
const { number, position, size, baseRefName } = req.stack;
|
|
36
|
-
lines.push(`Stack:
|
|
36
|
+
lines.push(`Stack: ${number} (layer ${position}/${size}, base ${baseRefName})`);
|
|
37
37
|
}
|
|
38
38
|
return lines;
|
|
39
39
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PrFingerprint } from "../github/fingerprint.mts";
|
|
2
|
+
import type { PrShepherdConfig } from "../config/load.mts";
|
|
3
|
+
import type { ShepherdReport } from "../types.mts";
|
|
4
|
+
export interface StoredPrFingerprint {
|
|
5
|
+
version: number;
|
|
6
|
+
inputDigest: string;
|
|
7
|
+
fingerprint: PrFingerprint;
|
|
8
|
+
report: ShepherdReport;
|
|
9
|
+
}
|
|
10
|
+
export declare function fingerprintInputDigest(config: PrShepherdConfig): string;
|
|
11
|
+
export declare function loadPrFingerprint(key: {
|
|
12
|
+
owner: string;
|
|
13
|
+
repo: string;
|
|
14
|
+
pr: number;
|
|
15
|
+
}): Promise<StoredPrFingerprint | null>;
|
|
16
|
+
export declare function storePrFingerprint(key: {
|
|
17
|
+
owner: string;
|
|
18
|
+
repo: string;
|
|
19
|
+
pr: number;
|
|
20
|
+
}, fingerprint: PrFingerprint, report: ShepherdReport, config: PrShepherdConfig): Promise<void>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { resolvePrStatePath } from "./base.mjs";
|
|
6
|
+
import { discoverRuleFiles } from "../classify/loader.mjs";
|
|
7
|
+
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
8
|
+
const VERSION = 3;
|
|
9
|
+
export function fingerprintInputDigest(config) {
|
|
10
|
+
const hash = createHash("sha256");
|
|
11
|
+
hash.update(JSON.stringify({
|
|
12
|
+
ignoreChecks: config.ignoreChecks,
|
|
13
|
+
botUsernames: config.botUsernames,
|
|
14
|
+
iterate: config.iterate,
|
|
15
|
+
watch: { readyDelayMinutes: config.watch.readyDelayMinutes },
|
|
16
|
+
checks: config.checks,
|
|
17
|
+
mergeStatus: config.mergeStatus,
|
|
18
|
+
actions: {
|
|
19
|
+
autoMinimizeSuppressed: config.actions.autoMinimizeSuppressed,
|
|
20
|
+
autoMarkReady: config.actions.autoMarkReady,
|
|
21
|
+
neverCancelRuns: config.actions.neverCancelRuns,
|
|
22
|
+
workWhileQueued: config.actions.workWhileQueued,
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
for (const file of discoverRuleFiles(getEffectiveCwd())) {
|
|
26
|
+
hash.update("\0");
|
|
27
|
+
hash.update(file);
|
|
28
|
+
hash.update("\0");
|
|
29
|
+
try {
|
|
30
|
+
hash.update(readFileSync(file));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
hash.update("missing");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return hash.digest("hex").slice(0, 16);
|
|
37
|
+
}
|
|
38
|
+
export async function loadPrFingerprint(key) {
|
|
39
|
+
const path = resolvePrStatePath(key, "fingerprint.json");
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
42
|
+
if (!isStoredFingerprint(parsed))
|
|
43
|
+
return null;
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function storePrFingerprint(key, fingerprint, report, config) {
|
|
51
|
+
const path = resolvePrStatePath(key, "fingerprint.json");
|
|
52
|
+
let tmp;
|
|
53
|
+
try {
|
|
54
|
+
await mkdir(dirname(path), { recursive: true });
|
|
55
|
+
tmp = `${path}.${randomUUID()}.tmp`;
|
|
56
|
+
const payload = {
|
|
57
|
+
version: VERSION,
|
|
58
|
+
inputDigest: fingerprintInputDigest(config),
|
|
59
|
+
fingerprint,
|
|
60
|
+
report,
|
|
61
|
+
};
|
|
62
|
+
await writeFile(tmp, `${JSON.stringify(payload)}\n`, "utf8");
|
|
63
|
+
await rename(tmp, path);
|
|
64
|
+
tmp = undefined;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Fingerprint cache is an optimization; a failed write just means the next tick refetches.
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
if (tmp !== undefined) {
|
|
71
|
+
try {
|
|
72
|
+
await unlink(tmp);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// best-effort cleanup
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isStoredFingerprint(value) {
|
|
81
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
82
|
+
return false;
|
|
83
|
+
const record = value;
|
|
84
|
+
return (record["version"] === VERSION &&
|
|
85
|
+
typeof record["inputDigest"] === "string" &&
|
|
86
|
+
record["fingerprint"] !== null &&
|
|
87
|
+
typeof record["fingerprint"] === "object" &&
|
|
88
|
+
record["report"] !== null &&
|
|
89
|
+
typeof record["report"] === "object");
|
|
90
|
+
}
|
package/bin/types/iterate.d.mts
CHANGED
|
@@ -44,6 +44,7 @@ export interface IterateResultBase {
|
|
|
44
44
|
mergeQueue?: import("./merge-queue.mts").MergeQueueReport;
|
|
45
45
|
apiUsage?: ApiUsage;
|
|
46
46
|
quotaWarning?: GraphqlQuotaWarning;
|
|
47
|
+
fingerprintReused?: true;
|
|
47
48
|
}
|
|
48
49
|
interface IterateResultWait extends IterateResultBase {
|
|
49
50
|
action: "wait";
|
|
@@ -141,6 +142,14 @@ export interface IterateCommandOptions extends GlobalOptions {
|
|
|
141
142
|
* only by `runPollCore`; excluded from the public `IterateInput` in api.mts.
|
|
142
143
|
*/
|
|
143
144
|
persistSeen?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Internal. `true` allows fingerprint reuse of a previous WAIT-shaped report.
|
|
147
|
+
* Poll sets this only for internal WAIT/MARK_READY continuation ticks — never the
|
|
148
|
+
* tick returned to the caller, never FIX_CODE debounce, never single-tick iterate.
|
|
149
|
+
* Writes still happen after a full fetch so the next poll can skip. Excluded from
|
|
150
|
+
* the public `IterateInput` in api.mts.
|
|
151
|
+
*/
|
|
152
|
+
fingerprintCache?: boolean;
|
|
144
153
|
/** Shepherd through readiness and emit the exact merge/queue command when ready. */
|
|
145
154
|
merge?: boolean;
|
|
146
155
|
/**
|
package/bin/types/report.d.mts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ShepherdReport {
|
|
|
23
23
|
nodeId: string;
|
|
24
24
|
/** GitHub PR head OID from the same batch used to decide the action. */
|
|
25
25
|
headSha?: string;
|
|
26
|
+
/** Internal. True when this report was reused from the fingerprint cache. */
|
|
27
|
+
fingerprintReused?: true;
|
|
26
28
|
repo: string;
|
|
27
29
|
/** Raw GitHub viewer fields used to decide which remote actions may be offered. */
|
|
28
30
|
viewerAuthorization?: ViewerAuthorization;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-shepherd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"husky": "^9.1.7",
|
|
90
90
|
"knip": "^6.14.1",
|
|
91
91
|
"marked": "^18.0.11",
|
|
92
|
-
"oxfmt": "^0.
|
|
92
|
+
"oxfmt": "^0.65.0",
|
|
93
93
|
"oxlint": "^1.60.0",
|
|
94
94
|
"typescript": "^7.0.2",
|
|
95
95
|
"vitest": "^4.1.4"
|