pr-shepherd 0.21.0 → 0.23.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 +87 -124
- package/bin/checks/triage.mjs +1 -0
- package/bin/cli/args.mjs +2 -0
- package/bin/cli/fix-formatter.mjs +38 -9
- package/bin/cli/formatters.mjs +3 -45
- package/bin/cli/handlers.mjs +22 -1
- package/bin/cli/help-command-pages.mjs +27 -2
- package/bin/cli/help-top-page.mjs +2 -0
- package/bin/cli/iterate-lean.mjs +4 -0
- package/bin/cli/list-formatters.mjs +55 -4
- package/bin/cli/mark-files-as-viewed-flags.mjs +34 -0
- package/bin/cli/mark-files-as-viewed-formatter.mjs +52 -0
- package/bin/cli/mutate-formatter.mjs +50 -0
- package/bin/cli-parser.mjs +11 -2
- package/bin/cli-parser.test-support.mjs +6 -1
- package/bin/commands/check-annotations.mjs +40 -0
- package/bin/commands/check.mjs +25 -57
- package/bin/commands/check.test-support.mjs +9 -2
- package/bin/commands/commit-suggestion-instruction.mjs +1 -1
- package/bin/commands/iterate/classify.mjs +25 -30
- package/bin/commands/iterate/escalate.mjs +16 -2
- package/bin/commands/iterate/fix-code.mjs +29 -18
- package/bin/commands/iterate/helpers.mjs +1 -0
- package/bin/commands/iterate/render.mjs +9 -3
- package/bin/commands/iterate/stall.mjs +43 -2
- package/bin/commands/iterate-test-support.mjs +1 -1
- package/bin/commands/iterate-thread-test-support.mjs +18 -0
- package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +10 -1
- package/bin/commands/mark-files-as-viewed.mjs +220 -0
- package/bin/commands/resolve-mutate.mjs +41 -4
- package/bin/commands/resolve.mjs +16 -76
- package/bin/commands/resolve.test-support.mjs +2 -0
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/comments/authors.mjs +14 -0
- package/bin/comments/minimize-policy.mjs +6 -3
- package/bin/comments/pending-ops.mjs +6 -0
- package/bin/comments/resolve.mjs +28 -11
- package/bin/comments/resolve.test-support.mjs +6 -1
- package/bin/comments/review-visibility.mjs +14 -0
- package/bin/comments/thread-visibility.mjs +60 -0
- package/bin/comments/visible-comments.mjs +1 -1
- package/bin/config.json +1 -1
- package/bin/github/batch-parser-helpers.mjs +3 -4
- package/bin/github/batch-parsers.mjs +35 -8
- package/bin/github/batch.mjs +2 -0
- package/bin/github/check-annotations.mjs +75 -0
- package/bin/github/client.test-support.mjs +55 -0
- package/bin/github/gql/batch-pr.gql +15 -2
- package/bin/github/gql/check-run-annotations.gql +32 -0
- package/bin/github/gql/review-thread-comments.gql +27 -0
- package/bin/github/queries.mjs +4 -0
- package/bin/github/thread-comments.mjs +34 -0
- package/bin/reporters/agent.mjs +27 -0
- package/bin/state/seen-comments.test-support.mjs +19 -0
- package/bin/threads/transcript.mjs +31 -0
- package/bin/types/agent-thread.mjs +1 -0
- package/bin/types/check-annotations.mjs +1 -0
- package/bin/types/review-thread.mjs +1 -0
- package/bin/types.mjs +3 -0
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +31 -0
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { graphql } from "./client.mjs";
|
|
3
|
+
import { CHECK_RUN_ANNOTATIONS_QUERY } from "./queries.mjs";
|
|
4
|
+
const ANNOTATIONS_PER_PAGE = 100;
|
|
5
|
+
const MAX_ANNOTATION_PAGES = 10;
|
|
6
|
+
export async function fetchCheckRunAnnotations(checkRunId) {
|
|
7
|
+
let cursor = null;
|
|
8
|
+
const nodes = [];
|
|
9
|
+
for (let page = 1; page <= MAX_ANNOTATION_PAGES; page++) {
|
|
10
|
+
// eslint-disable-next-line no-await-in-loop
|
|
11
|
+
const result = await fetchAnnotationPage(checkRunId, cursor);
|
|
12
|
+
nodes.push(...result.nodes);
|
|
13
|
+
if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor)
|
|
14
|
+
break;
|
|
15
|
+
if (page === MAX_ANNOTATION_PAGES) {
|
|
16
|
+
process.stderr.write(`pr-shepherd: annotation pagination cap (${MAX_ANNOTATION_PAGES * ANNOTATIONS_PER_PAGE} annotations) reached for check run ${checkRunId} — annotation output may be incomplete\n`);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
cursor = result.pageInfo.endCursor;
|
|
20
|
+
}
|
|
21
|
+
return nodes.map((node) => toCheckAnnotation(checkRunId, node));
|
|
22
|
+
}
|
|
23
|
+
async function fetchAnnotationPage(checkRunId, cursor) {
|
|
24
|
+
const res = await graphql(CHECK_RUN_ANNOTATIONS_QUERY, {
|
|
25
|
+
id: checkRunId,
|
|
26
|
+
...(cursor ? { cursor } : {}),
|
|
27
|
+
});
|
|
28
|
+
const node = res.data.node;
|
|
29
|
+
if (node?.__typename !== "CheckRun" || node.annotations === undefined) {
|
|
30
|
+
return { pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] };
|
|
31
|
+
}
|
|
32
|
+
return node.annotations;
|
|
33
|
+
}
|
|
34
|
+
function toCheckAnnotation(checkRunId, raw) {
|
|
35
|
+
const id = `check_annotation_${raw.fullDatabaseId ?? fallbackId(checkRunId, raw)}`;
|
|
36
|
+
const title = raw.title?.trim() || undefined;
|
|
37
|
+
const rawDetails = raw.rawDetails?.trim() || undefined;
|
|
38
|
+
const blobUrl = raw.blobUrl?.trim() || undefined;
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
path: raw.path,
|
|
42
|
+
startLine: raw.location?.start.line ?? null,
|
|
43
|
+
endLine: raw.location?.end.line ?? raw.location?.start.line ?? null,
|
|
44
|
+
...(raw.location?.start.column !== undefined && {
|
|
45
|
+
startColumn: raw.location.start.column,
|
|
46
|
+
}),
|
|
47
|
+
...(raw.location?.end.column !== undefined && {
|
|
48
|
+
endColumn: raw.location.end.column,
|
|
49
|
+
}),
|
|
50
|
+
level: raw.annotationLevel,
|
|
51
|
+
...(title !== undefined && { title }),
|
|
52
|
+
message: raw.message,
|
|
53
|
+
...(rawDetails !== undefined && { rawDetails }),
|
|
54
|
+
...(blobUrl !== undefined && { blobUrl }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function fallbackId(checkRunId, raw) {
|
|
58
|
+
const start = raw.location?.start;
|
|
59
|
+
const end = raw.location?.end;
|
|
60
|
+
const parts = [
|
|
61
|
+
checkRunId,
|
|
62
|
+
raw.path,
|
|
63
|
+
raw.annotationLevel,
|
|
64
|
+
raw.title ?? "",
|
|
65
|
+
raw.message,
|
|
66
|
+
raw.rawDetails ?? "",
|
|
67
|
+
raw.blobUrl ?? "",
|
|
68
|
+
String(start?.line ?? ""),
|
|
69
|
+
String(start?.column ?? ""),
|
|
70
|
+
String(end?.line ?? ""),
|
|
71
|
+
String(end?.column ?? ""),
|
|
72
|
+
];
|
|
73
|
+
const input = parts.map((part) => `${part.length}:${part}`).join("|");
|
|
74
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 24);
|
|
75
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { _resetTokenCache } from "./http.mjs";
|
|
3
|
+
export const mockFetch = vi.fn();
|
|
4
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
5
|
+
const { _mockExecFile } = vi.hoisted(() => ({ _mockExecFile: vi.fn() }));
|
|
6
|
+
export const mockExecFile = _mockExecFile;
|
|
7
|
+
vi.mock("node:child_process", () => ({
|
|
8
|
+
execFile: (cmd, args, optsOrCb, maybeCb) => {
|
|
9
|
+
const cb = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
|
|
10
|
+
_mockExecFile(cmd, args)
|
|
11
|
+
.then((result) => cb(null, result))
|
|
12
|
+
.catch((err) => cb(err, { stdout: "", stderr: "" }));
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
export function gqlOk(data) {
|
|
16
|
+
return {
|
|
17
|
+
ok: true,
|
|
18
|
+
status: 200,
|
|
19
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
20
|
+
json: () => Promise.resolve({ data }),
|
|
21
|
+
text: () => Promise.resolve(JSON.stringify({ data })),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function restOk(data) {
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
status: 200,
|
|
28
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
29
|
+
json: () => Promise.resolve(data),
|
|
30
|
+
text: () => Promise.resolve(JSON.stringify(data)),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function gqlErrors(errors) {
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
status: 200,
|
|
37
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
38
|
+
json: () => Promise.resolve({ data: null, errors }),
|
|
39
|
+
text: () => Promise.resolve(JSON.stringify({ data: null, errors })),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function registerClientHooks() {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
mockFetch.mockReset();
|
|
45
|
+
mockExecFile.mockReset();
|
|
46
|
+
_resetTokenCache();
|
|
47
|
+
delete process.env["GITHUB_TOKEN"];
|
|
48
|
+
delete process.env["GITHUB_PERSONAL_ACCESS_TOKEN"];
|
|
49
|
+
process.env["GH_TOKEN"] = "test-token";
|
|
50
|
+
});
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
delete process.env["GH_TOKEN"];
|
|
53
|
+
_resetTokenCache();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -67,8 +67,14 @@ query BatchPr(
|
|
|
67
67
|
id
|
|
68
68
|
isResolved
|
|
69
69
|
isOutdated
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
path
|
|
71
|
+
line
|
|
72
|
+
startLine
|
|
73
|
+
comments(first: 100) {
|
|
74
|
+
pageInfo {
|
|
75
|
+
hasNextPage
|
|
76
|
+
endCursor
|
|
77
|
+
}
|
|
72
78
|
nodes {
|
|
73
79
|
id
|
|
74
80
|
isMinimized
|
|
@@ -164,21 +170,28 @@ query BatchPr(
|
|
|
164
170
|
nodes {
|
|
165
171
|
__typename
|
|
166
172
|
... on CheckRun {
|
|
173
|
+
id
|
|
167
174
|
name
|
|
168
175
|
status
|
|
169
176
|
conclusion
|
|
170
177
|
detailsUrl
|
|
178
|
+
startedAt
|
|
171
179
|
title
|
|
172
180
|
summary
|
|
173
181
|
checkSuite {
|
|
182
|
+
createdAt
|
|
183
|
+
updatedAt
|
|
174
184
|
workflowRun {
|
|
175
185
|
event
|
|
186
|
+
createdAt
|
|
187
|
+
updatedAt
|
|
176
188
|
}
|
|
177
189
|
}
|
|
178
190
|
}
|
|
179
191
|
... on StatusContext {
|
|
180
192
|
context
|
|
181
193
|
state
|
|
194
|
+
createdAt
|
|
182
195
|
targetUrl
|
|
183
196
|
description
|
|
184
197
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
query CheckRunAnnotations($id: ID!, $cursor: String) {
|
|
2
|
+
node(id: $id) {
|
|
3
|
+
__typename
|
|
4
|
+
... on CheckRun {
|
|
5
|
+
annotations(first: 100, after: $cursor) {
|
|
6
|
+
pageInfo {
|
|
7
|
+
hasNextPage
|
|
8
|
+
endCursor
|
|
9
|
+
}
|
|
10
|
+
nodes {
|
|
11
|
+
fullDatabaseId
|
|
12
|
+
path
|
|
13
|
+
annotationLevel
|
|
14
|
+
title
|
|
15
|
+
message
|
|
16
|
+
rawDetails
|
|
17
|
+
blobUrl
|
|
18
|
+
location {
|
|
19
|
+
start {
|
|
20
|
+
line
|
|
21
|
+
column
|
|
22
|
+
}
|
|
23
|
+
end {
|
|
24
|
+
line
|
|
25
|
+
column
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
query ReviewThreadComments($threadId: ID!, $commentsCursor: String) {
|
|
2
|
+
node(id: $threadId) {
|
|
3
|
+
__typename
|
|
4
|
+
... on PullRequestReviewThread {
|
|
5
|
+
comments(first: 100, after: $commentsCursor) {
|
|
6
|
+
pageInfo {
|
|
7
|
+
hasNextPage
|
|
8
|
+
endCursor
|
|
9
|
+
}
|
|
10
|
+
nodes {
|
|
11
|
+
id
|
|
12
|
+
isMinimized
|
|
13
|
+
url
|
|
14
|
+
author {
|
|
15
|
+
__typename
|
|
16
|
+
login
|
|
17
|
+
}
|
|
18
|
+
body
|
|
19
|
+
path
|
|
20
|
+
line
|
|
21
|
+
startLine
|
|
22
|
+
createdAt
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/bin/github/queries.mjs
CHANGED
|
@@ -11,6 +11,10 @@ import { join } from "node:path";
|
|
|
11
11
|
const gql = (name) => readFileSync(join(import.meta.dirname, "gql", name), "utf8");
|
|
12
12
|
/** The primary batch query that fetches CI + comments + merge status in one round-trip. */
|
|
13
13
|
export const BATCH_PR_QUERY = gql("batch-pr.gql");
|
|
14
|
+
/** Fetches additional comments for a single review thread when its nested connection paginates. */
|
|
15
|
+
export const REVIEW_THREAD_COMMENTS_QUERY = gql("review-thread-comments.gql");
|
|
16
|
+
/** Fetch inline annotations for a single CheckRun by node ID. */
|
|
17
|
+
export const CHECK_RUN_ANNOTATIONS_QUERY = gql("check-run-annotations.gql");
|
|
14
18
|
/** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
|
|
15
19
|
export const GET_PR_HEAD_SHA_QUERY = gql("get-pr-head-sha.gql");
|
|
16
20
|
/** Look up PR number by branch name (for getCurrentPrNumber). */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { graphql } from "./client.mjs";
|
|
2
|
+
import { paginateForward } from "./pagination.mjs";
|
|
3
|
+
import { REVIEW_THREAD_COMMENTS_QUERY } from "./queries.mjs";
|
|
4
|
+
export async function hydrateThreadCommentPages(threads) {
|
|
5
|
+
const hydrated = [];
|
|
6
|
+
for (const thread of threads) {
|
|
7
|
+
hydrated.push(await hydrateThreadCommentPage(thread));
|
|
8
|
+
}
|
|
9
|
+
return hydrated;
|
|
10
|
+
}
|
|
11
|
+
async function hydrateThreadCommentPage(thread) {
|
|
12
|
+
const pageInfo = thread.comments.pageInfo;
|
|
13
|
+
if (!pageInfo?.hasNextPage || !pageInfo.endCursor)
|
|
14
|
+
return thread;
|
|
15
|
+
const extra = await paginateForward(async (cursor) => {
|
|
16
|
+
const res = await graphql(REVIEW_THREAD_COMMENTS_QUERY, {
|
|
17
|
+
threadId: thread.id,
|
|
18
|
+
...(cursor ? { commentsCursor: cursor } : {}),
|
|
19
|
+
});
|
|
20
|
+
const node = res.data.node;
|
|
21
|
+
if (!node?.comments) {
|
|
22
|
+
const nodeType = node?.__typename ?? "null";
|
|
23
|
+
throw new Error(`Review thread ${thread.id} did not resolve to PullRequestReviewThread while paginating comments (node type: ${nodeType})`);
|
|
24
|
+
}
|
|
25
|
+
return node.comments;
|
|
26
|
+
}, pageInfo.endCursor);
|
|
27
|
+
return {
|
|
28
|
+
...thread,
|
|
29
|
+
comments: {
|
|
30
|
+
pageInfo: { hasNextPage: false, endCursor: null },
|
|
31
|
+
nodes: [...thread.comments.nodes, ...extra],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
package/bin/reporters/agent.mjs
CHANGED
|
@@ -22,6 +22,16 @@ export function toAgentThread(t) {
|
|
|
22
22
|
...(t.authorType !== undefined && { authorType: t.authorType }),
|
|
23
23
|
body: t.body,
|
|
24
24
|
url: t.url,
|
|
25
|
+
...(t.edited === true && { edited: true }),
|
|
26
|
+
...(t.comments !== undefined && {
|
|
27
|
+
comments: t.comments.map((c) => ({
|
|
28
|
+
id: c.id,
|
|
29
|
+
author: c.author,
|
|
30
|
+
...(c.authorType !== undefined && { authorType: c.authorType }),
|
|
31
|
+
body: c.body,
|
|
32
|
+
url: c.url,
|
|
33
|
+
})),
|
|
34
|
+
}),
|
|
25
35
|
...(suggestion !== undefined && { suggestion }),
|
|
26
36
|
};
|
|
27
37
|
}
|
|
@@ -48,6 +58,23 @@ export function toAgentCheck(c) {
|
|
|
48
58
|
...(c.jobName !== undefined && { jobName: c.jobName }),
|
|
49
59
|
...(c.failedStep !== undefined && { failedStep: c.failedStep }),
|
|
50
60
|
...(c.summary !== undefined && { summary: c.summary }),
|
|
61
|
+
...(c.annotations !== undefined && { annotations: c.annotations }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function toAgentStalledCheck(c, nowSeconds) {
|
|
65
|
+
const createdAtUnix = c.createdAtUnix ?? nowSeconds;
|
|
66
|
+
const activityAtUnix = c.updatedAtUnix ?? createdAtUnix;
|
|
67
|
+
return {
|
|
68
|
+
name: c.name,
|
|
69
|
+
status: c.status,
|
|
70
|
+
source: c.source ?? "check_run",
|
|
71
|
+
runId: c.runId,
|
|
72
|
+
detailsUrl: c.detailsUrl || null,
|
|
73
|
+
...(c.createdAtUnix !== undefined && { createdAtUnix: c.createdAtUnix }),
|
|
74
|
+
...(c.startedAtUnix !== undefined && { startedAtUnix: c.startedAtUnix }),
|
|
75
|
+
...(c.updatedAtUnix !== undefined && { updatedAtUnix: c.updatedAtUnix }),
|
|
76
|
+
ageSeconds: Math.max(0, nowSeconds - activityAtUnix),
|
|
77
|
+
...(c.summary !== undefined && { summary: c.summary }),
|
|
51
78
|
};
|
|
52
79
|
}
|
|
53
80
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
4
|
+
export function idToFilename(id) {
|
|
5
|
+
return createHash("sha256").update(id, "utf8").digest("hex") + ".json";
|
|
6
|
+
}
|
|
7
|
+
export const testKey = { owner: "test-owner", repo: "test-repo", pr: 123 };
|
|
8
|
+
export const testId = "PRRT_kwDOTest123";
|
|
9
|
+
export let testStateDir;
|
|
10
|
+
export function registerHooks() {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
testStateDir = `${process.env["TMPDIR"] ?? "/tmp"}/shepherd-seen-test-${randomBytes(4).toString("hex")}`;
|
|
13
|
+
process.env["PR_SHEPHERD_STATE_DIR"] = testStateDir;
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
delete process.env["PR_SHEPHERD_STATE_DIR"];
|
|
17
|
+
await rm(testStateDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function threadComments(thread) {
|
|
2
|
+
if (thread.comments && thread.comments.length > 0) {
|
|
3
|
+
return thread.comments.map((c) => ({
|
|
4
|
+
id: c.id,
|
|
5
|
+
isMinimized: c.isMinimized ?? false,
|
|
6
|
+
author: c.author,
|
|
7
|
+
authorType: c.authorType ?? "Unknown",
|
|
8
|
+
body: c.body,
|
|
9
|
+
url: c.url,
|
|
10
|
+
createdAtUnix: c.createdAtUnix ?? 0,
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
id: "",
|
|
16
|
+
isMinimized: false,
|
|
17
|
+
author: thread.author,
|
|
18
|
+
authorType: thread.authorType ?? "Unknown",
|
|
19
|
+
body: thread.body,
|
|
20
|
+
url: thread.url ?? "",
|
|
21
|
+
createdAtUnix: thread.createdAtUnix ?? 0,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
const THREAD_COMMENT_SEPARATOR = "\n\n--- thread comment ---\n\n";
|
|
26
|
+
export function threadTranscriptBody(thread, appendedBodies = []) {
|
|
27
|
+
const bodies = thread.comments && thread.comments.length > 0
|
|
28
|
+
? threadComments(thread).map((c) => c.body)
|
|
29
|
+
: [thread.body];
|
|
30
|
+
return [...bodies, ...appendedBodies].join(THREAD_COMMENT_SEPARATOR);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/bin/types.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Shared type definitions for the shepherd CLI. */
|
|
2
2
|
export * from "./types/github.mjs";
|
|
3
|
+
export * from "./types/review-thread.mjs";
|
|
4
|
+
export * from "./types/agent-thread.mjs";
|
|
5
|
+
export * from "./types/check-annotations.mjs";
|
|
3
6
|
export * from "./types/report.mjs";
|
|
4
7
|
export * from "./types/iterate.mjs";
|
package/package.json
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mark-files-as-viewed
|
|
3
|
+
description: 'Mark PR changed files as viewed in GitHub with pr-shepherd. Use for requests like "mark tests as viewed" or "mark these files as viewed".'
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "[PR number or URL] [files|tests|--tests|--match REGEX]"
|
|
6
|
+
allowed-tools: ["Bash", "Read", "Grep", "Glob"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# mark-files-as-viewed
|
|
10
|
+
|
|
11
|
+
Thin dispatcher for marking GitHub PR changed files as viewed.
|
|
12
|
+
|
|
13
|
+
## Arguments: $ARGUMENTS
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
1. **Resolve arguments:**
|
|
18
|
+
- If `$ARGUMENTS` contains a PR number, use it.
|
|
19
|
+
- If `$ARGUMENTS` contains a GitHub PR URL, extract the number.
|
|
20
|
+
- Otherwise, infer: `gh pr view --json number --jq .number`
|
|
21
|
+
- Treat a standalone `tests` argument as `--tests`.
|
|
22
|
+
- Preserve explicit file paths, `--tests`, and `--match <regex>` arguments.
|
|
23
|
+
- If no PR found, report an error and stop.
|
|
24
|
+
|
|
25
|
+
2. **Run `pr-shepherd`:**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pr-shepherd mark-files-as-viewed <N> <selectors>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Print the full output.
|
|
@@ -39,5 +39,5 @@ Poll dispatcher for iterating a PR to completion.
|
|
|
39
39
|
|
|
40
40
|
4. **Stop conditions (terminal states):**
|
|
41
41
|
- Stop when the CLI emits `[CANCEL]` (ready-delay completed, or PR merged/closed).
|
|
42
|
-
- Stop when the CLI emits `[ESCALATE]`, including `stall-timeout` for repeated unchanged CI failures.
|
|
42
|
+
- Stop when the CLI emits `[ESCALATE]`, including `stall-timeout` for repeated unchanged CI failures or CI that never starts.
|
|
43
43
|
- **Do NOT merge the pull request** unless the human has explicitly requested or allowed it.
|