@henryqw/pi-pr 3.1.10 → 4.0.3
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/README.md +29 -12
- package/docs/pr-routing.svg +1 -1
- package/extensions/pr-ci.ts +908 -0
- package/extensions/pr-command.ts +124 -22
- package/extensions/pr-comment-sweep.ts +1102 -0
- package/extensions/pr-create.ts +674 -0
- package/extensions/pr-execution.ts +558 -0
- package/extensions/pr-feedback.ts +519 -0
- package/extensions/pr-github.ts +690 -163
- package/extensions/pr-merge.ts +4 -44
- package/extensions/pr-routing.ts +8 -3
- package/extensions/pr-ui.ts +2 -3
- package/extensions/pr-update-branch.ts +327 -0
- package/extensions/pr.ts +409 -26
- package/package.json +4 -2
- package/skills/pi-pr-comment-sweep/SKILL.md +45 -89
- package/skills/pi-pr-comment-sweep/references/recovery.md +24 -46
- package/skills/pi-pr-comment-sweep/references/thread-triage.md +2 -2
- package/skills/pi-pr-comment-sweep/scripts/pr-feedback.mjs +223 -1799
- package/skills/pi-pr-create/SKILL.md +6 -77
- package/skills/pi-pr-fix-ci/SKILL.md +39 -39
- package/skills/pi-pr-update-branch/SKILL.md +5 -108
package/extensions/pr-command.ts
CHANGED
|
@@ -15,46 +15,121 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
deriveNextStep,
|
|
17
17
|
type NextStep,
|
|
18
|
+
type PullRequestTarget,
|
|
18
19
|
} from "./pr-routing.ts";
|
|
19
20
|
|
|
20
|
-
type WorkflowNextStep = Extract<NextStep, "create" | "update-branch" | "sweep" | "fix-ci">;
|
|
21
|
+
export type WorkflowNextStep = Extract<NextStep, "create" | "update-branch" | "sweep" | "fix-ci">;
|
|
21
22
|
|
|
22
|
-
const WORKFLOWS: Record<WorkflowNextStep, string> = {
|
|
23
|
-
create: "skill:pi-pr-create",
|
|
24
|
-
"update-branch": "skill:pi-pr-update-branch",
|
|
25
|
-
sweep: "skill:pi-pr-comment-sweep",
|
|
26
|
-
"fix-ci": "skill:pi-pr-fix-ci",
|
|
23
|
+
const WORKFLOWS: Record<WorkflowNextStep, { command: string; action: string }> = {
|
|
24
|
+
create: { command: "skill:pi-pr-create", action: "prepare" },
|
|
25
|
+
"update-branch": { command: "skill:pi-pr-update-branch", action: "merge" },
|
|
26
|
+
sweep: { command: "skill:pi-pr-comment-sweep", action: "start" },
|
|
27
|
+
"fix-ci": { command: "skill:pi-pr-fix-ci", action: "collect" },
|
|
27
28
|
};
|
|
29
|
+
export type WorkflowReservation =
|
|
30
|
+
| { route: "create"; target: PullRequestTarget; base?: string }
|
|
31
|
+
| { route: Exclude<WorkflowNextStep, "create">; pullRequest: CurrentPullRequest };
|
|
28
32
|
|
|
29
33
|
type PrCommandPi = Pick<ExtensionAPI, "exec" | "getCommands" | "sendUserMessage">;
|
|
34
|
+
export type PrCommandInvocation = ((nextStep: NextStep) => void) & {
|
|
35
|
+
sessionGeneration: number;
|
|
36
|
+
assertCurrent(): void;
|
|
37
|
+
};
|
|
30
38
|
export type PrCommandHandler = (
|
|
31
39
|
args: string,
|
|
32
40
|
ctx: ExtensionCommandContext,
|
|
33
|
-
onRouteResolved?: (nextStep: NextStep) => void,
|
|
41
|
+
onRouteResolved?: PrCommandInvocation | ((nextStep: NextStep) => void),
|
|
34
42
|
) => Promise<NextStep>;
|
|
35
43
|
|
|
36
|
-
type
|
|
44
|
+
export type WorkflowPromptIdentity = Readonly<{
|
|
45
|
+
route: WorkflowNextStep;
|
|
46
|
+
skill: string;
|
|
47
|
+
runId: string;
|
|
48
|
+
action: string;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
export type PrCommandDependencies = {
|
|
37
52
|
loadCurrentPullRequest?: typeof loadCurrentPullRequest;
|
|
38
53
|
linkInferredPullRequest?: typeof linkInferredPullRequest;
|
|
54
|
+
reserveWorkflow?: (
|
|
55
|
+
reservation: WorkflowReservation,
|
|
56
|
+
ctx: ExtensionCommandContext,
|
|
57
|
+
invocation?: PrCommandInvocation,
|
|
58
|
+
) => Promise<string>;
|
|
59
|
+
markWorkflowPromptQueued?: (identity: WorkflowPromptIdentity, queued: boolean) => void;
|
|
60
|
+
releaseWorkflow?: (runId: string, invocation?: PrCommandInvocation) => void;
|
|
39
61
|
};
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
type ParsedPrArguments = {
|
|
64
|
+
base?: string;
|
|
65
|
+
instructions: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function parsePrArguments(args: string): ParsedPrArguments {
|
|
69
|
+
const leading = args.trimStart();
|
|
70
|
+
if (leading.startsWith("--base=")) throw new Error("/pr base syntax is --base <branch>");
|
|
71
|
+
if (!leading.startsWith("--base") || !/^--base(?:\s|$)/.test(leading)) {
|
|
72
|
+
return { instructions: args.trim() };
|
|
73
|
+
}
|
|
74
|
+
const value = /^--base\s+(\S+)/.exec(leading);
|
|
75
|
+
if (!value) throw new Error("/pr --base requires a branch");
|
|
76
|
+
return { base: value[1]!, instructions: leading.slice(value[0].length).trim() };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function workflowReservation(
|
|
80
|
+
nextStep: WorkflowNextStep,
|
|
81
|
+
discovery: Awaited<ReturnType<typeof loadCurrentPullRequest>>,
|
|
82
|
+
base: string | undefined,
|
|
45
83
|
instructions: string,
|
|
46
|
-
):
|
|
84
|
+
): WorkflowReservation {
|
|
85
|
+
if (nextStep === "create") {
|
|
86
|
+
if (discovery.kind !== "none") throw new Error("/pr create failed: creation target is unavailable");
|
|
87
|
+
return { route: "create", target: discovery.creationTarget, ...(base === undefined ? {} : { base }) };
|
|
88
|
+
}
|
|
89
|
+
if (instructions) throw new Error("The current /pr helper route does not accept instructions");
|
|
90
|
+
if (discovery.kind !== "current") throw new Error(`/pr ${nextStep} failed: pull request is unavailable`);
|
|
91
|
+
return { route: nextStep, pullRequest: discovery.pullRequest };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function packageWorkflowCommand(pi: PrCommandPi, route: WorkflowNextStep) {
|
|
95
|
+
const workflow = WORKFLOWS[route];
|
|
47
96
|
const command = pi.getCommands().find((candidate) =>
|
|
48
|
-
candidate.name ===
|
|
97
|
+
candidate.name === workflow.command &&
|
|
49
98
|
candidate.source === "skill" &&
|
|
50
99
|
candidate.sourceInfo.origin === "package"
|
|
51
100
|
);
|
|
52
|
-
if (!command) throw new Error(`${
|
|
101
|
+
if (!command) throw new Error(`${workflow.command} failed: bundled workflow is unavailable`);
|
|
102
|
+
return { command, action: workflow.action };
|
|
103
|
+
}
|
|
53
104
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
async function dispatchWorkflow(
|
|
106
|
+
pi: PrCommandPi,
|
|
107
|
+
ctx: ExtensionCommandContext,
|
|
108
|
+
route: WorkflowNextStep,
|
|
109
|
+
reservation: WorkflowReservation,
|
|
110
|
+
invocation: PrCommandInvocation | undefined,
|
|
111
|
+
reserve: NonNullable<PrCommandDependencies["reserveWorkflow"]>,
|
|
112
|
+
markPromptQueued: NonNullable<PrCommandDependencies["markWorkflowPromptQueued"]>,
|
|
113
|
+
release: NonNullable<PrCommandDependencies["releaseWorkflow"]>,
|
|
114
|
+
instructions: string,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const workflow = packageWorkflowCommand(pi, route);
|
|
117
|
+
let runId: string | undefined;
|
|
118
|
+
try {
|
|
119
|
+
runId = await reserve(reservation, ctx, invocation);
|
|
120
|
+
invocation?.assertCurrent();
|
|
121
|
+
const queued = !ctx.isIdle();
|
|
122
|
+
const identity = { route, skill: workflow.command.name, runId, action: workflow.action };
|
|
123
|
+
markPromptQueued(identity, queued);
|
|
124
|
+
const options = queued
|
|
125
|
+
? { deliverAs: "followUp" as const, expandPromptTemplates: true }
|
|
126
|
+
: { expandPromptTemplates: true };
|
|
127
|
+
invocation?.assertCurrent();
|
|
128
|
+
pi.sendUserMessage(`/${identity.skill} runId=${identity.runId} action=${identity.action}${instructions ? ` ${instructions}` : ""}`, options);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (runId !== undefined) release(runId, invocation);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
58
133
|
}
|
|
59
134
|
|
|
60
135
|
function noActionNotification(pullRequest: CurrentPullRequest): { message: string; type: "info" | "warning" } {
|
|
@@ -64,6 +139,9 @@ function noActionNotification(pullRequest: CurrentPullRequest): { message: strin
|
|
|
64
139
|
if (pullRequest.conditions.draft) {
|
|
65
140
|
return { message: `PR #${pullRequest.number} is draft; no action available`, type: "warning" };
|
|
66
141
|
}
|
|
142
|
+
if (pullRequest.conditions.ci === "failure-blocked") {
|
|
143
|
+
return { message: `PR #${pullRequest.number} has a failed CI check that cannot run the CI fix workflow`, type: "warning" };
|
|
144
|
+
}
|
|
67
145
|
const mutatingWorkflowSelected = pullRequest.conditions.baseUpdateRequired || pullRequest.conditions.conflict ||
|
|
68
146
|
pullRequest.conditions.changesRequested || pullRequest.conditions.unresolvedThreads > 0 ||
|
|
69
147
|
pullRequest.conditions.ci === "failure";
|
|
@@ -179,11 +257,23 @@ export function createPrCommandHandler(
|
|
|
179
257
|
): PrCommandHandler {
|
|
180
258
|
const load = dependencies.loadCurrentPullRequest ?? loadCurrentPullRequest;
|
|
181
259
|
const link = dependencies.linkInferredPullRequest ?? linkInferredPullRequest;
|
|
260
|
+
const reserve = dependencies.reserveWorkflow ?? (async () => {
|
|
261
|
+
throw new Error("/pr workflow tools are unavailable");
|
|
262
|
+
});
|
|
263
|
+
const markPromptQueued = dependencies.markWorkflowPromptQueued ?? (() => {});
|
|
264
|
+
const release = dependencies.releaseWorkflow ?? (() => {});
|
|
182
265
|
return async (args, ctx, onRouteResolved) => {
|
|
183
|
-
const
|
|
184
|
-
|
|
266
|
+
const commandInvocation = onRouteResolved && "assertCurrent" in onRouteResolved
|
|
267
|
+
? onRouteResolved as PrCommandInvocation
|
|
268
|
+
: undefined;
|
|
269
|
+
const { base, instructions } = parsePrArguments(args);
|
|
270
|
+
const discovery = await load(pi, ctx, undefined, undefined, base);
|
|
271
|
+
commandInvocation?.assertCurrent();
|
|
185
272
|
const nextStep = deriveNextStep(discovery);
|
|
186
273
|
onRouteResolved?.(nextStep);
|
|
274
|
+
if (base !== undefined && nextStep !== "create") {
|
|
275
|
+
throw new Error("/pr --base is accepted only for pull request creation");
|
|
276
|
+
}
|
|
187
277
|
if (instructions && !(nextStep in WORKFLOWS)) {
|
|
188
278
|
throw new Error("The current /pr route does not accept instructions");
|
|
189
279
|
}
|
|
@@ -209,7 +299,19 @@ export function createPrCommandHandler(
|
|
|
209
299
|
}
|
|
210
300
|
|
|
211
301
|
if (!(nextStep in WORKFLOWS)) throw new Error(`/pr cannot dispatch route ${nextStep}`);
|
|
212
|
-
|
|
302
|
+
const route = nextStep as WorkflowNextStep;
|
|
303
|
+
const reservation = workflowReservation(route, discovery, base, instructions);
|
|
304
|
+
await dispatchWorkflow(
|
|
305
|
+
pi,
|
|
306
|
+
ctx,
|
|
307
|
+
route,
|
|
308
|
+
reservation,
|
|
309
|
+
commandInvocation,
|
|
310
|
+
reserve,
|
|
311
|
+
markPromptQueued,
|
|
312
|
+
release,
|
|
313
|
+
route === "create" ? instructions : "",
|
|
314
|
+
);
|
|
213
315
|
return nextStep;
|
|
214
316
|
};
|
|
215
317
|
}
|