pr-shepherd 0.11.0 → 0.12.1

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.
@@ -6,7 +6,6 @@ import { loadConfig } from "../config/load.mjs";
6
6
  import { extractSuggestion } from "../suggestions/extract.mjs";
7
7
  import { buildFetchInstructions } from "./resolve-instructions.mjs";
8
8
  import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
9
- /** Fetch mode: auto-resolve outdated threads and return all active items for LLM triage. */
10
9
  export async function runResolveFetch(opts) {
11
10
  const repo = await getRepoInfo();
12
11
  const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
@@ -15,7 +14,7 @@ export async function runResolveFetch(opts) {
15
14
  }
16
15
  const { data } = await fetchPrBatch(prNumber, repo);
17
16
  const stateKey = { owner: repo.owner, repo: repo.name, pr: prNumber };
18
- const unresolvedThreads = data.reviewThreads.filter((t) => !t.isResolved && !t.isMinimized);
17
+ const unresolvedThreads = data.reviewThreads.filter((t) => !t.isResolved);
19
18
  const visibleComments = data.comments.filter((c) => !c.isMinimized);
20
19
  const outdatedCandidates = data.reviewThreads.filter((t) => t.isOutdated);
21
20
  const resolvedCandidates = data.reviewThreads.filter((t) => t.isResolved && !t.isOutdated);
@@ -68,7 +67,8 @@ export async function runResolveFetch(opts) {
68
67
  process.stderr.write(`pr-shepherd: auto-resolve outdated threads failed (continuing): ${errors.join(", ")}\n`);
69
68
  }
70
69
  }
71
- const activeThreads = unresolvedThreads.filter((t) => !t.isOutdated);
70
+ const activeThreads = unresolvedThreads.filter((t) => !t.isOutdated && !t.isMinimized);
71
+ const resolutionOnlyThreads = unresolvedThreads.filter((t) => !autoResolvedIds.has(t.id) && (t.isOutdated || t.isMinimized));
72
72
  const cfg = loadConfig();
73
73
  const actionableThreads = activeThreads.map(({ isResolved: _r, isOutdated: _o, ...rest }) => {
74
74
  const thread = rest;
@@ -118,6 +118,7 @@ export async function runResolveFetch(opts) {
118
118
  const result = {
119
119
  prNumber,
120
120
  actionableThreads,
121
+ resolutionOnlyThreads,
121
122
  firstLookThreads,
122
123
  actionableComments: visibleComments,
123
124
  firstLookComments,
@@ -127,7 +128,6 @@ export async function runResolveFetch(opts) {
127
128
  };
128
129
  return { ...result, instructions: buildFetchInstructions(prNumber, result) };
129
130
  }
130
- /** Mutation mode: resolve/minimize/dismiss by ID. */
131
131
  export async function runResolveMutate(opts) {
132
132
  const repo = await getRepoInfo();
133
133
  const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
@@ -30,7 +30,12 @@ async function resolveToken() {
30
30
  catch {
31
31
  // fall through to error
32
32
  }
33
- throw new Error("No GitHub token found. Set GH_TOKEN or GITHUB_TOKEN, or run `gh auth login`.");
33
+ const codexToken = process.env["GITHUB_PERSONAL_ACCESS_TOKEN"];
34
+ if (codexToken) {
35
+ _token = codexToken;
36
+ return _token;
37
+ }
38
+ throw new Error("No GitHub token found. Set GH_TOKEN, GITHUB_TOKEN, or GITHUB_PERSONAL_ACCESS_TOKEN, or run `gh auth login`.");
34
39
  }
35
40
  async function makeHeaders() {
36
41
  return {
package/bin/index.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  * pr-shepherd — unified GitHub PR status + auto-resolve CLI
4
4
  *
5
5
  * Usage:
6
+ * pr-shepherd [PR]
6
7
  * pr-shepherd check [PR]
7
8
  * pr-shepherd resolve [PR]
8
9
  * pr-shepherd iterate [PR]
@@ -3,11 +3,15 @@
3
3
  * All rebase policy, CI budget policy, and ready-to-merge gating live here so the
4
4
  * skill stays a thin dispatcher and these rules co-evolve with the CLI data model.
5
5
  */
6
- export function buildCheckInstructions(report) {
6
+ export function buildCheckInstructions(report, opts) {
7
+ const runtime = opts?.runtime ?? "claude";
7
8
  const { mergeStatus, checks, threads, comments, changesRequestedReviews, status } = report;
8
9
  const instructions = [];
9
10
  // 1. Summary
10
- const totalActionable = threads.actionable.length + comments.actionable.length + changesRequestedReviews.length;
11
+ const totalActionable = threads.actionable.length +
12
+ threads.resolutionOnly.length +
13
+ comments.actionable.length +
14
+ changesRequestedReviews.length;
11
15
  const total = checks.passing.length +
12
16
  checks.failing.length +
13
17
  checks.inProgress.length +
@@ -54,7 +58,9 @@ export function buildCheckInstructions(report) {
54
58
  }
55
59
  // 5. Continuous monitoring pointer (suppressed only when truly ready to merge)
56
60
  if (!isReady) {
57
- instructions.push("This is a one-shot check. For continuous monitoring that acts on these signals automatically, use `/pr-shepherd:monitor`.");
61
+ instructions.push(runtime === "codex"
62
+ ? `This is a one-shot check. For follow-up monitoring, run \`npx pr-shepherd ${report.pr}\`.`
63
+ : "This is a one-shot check. For continuous monitoring that acts on these signals automatically, use `/pr-shepherd:monitor`.");
58
64
  }
59
65
  return instructions;
60
66
  }
@@ -5,6 +5,6 @@
5
5
  * without string-scraping the human-readable text reporter.
6
6
  */
7
7
  import { buildCheckInstructions } from "./check-instructions.mjs";
8
- export function formatJson(report) {
9
- return JSON.stringify({ ...report, instructions: buildCheckInstructions(report) }, null, 2);
8
+ export function formatJson(report, opts) {
9
+ return JSON.stringify({ ...report, instructions: buildCheckInstructions(report, opts) }, null, 2);
10
10
  }
@@ -1,7 +1,7 @@
1
1
  import { buildCheckInstructions } from "./check-instructions.mjs";
2
- import { renderThreadBullet, renderCommentBullet, renderReviewBullet, renderFirstLookStatusTag, } from "../cli/list-formatters.mjs";
2
+ import { renderThreadBullet, renderCommentBullet, renderFirstLookStatusTag, renderReviewListSection, renderThreadResolutionStatusTag, } from "../cli/list-formatters.mjs";
3
3
  import { joinSections } from "../util/markdown.mjs";
4
- export function formatText(report) {
4
+ export function formatText(report, opts) {
5
5
  const header = [
6
6
  `# PR #${report.pr} [CHECK] — ${report.repo}`,
7
7
  `Status: ${report.status}`,
@@ -60,8 +60,11 @@ export function formatText(report) {
60
60
  ciSubsections.push(lines.join("\n"));
61
61
  }
62
62
  const ciSection = `## CI Checks\n\n${ciSubsections.join("\n\n")}`;
63
- const { actionable: actionableThreads, autoResolved, autoResolveErrors, firstLook: firstLookThreads, } = report.threads;
64
- const hasThreadSection = autoResolved.length > 0 || autoResolveErrors.length > 0 || actionableThreads.length > 0;
63
+ const { actionable: actionableThreads, resolutionOnly: resolutionOnlyThreads, autoResolved, autoResolveErrors, firstLook: firstLookThreads, } = report.threads;
64
+ const hasThreadSection = autoResolved.length > 0 ||
65
+ autoResolveErrors.length > 0 ||
66
+ actionableThreads.length > 0 ||
67
+ resolutionOnlyThreads.length > 0;
65
68
  let threadsSection = null;
66
69
  if (hasThreadSection) {
67
70
  const subparts = [];
@@ -86,6 +89,13 @@ export function formatText(report) {
86
89
  }
87
90
  subparts.push(lines.join("\n"));
88
91
  }
92
+ if (resolutionOnlyThreads.length > 0) {
93
+ const lines = [`### Needs resolution only (${resolutionOnlyThreads.length})`, ""];
94
+ for (const t of resolutionOnlyThreads) {
95
+ lines.push(renderThreadBullet(t, { statusTag: renderThreadResolutionStatusTag(t) }));
96
+ }
97
+ subparts.push(lines.join("\n"));
98
+ }
89
99
  threadsSection = `## Review Threads\n\n${subparts.join("\n\n")}`;
90
100
  }
91
101
  const { actionable: actionableComments, firstLook: firstLookComments } = report.comments;
@@ -97,15 +107,15 @@ export function formatText(report) {
97
107
  }
98
108
  commentsSection = `## PR Comments\n\n${lines.join("\n")}`;
99
109
  }
100
- const changesRequestedSection = reviewListSection("CHANGES_REQUESTED Reviews", report.changesRequestedReviews);
110
+ const changesRequestedSection = renderReviewListSection("CHANGES_REQUESTED Reviews", report.changesRequestedReviews);
101
111
  const allSummaries = [...report.firstLookSummaries, ...report.reviewSummaries];
102
- const reviewSummariesSection = reviewListSection("Review Summaries", allSummaries);
103
- const approvedReviewsSection = reviewListSection("Approved Reviews", report.approvedReviews);
112
+ const reviewSummariesSection = renderReviewListSection("Review Summaries", allSummaries);
113
+ const approvedReviewsSection = renderReviewListSection("Approved Reviews", report.approvedReviews);
104
114
  const firstLookTotal = firstLookThreads.length + firstLookComments.length;
105
115
  let firstLookSection = null;
106
116
  if (firstLookTotal > 0) {
107
117
  const lines = [
108
- `## First-look items (${firstLookTotal}) — already closed on GitHub; acknowledge only`,
118
+ `## First-look items (${firstLookTotal}) — acknowledge status before acting`,
109
119
  "",
110
120
  ];
111
121
  for (const t of firstLookThreads) {
@@ -117,15 +127,18 @@ export function formatText(report) {
117
127
  }
118
128
  firstLookSection = lines.join("\n");
119
129
  }
120
- const totalActionable = actionableThreads.length + actionableComments.length + report.changesRequestedReviews.length;
130
+ const totalActionable = actionableThreads.length +
131
+ resolutionOnlyThreads.length +
132
+ actionableComments.length +
133
+ report.changesRequestedReviews.length;
121
134
  const counts = [];
122
135
  if (totalActionable > 0)
123
136
  counts.push(`${totalActionable} actionable`);
124
137
  if (firstLookTotal > 0)
125
138
  counts.push(`${firstLookTotal} first-look`);
126
- const summaryLine = counts.join(", ") || "0 actionable — all threads resolved/minimized";
139
+ const summaryLine = counts.join(", ") || "0 actionable — no unresolved review items";
127
140
  const summarySection = `## Summary\n\n${summaryLine}`;
128
- const instructions = buildCheckInstructions(report);
141
+ const instructions = buildCheckInstructions(report, opts);
129
142
  const instructionsSection = `## Instructions\n\n${instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}`;
130
143
  return joinSections([
131
144
  header,
@@ -141,8 +154,3 @@ export function formatText(report) {
141
154
  instructionsSection,
142
155
  ]);
143
156
  }
144
- function reviewListSection(heading, items) {
145
- if (items.length === 0)
146
- return null;
147
- return `## ${heading}\n\n${items.map((r) => renderReviewBullet(r, { includeBody: true })).join("\n")}`;
148
- }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.11.0",
4
- "description": "Autonomous PR CI monitor and review-comment resolver for Claude Code",
3
+ "version": "0.12.1",
4
+ "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Ong",
7
7
  "type": "module",
@@ -49,6 +49,7 @@
49
49
  "ci",
50
50
  "code-review",
51
51
  "automation",
52
+ "codex",
52
53
  "claude",
53
54
  "claude-code"
54
55
  ],