pr-shepherd 0.9.0 → 0.10.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 +2 -2
- package/bin/checks/triage.mjs +32 -40
- package/bin/cli/fence.mjs +4 -0
- package/bin/cli/fix-formatter.mjs +21 -24
- package/bin/cli/formatters.mjs +33 -51
- package/bin/cli/handlers.mjs +1 -1
- package/bin/cli/iterate-formatter.mjs +20 -35
- package/bin/cli/iterate-lean.mjs +0 -3
- package/bin/cli/list-formatters.mjs +32 -0
- package/bin/cli/suggestion-renderer.mjs +31 -0
- package/bin/cli-parser.iterate-fixtures.mjs +1 -2
- package/bin/cli-parser.mjs +27 -3
- package/bin/commands/check.mjs +1 -2
- package/bin/commands/commit-suggestion.mjs +12 -10
- package/bin/commands/iterate/classify.mjs +5 -29
- package/bin/commands/iterate/escalate.mjs +3 -12
- package/bin/commands/iterate/fix-code.mjs +4 -9
- package/bin/commands/iterate/render.mjs +9 -2
- package/bin/commands/log-file.mjs +7 -0
- package/bin/commands/monitor.mjs +7 -4
- package/bin/commands/ready-delay.mjs +2 -2
- package/bin/commands/resolve-instructions.mjs +5 -2
- package/bin/commands/resolve.mjs +1 -20
- package/bin/commands/status.mjs +40 -28
- package/bin/comments/resolve.mjs +75 -65
- package/bin/config.json +2 -2
- package/bin/github/batch-parsers.mjs +2 -0
- package/bin/github/client.mjs +11 -32
- package/bin/github/gql/batch-pr.gql +4 -0
- package/bin/github/gql/get-pr-head-sha.gql +7 -0
- package/bin/github/http.mjs +166 -11
- package/bin/github/queries.mjs +7 -11
- package/bin/log/log-file.mjs +88 -0
- package/bin/log/session.mjs +100 -0
- package/bin/log/setup.mjs +54 -0
- package/bin/reporters/agent.mjs +14 -1
- package/bin/reporters/text.mjs +81 -102
- package/bin/state/base.mjs +5 -0
- package/bin/state/fix-attempts.mjs +2 -2
- package/bin/state/iterate-stall.mjs +2 -2
- package/bin/state/seen-comments.mjs +2 -2
- package/bin/suggestions/extract.mjs +15 -0
- package/bin/suggestions/parse.mjs +1 -26
- package/bin/util/markdown.mjs +7 -0
- package/bin/util/worktree.mjs +23 -0
- package/package.json +2 -2
- package/bin/github/gql/dismiss-review.gql +0 -7
- package/bin/github/gql/minimize-comment.gql +0 -7
- package/bin/github/gql/multi-pr-status.gql +0 -32
- package/bin/github/gql/resolve-thread.gql +0 -7
package/bin/reporters/text.mjs
CHANGED
|
@@ -1,167 +1,146 @@
|
|
|
1
1
|
import { buildCheckInstructions } from "./check-instructions.mjs";
|
|
2
|
+
import { renderThreadBullet, renderCommentBullet, renderReviewBullet, renderFirstLookStatusTag, } from "../cli/list-formatters.mjs";
|
|
3
|
+
import { joinSections } from "../util/markdown.mjs";
|
|
2
4
|
export function formatText(report) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const header = [
|
|
6
|
+
`# PR #${report.pr} [CHECK] — ${report.repo}`,
|
|
7
|
+
`Status: ${report.status}`,
|
|
8
|
+
`Base: ${report.baseBranch}`,
|
|
9
|
+
].join("\n");
|
|
8
10
|
const ms = report.mergeStatus;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
const mergeStatusSection = [
|
|
12
|
+
"## Merge Status",
|
|
13
|
+
"",
|
|
14
|
+
`- status: \`${ms.status}\``,
|
|
15
|
+
`- mergeStateStatus: \`${ms.mergeStateStatus}\``,
|
|
16
|
+
`- mergeable: \`${ms.mergeable}\``,
|
|
17
|
+
`- reviewDecision: \`${ms.reviewDecision ?? "(none)"}\``,
|
|
18
|
+
`- isDraft: \`${ms.isDraft}\``,
|
|
19
|
+
`- copilotReviewInProgress: \`${ms.copilotReviewInProgress}\``,
|
|
20
|
+
].join("\n");
|
|
18
21
|
const { passing, failing, inProgress, skipped } = report.checks;
|
|
19
22
|
const total = passing.length + failing.length + inProgress.length + skipped.length;
|
|
20
|
-
|
|
21
|
-
parts.push("");
|
|
22
|
-
parts.push(`${passing.length}/${total} passed`);
|
|
23
|
-
parts.push("");
|
|
23
|
+
const ciSubsections = [`${passing.length}/${total} passed`];
|
|
24
24
|
if (failing.length > 0) {
|
|
25
|
-
|
|
26
|
-
parts.push("");
|
|
25
|
+
const lines = [`### Failed (${failing.length})`, ""];
|
|
27
26
|
for (const c of failing) {
|
|
28
27
|
const triaged = c;
|
|
29
28
|
const prefix = triaged.workflowName ? `${triaged.workflowName} › ` : "";
|
|
30
|
-
|
|
29
|
+
lines.push(`- ${prefix}${c.name}: ${c.conclusion ?? c.status}`);
|
|
31
30
|
if (triaged.failedStep) {
|
|
32
|
-
|
|
31
|
+
lines.push(` failed step: ${triaged.failedStep}`);
|
|
33
32
|
}
|
|
34
33
|
if (triaged.summary) {
|
|
35
|
-
|
|
34
|
+
lines.push(` summary: ${triaged.summary}`);
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
|
-
|
|
37
|
+
ciSubsections.push(lines.join("\n"));
|
|
39
38
|
}
|
|
40
39
|
if (inProgress.length > 0) {
|
|
41
|
-
|
|
42
|
-
parts.push("");
|
|
40
|
+
const lines = [`### In Progress (${inProgress.length})`, ""];
|
|
43
41
|
for (const c of inProgress) {
|
|
44
|
-
|
|
42
|
+
lines.push(`- ${c.name}: ${c.status}`);
|
|
45
43
|
}
|
|
46
|
-
|
|
44
|
+
ciSubsections.push(lines.join("\n"));
|
|
47
45
|
}
|
|
48
46
|
if (skipped.length > 0) {
|
|
49
|
-
|
|
50
|
-
parts.push("");
|
|
47
|
+
ciSubsections.push(`### Skipped (${skipped.length}): ${skipped.map((c) => c.name).join(", ")}`);
|
|
51
48
|
}
|
|
52
49
|
if (report.checks.filtered.length > 0) {
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
const lines = [
|
|
51
|
+
`### Filtered non-PR-trigger (${report.checks.filtered.length}): ${report.checks.filtered.map((c) => c.name).join(", ")}`,
|
|
52
|
+
"",
|
|
53
|
+
];
|
|
55
54
|
if (report.checks.blockedByFilteredCheck) {
|
|
56
|
-
|
|
55
|
+
lines.push("> Note: PR is BLOCKED and all filtered checks are non-PR-trigger — one of these filtered checks may be a required status check blocking merge.");
|
|
57
56
|
}
|
|
58
57
|
else if (report.mergeStatus.status === "BLOCKED") {
|
|
59
|
-
|
|
58
|
+
lines.push("> Note: one or more of these filtered checks may be a required status check blocking merge.");
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
ciSubsections.push(lines.join("\n"));
|
|
62
61
|
}
|
|
62
|
+
const ciSection = `## CI Checks\n\n${ciSubsections.join("\n\n")}`;
|
|
63
63
|
const { actionable: actionableThreads, autoResolved, autoResolveErrors, firstLook: firstLookThreads, } = report.threads;
|
|
64
64
|
const hasThreadSection = autoResolved.length > 0 || autoResolveErrors.length > 0 || actionableThreads.length > 0;
|
|
65
|
+
let threadsSection = null;
|
|
65
66
|
if (hasThreadSection) {
|
|
66
|
-
|
|
67
|
-
parts.push("");
|
|
67
|
+
const subparts = [];
|
|
68
68
|
if (autoResolved.length > 0) {
|
|
69
|
-
|
|
69
|
+
const lines = [`Auto-resolved outdated (${autoResolved.length}):`];
|
|
70
70
|
for (const t of autoResolved) {
|
|
71
|
-
|
|
71
|
+
lines.push(renderThreadBullet(t));
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
subparts.push(lines.join("\n"));
|
|
74
74
|
}
|
|
75
75
|
if (autoResolveErrors.length > 0) {
|
|
76
|
-
|
|
76
|
+
const lines = [`Auto-resolve errors (${autoResolveErrors.length}):`];
|
|
77
77
|
for (const e of autoResolveErrors) {
|
|
78
|
-
|
|
78
|
+
lines.push(`- ${e}`);
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
subparts.push(lines.join("\n"));
|
|
81
81
|
}
|
|
82
82
|
if (actionableThreads.length > 0) {
|
|
83
|
-
|
|
84
|
-
parts.push("");
|
|
83
|
+
const lines = [`### Actionable (${actionableThreads.length})`, ""];
|
|
85
84
|
for (const t of actionableThreads) {
|
|
86
|
-
|
|
87
|
-
parts.push(`- threadId=${t.id} ${label} (@${t.author})`);
|
|
88
|
-
parts.push(` ${firstLine(t.body)}`);
|
|
85
|
+
lines.push(renderThreadBullet(t));
|
|
89
86
|
}
|
|
90
|
-
|
|
87
|
+
subparts.push(lines.join("\n"));
|
|
91
88
|
}
|
|
89
|
+
threadsSection = `## Review Threads\n\n${subparts.join("\n\n")}`;
|
|
92
90
|
}
|
|
93
91
|
const { actionable: actionableComments, firstLook: firstLookComments } = report.comments;
|
|
92
|
+
let commentsSection = null;
|
|
94
93
|
if (actionableComments.length > 0) {
|
|
95
|
-
|
|
96
|
-
parts.push("");
|
|
97
|
-
parts.push(`### Actionable (${actionableComments.length})`);
|
|
98
|
-
parts.push("");
|
|
94
|
+
const lines = [`### Actionable (${actionableComments.length})`, ""];
|
|
99
95
|
for (const c of actionableComments) {
|
|
100
|
-
|
|
96
|
+
lines.push(renderCommentBullet(c));
|
|
101
97
|
}
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
if (report.changesRequestedReviews.length > 0) {
|
|
105
|
-
parts.push("## CHANGES_REQUESTED Reviews");
|
|
106
|
-
parts.push("");
|
|
107
|
-
for (const r of report.changesRequestedReviews) {
|
|
108
|
-
parts.push(`- reviewId=${r.id} (@${r.author}): ${firstLine(r.body)}`);
|
|
109
|
-
}
|
|
110
|
-
parts.push("");
|
|
111
|
-
}
|
|
112
|
-
if (report.reviewSummaries.length > 0) {
|
|
113
|
-
parts.push("## Review Summaries");
|
|
114
|
-
parts.push("");
|
|
115
|
-
for (const r of report.reviewSummaries) {
|
|
116
|
-
parts.push(`- reviewId=${r.id} (@${r.author}): ${firstLine(r.body)}`);
|
|
117
|
-
}
|
|
118
|
-
parts.push("");
|
|
119
|
-
}
|
|
120
|
-
if (report.approvedReviews.length > 0) {
|
|
121
|
-
parts.push("## Approved Reviews");
|
|
122
|
-
parts.push("");
|
|
123
|
-
for (const r of report.approvedReviews) {
|
|
124
|
-
parts.push(`- reviewId=${r.id} (@${r.author}): ${firstLine(r.body)}`);
|
|
125
|
-
}
|
|
126
|
-
parts.push("");
|
|
98
|
+
commentsSection = `## PR Comments\n\n${lines.join("\n")}`;
|
|
127
99
|
}
|
|
100
|
+
const changesRequestedSection = reviewListSection("CHANGES_REQUESTED Reviews", report.changesRequestedReviews);
|
|
101
|
+
const reviewSummariesSection = reviewListSection("Review Summaries", report.reviewSummaries);
|
|
102
|
+
const approvedReviewsSection = reviewListSection("Approved Reviews", report.approvedReviews);
|
|
128
103
|
const firstLookTotal = firstLookThreads.length + firstLookComments.length;
|
|
104
|
+
let firstLookSection = null;
|
|
129
105
|
if (firstLookTotal > 0) {
|
|
130
|
-
|
|
131
|
-
|
|
106
|
+
const lines = [
|
|
107
|
+
`## First-look items (${firstLookTotal}) — already closed on GitHub; acknowledge only`,
|
|
108
|
+
"",
|
|
109
|
+
];
|
|
132
110
|
for (const t of firstLookThreads) {
|
|
133
|
-
|
|
134
|
-
? `[status: outdated, auto-resolved]`
|
|
135
|
-
: `[status: ${t.firstLookStatus}]`;
|
|
136
|
-
const loc = t.path ? `${t.path}:${t.line ?? "?"}` : "(no location)";
|
|
137
|
-
parts.push(`- threadId=${t.id} ${loc} (@${t.author}) ${statusTag}`);
|
|
138
|
-
parts.push(` ${firstLine(t.body)}`);
|
|
111
|
+
lines.push(renderThreadBullet(t, { statusTag: renderFirstLookStatusTag(t) }));
|
|
139
112
|
}
|
|
140
113
|
for (const c of firstLookComments) {
|
|
141
|
-
|
|
142
|
-
parts.push(` ${firstLine(c.body)}`);
|
|
114
|
+
lines.push(renderCommentBullet(c, { statusTag: "[status: minimized]" }));
|
|
143
115
|
}
|
|
144
|
-
|
|
116
|
+
firstLookSection = lines.join("\n");
|
|
145
117
|
}
|
|
146
118
|
const totalActionable = actionableThreads.length + actionableComments.length + report.changesRequestedReviews.length;
|
|
147
|
-
parts.push("## Summary");
|
|
148
|
-
parts.push("");
|
|
149
119
|
const counts = [];
|
|
150
120
|
if (totalActionable > 0)
|
|
151
121
|
counts.push(`${totalActionable} actionable`);
|
|
152
122
|
if (firstLookTotal > 0)
|
|
153
123
|
counts.push(`${firstLookTotal} first-look`);
|
|
154
124
|
const summaryLine = counts.join(", ") || "0 actionable — all threads resolved/minimized";
|
|
155
|
-
|
|
156
|
-
parts.push("");
|
|
157
|
-
parts.push("## Instructions");
|
|
158
|
-
parts.push("");
|
|
125
|
+
const summarySection = `## Summary\n\n${summaryLine}`;
|
|
159
126
|
const instructions = buildCheckInstructions(report);
|
|
160
|
-
instructions.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
127
|
+
const instructionsSection = `## Instructions\n\n${instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}`;
|
|
128
|
+
return joinSections([
|
|
129
|
+
header,
|
|
130
|
+
mergeStatusSection,
|
|
131
|
+
ciSection,
|
|
132
|
+
threadsSection,
|
|
133
|
+
commentsSection,
|
|
134
|
+
changesRequestedSection,
|
|
135
|
+
reviewSummariesSection,
|
|
136
|
+
approvedReviewsSection,
|
|
137
|
+
firstLookSection,
|
|
138
|
+
summarySection,
|
|
139
|
+
instructionsSection,
|
|
140
|
+
]);
|
|
164
141
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
142
|
+
function reviewListSection(heading, items) {
|
|
143
|
+
if (items.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return `## ${heading}\n\n${items.map((r) => renderReviewBullet(r, { includeBody: true })).join("\n")}`;
|
|
167
146
|
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { join, dirname } from "node:path";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
13
|
import { SAFE_SEGMENT } from "../util/path-segment.mjs";
|
|
14
|
+
import { resolveStateBase } from "./base.mjs";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Public API
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
@@ -62,6 +62,6 @@ function resolvePath(key) {
|
|
|
62
62
|
throw new Error(`Invalid state key segment "${field}": ${value}`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
const base =
|
|
65
|
+
const base = resolveStateBase();
|
|
66
66
|
return join(base, `${key.owner}-${key.repo}`, String(key.pr), "fix-attempts.json");
|
|
67
67
|
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { join, dirname } from "node:path";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
13
|
import { SAFE_SEGMENT } from "../util/path-segment.mjs";
|
|
14
|
+
import { resolveStateBase } from "./base.mjs";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Public API
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
@@ -69,6 +69,6 @@ function resolvePath(key) {
|
|
|
69
69
|
throw new Error(`Invalid state key segment "${field}": ${value}`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
const base =
|
|
72
|
+
const base = resolveStateBase();
|
|
73
73
|
return join(base, `${key.owner}-${key.repo}`, String(key.pr), "iterate-stall.json");
|
|
74
74
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir, access, readdir } from "node:fs/promises";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
3
|
import { SAFE_SEGMENT } from "../util/path-segment.mjs";
|
|
4
|
+
import { resolveStateBase } from "./base.mjs";
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
// Public API
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
@@ -65,7 +65,7 @@ function resolveDir(key) {
|
|
|
65
65
|
throw new Error(`Invalid state key segment "${field}": ${value}`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
const base =
|
|
68
|
+
const base = resolveStateBase();
|
|
69
69
|
return join(base, `${key.owner}-${key.repo}`, String(key.pr), "seen");
|
|
70
70
|
}
|
|
71
71
|
function resolvePath(key, id) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseSuggestion } from "./parse.mjs";
|
|
2
|
+
export function extractSuggestion(thread) {
|
|
3
|
+
if (!thread.path || thread.line === null)
|
|
4
|
+
return null;
|
|
5
|
+
const parsed = parseSuggestion(thread.body);
|
|
6
|
+
if (!parsed)
|
|
7
|
+
return null;
|
|
8
|
+
const startLine = thread.startLine ?? thread.line;
|
|
9
|
+
return {
|
|
10
|
+
startLine,
|
|
11
|
+
endLine: thread.line,
|
|
12
|
+
lines: parsed.lines,
|
|
13
|
+
author: thread.author,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* GitHub's "Commit suggestion" UI button treats the first ```suggestion fenced
|
|
5
5
|
* block in a review comment as a replacement for the commented line range.
|
|
6
|
-
* This module extracts that block from the comment body
|
|
7
|
-
* parsed suggestion to a file's contents.
|
|
6
|
+
* This module extracts that block from the comment body.
|
|
8
7
|
*
|
|
9
8
|
* There is no GitHub API for applying suggestions — tools that reproduce the
|
|
10
9
|
* button (this one included) must parse + commit themselves.
|
|
@@ -93,27 +92,3 @@ export function isCommittableSuggestion(parsed) {
|
|
|
93
92
|
const fenceRuns = (replacement.match(/`{3,}/g) ?? []).length;
|
|
94
93
|
return fenceRuns % 2 === 0;
|
|
95
94
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Apply a suggestion to a file's full contents by replacing lines
|
|
98
|
-
* [startLine..endLine] (1-indexed, inclusive) with the given replacement lines.
|
|
99
|
-
*
|
|
100
|
-
* Pass `[]` to delete the range, `[""]` to replace with a single blank line,
|
|
101
|
-
* or `["a", "b", ...]` for arbitrary replacements. The file's trailing-newline
|
|
102
|
-
* state is preserved exactly.
|
|
103
|
-
*/
|
|
104
|
-
export function applySuggestionToFile(fileContent, startLine, endLine, replacementLines) {
|
|
105
|
-
if (startLine < 1 || endLine < startLine) {
|
|
106
|
-
throw new Error(`Invalid line range: start=${startLine}, end=${endLine}`);
|
|
107
|
-
}
|
|
108
|
-
const endsWithNewline = fileContent.endsWith("\n");
|
|
109
|
-
// Strip a single trailing \n so split/join round-trips exactly.
|
|
110
|
-
const body = endsWithNewline ? fileContent.slice(0, -1) : fileContent;
|
|
111
|
-
const lines = body.split("\n");
|
|
112
|
-
if (endLine > lines.length) {
|
|
113
|
-
throw new Error(`Line ${endLine} is out of range (file has ${lines.length} line(s))`);
|
|
114
|
-
}
|
|
115
|
-
const before = lines.slice(0, startLine - 1);
|
|
116
|
-
const after = lines.slice(endLine);
|
|
117
|
-
const result = [...before, ...replacementLines, ...after].join("\n");
|
|
118
|
-
return endsWithNewline ? `${result}\n` : result;
|
|
119
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { basename } from "node:path";
|
|
5
|
+
import { SAFE_SEGMENT } from "./path-segment.mjs";
|
|
6
|
+
const execFile = promisify(execFileCb);
|
|
7
|
+
/** Returns the absolute path to the current git worktree root. Throws outside a git repo. */
|
|
8
|
+
export async function getWorktreeRoot() {
|
|
9
|
+
const { stdout } = await execFile("git", ["rev-parse", "--show-toplevel"]);
|
|
10
|
+
return stdout.trim();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns a filesystem-safe key for the current worktree, unique across
|
|
14
|
+
* worktrees of the same repo. Format: `<basename>-<sha8>` where sha8 is
|
|
15
|
+
* the first 8 hex chars of sha256(toplevel). Falls back to sha8-only if
|
|
16
|
+
* the basename contains characters outside SAFE_SEGMENT.
|
|
17
|
+
*/
|
|
18
|
+
export async function getWorktreeKey() {
|
|
19
|
+
const root = await getWorktreeRoot();
|
|
20
|
+
const sha8 = createHash("sha256").update(root).digest("hex").slice(0, 8);
|
|
21
|
+
const base = basename(root);
|
|
22
|
+
return SAFE_SEGMENT.test(base) ? `${base}-${sha8}` : sha8;
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-shepherd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Autonomous PR CI monitor and review-comment resolver for Claude Code",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Ong",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^25.6.0",
|
|
27
|
-
"oxfmt": "^0.
|
|
27
|
+
"oxfmt": "^0.46.0",
|
|
28
28
|
"oxlint": "^1.60.0",
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"vitest": "^4.1.4",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
query MultiPrStatus($owner: String!, $repo: String!, $pr: Int!) {
|
|
2
|
-
repository(owner: $owner, name: $repo) {
|
|
3
|
-
pullRequest(number: $pr) {
|
|
4
|
-
number
|
|
5
|
-
state
|
|
6
|
-
isDraft
|
|
7
|
-
title
|
|
8
|
-
mergeStateStatus
|
|
9
|
-
reviewDecision
|
|
10
|
-
# totalCount lets callers detect truncation; pageInfo enables backward pagination.
|
|
11
|
-
reviewThreads(last: 100) {
|
|
12
|
-
totalCount
|
|
13
|
-
pageInfo {
|
|
14
|
-
hasPreviousPage
|
|
15
|
-
startCursor
|
|
16
|
-
}
|
|
17
|
-
nodes {
|
|
18
|
-
isResolved
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
commits(last: 1) {
|
|
22
|
-
nodes {
|
|
23
|
-
commit {
|
|
24
|
-
statusCheckRollup {
|
|
25
|
-
state
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|