pr-shepherd 0.51.0 → 0.52.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/README.md +25 -17
- package/bin/checks/log-excerpt.d.mts +1 -0
- package/bin/checks/log-excerpt.mjs +192 -0
- package/bin/checks/triage.mjs +2 -121
- package/bin/cli/iterate-lean.mjs +0 -3
- package/bin/cli/poll-summary-emitter.mjs +2 -3
- package/bin/cli/poll-summary-formatter.mjs +9 -2
- package/bin/commands/check.mjs +1 -0
- package/bin/commands/iterate/escalate.mjs +0 -13
- package/bin/commands/iterate/fix-code.d.mts +2 -0
- package/bin/commands/iterate/fix-code.mjs +14 -42
- package/bin/commands/iterate/index.mjs +140 -3
- package/bin/commands/iterate/merge-state.mjs +33 -18
- package/bin/commands/iterate/parent-first.d.mts +9 -0
- package/bin/commands/iterate/parent-first.mjs +57 -0
- package/bin/commands/iterate/stale-ancestry.d.mts +22 -0
- package/bin/commands/iterate/stale-ancestry.mjs +37 -0
- package/bin/commands/poll-summary-instructions.mjs +179 -99
- package/bin/commands/poll-summary.mjs +28 -4
- package/bin/exit-codes.d.mts +2 -0
- package/bin/exit-codes.mjs +2 -0
- package/bin/github/batch-parsers.mjs +1 -0
- package/bin/github/batch-raw-rules.d.mts +3 -0
- package/bin/github/gql/poll-summary-fragment.gql +54 -0
- package/bin/github/gql/pr-merge-policy.gql +3 -0
- package/bin/github/poll-summary-fingerprint.d.mts +8 -0
- package/bin/github/poll-summary-fingerprint.mjs +28 -0
- package/bin/github/poll-summary-projector.mjs +67 -14
- package/bin/github/poll-summary-queue-removal.d.mts +5 -0
- package/bin/github/poll-summary-queue-removal.mjs +16 -0
- package/bin/github/poll-summary-raw.d.mts +33 -0
- package/bin/github/poll-summary-readiness.d.mts +6 -0
- package/bin/github/poll-summary-readiness.mjs +25 -0
- package/bin/github/poll-summary.d.mts +3 -0
- package/bin/github/poll-summary.mjs +5 -0
- package/bin/state/ready-receipts.d.mts +45 -0
- package/bin/state/ready-receipts.mjs +86 -0
- package/bin/types/escalate.d.mts +2 -3
- package/bin/types/github.d.mts +2 -0
- package/bin/types/poll-summary.d.mts +12 -2
- package/bin/types/report.d.mts +2 -0
- package/package.json +4 -4
- 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/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +3 -3
- package/bin/state/bot-cr-seen.d.mts +0 -51
- package/bin/state/bot-cr-seen.mjs +0 -100
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent first-seen state for bot CHANGES_REQUESTED reviews.
|
|
3
|
-
*
|
|
4
|
-
* Bot CRs are auto-dismissed via `--dismiss-review-ids` in the post-push
|
|
5
|
-
* `apply review:` command. If the agent drops that flag, the bot CR keeps the PR in
|
|
6
|
-
* `CHANGES_REQUESTED` state. This file tracks when each bot CR was first
|
|
7
|
-
* observed so the iterate loop can escalate after `iterate.stallTimeoutMinutes`
|
|
8
|
-
* — independent of the broader fingerprint-based `stall-timeout` mechanism in
|
|
9
|
-
* `iterate-stall.mts` (which only fires when no field of the iterate result
|
|
10
|
-
* changed).
|
|
11
|
-
*
|
|
12
|
-
* State lives in
|
|
13
|
-
* `$TMPDIR/pr-shepherd-state/<owner>-<repo>/<pr>/bot-cr-seen.json`.
|
|
14
|
-
*
|
|
15
|
-
* `bodyHash` lets us reset `firstSeenAt` when the bot re-issues the review
|
|
16
|
-
* with a different body, so a fresh review gets the full timeout window.
|
|
17
|
-
*/
|
|
18
|
-
import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
|
|
19
|
-
import { randomUUID } from "node:crypto";
|
|
20
|
-
import { dirname } from "node:path";
|
|
21
|
-
import { resolvePrStatePath } from "./base.mjs";
|
|
22
|
-
import { hashBody } from "./seen-comments.mjs";
|
|
23
|
-
export async function readBotCrSeenState(key) {
|
|
24
|
-
try {
|
|
25
|
-
const raw = await readFile(resolvePath(key), "utf8");
|
|
26
|
-
const parsed = JSON.parse(raw);
|
|
27
|
-
if (parsed === null || typeof parsed !== "object")
|
|
28
|
-
return null;
|
|
29
|
-
const reviews = parsed["reviews"];
|
|
30
|
-
if (reviews === null || typeof reviews !== "object" || Array.isArray(reviews))
|
|
31
|
-
return null;
|
|
32
|
-
const validated = {};
|
|
33
|
-
for (const [id, entry] of Object.entries(reviews)) {
|
|
34
|
-
if (entry === null || typeof entry !== "object")
|
|
35
|
-
continue;
|
|
36
|
-
const e = entry;
|
|
37
|
-
if (typeof e["firstSeenAt"] !== "number" || !Number.isFinite(e["firstSeenAt"]))
|
|
38
|
-
continue;
|
|
39
|
-
if (typeof e["bodyHash"] !== "string")
|
|
40
|
-
continue;
|
|
41
|
-
validated[id] = { firstSeenAt: e["firstSeenAt"], bodyHash: e["bodyHash"] };
|
|
42
|
-
}
|
|
43
|
-
return { reviews: validated };
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export async function writeBotCrSeenState(key, state) {
|
|
50
|
-
let tmp;
|
|
51
|
-
try {
|
|
52
|
-
const path = resolvePath(key);
|
|
53
|
-
tmp = `${path}.${randomUUID()}.tmp`;
|
|
54
|
-
await mkdir(dirname(path), { recursive: true });
|
|
55
|
-
await writeFile(tmp, JSON.stringify(state), "utf8");
|
|
56
|
-
await rename(tmp, path);
|
|
57
|
-
tmp = undefined;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// best-effort
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
if (tmp !== undefined) {
|
|
64
|
-
try {
|
|
65
|
-
await unlink(tmp);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
// best-effort cleanup
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Update tracked entries against the current set of bot CR reviews:
|
|
75
|
-
* - Insert any new IDs with `firstSeenAt = now` and the current body hash.
|
|
76
|
-
* - Reset `firstSeenAt` for entries whose body hash changed (review re-issued).
|
|
77
|
-
* - Drop entries whose IDs are no longer in the input (review was dismissed
|
|
78
|
-
* or superseded by an approval).
|
|
79
|
-
*
|
|
80
|
-
* Pure function: returns the next state and the IDs whose age has reached the
|
|
81
|
-
* `stallTimeoutSeconds` threshold (escalate candidates).
|
|
82
|
-
*/
|
|
83
|
-
export function updateBotCrSeenState(previous, currentBotCrReviews, nowSeconds, stallTimeoutSeconds) {
|
|
84
|
-
const previousReviews = previous?.reviews ?? {};
|
|
85
|
-
const next = {};
|
|
86
|
-
const staleIds = [];
|
|
87
|
-
for (const r of currentBotCrReviews) {
|
|
88
|
-
const bodyHash = hashBody(r.body);
|
|
89
|
-
const prior = previousReviews[r.id];
|
|
90
|
-
const entry = prior && prior.bodyHash === bodyHash ? prior : { firstSeenAt: nowSeconds, bodyHash };
|
|
91
|
-
next[r.id] = entry;
|
|
92
|
-
if (stallTimeoutSeconds > 0 && nowSeconds - entry.firstSeenAt >= stallTimeoutSeconds) {
|
|
93
|
-
staleIds.push(r.id);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return { next: { reviews: next }, staleIds };
|
|
97
|
-
}
|
|
98
|
-
function resolvePath(key) {
|
|
99
|
-
return resolvePrStatePath(key, "bot-cr-seen.json");
|
|
100
|
-
}
|