pr-shepherd 0.55.0 → 0.55.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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/cli/iterate-formatter.mjs +3 -0
- package/bin/cli/iterate-lean.mjs +2 -0
- package/bin/commands/check-unreported.d.mts +2 -0
- package/bin/commands/check-unreported.mjs +30 -2
- package/bin/commands/check.mjs +1 -0
- package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
- package/bin/commands/commit-suggestion-instruction.mjs +1 -1
- package/bin/commands/iterate/base.mjs +2 -0
- package/bin/commands/iterate/check-instructions.d.mts +1 -1
- package/bin/commands/iterate/check-instructions.mjs +1 -1
- package/bin/commands/iterate/escalate.mjs +1 -1
- package/bin/commands/iterate/unreported-required.mjs +33 -19
- package/bin/commands/shepherd-journal.d.mts +1 -1
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/github/gql/base-behind.gql +20 -0
- package/bin/github/merge-target-rules.d.mts +8 -0
- package/bin/github/merge-target-rules.mjs +24 -1
- package/bin/github/queries.d.mts +2 -0
- package/bin/github/queries.mjs +2 -0
- package/bin/types/iterate.d.mts +2 -0
- package/bin/types/report.d.mts +2 -0
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +1 -1
|
@@ -181,4 +181,7 @@ function appendUnreportedLines(lines, result) {
|
|
|
181
181
|
if (result.trunkBehindBy !== undefined && result.trunkBehindBy > 0) {
|
|
182
182
|
lines.push(`**trunk behind** \`${result.trunkBehindBy}\``);
|
|
183
183
|
}
|
|
184
|
+
if (result.baseBehindBy !== undefined && result.baseBehindBy > 0) {
|
|
185
|
+
lines.push(`**behind** \`${result.baseBehindBy}\``);
|
|
186
|
+
}
|
|
184
187
|
}
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -72,6 +72,8 @@ export function projectIterateLean(result, opts) {
|
|
|
72
72
|
}),
|
|
73
73
|
...(result.trunkBehindBy !== undefined &&
|
|
74
74
|
result.trunkBehindBy > 0 && { trunkBehindBy: result.trunkBehindBy }),
|
|
75
|
+
...(result.baseBehindBy !== undefined &&
|
|
76
|
+
result.baseBehindBy > 0 && { baseBehindBy: result.baseBehindBy }),
|
|
75
77
|
...(result.quotaWarning && { quotaWarning: result.quotaWarning }),
|
|
76
78
|
...(result.ruleAutoResolve && {
|
|
77
79
|
ruleAutoResolve: projectRuleAutoResolve(result.ruleAutoResolve),
|
|
@@ -4,6 +4,8 @@ import type { RepoInfo } from "../github/client.mts";
|
|
|
4
4
|
export interface UnreportedRequiredFields {
|
|
5
5
|
unreportedRequiredChecks?: string[];
|
|
6
6
|
trunkBehindBy?: number;
|
|
7
|
+
/** Commits on the PR base that this head does not contain. Omitted when zero or a stack. */
|
|
8
|
+
baseBehindBy?: number;
|
|
7
9
|
actionsWorkflowInProgress?: true;
|
|
8
10
|
stackBottomPr?: number;
|
|
9
11
|
headRefName: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { actionsWorkflowInProgress, reportedCheckNames, unreportedRequiredContexts, } from "../checks/unreported-required.mjs";
|
|
2
|
-
import { loadMergeTargetStatus } from "../github/merge-target-rules.mjs";
|
|
2
|
+
import { loadBaseBehindBy, loadMergeTargetStatus } from "../github/merge-target-rules.mjs";
|
|
3
3
|
/** Load trunk rules for a stack and diff them against the head's check names. */
|
|
4
4
|
export async function collectUnreportedRequired(input) {
|
|
5
5
|
const localContexts = input.batchData.branchRules?.requiredStatusCheckContexts ??
|
|
@@ -16,10 +16,14 @@ export async function collectUnreportedRequired(input) {
|
|
|
16
16
|
});
|
|
17
17
|
const unreported = unreportedRequiredContexts(target.contexts, reportedCheckNames(input.checks));
|
|
18
18
|
const actionsRunning = actionsWorkflowInProgress(input.suites, new Set(input.relevantEvents));
|
|
19
|
+
const baseBehindBy = unreported.length > 0 && !input.batchData.stack
|
|
20
|
+
? await loadBaseBehindBy(input.owner, input.name, input.batchData.baseRefName, input.batchData.headRefOid)
|
|
21
|
+
: 0;
|
|
19
22
|
return {
|
|
20
23
|
...(unreported.length > 0 && { unreportedRequiredChecks: unreported }),
|
|
21
24
|
...(target.trunkBehindBy !== undefined &&
|
|
22
25
|
target.trunkBehindBy > 0 && { trunkBehindBy: target.trunkBehindBy }),
|
|
26
|
+
...(baseBehindBy > 0 && { baseBehindBy }),
|
|
23
27
|
...(actionsRunning && { actionsWorkflowInProgress: true }),
|
|
24
28
|
...(target.stackBottomPr !== undefined && { stackBottomPr: target.stackBottomPr }),
|
|
25
29
|
headRefName: input.batchData.headRefName,
|
|
@@ -32,8 +36,10 @@ export async function collectUnreportedRequired(input) {
|
|
|
32
36
|
*/
|
|
33
37
|
export async function refreshCachedUnreported(report, repo) {
|
|
34
38
|
const stack = report.mergeStatus?.mergeRequirements?.stack;
|
|
35
|
-
if (!
|
|
39
|
+
if (!report.headRefName)
|
|
36
40
|
return report;
|
|
41
|
+
if (!stack)
|
|
42
|
+
return refreshBaseBehind(report, repo);
|
|
37
43
|
const target = await loadMergeTargetStatus({
|
|
38
44
|
owner: repo.owner,
|
|
39
45
|
name: repo.name,
|
|
@@ -60,6 +66,28 @@ export async function refreshCachedUnreported(report, repo) {
|
|
|
60
66
|
next.status = "PENDING";
|
|
61
67
|
return next;
|
|
62
68
|
}
|
|
69
|
+
/** A fingerprint hit can keep a stale base compare after main moves. */
|
|
70
|
+
async function refreshBaseBehind(report, repo) {
|
|
71
|
+
if ((report.unreportedRequiredChecks?.length ?? 0) === 0) {
|
|
72
|
+
if (report.baseBehindBy === undefined)
|
|
73
|
+
return report;
|
|
74
|
+
const cleared = { ...report };
|
|
75
|
+
delete cleared.baseBehindBy;
|
|
76
|
+
return cleared;
|
|
77
|
+
}
|
|
78
|
+
// A missing head OID must not fall back to the branch name. That name can
|
|
79
|
+
// resolve inside the base repository when the PR comes from a fork.
|
|
80
|
+
const headOid = report.headSha;
|
|
81
|
+
if (!headOid)
|
|
82
|
+
return report;
|
|
83
|
+
const behind = await loadBaseBehindBy(repo.owner, repo.name, report.baseBranch, headOid);
|
|
84
|
+
const next = { ...report };
|
|
85
|
+
if (behind > 0)
|
|
86
|
+
next.baseBehindBy = behind;
|
|
87
|
+
else
|
|
88
|
+
delete next.baseBehindBy;
|
|
89
|
+
return next;
|
|
90
|
+
}
|
|
63
91
|
function reportChecks(report) {
|
|
64
92
|
return [
|
|
65
93
|
...report.checks.passing,
|
package/bin/commands/check.mjs
CHANGED
|
@@ -347,6 +347,7 @@ export async function runCheck(opts) {
|
|
|
347
347
|
unreportedRequiredChecks: unreported.unreportedRequiredChecks,
|
|
348
348
|
}),
|
|
349
349
|
...(unreported.trunkBehindBy !== undefined && { trunkBehindBy: unreported.trunkBehindBy }),
|
|
350
|
+
...(unreported.baseBehindBy !== undefined && { baseBehindBy: unreported.baseBehindBy }),
|
|
350
351
|
...(unreported.actionsWorkflowInProgress && {
|
|
351
352
|
actionsWorkflowInProgress: true,
|
|
352
353
|
}),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
4
4
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
5
5
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
6
|
-
* instead of being re-emitted every tick (see
|
|
6
|
+
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
7
7
|
* minimal").
|
|
8
8
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
9
9
|
* e.g. `"## Review threads"`.
|
|
@@ -4,7 +4,7 @@ import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
|
4
4
|
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
5
5
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
6
6
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
7
|
-
* instead of being re-emitted every tick (see
|
|
7
|
+
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
8
8
|
* minimal").
|
|
9
9
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
10
10
|
* e.g. `"## Review threads"`.
|
|
@@ -29,6 +29,8 @@ export function buildIterateBase(report, readyState) {
|
|
|
29
29
|
}),
|
|
30
30
|
...(report.trunkBehindBy !== undefined &&
|
|
31
31
|
report.trunkBehindBy > 0 && { trunkBehindBy: report.trunkBehindBy }),
|
|
32
|
+
...(report.baseBehindBy !== undefined &&
|
|
33
|
+
report.baseBehindBy > 0 && { baseBehindBy: report.baseBehindBy }),
|
|
32
34
|
...(report.fingerprintReused === true && { fingerprintReused: true }),
|
|
33
35
|
...(ruleAutoResolve && { ruleAutoResolve }),
|
|
34
36
|
};
|
|
@@ -4,7 +4,7 @@ export declare function buildCrStaleClause(reviews: Review[]): string;
|
|
|
4
4
|
/**
|
|
5
5
|
* Build the optional behind-base push hint. Empty unless the branch is actually behind its base
|
|
6
6
|
* and the user configured a non-blank `iterate.behindBaseHint` — the CLI never prescribes
|
|
7
|
-
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in
|
|
7
|
+
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in AGENTS.md); this
|
|
8
8
|
* only echoes back the caller's own configured pointer. `hint` is trimmed and type-checked at the
|
|
9
9
|
* point of use (rather than at config load) so a malformed rc file value (non-string, or
|
|
10
10
|
* whitespace-only) degrades to "no hint" instead of rendering garbage into agent-facing text or
|
|
@@ -8,7 +8,7 @@ export function buildCrStaleClause(reviews) {
|
|
|
8
8
|
/**
|
|
9
9
|
* Build the optional behind-base push hint. Empty unless the branch is actually behind its base
|
|
10
10
|
* and the user configured a non-blank `iterate.behindBaseHint` — the CLI never prescribes
|
|
11
|
-
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in
|
|
11
|
+
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in AGENTS.md); this
|
|
12
12
|
* only echoes back the caller's own configured pointer. `hint` is trimmed and type-checked at the
|
|
13
13
|
* point of use (rather than at config load) so a malformed rc file value (non-string, or
|
|
14
14
|
* whitespace-only) degrades to "no hint" instead of rendering garbage into agent-facing text or
|
|
@@ -242,7 +242,7 @@ export function buildEscalateSuggestion(triggers, detail) {
|
|
|
242
242
|
}
|
|
243
243
|
if (triggers.includes("required-checks-unreported")) {
|
|
244
244
|
const names = detail ? ` (${detail})` : "";
|
|
245
|
-
return `Required checks are still unreported
|
|
245
|
+
return `Required checks are still unreported${names}, and no CI is running. Rebase and push is the fix when the branch is behind. This head is current, and another reopen will not create a job the workflow does not emit. Path filters are the usual cause. Investigate that, then handle the missing checks manually.`;
|
|
246
246
|
}
|
|
247
247
|
if (triggers.includes("stall-state-unavailable")) {
|
|
248
248
|
const reason = detail ?? "unknown error";
|
|
@@ -12,23 +12,35 @@ function decideUnreportedRequired(input) {
|
|
|
12
12
|
return input.otherAutonomousWork ? "none" : "escalate";
|
|
13
13
|
return "reopen";
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
function listedChecks(names) {
|
|
16
|
+
return names.map((name) => `\`${name}\``).join(", ");
|
|
17
|
+
}
|
|
18
|
+
/** Facts, then rebase-and-push, then investigate or escalate if that push does not start CI. */
|
|
19
|
+
function buildUnreportedRequiredInstructions(input) {
|
|
20
|
+
const names = listedChecks(input.names);
|
|
21
|
+
const gap = input.behindBy > 0 && input.trunk
|
|
22
|
+
? `The stack trunk is behind by ${input.behindBy} commits.`
|
|
23
|
+
: input.behindBy > 0
|
|
24
|
+
? `This branch is behind \`${input.baseBranch}\` by ${input.behindBy} commits.`
|
|
25
|
+
: input.behind
|
|
26
|
+
? "This PR's derived merge status is BEHIND."
|
|
27
|
+
: `This branch is not behind \`${input.baseBranch}\`.`;
|
|
28
|
+
const facts = `${gap} No CI checks are running, and required checks have not passed: ${names}.`;
|
|
29
|
+
if (!input.behind) {
|
|
30
|
+
return [
|
|
31
|
+
facts,
|
|
32
|
+
`Retrigger workflows once for this head with \`gh pr close ${input.pr} -R ${input.repo}\` then \`gh pr reopen ${input.pr} -R ${input.repo}\`.`,
|
|
33
|
+
"If those checks are still missing on the next poll, investigate why the workflows did not start. Do not close the PR again. Shepherd escalates with `required-checks-unreported` when this remains the only blocker.",
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
const rebase = input.stackRebase
|
|
23
37
|
? `${input.stackRebase} Then push the rewritten stack with \`gh stack push\`.`
|
|
24
|
-
: `Rebase
|
|
38
|
+
: `Rebase onto \`${input.baseBranch}\` and push.`;
|
|
25
39
|
return [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"Do not close the PR again if those checks are still missing on the next poll.",
|
|
31
|
-
].join(" ");
|
|
40
|
+
facts,
|
|
41
|
+
`${rebase} Rebase and push is how these checks start.`,
|
|
42
|
+
"If that push does not start the checks, investigate why the workflows did not run. Shepherd escalates with `required-checks-unreported` when the branch is current and this remains the only blocker.",
|
|
43
|
+
];
|
|
32
44
|
}
|
|
33
45
|
export async function planUnreportedRequired(input) {
|
|
34
46
|
const names = input.report.unreportedRequiredChecks ?? [];
|
|
@@ -38,7 +50,8 @@ export async function planUnreportedRequired(input) {
|
|
|
38
50
|
const inQueue = input.report.mergeQueue?.inQueue === true;
|
|
39
51
|
if (names.length === 0 || actionsRunning || failing || conflicts || inQueue)
|
|
40
52
|
return {};
|
|
41
|
-
const
|
|
53
|
+
const behindBy = input.report.trunkBehindBy ?? input.report.baseBehindBy ?? 0;
|
|
54
|
+
const behind = behindBy > 0 || input.report.mergeStatus.status === "BEHIND";
|
|
42
55
|
const decision = decideUnreportedRequired({
|
|
43
56
|
behind,
|
|
44
57
|
alreadyRetriggered: sameCiRetrigger(await readCiRetrigger(input.stateKey), input.headSha, names),
|
|
@@ -49,19 +62,20 @@ export async function planUnreportedRequired(input) {
|
|
|
49
62
|
if (decision === "escalate")
|
|
50
63
|
return { escalate: escalateUnreported(input.base, input.report, names) };
|
|
51
64
|
const rebase = stackRebase(input.report);
|
|
52
|
-
const
|
|
65
|
+
const instructions = buildUnreportedRequiredInstructions({
|
|
53
66
|
names,
|
|
54
67
|
repo: input.report.repo,
|
|
55
68
|
pr: input.report.pr,
|
|
56
69
|
baseBranch: input.report.baseBranch,
|
|
57
70
|
behind,
|
|
58
|
-
|
|
71
|
+
behindBy,
|
|
72
|
+
trunk: (input.report.trunkBehindBy ?? 0) > 0,
|
|
59
73
|
...(rebase && { stackRebase: rebase }),
|
|
60
74
|
});
|
|
61
75
|
if (decision === "reopen") {
|
|
62
76
|
await writeCiRetrigger(input.stateKey, { headSha: input.headSha, contexts: names });
|
|
63
77
|
}
|
|
64
|
-
return { repairInstructions:
|
|
78
|
+
return { repairInstructions: instructions };
|
|
65
79
|
}
|
|
66
80
|
export function buildUnreportedFixResult(base, report, instructions) {
|
|
67
81
|
const prUrl = formatPrUrl(report.repo, report.pr);
|
|
@@ -9,6 +9,6 @@ export declare const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body un
|
|
|
9
9
|
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
10
10
|
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
11
11
|
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
12
|
-
* of being re-emitted every tick (see
|
|
12
|
+
* of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts minimal").
|
|
13
13
|
*/
|
|
14
14
|
export declare function buildShepherdJournalInstruction(prReference: string | number): string;
|
|
@@ -10,7 +10,7 @@ export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `##
|
|
|
10
10
|
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
11
11
|
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
12
12
|
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
13
|
-
* of being re-emitted every tick (see
|
|
13
|
+
* of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts minimal").
|
|
14
14
|
*/
|
|
15
15
|
export function buildShepherdJournalInstruction(prReference) {
|
|
16
16
|
// Single-quote the placeholder so a substituted decision stays literal in the shell.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Commits on the PR base that the head branch does not contain.
|
|
2
|
+
# Fetched only when required checks never reported, so a BLOCKED merge state
|
|
3
|
+
# cannot hide a behind base.
|
|
4
|
+
query BaseBehind($owner: String!, $repo: String!, $qualifiedName: String!, $headRef: String!) {
|
|
5
|
+
_shepherdRateLimit: rateLimit {
|
|
6
|
+
cost
|
|
7
|
+
limit
|
|
8
|
+
nodeCount
|
|
9
|
+
remaining
|
|
10
|
+
resetAt
|
|
11
|
+
used
|
|
12
|
+
}
|
|
13
|
+
repository(owner: $owner, name: $repo) {
|
|
14
|
+
ref(qualifiedName: $qualifiedName) {
|
|
15
|
+
compare(headRef: $headRef) {
|
|
16
|
+
behindBy
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -18,3 +18,11 @@ export declare function loadMergeTargetStatus(input: {
|
|
|
18
18
|
baseRefName: string;
|
|
19
19
|
} | null;
|
|
20
20
|
}): Promise<MergeTargetStatus>;
|
|
21
|
+
/**
|
|
22
|
+
* Commits on the PR base that `headRef` does not contain.
|
|
23
|
+
* Pass the head commit OID. A fork's branch name can exist on the base
|
|
24
|
+
* repository, or fail to resolve, and either result hides a real behind count.
|
|
25
|
+
* `mergeStateStatus` stays `BLOCKED` when a conversation or an expected check
|
|
26
|
+
* is also open, so this compare is the behind count that status hides.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadBaseBehindBy(owner: string, name: string, baseRefName: string, headRef: string): Promise<number>;
|
|
@@ -2,7 +2,7 @@ import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
|
2
2
|
import { parseBranchRules } from "./batch-parsers-rules.mjs";
|
|
3
3
|
import { graphqlWithRateLimit } from "./client.mjs";
|
|
4
4
|
import { missingRepositoryError } from "./errors.mjs";
|
|
5
|
-
import { REF_RULES_QUERY } from "./queries.mjs";
|
|
5
|
+
import { BASE_BEHIND_QUERY, REF_RULES_QUERY } from "./queries.mjs";
|
|
6
6
|
import { readStackTopology } from "./stack-read.mjs";
|
|
7
7
|
/**
|
|
8
8
|
* Required status contexts for the branch GitHub actually merges into.
|
|
@@ -34,6 +34,29 @@ async function bottomOpenLayer(pr, repo, trunk) {
|
|
|
34
34
|
}
|
|
35
35
|
return bottom;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Commits on the PR base that `headRef` does not contain.
|
|
39
|
+
* Pass the head commit OID. A fork's branch name can exist on the base
|
|
40
|
+
* repository, or fail to resolve, and either result hides a real behind count.
|
|
41
|
+
* `mergeStateStatus` stays `BLOCKED` when a conversation or an expected check
|
|
42
|
+
* is also open, so this compare is the behind count that status hides.
|
|
43
|
+
*/
|
|
44
|
+
export async function loadBaseBehindBy(owner, name, baseRefName, headRef) {
|
|
45
|
+
const qualifiedName = `refs/heads/${baseRefName}`;
|
|
46
|
+
const { data } = await graphqlWithRateLimit(BASE_BEHIND_QUERY, {
|
|
47
|
+
owner,
|
|
48
|
+
repo: name,
|
|
49
|
+
qualifiedName,
|
|
50
|
+
headRef,
|
|
51
|
+
});
|
|
52
|
+
if (!data.repository)
|
|
53
|
+
throw missingRepositoryError({ owner, name });
|
|
54
|
+
const ref = data.repository.ref;
|
|
55
|
+
if (!ref) {
|
|
56
|
+
throw new ShepherdError(`Branch ${qualifiedName} was not found in ${owner}/${name}`, EXIT.TEMPFAIL);
|
|
57
|
+
}
|
|
58
|
+
return ref.compare?.behindBy ?? 0;
|
|
59
|
+
}
|
|
37
60
|
async function fetchRefRules(owner, name, qualifiedName, headRef) {
|
|
38
61
|
const { data } = await graphqlWithRateLimit(REF_RULES_QUERY, {
|
|
39
62
|
owner,
|
package/bin/github/queries.d.mts
CHANGED
|
@@ -16,6 +16,8 @@ export declare const PR_FINGERPRINT_QUERY: string;
|
|
|
16
16
|
export declare const POLL_SUMMARY_FRAGMENT: string;
|
|
17
17
|
/** Trunk branch rules plus how far `headRef` is behind that branch. */
|
|
18
18
|
export declare const REF_RULES_QUERY: string;
|
|
19
|
+
/** How far a non-stack head is behind its PR base. One compare, no rules payload. */
|
|
20
|
+
export declare const BASE_BEHIND_QUERY: string;
|
|
19
21
|
/**
|
|
20
22
|
* Pages older status contexts for one commit with the same node selection as
|
|
21
23
|
* the compact summary, so hydrated and first-page nodes fingerprint alike.
|
package/bin/github/queries.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT = gql("poll-summary-check-contexts.gq
|
|
|
21
21
|
export const POLL_SUMMARY_FRAGMENT = `${gql("ref-rules.gql")}\n${POLL_SUMMARY_CHECK_CONTEXTS_FRAGMENT}\n${gql("poll-summary-fragment.gql")}`;
|
|
22
22
|
/** Trunk branch rules plus how far `headRef` is behind that branch. */
|
|
23
23
|
export const REF_RULES_QUERY = `${gql("ref-rules.gql")}\n${gql("ref-rules-query.gql")}`;
|
|
24
|
+
/** How far a non-stack head is behind its PR base. One compare, no rules payload. */
|
|
25
|
+
export const BASE_BEHIND_QUERY = gql("base-behind.gql");
|
|
24
26
|
/**
|
|
25
27
|
* Pages older status contexts for one commit with the same node selection as
|
|
26
28
|
* the compact summary, so hydrated and first-page nodes fingerprint alike.
|
package/bin/types/iterate.d.mts
CHANGED
|
@@ -49,6 +49,8 @@ export interface IterateResultBase {
|
|
|
49
49
|
unreportedRequiredChecks?: string[];
|
|
50
50
|
/** Commits on the stack trunk that the bottom open layer does not contain. Omitted when zero. */
|
|
51
51
|
trunkBehindBy?: number;
|
|
52
|
+
/** Commits on the PR base that this head does not contain. Omitted when zero. */
|
|
53
|
+
baseBehindBy?: number;
|
|
52
54
|
activity?: PrActivitySummary;
|
|
53
55
|
mergeQueue?: import("./merge-queue.mts").MergeQueueReport;
|
|
54
56
|
apiUsage?: ApiUsage;
|
package/bin/types/report.d.mts
CHANGED
|
@@ -110,6 +110,8 @@ export interface ShepherdReport {
|
|
|
110
110
|
unreportedRequiredChecks?: string[];
|
|
111
111
|
/** Commits on the stack trunk that the bottom open layer does not contain. Omitted when zero. */
|
|
112
112
|
trunkBehindBy?: number;
|
|
113
|
+
/** Commits on the PR base that this head does not contain. Omitted when zero. */
|
|
114
|
+
baseBehindBy?: number;
|
|
113
115
|
/** A relevant Actions workflow suite on the head has not completed. */
|
|
114
116
|
actionsWorkflowInProgress?: true;
|
|
115
117
|
/** Bottom open layer of this PR's native stack. Starts a trunk rebase. */
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ If the requested PR does not exist yet, review and commit the in-scope changes,
|
|
|
18
18
|
|
|
19
19
|
## Arguments: $ARGUMENTS
|
|
20
20
|
|
|
21
|
-
1. Parse optional PR numbers, repository-qualified `owner/repo#N` references, or GitHub PR URLs and an optional `--merge` flag from `$ARGUMENTS`; alternatively parse one `--stack PR` selector. A clear request to merge, land, or enqueue the selected PR or stack also opts into `--merge` without a literal flag; a request only to create or open a PR does not. When the user asks to shepherd or merge a native stack and supplies an anchor PR without a literal `--stack`, use that PR as the `--stack` selector. Otherwise let pr-shepherd infer the current branch PR. Reject any remaining argument. Follow the target repository's local `AGENTS.md`
|
|
21
|
+
1. Parse optional PR numbers, repository-qualified `owner/repo#N` references, or GitHub PR URLs and an optional `--merge` flag from `$ARGUMENTS`; alternatively parse one `--stack PR` selector. A clear request to merge, land, or enqueue the selected PR or stack also opts into `--merge` without a literal flag; a request only to create or open a PR does not. When the user asks to shepherd or merge a native stack and supplies an anchor PR without a literal `--stack`, use that PR as the `--stack` selector. Otherwise let pr-shepherd infer the current branch PR. Reject any remaining argument. Follow the target repository's local `AGENTS.md` standards while making changes.
|
|
22
22
|
|
|
23
23
|
2. For the CLI, convert supplied `owner/repo#N` references to `https://github.com/owner/repo/pull/N`; otherwise pass supplied URLs or bare numbers unchanged, then run `pr-shepherd [PR ...] --until-terminal`, or `pr-shepherd --stack PR --until-terminal` for a stack, omitting `[PR ...]` when none was supplied and appending `--merge` when requested. This command keeps ordinary `[WAIT]` and `[MARK_READY]` ticks inside the same invocation; aggregate selectors return their next stack action or terminal result. A qualified reference may name a fork or upstream repository: it is the GitHub target, while the current checkout continues to supply local git/config/rules context. Do not run `pr-shepherd iterate`. If the CLI is unavailable and the `iterate` MCP tool is available, first repository-qualify every supplied reference with its GitHub URL or `owner/repo#N`; resolve bare numbers through `gh pr view <number> --json url --jq .url`, and resolve an omitted target with `gh pr view --json url --jq .url`. If that does not produce the required qualified selector, stop and report that MCP cannot safely determine it. Otherwise call `iterate` with `pr`, `prs`, or `stack` as selected, plus `merge: true` when merge intent was requested, and print its full result.
|
|
24
24
|
|