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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +25 -17
  3. package/bin/checks/log-excerpt.d.mts +1 -0
  4. package/bin/checks/log-excerpt.mjs +192 -0
  5. package/bin/checks/triage.mjs +2 -121
  6. package/bin/cli/iterate-lean.mjs +0 -3
  7. package/bin/cli/poll-summary-emitter.mjs +2 -3
  8. package/bin/cli/poll-summary-formatter.mjs +9 -2
  9. package/bin/commands/check.mjs +1 -0
  10. package/bin/commands/iterate/escalate.mjs +0 -13
  11. package/bin/commands/iterate/fix-code.d.mts +2 -0
  12. package/bin/commands/iterate/fix-code.mjs +14 -42
  13. package/bin/commands/iterate/index.mjs +140 -3
  14. package/bin/commands/iterate/merge-state.mjs +33 -18
  15. package/bin/commands/iterate/parent-first.d.mts +9 -0
  16. package/bin/commands/iterate/parent-first.mjs +57 -0
  17. package/bin/commands/iterate/stale-ancestry.d.mts +22 -0
  18. package/bin/commands/iterate/stale-ancestry.mjs +37 -0
  19. package/bin/commands/poll-summary-instructions.mjs +179 -99
  20. package/bin/commands/poll-summary.mjs +28 -4
  21. package/bin/exit-codes.d.mts +2 -0
  22. package/bin/exit-codes.mjs +2 -0
  23. package/bin/github/batch-parsers.mjs +1 -0
  24. package/bin/github/batch-raw-rules.d.mts +3 -0
  25. package/bin/github/gql/poll-summary-fragment.gql +54 -0
  26. package/bin/github/gql/pr-merge-policy.gql +3 -0
  27. package/bin/github/poll-summary-fingerprint.d.mts +8 -0
  28. package/bin/github/poll-summary-fingerprint.mjs +28 -0
  29. package/bin/github/poll-summary-projector.mjs +67 -14
  30. package/bin/github/poll-summary-queue-removal.d.mts +5 -0
  31. package/bin/github/poll-summary-queue-removal.mjs +16 -0
  32. package/bin/github/poll-summary-raw.d.mts +33 -0
  33. package/bin/github/poll-summary-readiness.d.mts +6 -0
  34. package/bin/github/poll-summary-readiness.mjs +25 -0
  35. package/bin/github/poll-summary.d.mts +3 -0
  36. package/bin/github/poll-summary.mjs +5 -0
  37. package/bin/state/ready-receipts.d.mts +45 -0
  38. package/bin/state/ready-receipts.mjs +86 -0
  39. package/bin/types/escalate.d.mts +2 -3
  40. package/bin/types/github.d.mts +2 -0
  41. package/bin/types/poll-summary.d.mts +12 -2
  42. package/bin/types/report.d.mts +2 -0
  43. package/package.json +4 -4
  44. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  45. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  46. package/plugins/pr-shepherd/.mcp.json +1 -1
  47. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +3 -3
  48. package/bin/state/bot-cr-seen.d.mts +0 -51
  49. 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
- }