pr-shepherd 0.22.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 +86 -125
- package/bin/cli/args.mjs +2 -0
- package/bin/cli/fix-formatter.mjs +2 -1
- 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/list-formatters.mjs +3 -1
- 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.mjs +20 -55
- package/bin/commands/commit-suggestion-instruction.mjs +1 -1
- package/bin/commands/iterate/classify.mjs +25 -30
- package/bin/commands/iterate/fix-code.mjs +21 -17
- package/bin/commands/iterate/render.mjs +3 -3
- package/bin/commands/iterate-thread-test-support.mjs +18 -0
- package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +7 -3
- package/bin/commands/mark-files-as-viewed.mjs +220 -0
- package/bin/commands/resolve-mutate.mjs +41 -4
- package/bin/commands/resolve.mjs +15 -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/github/batch-parser-helpers.mjs +3 -4
- package/bin/github/batch-parsers.mjs +6 -6
- package/bin/reporters/agent.mjs +1 -0
- package/bin/threads/transcript.mjs +6 -6
- 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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { classifyItem } from "../state/seen-comments.mjs";
|
|
2
|
+
import { threadTranscriptBody } from "../threads/transcript.mjs";
|
|
3
|
+
function withEdited(thread, edited) {
|
|
4
|
+
return edited ? { ...thread, edited: true } : thread;
|
|
5
|
+
}
|
|
6
|
+
function classifyVisibleThread(thread, seenMap) {
|
|
7
|
+
const cls = classifyItem(thread.id, threadTranscriptBody(thread), seenMap);
|
|
8
|
+
if (cls === "unchanged")
|
|
9
|
+
return null;
|
|
10
|
+
return withEdited(thread, cls === "edited");
|
|
11
|
+
}
|
|
12
|
+
function classifyFirstLookThread(thread, seenMap, firstLookStatus) {
|
|
13
|
+
const visible = classifyVisibleThread(thread, seenMap);
|
|
14
|
+
if (visible === null)
|
|
15
|
+
return null;
|
|
16
|
+
return { ...visible, firstLookStatus };
|
|
17
|
+
}
|
|
18
|
+
export function classifyThreadVisibility(threads, seenMap) {
|
|
19
|
+
const unresolvedThreads = threads.filter((t) => !t.isResolved);
|
|
20
|
+
const activeThreads = unresolvedThreads
|
|
21
|
+
.filter((t) => !t.isOutdated && !t.isMinimized)
|
|
22
|
+
.flatMap((t) => {
|
|
23
|
+
const visible = classifyVisibleThread(t, seenMap);
|
|
24
|
+
return visible ? [visible] : [];
|
|
25
|
+
});
|
|
26
|
+
const resolutionOnlyThreads = unresolvedThreads
|
|
27
|
+
.filter((t) => t.isOutdated || t.isMinimized)
|
|
28
|
+
.flatMap((t) => {
|
|
29
|
+
const visible = classifyVisibleThread(t, seenMap);
|
|
30
|
+
return visible ? [visible] : [];
|
|
31
|
+
});
|
|
32
|
+
const firstLookThreads = [
|
|
33
|
+
...threads.flatMap((t) => {
|
|
34
|
+
if (!t.isOutdated)
|
|
35
|
+
return [];
|
|
36
|
+
const visible = classifyFirstLookThread(t, seenMap, "outdated");
|
|
37
|
+
return visible ? [visible] : [];
|
|
38
|
+
}),
|
|
39
|
+
...threads.flatMap((t) => {
|
|
40
|
+
if (!t.isResolved || t.isOutdated)
|
|
41
|
+
return [];
|
|
42
|
+
const visible = classifyFirstLookThread(t, seenMap, "resolved");
|
|
43
|
+
return visible ? [visible] : [];
|
|
44
|
+
}),
|
|
45
|
+
...threads.flatMap((t) => {
|
|
46
|
+
if (!t.isMinimized || t.isResolved || t.isOutdated)
|
|
47
|
+
return [];
|
|
48
|
+
const visible = classifyFirstLookThread(t, seenMap, "minimized");
|
|
49
|
+
return visible ? [visible] : [];
|
|
50
|
+
}),
|
|
51
|
+
];
|
|
52
|
+
const seenMarkerIds = new Set();
|
|
53
|
+
const toMarkSeen = [...activeThreads, ...resolutionOnlyThreads, ...firstLookThreads].filter((thread) => {
|
|
54
|
+
if (seenMarkerIds.has(thread.id))
|
|
55
|
+
return false;
|
|
56
|
+
seenMarkerIds.add(thread.id);
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
return { activeThreads, resolutionOnlyThreads, firstLookThreads, toMarkSeen };
|
|
60
|
+
}
|
|
@@ -5,7 +5,7 @@ export function classifyVisibleComments(comments, seenMap, minimizeComments) {
|
|
|
5
5
|
const minimizeIds = [];
|
|
6
6
|
const toMarkSeen = [];
|
|
7
7
|
for (const c of comments.filter((comment) => !comment.isMinimized)) {
|
|
8
|
-
if (shouldMinimizeAuthor(c.authorType, minimizeComments)) {
|
|
8
|
+
if (shouldMinimizeAuthor(c.authorType, minimizeComments, c.author)) {
|
|
9
9
|
actionable.push(c);
|
|
10
10
|
minimizeIds.push(c.id);
|
|
11
11
|
continue;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return "Unknown";
|
|
1
|
+
import { normalizeAuthorType } from "../comments/authors.mjs";
|
|
2
|
+
export function mapAuthorType(typeName, login) {
|
|
3
|
+
return normalizeAuthorType(typeName, login);
|
|
5
4
|
}
|
|
6
5
|
export function parseCreatedAt(iso) {
|
|
7
6
|
const ms = new Date(iso).getTime();
|
|
@@ -14,7 +14,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
14
14
|
id: c.id,
|
|
15
15
|
isMinimized: c.isMinimized,
|
|
16
16
|
author: c.author?.login ?? "unknown",
|
|
17
|
-
authorType: mapAuthorType(c.author?.__typename),
|
|
17
|
+
authorType: mapAuthorType(c.author?.__typename, c.author?.login),
|
|
18
18
|
body: c.body,
|
|
19
19
|
url: c.url,
|
|
20
20
|
createdAtUnix: parseCreatedAt(c.createdAt),
|
|
@@ -28,7 +28,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
28
28
|
line: t.line ?? comment?.line ?? null,
|
|
29
29
|
startLine: t.startLine ?? comment?.startLine ?? null,
|
|
30
30
|
author: comment?.author?.login ?? "unknown",
|
|
31
|
-
authorType: mapAuthorType(comment?.author?.__typename),
|
|
31
|
+
authorType: mapAuthorType(comment?.author?.__typename, comment?.author?.login),
|
|
32
32
|
body: comment?.body ?? "",
|
|
33
33
|
url: comment?.url ?? "",
|
|
34
34
|
createdAtUnix: comment ? parseCreatedAt(comment.createdAt) : 0,
|
|
@@ -39,7 +39,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
39
39
|
id: c.id,
|
|
40
40
|
isMinimized: c.isMinimized,
|
|
41
41
|
author: c.author?.login ?? "unknown",
|
|
42
|
-
authorType: mapAuthorType(c.author?.__typename),
|
|
42
|
+
authorType: mapAuthorType(c.author?.__typename, c.author?.login),
|
|
43
43
|
body: c.body,
|
|
44
44
|
url: c.url,
|
|
45
45
|
createdAtUnix: parseCreatedAt(c.createdAt),
|
|
@@ -47,7 +47,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
47
47
|
const changesRequestedReviews = rawReviewNodes.map((r) => ({
|
|
48
48
|
id: r.id,
|
|
49
49
|
author: r.author?.login ?? "unknown",
|
|
50
|
-
authorType: mapAuthorType(r.author?.__typename),
|
|
50
|
+
authorType: mapAuthorType(r.author?.__typename, r.author?.login),
|
|
51
51
|
body: r.body,
|
|
52
52
|
}));
|
|
53
53
|
const reviewSummaries = rawReviewSummaryNodes
|
|
@@ -55,7 +55,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
55
55
|
.map((r) => ({
|
|
56
56
|
id: r.id,
|
|
57
57
|
author: r.author?.login ?? "unknown",
|
|
58
|
-
authorType: mapAuthorType(r.author?.__typename),
|
|
58
|
+
authorType: mapAuthorType(r.author?.__typename, r.author?.login),
|
|
59
59
|
body: r.body,
|
|
60
60
|
}));
|
|
61
61
|
// APPROVED reviews often have empty bodies (clicking "Approve" without a comment), so
|
|
@@ -66,7 +66,7 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
|
|
|
66
66
|
.map((r) => ({
|
|
67
67
|
id: r.id,
|
|
68
68
|
author: r.author?.login ?? "unknown",
|
|
69
|
-
authorType: mapAuthorType(r.author?.__typename),
|
|
69
|
+
authorType: mapAuthorType(r.author?.__typename, r.author?.login),
|
|
70
70
|
body: r.body,
|
|
71
71
|
}));
|
|
72
72
|
const checks = rawCheckNodes.flatMap((node) => {
|
package/bin/reporters/agent.mjs
CHANGED
|
@@ -22,6 +22,7 @@ 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 }),
|
|
25
26
|
...(t.comments !== undefined && {
|
|
26
27
|
comments: t.comments.map((c) => ({
|
|
27
28
|
id: c.id,
|
|
@@ -22,10 +22,10 @@ export function threadComments(thread) {
|
|
|
22
22
|
},
|
|
23
23
|
];
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
31
|
}
|
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.
|