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.
Files changed (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +86 -125
  3. package/bin/cli/args.mjs +2 -0
  4. package/bin/cli/fix-formatter.mjs +2 -1
  5. package/bin/cli/formatters.mjs +3 -45
  6. package/bin/cli/handlers.mjs +22 -1
  7. package/bin/cli/help-command-pages.mjs +27 -2
  8. package/bin/cli/help-top-page.mjs +2 -0
  9. package/bin/cli/list-formatters.mjs +3 -1
  10. package/bin/cli/mark-files-as-viewed-flags.mjs +34 -0
  11. package/bin/cli/mark-files-as-viewed-formatter.mjs +52 -0
  12. package/bin/cli/mutate-formatter.mjs +50 -0
  13. package/bin/cli-parser.mjs +11 -2
  14. package/bin/cli-parser.test-support.mjs +6 -1
  15. package/bin/commands/check.mjs +20 -55
  16. package/bin/commands/commit-suggestion-instruction.mjs +1 -1
  17. package/bin/commands/iterate/classify.mjs +25 -30
  18. package/bin/commands/iterate/fix-code.mjs +21 -17
  19. package/bin/commands/iterate/render.mjs +3 -3
  20. package/bin/commands/iterate-thread-test-support.mjs +18 -0
  21. package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +7 -3
  22. package/bin/commands/mark-files-as-viewed.mjs +220 -0
  23. package/bin/commands/resolve-mutate.mjs +41 -4
  24. package/bin/commands/resolve.mjs +15 -76
  25. package/bin/commands/resolve.test-support.mjs +2 -0
  26. package/bin/commands/shepherd-journal.mjs +1 -1
  27. package/bin/comments/authors.mjs +14 -0
  28. package/bin/comments/minimize-policy.mjs +6 -3
  29. package/bin/comments/pending-ops.mjs +6 -0
  30. package/bin/comments/resolve.mjs +28 -11
  31. package/bin/comments/resolve.test-support.mjs +6 -1
  32. package/bin/comments/review-visibility.mjs +14 -0
  33. package/bin/comments/thread-visibility.mjs +60 -0
  34. package/bin/comments/visible-comments.mjs +1 -1
  35. package/bin/github/batch-parser-helpers.mjs +3 -4
  36. package/bin/github/batch-parsers.mjs +6 -6
  37. package/bin/reporters/agent.mjs +1 -0
  38. package/bin/threads/transcript.mjs +6 -6
  39. package/package.json +1 -1
  40. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  41. 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
- export function mapAuthorType(typeName) {
2
- if (typeName === "User" || typeName === "Bot")
3
- return typeName;
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) => {
@@ -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
- export function threadTranscriptBody(thread) {
26
- if (!thread.comments || thread.comments.length === 0)
27
- return thread.body;
28
- return threadComments(thread)
29
- .map((c) => `${c.id}\n${c.body}`)
30
- .join("\n\n--- thread comment ---\n\n");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -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.