pr-shepherd 0.15.2 → 0.16.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +23 -89
- package/bin/cli/args.mjs +0 -1
- package/bin/cli/default-iterate.mjs +2 -7
- package/bin/cli/exit-codes.mjs +0 -4
- package/bin/cli/fix-formatter.mjs +8 -8
- package/bin/cli/handlers.mjs +3 -72
- package/bin/cli/iterate-formatter.mjs +8 -17
- package/bin/cli/iterate-instructions.mjs +13 -47
- package/bin/cli/iterate-lean.mjs +4 -11
- package/bin/cli/list-formatters.mjs +6 -3
- package/bin/cli-parser.iterate-fixtures.mjs +4 -7
- package/bin/cli-parser.mjs +4 -33
- package/bin/commands/check-status.mjs +2 -2
- package/bin/commands/check.mjs +7 -6
- package/bin/commands/commit-suggestion-instruction.mjs +23 -0
- package/bin/commands/iterate/check-instructions.mjs +1 -1
- package/bin/commands/iterate/classify.mjs +9 -4
- package/bin/commands/iterate/escalate.mjs +5 -6
- package/bin/commands/iterate/fix-code.mjs +3 -3
- package/bin/commands/iterate/helpers.mjs +0 -29
- package/bin/commands/iterate/index.mjs +18 -19
- package/bin/commands/iterate/render.mjs +5 -17
- package/bin/commands/resolve-instructions.mjs +2 -10
- package/bin/commands/resolve-mutate.mjs +16 -0
- package/bin/commands/resolve.mjs +6 -17
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/comments/minimize-policy.mjs +15 -0
- package/bin/comments/visible-comments.mjs +20 -0
- package/bin/config/load.mjs +10 -3
- package/bin/config.json +2 -3
- package/bin/github/batch-parsers.mjs +10 -0
- package/bin/github/gql/batch-pr.gql +6 -0
- package/bin/github/queries.mjs +0 -2
- package/bin/index.mjs +0 -2
- package/bin/merge-status/derive.mjs +6 -6
- package/bin/reporters/agent.mjs +10 -3
- package/bin/state/iterate-stall.mjs +9 -0
- package/package.json +5 -6
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +31 -26
- package/bin/commands/iterate.mjs +0 -2
- package/bin/commands/monitor.mjs +0 -139
- package/bin/commands/status.mjs +0 -126
- package/bin/github/gql/multi-pr-status-paged.gql +0 -31
- package/bin/reporters/check-instructions.mjs +0 -69
- package/bin/reporters/json.mjs +0 -10
- package/bin/reporters/text.mjs +0 -156
- package/plugin/skills/check/SKILL.md +0 -37
- package/plugin/skills/monitor/SKILL.md +0 -40
- package/plugin/skills/resolve/SKILL.md +0 -38
package/bin/commands/status.mjs
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `shepherd status PR1 [PR2 PR3 …]`
|
|
3
|
-
*
|
|
4
|
-
* Fetches readiness status for one or more PRs and prints a table.
|
|
5
|
-
* Issues a single GraphQL request with one alias per PR number rather than
|
|
6
|
-
* N separate requests, so the round-trip count is always 1 (plus optional
|
|
7
|
-
* per-PR pagination calls when a PR has > 100 review threads).
|
|
8
|
-
*
|
|
9
|
-
* Exit code: 0 if all PRs are READY, non-zero otherwise.
|
|
10
|
-
*/
|
|
11
|
-
import { graphql, getRepoInfo } from "../github/client.mjs";
|
|
12
|
-
import { MULTI_PR_STATUS_QUERY_WITH_CURSOR } from "../github/queries.mjs";
|
|
13
|
-
export async function runStatus(opts) {
|
|
14
|
-
if (opts.prNumbers.length === 0)
|
|
15
|
-
return [];
|
|
16
|
-
const repo = await getRepoInfo();
|
|
17
|
-
const doc = buildBatchStatusQuery(opts.prNumbers);
|
|
18
|
-
const result = await graphql(doc, {
|
|
19
|
-
owner: repo.owner,
|
|
20
|
-
repo: repo.name,
|
|
21
|
-
});
|
|
22
|
-
const summaries = await Promise.all(opts.prNumbers.map((pr) => {
|
|
23
|
-
const rawPr = result.data.repository[`pr_${pr}`];
|
|
24
|
-
if (!rawPr) {
|
|
25
|
-
throw new Error(`PR #${pr} not found in ${repo.owner}/${repo.name}`);
|
|
26
|
-
}
|
|
27
|
-
return paginateAndBuild(pr, rawPr, repo.owner, repo.name);
|
|
28
|
-
}));
|
|
29
|
-
return summaries;
|
|
30
|
-
}
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Internal
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
function buildBatchStatusQuery(prNumbers) {
|
|
35
|
-
const uniquePrs = [...new Set(prNumbers.filter((n) => n > 0))];
|
|
36
|
-
const f = "number title state isDraft mergeStateStatus reviewDecision " +
|
|
37
|
-
"reviewThreads(last:100){totalCount pageInfo{hasPreviousPage startCursor} nodes{isResolved}} " +
|
|
38
|
-
"commits(last:1){nodes{commit{statusCheckRollup{state}}}}";
|
|
39
|
-
const aliases = uniquePrs.map((n) => `pr_${n}:pullRequest(number:${n}){${f}}`).join(" ");
|
|
40
|
-
return `query MultiPrStatusBatch($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){${aliases}}}`;
|
|
41
|
-
}
|
|
42
|
-
async function paginateAndBuild(pr, p, owner, repo) {
|
|
43
|
-
let allNodes = p.reviewThreads.nodes;
|
|
44
|
-
if (p.reviewThreads.totalCount > p.reviewThreads.nodes.length) {
|
|
45
|
-
const MAX_THREAD_PAGES = 10;
|
|
46
|
-
let pagesFetched = 0;
|
|
47
|
-
const totalCount = p.reviewThreads.totalCount;
|
|
48
|
-
let cursor = p.reviewThreads.pageInfo?.startCursor ?? null;
|
|
49
|
-
while (cursor !== null) {
|
|
50
|
-
if (++pagesFetched > MAX_THREAD_PAGES) {
|
|
51
|
-
process.stderr.write(`pr-shepherd: thread pagination cap reached for PR #${pr}: fetched ${allNodes.length} of ${totalCount} threads\n`);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
// eslint-disable-next-line no-await-in-loop
|
|
55
|
-
const extra = await graphql(MULTI_PR_STATUS_QUERY_WITH_CURSOR, {
|
|
56
|
-
owner,
|
|
57
|
-
repo,
|
|
58
|
-
pr,
|
|
59
|
-
cursor,
|
|
60
|
-
});
|
|
61
|
-
const p2 = extra.data.repository.pullRequest;
|
|
62
|
-
if (!p2)
|
|
63
|
-
break;
|
|
64
|
-
allNodes = [...p2.reviewThreads.nodes, ...allNodes];
|
|
65
|
-
if (!p2.reviewThreads.pageInfo?.hasPreviousPage || !p2.reviewThreads.pageInfo.startCursor) {
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
cursor = p2.reviewThreads.pageInfo.startCursor;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const unresolvedThreads = allNodes.filter((n) => !n.isResolved).length;
|
|
72
|
-
const ciState = p.commits.nodes[0]?.commit.statusCheckRollup?.state ?? null;
|
|
73
|
-
const threadsTruncated = p.reviewThreads.totalCount > allNodes.length;
|
|
74
|
-
return {
|
|
75
|
-
number: p.number,
|
|
76
|
-
title: p.title,
|
|
77
|
-
state: p.state,
|
|
78
|
-
isDraft: p.isDraft,
|
|
79
|
-
mergeStateStatus: p.mergeStateStatus,
|
|
80
|
-
reviewDecision: p.reviewDecision,
|
|
81
|
-
unresolvedThreads,
|
|
82
|
-
ciState,
|
|
83
|
-
threadsTruncated,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// Output helpers
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
export function formatStatusTable(summaries, repoFull) {
|
|
90
|
-
const heading = `# ${repoFull} — PR status (${summaries.length})`;
|
|
91
|
-
if (summaries.length === 0)
|
|
92
|
-
return heading;
|
|
93
|
-
const rows = summaries.map((s) => {
|
|
94
|
-
const raw = s.title.length > 50 ? `${s.title.slice(0, 47)}...` : s.title;
|
|
95
|
-
const title = raw.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
96
|
-
return `| #${s.number} | ${title} | ${deriveVerdict(s)} | ${s.ciState ?? "—"} |`;
|
|
97
|
-
});
|
|
98
|
-
const table = ["| PR | Title | Verdict | CI |", "| --- | --- | --- | --- |", ...rows].join("\n");
|
|
99
|
-
const footnotes = summaries
|
|
100
|
-
.filter((s) => s.threadsTruncated)
|
|
101
|
-
.map((s) => `> Note: PR #${s.number} threads truncated — run \`pr-shepherd check ${s.number}\` for full count.`);
|
|
102
|
-
return [heading, table, ...footnotes].join("\n\n");
|
|
103
|
-
}
|
|
104
|
-
export function deriveVerdict(s) {
|
|
105
|
-
if (s.state === "MERGED")
|
|
106
|
-
return "MERGED";
|
|
107
|
-
if (s.state === "CLOSED")
|
|
108
|
-
return "CLOSED";
|
|
109
|
-
if (s.isDraft)
|
|
110
|
-
return "DRAFT";
|
|
111
|
-
if (s.mergeStateStatus === "CLEAN" &&
|
|
112
|
-
s.unresolvedThreads === 0 &&
|
|
113
|
-
s.ciState === "SUCCESS" &&
|
|
114
|
-
s.reviewDecision !== "CHANGES_REQUESTED") {
|
|
115
|
-
return "READY";
|
|
116
|
-
}
|
|
117
|
-
if (s.mergeStateStatus === "BLOCKED" || s.mergeStateStatus === "HAS_HOOKS")
|
|
118
|
-
return "BLOCKED";
|
|
119
|
-
if (s.mergeStateStatus === "DIRTY")
|
|
120
|
-
return "CONFLICTS";
|
|
121
|
-
if (s.ciState === "PENDING" || s.ciState === "EXPECTED")
|
|
122
|
-
return "IN PROGRESS";
|
|
123
|
-
if (s.ciState === "FAILURE" || s.ciState === "ERROR")
|
|
124
|
-
return "FAILING";
|
|
125
|
-
return s.mergeStateStatus;
|
|
126
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
query MultiPrStatusPaged($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
|
|
2
|
-
repository(owner: $owner, name: $repo) {
|
|
3
|
-
pullRequest(number: $pr) {
|
|
4
|
-
number
|
|
5
|
-
title
|
|
6
|
-
state
|
|
7
|
-
isDraft
|
|
8
|
-
mergeStateStatus
|
|
9
|
-
reviewDecision
|
|
10
|
-
reviewThreads(last: 100, before: $cursor) {
|
|
11
|
-
totalCount
|
|
12
|
-
pageInfo {
|
|
13
|
-
hasPreviousPage
|
|
14
|
-
startCursor
|
|
15
|
-
}
|
|
16
|
-
nodes {
|
|
17
|
-
isResolved
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
commits(last: 1) {
|
|
21
|
-
nodes {
|
|
22
|
-
commit {
|
|
23
|
-
statusCheckRollup {
|
|
24
|
-
state
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
2
|
-
/**
|
|
3
|
-
* Build the numbered instruction steps for the agent to follow after a `check` run.
|
|
4
|
-
* All rebase policy, CI budget policy, and ready-to-merge gating live here so the
|
|
5
|
-
* skill stays a thin dispatcher and these rules co-evolve with the CLI data model.
|
|
6
|
-
*/
|
|
7
|
-
export function buildCheckInstructions(report, opts) {
|
|
8
|
-
const runtime = opts?.runtime ?? "claude";
|
|
9
|
-
const { mergeStatus, checks, threads, comments, changesRequestedReviews, status } = report;
|
|
10
|
-
const instructions = [];
|
|
11
|
-
// 1. Summary
|
|
12
|
-
const totalActionable = threads.actionable.length +
|
|
13
|
-
threads.resolutionOnly.length +
|
|
14
|
-
comments.actionable.length +
|
|
15
|
-
changesRequestedReviews.length;
|
|
16
|
-
const total = checks.passing.length +
|
|
17
|
-
checks.failing.length +
|
|
18
|
-
checks.inProgress.length +
|
|
19
|
-
checks.skipped.length;
|
|
20
|
-
const copilotNote = mergeStatus.copilotReviewInProgress ? " (Copilot review in progress)" : "";
|
|
21
|
-
instructions.push(`Report: merge status is ${mergeStatus.status}${copilotNote}, CI ${checks.passing.length}/${total} passed` +
|
|
22
|
-
(checks.failing.length > 0 ? ` (${checks.failing.length} failing)` : "") +
|
|
23
|
-
(checks.inProgress.length > 0 ? ` (${checks.inProgress.length} in progress)` : "") +
|
|
24
|
-
`, ${totalActionable} actionable review item(s).`);
|
|
25
|
-
// 2. Rebase policy (only emit when relevant)
|
|
26
|
-
if (mergeStatus.status === "CONFLICTS") {
|
|
27
|
-
instructions.push("Rebase required: the branch has merge conflicts that must be resolved before this PR can land.");
|
|
28
|
-
}
|
|
29
|
-
else if (mergeStatus.status === "BEHIND") {
|
|
30
|
-
instructions.push("The PR is behind the base branch. A rebase is optional if all CI checks pass.");
|
|
31
|
-
}
|
|
32
|
-
// 3. CI budget policy — one instruction per failing check
|
|
33
|
-
for (const c of checks.failing) {
|
|
34
|
-
const stepHint = c.failedStep ? ` (failed step: \`${c.failedStep}\`)` : "";
|
|
35
|
-
const diagnosisHint = c.runId
|
|
36
|
-
? c.conclusion === "CANCELLED"
|
|
37
|
-
? `cancelled — if unintended, rerun with \`gh run rerun ${c.runId}\``
|
|
38
|
-
: c.conclusion === "STARTUP_FAILURE"
|
|
39
|
-
? `startup failure — run \`gh run view ${c.runId}\` to inspect the workflow run; rerun with \`gh run rerun ${c.runId}\` if appropriate`
|
|
40
|
-
: `run \`gh run view ${c.runId} --log-failed\`${stepHint} to diagnose — if transient, rerun with \`gh run rerun ${c.runId} --failed\`; otherwise apply a fix`
|
|
41
|
-
: c.detailsUrl
|
|
42
|
-
? `open the check details (${c.detailsUrl}) to diagnose the failure`
|
|
43
|
-
: `no run or details URL available — escalate to a human`;
|
|
44
|
-
instructions.push(`Failing check: \`${c.name}\` — ${diagnosisHint}.`);
|
|
45
|
-
}
|
|
46
|
-
// 4. Ready-to-merge gate
|
|
47
|
-
const isClean = mergeStatus.mergeStateStatus === "CLEAN";
|
|
48
|
-
const isReady = isClean && status === "READY" && !mergeStatus.copilotReviewInProgress;
|
|
49
|
-
if (isReady) {
|
|
50
|
-
instructions.push("This PR is ready to merge: mergeStateStatus is CLEAN, status is READY, and no Copilot review is in progress.");
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
const blockers = [];
|
|
54
|
-
if (!isClean)
|
|
55
|
-
blockers.push(`mergeStateStatus is ${mergeStatus.mergeStateStatus} (not CLEAN)`);
|
|
56
|
-
if (status !== "READY")
|
|
57
|
-
blockers.push(`status is ${status} (not READY)`);
|
|
58
|
-
if (mergeStatus.copilotReviewInProgress)
|
|
59
|
-
blockers.push("Copilot review is still in progress");
|
|
60
|
-
instructions.push(`Do not declare this PR ready to merge: ${blockers.join("; ")}.`);
|
|
61
|
-
}
|
|
62
|
-
// 5. Continuous monitoring pointer (suppressed only when truly ready to merge)
|
|
63
|
-
if (!isReady) {
|
|
64
|
-
instructions.push(runtime === "codex"
|
|
65
|
-
? `This is a one-shot check. For follow-up monitoring, run \`${buildPrShepherdCommand([String(report.pr)], { runner: opts?.runner }).text}\`.`
|
|
66
|
-
: "This is a one-shot check. For continuous monitoring that acts on these signals automatically, use `/pr-shepherd:monitor`.");
|
|
67
|
-
}
|
|
68
|
-
return instructions;
|
|
69
|
-
}
|
package/bin/reporters/json.mjs
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Machine-readable JSON reporter for shepherd check output.
|
|
3
|
-
*
|
|
4
|
-
* Slash commands parse this output to extract IDs, status, and actionable items
|
|
5
|
-
* without string-scraping the human-readable text reporter.
|
|
6
|
-
*/
|
|
7
|
-
import { buildCheckInstructions } from "./check-instructions.mjs";
|
|
8
|
-
export function formatJson(report, opts) {
|
|
9
|
-
return JSON.stringify({ ...report, instructions: buildCheckInstructions(report, opts) }, null, 2);
|
|
10
|
-
}
|
package/bin/reporters/text.mjs
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { buildCheckInstructions } from "./check-instructions.mjs";
|
|
2
|
-
import { renderThreadBullet, renderCommentBullet, renderFirstLookStatusTag, renderReviewListSection, renderThreadResolutionStatusTag, } from "../cli/list-formatters.mjs";
|
|
3
|
-
import { joinSections } from "../util/markdown.mjs";
|
|
4
|
-
export function formatText(report, opts) {
|
|
5
|
-
const header = [
|
|
6
|
-
`# PR #${report.pr} [CHECK] — ${report.repo}`,
|
|
7
|
-
`Status: ${report.status}`,
|
|
8
|
-
`Base: ${report.baseBranch}`,
|
|
9
|
-
].join("\n");
|
|
10
|
-
const ms = report.mergeStatus;
|
|
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");
|
|
21
|
-
const { passing, failing, inProgress, skipped } = report.checks;
|
|
22
|
-
const total = passing.length + failing.length + inProgress.length + skipped.length;
|
|
23
|
-
const ciSubsections = [`${passing.length}/${total} passed`];
|
|
24
|
-
if (failing.length > 0) {
|
|
25
|
-
const lines = [`### Failed (${failing.length})`, ""];
|
|
26
|
-
for (const c of failing) {
|
|
27
|
-
const triaged = c;
|
|
28
|
-
const prefix = triaged.workflowName ? `${triaged.workflowName} › ` : "";
|
|
29
|
-
lines.push(`- ${prefix}${c.name}: ${c.conclusion ?? c.status}`);
|
|
30
|
-
if (triaged.failedStep) {
|
|
31
|
-
lines.push(` failed step: ${triaged.failedStep}`);
|
|
32
|
-
}
|
|
33
|
-
if (triaged.summary) {
|
|
34
|
-
lines.push(` summary: ${triaged.summary}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
ciSubsections.push(lines.join("\n"));
|
|
38
|
-
}
|
|
39
|
-
if (inProgress.length > 0) {
|
|
40
|
-
const lines = [`### In Progress (${inProgress.length})`, ""];
|
|
41
|
-
for (const c of inProgress) {
|
|
42
|
-
lines.push(`- ${c.name}: ${c.status}`);
|
|
43
|
-
}
|
|
44
|
-
ciSubsections.push(lines.join("\n"));
|
|
45
|
-
}
|
|
46
|
-
if (skipped.length > 0) {
|
|
47
|
-
ciSubsections.push(`### Skipped (${skipped.length}): ${skipped.map((c) => c.name).join(", ")}`);
|
|
48
|
-
}
|
|
49
|
-
if (report.checks.filtered.length > 0) {
|
|
50
|
-
const lines = [
|
|
51
|
-
`### Filtered non-PR-trigger (${report.checks.filtered.length}): ${report.checks.filtered.map((c) => c.name).join(", ")}`,
|
|
52
|
-
"",
|
|
53
|
-
];
|
|
54
|
-
if (report.checks.blockedByFilteredCheck) {
|
|
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.");
|
|
56
|
-
}
|
|
57
|
-
else if (report.mergeStatus.status === "BLOCKED") {
|
|
58
|
-
lines.push("> Note: one or more of these filtered checks may be a required status check blocking merge.");
|
|
59
|
-
}
|
|
60
|
-
ciSubsections.push(lines.join("\n"));
|
|
61
|
-
}
|
|
62
|
-
const ciSection = `## CI Checks\n\n${ciSubsections.join("\n\n")}`;
|
|
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;
|
|
68
|
-
let threadsSection = null;
|
|
69
|
-
if (hasThreadSection) {
|
|
70
|
-
const subparts = [];
|
|
71
|
-
if (autoResolved.length > 0) {
|
|
72
|
-
const lines = [`Auto-resolved outdated (${autoResolved.length}):`];
|
|
73
|
-
for (const t of autoResolved) {
|
|
74
|
-
lines.push(renderThreadBullet(t));
|
|
75
|
-
}
|
|
76
|
-
subparts.push(lines.join("\n"));
|
|
77
|
-
}
|
|
78
|
-
if (autoResolveErrors.length > 0) {
|
|
79
|
-
const lines = [`Auto-resolve errors (${autoResolveErrors.length}):`];
|
|
80
|
-
for (const e of autoResolveErrors) {
|
|
81
|
-
lines.push(`- ${e}`);
|
|
82
|
-
}
|
|
83
|
-
subparts.push(lines.join("\n"));
|
|
84
|
-
}
|
|
85
|
-
if (actionableThreads.length > 0) {
|
|
86
|
-
const lines = [`### Actionable (${actionableThreads.length})`, ""];
|
|
87
|
-
for (const t of actionableThreads) {
|
|
88
|
-
lines.push(renderThreadBullet(t));
|
|
89
|
-
}
|
|
90
|
-
subparts.push(lines.join("\n"));
|
|
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
|
-
}
|
|
99
|
-
threadsSection = `## Review Threads\n\n${subparts.join("\n\n")}`;
|
|
100
|
-
}
|
|
101
|
-
const { actionable: actionableComments, firstLook: firstLookComments } = report.comments;
|
|
102
|
-
let commentsSection = null;
|
|
103
|
-
if (actionableComments.length > 0) {
|
|
104
|
-
const lines = [`### Actionable (${actionableComments.length})`, ""];
|
|
105
|
-
for (const c of actionableComments) {
|
|
106
|
-
lines.push(renderCommentBullet(c));
|
|
107
|
-
}
|
|
108
|
-
commentsSection = `## PR Comments\n\n${lines.join("\n")}`;
|
|
109
|
-
}
|
|
110
|
-
const changesRequestedSection = renderReviewListSection("CHANGES_REQUESTED Reviews", report.changesRequestedReviews);
|
|
111
|
-
const allSummaries = [...report.firstLookSummaries, ...report.reviewSummaries];
|
|
112
|
-
const reviewSummariesSection = renderReviewListSection("Review Summaries", allSummaries);
|
|
113
|
-
const approvedReviewsSection = renderReviewListSection("Approved Reviews", report.approvedReviews);
|
|
114
|
-
const firstLookTotal = firstLookThreads.length + firstLookComments.length;
|
|
115
|
-
let firstLookSection = null;
|
|
116
|
-
if (firstLookTotal > 0) {
|
|
117
|
-
const lines = [
|
|
118
|
-
`## First-look items (${firstLookTotal}) — acknowledge status before acting`,
|
|
119
|
-
"",
|
|
120
|
-
];
|
|
121
|
-
for (const t of firstLookThreads) {
|
|
122
|
-
lines.push(renderThreadBullet(t, { statusTag: renderFirstLookStatusTag(t) }));
|
|
123
|
-
}
|
|
124
|
-
for (const c of firstLookComments) {
|
|
125
|
-
const editedSuffix = c.edited ? ", edited" : "";
|
|
126
|
-
lines.push(renderCommentBullet(c, { statusTag: `[status: minimized${editedSuffix}]` }));
|
|
127
|
-
}
|
|
128
|
-
firstLookSection = lines.join("\n");
|
|
129
|
-
}
|
|
130
|
-
const totalActionable = actionableThreads.length +
|
|
131
|
-
resolutionOnlyThreads.length +
|
|
132
|
-
actionableComments.length +
|
|
133
|
-
report.changesRequestedReviews.length;
|
|
134
|
-
const counts = [];
|
|
135
|
-
if (totalActionable > 0)
|
|
136
|
-
counts.push(`${totalActionable} actionable`);
|
|
137
|
-
if (firstLookTotal > 0)
|
|
138
|
-
counts.push(`${firstLookTotal} first-look`);
|
|
139
|
-
const summaryLine = counts.join(", ") || "0 actionable — no unresolved review items";
|
|
140
|
-
const summarySection = `## Summary\n\n${summaryLine}`;
|
|
141
|
-
const instructions = buildCheckInstructions(report, opts);
|
|
142
|
-
const instructionsSection = `## Instructions\n\n${instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}`;
|
|
143
|
-
return joinSections([
|
|
144
|
-
header,
|
|
145
|
-
mergeStatusSection,
|
|
146
|
-
ciSection,
|
|
147
|
-
threadsSection,
|
|
148
|
-
commentsSection,
|
|
149
|
-
changesRequestedSection,
|
|
150
|
-
reviewSummariesSection,
|
|
151
|
-
approvedReviewsSection,
|
|
152
|
-
firstLookSection,
|
|
153
|
-
summarySection,
|
|
154
|
-
instructionsSection,
|
|
155
|
-
]);
|
|
156
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: check
|
|
3
|
-
description: "Check GitHub CI status and review comments for the current PR"
|
|
4
|
-
argument-hint: "[PR number or URL ...]"
|
|
5
|
-
user-invocable: true
|
|
6
|
-
allowed-tools: ["Bash"]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# pr-shepherd check — PR Status
|
|
10
|
-
|
|
11
|
-
## Arguments: $ARGUMENTS
|
|
12
|
-
|
|
13
|
-
## Steps
|
|
14
|
-
|
|
15
|
-
1. **Parse `$ARGUMENTS`:** extract PR numbers or GitHub PR URLs. If none, infer:
|
|
16
|
-
`gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --json number --jq '.[0].number'`
|
|
17
|
-
If no PR found, report an error and stop.
|
|
18
|
-
|
|
19
|
-
2. **Short-circuit if merged:**
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
gh pr view <N> --json state --jq '.state'
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
If `MERGED`, output: `PR #N is already merged. Nothing to check.` and skip.
|
|
26
|
-
|
|
27
|
-
3. **Run the check and follow instructions:**
|
|
28
|
-
Use the repository package runner selected by `packageManager` or lockfile
|
|
29
|
-
(for example, `pnpm exec`, `yarn run`, or `npx --no-install`).
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
<runner> pr-shepherd check <N>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Print the full output. Follow the `## Instructions` section exactly.
|
|
36
|
-
|
|
37
|
-
4. For multiple PRs, repeat steps 2–3 for each.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: monitor
|
|
3
|
-
description: "Start continuous CI monitoring — marks PR ready for review when all checks pass"
|
|
4
|
-
argument-hint: "[PR number or URL]"
|
|
5
|
-
user-invocable: true
|
|
6
|
-
allowed-tools:
|
|
7
|
-
[
|
|
8
|
-
"Bash",
|
|
9
|
-
"Read",
|
|
10
|
-
"Grep",
|
|
11
|
-
"Edit",
|
|
12
|
-
"Write",
|
|
13
|
-
"Glob",
|
|
14
|
-
"Skill",
|
|
15
|
-
"ScheduleWakeup",
|
|
16
|
-
"CronList",
|
|
17
|
-
"CronDelete",
|
|
18
|
-
]
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# pr-shepherd monitor — Continuous PR Monitor
|
|
22
|
-
|
|
23
|
-
## Arguments: $ARGUMENTS
|
|
24
|
-
|
|
25
|
-
## Steps
|
|
26
|
-
|
|
27
|
-
1. **Resolve PR number:**
|
|
28
|
-
- If `$ARGUMENTS` contains a PR number or GitHub PR URL, extract the number.
|
|
29
|
-
- Otherwise, infer: `gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --json number --jq '.[0].number'`
|
|
30
|
-
- If no PR found, report an error and stop.
|
|
31
|
-
|
|
32
|
-
2. **Run the bootstrap command and follow its instructions:**
|
|
33
|
-
Use the repository package runner selected by `packageManager` or lockfile
|
|
34
|
-
(for example, `pnpm exec`, `yarn run`, or `npx --no-install`).
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
<runner> pr-shepherd monitor <PR_NUMBER>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Print the full output. Follow the `## Instructions` section exactly.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: resolve
|
|
3
|
-
description: "Resolve all inline review comments on the current PR"
|
|
4
|
-
argument-hint: "[PR number or URL]"
|
|
5
|
-
user-invocable: true
|
|
6
|
-
allowed-tools: ["Bash", "Read", "Grep", "Edit", "Write", "Glob", "Skill"]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# pr-shepherd resolve — Fix and Resolve Review Comments
|
|
10
|
-
|
|
11
|
-
Resolve unresolved review threads and minimize PR comments on the current PR — from ALL authors.
|
|
12
|
-
|
|
13
|
-
## Arguments: $ARGUMENTS
|
|
14
|
-
|
|
15
|
-
## Steps
|
|
16
|
-
|
|
17
|
-
1. **Resolve PR number:**
|
|
18
|
-
- If `$ARGUMENTS` contains a PR number or GitHub PR URL, extract the number.
|
|
19
|
-
- Otherwise, infer: `gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --json number --jq '.[0].number'`
|
|
20
|
-
- If no PR found, report an error and stop.
|
|
21
|
-
|
|
22
|
-
2. **Short-circuit if merged:**
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
gh pr view <N> --json state --jq '.state'
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
If `MERGED`, invoke `/loop cancel` via the Skill tool, output a merged message, and stop.
|
|
29
|
-
|
|
30
|
-
3. **Fetch and follow instructions:**
|
|
31
|
-
Use the repository package runner selected by `packageManager` or lockfile
|
|
32
|
-
(for example, `pnpm exec`, `yarn run`, or `npx --no-install`).
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
<runner> pr-shepherd resolve <N> --fetch
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Print the full output. Follow the `## Instructions` section exactly.
|