pr-shepherd 0.16.2 → 0.16.4

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 (31) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/checks/triage.test-support.mjs +61 -0
  3. package/bin/cli/iterate-lean.test-support.mjs +5 -0
  4. package/bin/cli-parser.commit-suggestion.test-support.mjs +66 -0
  5. package/bin/cli-parser.iterate-fix.test-support.mjs +48 -0
  6. package/bin/cli-parser.iterate.test-support.mjs +49 -0
  7. package/bin/cli-parser.test-support.mjs +43 -0
  8. package/bin/commands/check.mjs +14 -17
  9. package/bin/commands/check.test-support.mjs +140 -0
  10. package/bin/commands/commit-suggestion.apply.test-support.mjs +87 -0
  11. package/bin/commands/commit-suggestion.test-support.mjs +112 -0
  12. package/bin/commands/iterate/index.mjs +20 -16
  13. package/bin/commands/iterate-stall.test-support.mjs +25 -0
  14. package/bin/commands/iterate-test-support.mjs +149 -0
  15. package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +118 -0
  16. package/bin/commands/ready-mergeability.mjs +30 -0
  17. package/bin/commands/resolve.test-support.mjs +114 -0
  18. package/bin/commands/shepherd-journal.test-support.mjs +9 -0
  19. package/bin/comments/resolve.test-support.mjs +40 -0
  20. package/bin/github/batch-parsers.test-support.mjs +67 -0
  21. package/bin/github/batch.test-support.mjs +67 -0
  22. package/bin/github/graphql-http.mjs +73 -0
  23. package/bin/github/http-auth.mjs +48 -0
  24. package/bin/github/http-request.mjs +15 -0
  25. package/bin/github/http-utils.mjs +34 -0
  26. package/bin/github/http.mjs +4 -319
  27. package/bin/github/http.test-support.mjs +52 -0
  28. package/bin/github/rest-http.mjs +131 -0
  29. package/bin/suggestions/patch.test-support.mjs +4 -0
  30. package/package.json +2 -2
  31. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
@@ -0,0 +1,131 @@
1
+ import { appendEntry, nextEntry } from "../log/log-file.mjs";
2
+ import { formatRequestEntry, formatResponseEntry } from "../log/session.mjs";
3
+ import { makeHeaders } from "./http-auth.mjs";
4
+ import { requestWithTokenRetry } from "./http-request.mjs";
5
+ import { redactToken, redactUrl, sanitizeBody } from "./http-utils.mjs";
6
+ const BASE_URL = "https://api.github.com";
7
+ export async function rest(method, path, body) {
8
+ const url = `${BASE_URL}${path}`;
9
+ const n = nextEntry();
10
+ appendEntry(formatRequestEntry({ n, kind: "REST", method, url, body }));
11
+ const t0 = performance.now();
12
+ const { res, attempt, retryT0 } = await requestWithTokenRetry(async () => fetch(url, {
13
+ method,
14
+ headers: await makeHeaders(),
15
+ body: body !== undefined ? JSON.stringify(body) : undefined,
16
+ }), t0, (status, durationMs) => appendEntry(formatResponseEntry({ n, kind: "REST", method, url, status, durationMs })));
17
+ const durationMs = Math.round(performance.now() - retryT0);
18
+ const ct = res.headers.get("content-type") ?? "";
19
+ if (!res.ok) {
20
+ const text = await res.text();
21
+ appendEntry(formatResponseEntry({
22
+ n,
23
+ kind: "REST",
24
+ method,
25
+ url,
26
+ status: res.status,
27
+ durationMs,
28
+ textBody: redactToken(text),
29
+ attempt: attempt > 1 ? attempt : undefined,
30
+ }));
31
+ throw new Error(`GitHub REST ${method} ${path} failed: ${res.status} ${sanitizeBody(text)}`);
32
+ }
33
+ if (ct.includes("application/json")) {
34
+ const json = (await res.json());
35
+ appendEntry(formatResponseEntry({
36
+ n,
37
+ kind: "REST",
38
+ method,
39
+ url,
40
+ status: res.status,
41
+ durationMs,
42
+ contentType: ct,
43
+ body: json,
44
+ attempt: attempt > 1 ? attempt : undefined,
45
+ }));
46
+ return json;
47
+ }
48
+ appendEntry(formatResponseEntry({
49
+ n,
50
+ kind: "REST",
51
+ method,
52
+ url,
53
+ status: res.status,
54
+ durationMs,
55
+ contentType: ct || undefined,
56
+ attempt: attempt > 1 ? attempt : undefined,
57
+ }));
58
+ return undefined;
59
+ }
60
+ export async function restText(path) {
61
+ const url = `${BASE_URL}${path}`;
62
+ const n = nextEntry();
63
+ appendEntry(formatRequestEntry({ n, kind: "restText", method: "GET", url }));
64
+ const t0 = performance.now();
65
+ const { res, attempt, retryT0 } = await requestWithTokenRetry(async () => fetch(url, { method: "GET", headers: await makeHeaders(), redirect: "manual" }), t0, (status, durationMs) => appendEntry(formatResponseEntry({ n, kind: "restText", method: "GET", url, status, durationMs })));
66
+ const durationMs = Math.round(performance.now() - retryT0);
67
+ if ([301, 302, 307, 308].includes(res.status)) {
68
+ const redirected = await followRestTextRedirect(res, { n, url, durationMs, attempt });
69
+ if (redirected !== null)
70
+ return redirected;
71
+ }
72
+ if (!res.ok) {
73
+ const text = await res.text();
74
+ appendEntry(formatResponseEntry({
75
+ n,
76
+ kind: "restText",
77
+ method: "GET",
78
+ url,
79
+ status: res.status,
80
+ durationMs,
81
+ attempt: attempt > 1 ? attempt : undefined,
82
+ }));
83
+ throw new Error(`GitHub REST GET ${path} failed: ${res.status} ${sanitizeBody(text)}`);
84
+ }
85
+ appendEntry(formatResponseEntry({
86
+ n,
87
+ kind: "restText",
88
+ method: "GET",
89
+ url,
90
+ status: res.status,
91
+ durationMs,
92
+ contentLength: parseContentLength(res.headers),
93
+ attempt: attempt > 1 ? attempt : undefined,
94
+ }));
95
+ return res.text();
96
+ }
97
+ async function followRestTextRedirect(res, entry) {
98
+ appendEntry(formatResponseEntry({
99
+ n: entry.n,
100
+ kind: "restText",
101
+ method: "GET",
102
+ url: entry.url,
103
+ status: res.status,
104
+ durationMs: entry.durationMs,
105
+ attempt: entry.attempt > 1 ? entry.attempt : undefined,
106
+ }));
107
+ const location = res.headers.get("location");
108
+ if (!location)
109
+ return null;
110
+ const n2 = nextEntry();
111
+ const logUrl = redactUrl(location);
112
+ appendEntry(formatRequestEntry({ n: n2, kind: "restText", method: "GET", url: logUrl }));
113
+ const t1 = performance.now();
114
+ const redirectRes = await fetch(location);
115
+ appendEntry(formatResponseEntry({
116
+ n: n2,
117
+ kind: "restText",
118
+ method: "GET",
119
+ url: logUrl,
120
+ status: redirectRes.status,
121
+ durationMs: Math.round(performance.now() - t1),
122
+ contentLength: parseContentLength(redirectRes.headers),
123
+ }));
124
+ if (!redirectRes.ok)
125
+ throw new Error(`redirect target ${location} failed: ${redirectRes.status}`);
126
+ return redirectRes.text();
127
+ }
128
+ function parseContentLength(headers) {
129
+ const raw = headers.get("content-length");
130
+ return raw !== null && Number.isFinite(Number(raw)) ? Number(raw) : undefined;
131
+ }
@@ -0,0 +1,4 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect } from "vitest";
3
+ import { buildUnifiedDiff } from "./patch.mjs";
4
+ export { buildUnifiedDiff };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -42,7 +42,7 @@
42
42
  "format": "oxfmt src/ plugins/ .agents/plugins/ docs/ README.md",
43
43
  "format:check": "oxfmt --check src/ plugins/ .agents/plugins/ docs/ README.md",
44
44
  "test": "vitest run",
45
- "test:coverage": "vitest run --coverage",
45
+ "test:coverage": "vitest run --coverage && node scripts/strip-lcov-branches.mjs",
46
46
  "test:watch": "vitest"
47
47
  },
48
48
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",