pi-graphite 0.2.2 → 0.3.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/README.md +69 -34
- package/package.json +6 -2
- package/skills/graphite/SKILL.md +218 -0
- package/src/index.ts +40 -53
- package/src/lib/argv.ts +59 -0
- package/src/lib/exec.ts +98 -17
- package/src/lib/result.ts +29 -13
- package/src/lib/schema.ts +5 -4
- package/src/tools/change.ts +140 -0
- package/src/tools/navigate.ts +99 -0
- package/src/tools/recover.ts +147 -0
- package/src/tools/setup.ts +121 -0
- package/src/tools/status.ts +55 -0
- package/src/tools/submit.ts +127 -0
- package/src/tools/sync.ts +79 -0
- package/src/tools/branch.ts +0 -428
- package/src/tools/pr.ts +0 -225
- package/src/tools/recovery.ts +0 -53
- package/src/tools/remote.ts +0 -108
- package/src/tools/repo.ts +0 -122
- package/src/tools/stack.ts +0 -185
package/src/tools/branch.ts
DELETED
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { runGt, type GtRunResult } from "../lib/exec";
|
|
3
|
-
import {
|
|
4
|
-
ensureAllSuccess,
|
|
5
|
-
ensureSuccess,
|
|
6
|
-
renderText,
|
|
7
|
-
type FormattedResult,
|
|
8
|
-
} from "../lib/result";
|
|
9
|
-
import {
|
|
10
|
-
CwdParam,
|
|
11
|
-
StageMode,
|
|
12
|
-
StringEnum,
|
|
13
|
-
Type,
|
|
14
|
-
requireConfirm,
|
|
15
|
-
stageArgs,
|
|
16
|
-
type ToolReturn,
|
|
17
|
-
} from "../lib/schema";
|
|
18
|
-
|
|
19
|
-
/* --------------------------- branch_inspect --------------------------- */
|
|
20
|
-
|
|
21
|
-
interface InspectSection {
|
|
22
|
-
label: string;
|
|
23
|
-
args: string[];
|
|
24
|
-
/** Display heading; null hides the section in the rendered output. */
|
|
25
|
-
heading: string | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function registerBranchInspect(pi: ExtensionAPI) {
|
|
29
|
-
pi.registerTool({
|
|
30
|
-
name: "graphite_branch_inspect",
|
|
31
|
-
label: "Graphite: branch inspect",
|
|
32
|
-
description:
|
|
33
|
-
"Inspect a branch with separately-labeled sections: summary (parent/PR), parent, children, diffstat, body, and (optionally) diff or patch. Read-only.",
|
|
34
|
-
promptSnippet:
|
|
35
|
-
"graphite_branch_inspect: structured `gt info` + parent/children sections, optional diffstat/body/diff",
|
|
36
|
-
parameters: Type.Object({
|
|
37
|
-
cwd: CwdParam,
|
|
38
|
-
branch: Type.Optional(
|
|
39
|
-
Type.String({ description: "Branch to inspect (default current)." }),
|
|
40
|
-
),
|
|
41
|
-
body: Type.Optional(
|
|
42
|
-
Type.Boolean({ description: "Include the PR body section." }),
|
|
43
|
-
),
|
|
44
|
-
stat: Type.Optional(
|
|
45
|
-
Type.Boolean({ description: "Include the diffstat section." }),
|
|
46
|
-
),
|
|
47
|
-
diff: Type.Optional(
|
|
48
|
-
Type.Boolean({ description: "Include the full diff. Takes precedence over `patch`." }),
|
|
49
|
-
),
|
|
50
|
-
patch: Type.Optional(
|
|
51
|
-
Type.Boolean({ description: "Include per-commit patch. Ignored if `diff` is set." }),
|
|
52
|
-
),
|
|
53
|
-
withParentChildren: Type.Optional(
|
|
54
|
-
Type.Boolean({
|
|
55
|
-
description:
|
|
56
|
-
"Also run `gt parent` and `gt children` (only meaningful when inspecting the currently checked-out branch).",
|
|
57
|
-
}),
|
|
58
|
-
),
|
|
59
|
-
}),
|
|
60
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
61
|
-
const branchArg = p.branch ? [p.branch] : [];
|
|
62
|
-
|
|
63
|
-
const sections: InspectSection[] = [
|
|
64
|
-
{
|
|
65
|
-
label: "gt info",
|
|
66
|
-
heading: "## summary",
|
|
67
|
-
args: ["info", ...branchArg],
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
if (p.withParentChildren) {
|
|
72
|
-
sections.push({ label: "gt parent", heading: "## parent", args: ["parent"] });
|
|
73
|
-
sections.push({ label: "gt children", heading: "## children", args: ["children"] });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (p.stat && !p.diff && !p.patch) {
|
|
77
|
-
sections.push({
|
|
78
|
-
label: "gt info --stat",
|
|
79
|
-
heading: "## diffstat",
|
|
80
|
-
args: ["info", ...branchArg, "--stat"],
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (p.body) {
|
|
85
|
-
sections.push({
|
|
86
|
-
label: "gt info --body",
|
|
87
|
-
heading: "## body",
|
|
88
|
-
args: ["info", ...branchArg, "--body"],
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (p.diff) {
|
|
93
|
-
const args = ["info", ...branchArg, "--diff"];
|
|
94
|
-
if (p.stat) args.push("--stat");
|
|
95
|
-
sections.push({ label: "gt info --diff", heading: "## diff", args });
|
|
96
|
-
} else if (p.patch) {
|
|
97
|
-
const args = ["info", ...branchArg, "--patch"];
|
|
98
|
-
if (p.stat) args.push("--stat");
|
|
99
|
-
sections.push({ label: "gt info --patch", heading: "## patch", args });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const results = await Promise.all(
|
|
103
|
-
sections.map((s) => runGt(s.args, { cwd: p.cwd, signal })),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const formatted = await ensureAllSuccess(
|
|
107
|
-
sections.map((s, i) => ({ label: s.label, result: results[i] })),
|
|
108
|
-
p.cwd,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Compose section-by-section structured output.
|
|
112
|
-
const blocks: string[] = [];
|
|
113
|
-
formatted.forEach((f, i) => {
|
|
114
|
-
const s = sections[i];
|
|
115
|
-
if (s.heading) blocks.push(s.heading);
|
|
116
|
-
blocks.push(stripChrome(f.result.stdout));
|
|
117
|
-
blocks.push("");
|
|
118
|
-
});
|
|
119
|
-
blocks.push("--- raw ---");
|
|
120
|
-
formatted.forEach((f, i) => {
|
|
121
|
-
blocks.push(renderText(sections[i].label, f));
|
|
122
|
-
blocks.push("");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const details: Record<string, FormattedResult> = {};
|
|
126
|
-
sections.forEach((s, i) => {
|
|
127
|
-
const key = s.label.replace(/^gt\s+/, "").replace(/[^a-z0-9]/gi, "_");
|
|
128
|
-
details[key] = formatted[i];
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
content: [{ type: "text", text: blocks.join("\n").trim() }],
|
|
133
|
-
details,
|
|
134
|
-
};
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function stripChrome(s: string): string {
|
|
140
|
-
return s.replace(/\s+$/, "");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* --------------------------- branch_create --------------------------- */
|
|
144
|
-
|
|
145
|
-
export function registerBranchCreate(pi: ExtensionAPI) {
|
|
146
|
-
pi.registerTool({
|
|
147
|
-
name: "graphite_branch_create",
|
|
148
|
-
label: "Graphite: branch create",
|
|
149
|
-
description:
|
|
150
|
-
"Create a new branch stacked on top of the current (or `onto`) branch and commit staged changes. Local mutation.",
|
|
151
|
-
promptSnippet:
|
|
152
|
-
"graphite_branch_create: `gt create` with explicit message + stage mode",
|
|
153
|
-
promptGuidelines: [
|
|
154
|
-
"When using graphite_branch_create, always provide `message` unless the user explicitly asked for AI-generated metadata.",
|
|
155
|
-
],
|
|
156
|
-
parameters: Type.Object({
|
|
157
|
-
cwd: CwdParam,
|
|
158
|
-
message: Type.Optional(
|
|
159
|
-
Type.String({ description: "Commit message. Required unless ai=true." }),
|
|
160
|
-
),
|
|
161
|
-
name: Type.Optional(Type.String({ description: "Branch name (default generated)." })),
|
|
162
|
-
stage: Type.Optional(StageMode),
|
|
163
|
-
onto: Type.Optional(Type.String({ description: "Create on top of this branch instead of HEAD (--onto)." })),
|
|
164
|
-
insert: Type.Optional(
|
|
165
|
-
Type.Boolean({ description: "Insert between current branch and its child (--insert)." }),
|
|
166
|
-
),
|
|
167
|
-
ai: Type.Optional(
|
|
168
|
-
Type.Boolean({ description: "Use AI to generate branch name + message (--ai). Default false (--no-ai)." }),
|
|
169
|
-
),
|
|
170
|
-
}),
|
|
171
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
172
|
-
if (!p.ai && !p.message) {
|
|
173
|
-
throw new Error("graphite_branch_create requires `message` unless ai=true.");
|
|
174
|
-
}
|
|
175
|
-
const args = ["create"];
|
|
176
|
-
if (p.name) args.push(p.name);
|
|
177
|
-
if (p.message) args.push("--message", p.message);
|
|
178
|
-
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
179
|
-
if (p.onto) args.push("--onto", p.onto);
|
|
180
|
-
if (p.insert) args.push("--insert");
|
|
181
|
-
args.push(p.ai ? "--ai" : "--no-ai");
|
|
182
|
-
const label = `gt ${args.join(" ")}`;
|
|
183
|
-
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
184
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
185
|
-
return {
|
|
186
|
-
content: [{ type: "text", text: renderText(label, f) }],
|
|
187
|
-
details: { result: f },
|
|
188
|
-
};
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/* --------------------------- branch_update --------------------------- */
|
|
194
|
-
|
|
195
|
-
export function registerBranchUpdate(pi: ExtensionAPI) {
|
|
196
|
-
pi.registerTool({
|
|
197
|
-
name: "graphite_branch_update",
|
|
198
|
-
label: "Graphite: branch update",
|
|
199
|
-
description:
|
|
200
|
-
"Mutate the current (or named) branch: amend, add a new commit, absorb staged hunks, squash, pop, rename, or delete. Auto-restacks descendants where applicable.",
|
|
201
|
-
promptSnippet:
|
|
202
|
-
"graphite_branch_update: amend/new_commit/absorb/squash/pop/rename/delete on a branch",
|
|
203
|
-
promptGuidelines: [
|
|
204
|
-
"Use graphite_branch_update with action=absorb (dryRun:true first) to distribute staged hunks across downstack commits.",
|
|
205
|
-
"graphite_branch_update with action=delete and close:true requires confirmRemote.",
|
|
206
|
-
],
|
|
207
|
-
parameters: Type.Object({
|
|
208
|
-
cwd: CwdParam,
|
|
209
|
-
action: StringEnum([
|
|
210
|
-
"amend",
|
|
211
|
-
"new_commit",
|
|
212
|
-
"absorb",
|
|
213
|
-
"squash",
|
|
214
|
-
"pop",
|
|
215
|
-
"rename",
|
|
216
|
-
"delete",
|
|
217
|
-
] as const),
|
|
218
|
-
message: Type.Optional(Type.String()),
|
|
219
|
-
stage: Type.Optional(StageMode),
|
|
220
|
-
into: Type.Optional(
|
|
221
|
-
Type.String({ description: "action=amend: amend into a downstack branch (`gt modify --into`)." }),
|
|
222
|
-
),
|
|
223
|
-
edit: Type.Optional(Type.Boolean({ description: "Open editor for commit message." })),
|
|
224
|
-
resetAuthor: Type.Optional(Type.Boolean({ description: "action=amend: reset commit author." })),
|
|
225
|
-
newName: Type.Optional(Type.String({ description: "action=rename: new branch name." })),
|
|
226
|
-
branch: Type.Optional(Type.String({ description: "action=delete: branch name to delete." })),
|
|
227
|
-
force: Type.Optional(Type.Boolean({ description: "action=delete or rename: force." })),
|
|
228
|
-
close: Type.Optional(
|
|
229
|
-
Type.Boolean({ description: "action=delete: also close associated PR (requires confirmRemote)." }),
|
|
230
|
-
),
|
|
231
|
-
downstack: Type.Optional(Type.Boolean({ description: "action=delete: also delete ancestors." })),
|
|
232
|
-
upstack: Type.Optional(Type.Boolean({ description: "action=delete: also delete descendants." })),
|
|
233
|
-
dryRun: Type.Optional(
|
|
234
|
-
Type.Boolean({ description: "action=absorb: dry-run only (default true)." }),
|
|
235
|
-
),
|
|
236
|
-
patch: Type.Optional(Type.Boolean({ description: "action=absorb: pick hunks (--patch)." })),
|
|
237
|
-
confirmRemote: Type.Optional(Type.Boolean()),
|
|
238
|
-
}),
|
|
239
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
240
|
-
let args: string[];
|
|
241
|
-
|
|
242
|
-
switch (p.action) {
|
|
243
|
-
case "amend": {
|
|
244
|
-
args = ["modify"];
|
|
245
|
-
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
246
|
-
if (p.message) args.push("--message", p.message);
|
|
247
|
-
if (p.edit) args.push("--edit");
|
|
248
|
-
if (p.resetAuthor) args.push("--reset-author");
|
|
249
|
-
if (p.into) args.push("--into", p.into);
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
case "new_commit": {
|
|
253
|
-
if (!p.message && !p.edit) {
|
|
254
|
-
throw new Error("action=new_commit requires `message` or edit=true.");
|
|
255
|
-
}
|
|
256
|
-
args = ["modify", "--commit"];
|
|
257
|
-
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
258
|
-
if (p.message) args.push("--message", p.message);
|
|
259
|
-
if (p.edit) args.push("--edit");
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
case "absorb": {
|
|
263
|
-
const dryRun = p.dryRun !== false; // default true
|
|
264
|
-
args = ["absorb"];
|
|
265
|
-
if (dryRun) args.push("--dry-run");
|
|
266
|
-
else args.push("--force");
|
|
267
|
-
const stage = (p.stage ?? "none") as "none" | "all" | "update" | "patch";
|
|
268
|
-
if (stage === "all") args.push("--all");
|
|
269
|
-
if (p.patch) args.push("--patch");
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
case "squash": {
|
|
273
|
-
args = ["squash"];
|
|
274
|
-
if (p.message) args.push("--message", p.message);
|
|
275
|
-
if (p.edit) args.push("--edit");
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
case "pop": {
|
|
279
|
-
args = ["pop"];
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
case "rename": {
|
|
283
|
-
if (!p.newName) throw new Error("action=rename requires `newName`.");
|
|
284
|
-
args = ["rename", p.newName];
|
|
285
|
-
if (p.force) args.push("--force");
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
case "delete": {
|
|
289
|
-
if (p.close) requireConfirm(p.confirmRemote, "delete --close (closes PR on GitHub)");
|
|
290
|
-
args = ["delete"];
|
|
291
|
-
if (p.branch) args.push(p.branch);
|
|
292
|
-
if (p.force) args.push("--force");
|
|
293
|
-
if (p.close) args.push("--close");
|
|
294
|
-
if (p.downstack) args.push("--downstack");
|
|
295
|
-
if (p.upstack) args.push("--upstack");
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const label = `gt ${args.join(" ")}`;
|
|
301
|
-
const r: GtRunResult = await runGt(args, { cwd: p.cwd, signal });
|
|
302
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
303
|
-
return {
|
|
304
|
-
content: [{ type: "text", text: renderText(label, f) }],
|
|
305
|
-
details: { action: p.action, result: f },
|
|
306
|
-
};
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/* --------------------------- branch_tracking --------------------------- */
|
|
312
|
-
|
|
313
|
-
export function registerBranchTracking(pi: ExtensionAPI) {
|
|
314
|
-
pi.registerTool({
|
|
315
|
-
name: "graphite_branch_tracking",
|
|
316
|
-
label: "Graphite: branch tracking",
|
|
317
|
-
description:
|
|
318
|
-
"Track / untrack a branch with Graphite, or freeze / unfreeze to prevent local modifications.",
|
|
319
|
-
promptSnippet:
|
|
320
|
-
"graphite_branch_tracking: track/untrack/freeze/unfreeze a branch",
|
|
321
|
-
parameters: Type.Object({
|
|
322
|
-
cwd: CwdParam,
|
|
323
|
-
action: StringEnum(["track", "untrack", "freeze", "unfreeze"] as const),
|
|
324
|
-
branch: Type.Optional(Type.String()),
|
|
325
|
-
parent: Type.Optional(
|
|
326
|
-
Type.String({ description: "action=track: explicit parent branch." }),
|
|
327
|
-
),
|
|
328
|
-
force: Type.Optional(Type.Boolean()),
|
|
329
|
-
}),
|
|
330
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
331
|
-
let args: string[];
|
|
332
|
-
switch (p.action) {
|
|
333
|
-
case "track":
|
|
334
|
-
args = ["track"];
|
|
335
|
-
if (p.branch) args.push(p.branch);
|
|
336
|
-
if (p.parent) args.push("--parent", p.parent);
|
|
337
|
-
if (p.force) args.push("--force");
|
|
338
|
-
break;
|
|
339
|
-
case "untrack":
|
|
340
|
-
args = ["untrack"];
|
|
341
|
-
if (p.branch) args.push(p.branch);
|
|
342
|
-
if (p.force) args.push("--force");
|
|
343
|
-
break;
|
|
344
|
-
case "freeze":
|
|
345
|
-
args = ["freeze"];
|
|
346
|
-
if (p.branch) args.push(p.branch);
|
|
347
|
-
break;
|
|
348
|
-
case "unfreeze":
|
|
349
|
-
args = ["unfreeze"];
|
|
350
|
-
if (p.branch) args.push(p.branch);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
const label = `gt ${args.join(" ")}`;
|
|
354
|
-
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
355
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
356
|
-
return {
|
|
357
|
-
content: [{ type: "text", text: renderText(label, f) }],
|
|
358
|
-
details: { action: p.action, result: f },
|
|
359
|
-
};
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/* --------------------------- branch_navigate --------------------------- */
|
|
365
|
-
|
|
366
|
-
export function registerBranchNavigate(pi: ExtensionAPI) {
|
|
367
|
-
pi.registerTool({
|
|
368
|
-
name: "graphite_branch_navigate",
|
|
369
|
-
label: "Graphite: navigate",
|
|
370
|
-
description:
|
|
371
|
-
"Navigate the current stack: checkout a branch, move up/down N levels, or jump to top/bottom.",
|
|
372
|
-
promptSnippet:
|
|
373
|
-
"graphite_branch_navigate: checkout/up/down/top/bottom across the stack",
|
|
374
|
-
parameters: Type.Object({
|
|
375
|
-
cwd: CwdParam,
|
|
376
|
-
action: StringEnum(["checkout", "up", "down", "top", "bottom"] as const),
|
|
377
|
-
branch: Type.Optional(Type.String({ description: "action=checkout: branch to checkout." })),
|
|
378
|
-
steps: Type.Optional(Type.Integer({ minimum: 1, description: "action=up|down step count." })),
|
|
379
|
-
to: Type.Optional(
|
|
380
|
-
Type.String({ description: "action=up: target descendant when multiple children exist (--to)." }),
|
|
381
|
-
),
|
|
382
|
-
showAllTrunks: Type.Optional(Type.Boolean({ description: "action=checkout: --all." })),
|
|
383
|
-
showUntracked: Type.Optional(Type.Boolean({ description: "action=checkout: --show-untracked." })),
|
|
384
|
-
stackOnly: Type.Optional(Type.Boolean({ description: "action=checkout: only stack branches (--stack)." })),
|
|
385
|
-
checkoutTrunk: Type.Optional(Type.Boolean({ description: "action=checkout: jump to trunk (--trunk)." })),
|
|
386
|
-
}),
|
|
387
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
388
|
-
let args: string[];
|
|
389
|
-
switch (p.action) {
|
|
390
|
-
case "checkout":
|
|
391
|
-
args = ["checkout"];
|
|
392
|
-
if (p.branch) args.push(p.branch);
|
|
393
|
-
if (p.showAllTrunks) args.push("--all");
|
|
394
|
-
if (p.showUntracked) args.push("--show-untracked");
|
|
395
|
-
if (p.stackOnly) args.push("--stack");
|
|
396
|
-
if (p.checkoutTrunk) args.push("--trunk");
|
|
397
|
-
if (!p.branch && !p.checkoutTrunk) {
|
|
398
|
-
throw new Error(
|
|
399
|
-
"action=checkout requires `branch` or `checkoutTrunk:true` (interactive selector disabled).",
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
break;
|
|
403
|
-
case "up":
|
|
404
|
-
args = ["up"];
|
|
405
|
-
if (p.steps != null) args.push("--steps", String(p.steps));
|
|
406
|
-
if (p.to) args.push("--to", p.to);
|
|
407
|
-
break;
|
|
408
|
-
case "down":
|
|
409
|
-
args = ["down"];
|
|
410
|
-
if (p.steps != null) args.push("--steps", String(p.steps));
|
|
411
|
-
break;
|
|
412
|
-
case "top":
|
|
413
|
-
args = ["top"];
|
|
414
|
-
break;
|
|
415
|
-
case "bottom":
|
|
416
|
-
args = ["bottom"];
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
const label = `gt ${args.join(" ")}`;
|
|
420
|
-
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
421
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
422
|
-
return {
|
|
423
|
-
content: [{ type: "text", text: renderText(label, f) }],
|
|
424
|
-
details: { action: p.action, result: f },
|
|
425
|
-
};
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
}
|
package/src/tools/pr.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { runGt } from "../lib/exec";
|
|
3
|
-
import { ensureSuccess, renderText } from "../lib/result";
|
|
4
|
-
import {
|
|
5
|
-
CwdParam,
|
|
6
|
-
StringEnum,
|
|
7
|
-
Type,
|
|
8
|
-
requireConfirm,
|
|
9
|
-
type ToolReturn,
|
|
10
|
-
} from "../lib/schema";
|
|
11
|
-
|
|
12
|
-
/* ------------------------------ pr_submit ------------------------------ */
|
|
13
|
-
|
|
14
|
-
function shellQuote(s: string): string {
|
|
15
|
-
if (s === "") return "''";
|
|
16
|
-
if (/^[A-Za-z0-9_./:@%+=-]+$/.test(s)) return s;
|
|
17
|
-
return `'${s.replace(/'/g, `'"'"'`)}'`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function registerPrSubmit(pi: ExtensionAPI) {
|
|
21
|
-
pi.registerTool({
|
|
22
|
-
name: "graphite_pr_submit",
|
|
23
|
-
label: "Graphite: PR submit",
|
|
24
|
-
description:
|
|
25
|
-
"Submit branches as pull requests via `gt submit`. Defaults to a dry-run plan; set apply:true (with confirmRemote:true) to actually push and create/update PRs.",
|
|
26
|
-
promptSnippet:
|
|
27
|
-
"graphite_pr_submit: plan or apply `gt submit` for a branch or stack",
|
|
28
|
-
promptGuidelines: [
|
|
29
|
-
"Always call graphite_pr_submit with apply:false (default) first to see the dry-run plan, then call again with apply:true and confirmRemote:true to actually submit.",
|
|
30
|
-
"`gt submit` cannot set PR title/body inline. If you pass `title`/`body` to graphite_pr_submit, the tool will return a `gh pr edit` command for you to run via bash to apply the metadata.",
|
|
31
|
-
],
|
|
32
|
-
parameters: Type.Object({
|
|
33
|
-
cwd: CwdParam,
|
|
34
|
-
apply: Type.Optional(
|
|
35
|
-
Type.Boolean({
|
|
36
|
-
description: "false => --dry-run (default). true => actually push (requires confirmRemote).",
|
|
37
|
-
}),
|
|
38
|
-
),
|
|
39
|
-
stack: Type.Optional(
|
|
40
|
-
Type.Boolean({
|
|
41
|
-
description:
|
|
42
|
-
"true => --stack (include descendants). false => --no-stack. Omitted => default (gt prompts based on config).",
|
|
43
|
-
}),
|
|
44
|
-
),
|
|
45
|
-
branch: Type.Optional(Type.String({ description: "Run from this branch (--branch)." })),
|
|
46
|
-
updateOnly: Type.Optional(Type.Boolean({ description: "--update-only" })),
|
|
47
|
-
draft: Type.Optional(Type.Boolean({ description: "--draft for new PRs" })),
|
|
48
|
-
publish: Type.Optional(Type.Boolean({ description: "--publish all PRs" })),
|
|
49
|
-
mergeWhenReady: Type.Optional(Type.Boolean({ description: "--merge-when-ready" })),
|
|
50
|
-
rerequestReview: Type.Optional(Type.Boolean()),
|
|
51
|
-
reviewers: Type.Optional(Type.Array(Type.String(), { description: "User reviewers (--reviewers)." })),
|
|
52
|
-
teamReviewers: Type.Optional(Type.Array(Type.String())),
|
|
53
|
-
comment: Type.Optional(Type.String({ description: "--comment <msg>" })),
|
|
54
|
-
targetTrunk: Type.Optional(Type.String()),
|
|
55
|
-
editMode: Type.Optional(
|
|
56
|
-
StringEnum(["none", "cli", "web"] as const, {
|
|
57
|
-
description:
|
|
58
|
-
"none (default) => --no-edit; cli => --edit --cli; web => --web. Affects PR metadata prompting.",
|
|
59
|
-
}),
|
|
60
|
-
),
|
|
61
|
-
ai: Type.Optional(
|
|
62
|
-
Type.Boolean({
|
|
63
|
-
description: "true => --ai (let gt generate PR title/body). Default false (--no-ai).",
|
|
64
|
-
}),
|
|
65
|
-
),
|
|
66
|
-
forcePush: Type.Optional(
|
|
67
|
-
Type.Boolean({ description: "--force (instead of default --force-with-lease). Requires confirmRemote." }),
|
|
68
|
-
),
|
|
69
|
-
ignoreOutOfSyncTrunk: Type.Optional(Type.Boolean()),
|
|
70
|
-
view: Type.Optional(Type.Boolean({ description: "--view (open PR in browser after submit)." })),
|
|
71
|
-
confirmRemote: Type.Optional(Type.Boolean()),
|
|
72
|
-
|
|
73
|
-
title: Type.Optional(
|
|
74
|
-
Type.String({
|
|
75
|
-
description:
|
|
76
|
-
"Desired PR title. `gt submit` has no inline flag for this; the tool emits a `gh pr edit` command to run after submit.",
|
|
77
|
-
}),
|
|
78
|
-
),
|
|
79
|
-
body: Type.Optional(
|
|
80
|
-
Type.String({
|
|
81
|
-
description:
|
|
82
|
-
"Desired PR body. `gt submit` has no inline flag for this; the tool emits a `gh pr edit` command to run after submit.",
|
|
83
|
-
}),
|
|
84
|
-
),
|
|
85
|
-
}),
|
|
86
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
87
|
-
const apply = p.apply === true;
|
|
88
|
-
if (apply) requireConfirm(p.confirmRemote, "gt submit (push branches + create/update PRs)");
|
|
89
|
-
if (p.forcePush) requireConfirm(p.confirmRemote, "gt submit --force");
|
|
90
|
-
|
|
91
|
-
const wantsCustomMetadata = p.title != null || p.body != null;
|
|
92
|
-
|
|
93
|
-
const args: string[] = ["submit"];
|
|
94
|
-
if (!apply) args.push("--dry-run");
|
|
95
|
-
|
|
96
|
-
if (p.stack === true) args.push("--stack");
|
|
97
|
-
else if (p.stack === false) args.push("--no-stack");
|
|
98
|
-
|
|
99
|
-
if (p.branch) args.push("--branch", p.branch);
|
|
100
|
-
if (p.updateOnly) args.push("--update-only");
|
|
101
|
-
if (p.draft) args.push("--draft");
|
|
102
|
-
if (p.publish) args.push("--publish");
|
|
103
|
-
if (p.mergeWhenReady) args.push("--merge-when-ready");
|
|
104
|
-
if (p.rerequestReview) args.push("--rerequest-review");
|
|
105
|
-
|
|
106
|
-
if (p.reviewers && p.reviewers.length)
|
|
107
|
-
args.push("--reviewers", p.reviewers.join(","));
|
|
108
|
-
if (p.teamReviewers && p.teamReviewers.length)
|
|
109
|
-
args.push("--team-reviewers", p.teamReviewers.join(","));
|
|
110
|
-
if (p.comment) args.push("--comment", p.comment);
|
|
111
|
-
if (p.targetTrunk) args.push("--target-trunk", p.targetTrunk);
|
|
112
|
-
|
|
113
|
-
// If the caller supplied title/body, force --no-edit so gt doesn't try
|
|
114
|
-
// to prompt or open a web editor with conflicting metadata. The actual
|
|
115
|
-
// metadata is applied via the suggested `gh pr edit` command instead.
|
|
116
|
-
const editMode = wantsCustomMetadata ? "none" : (p.editMode ?? "none");
|
|
117
|
-
if (editMode === "none") args.push("--no-edit");
|
|
118
|
-
else if (editMode === "cli") args.push("--edit", "--cli");
|
|
119
|
-
else if (editMode === "web") args.push("--web");
|
|
120
|
-
|
|
121
|
-
args.push(p.ai ? "--ai" : "--no-ai");
|
|
122
|
-
|
|
123
|
-
if (p.forcePush) args.push("--force");
|
|
124
|
-
if (p.ignoreOutOfSyncTrunk) args.push("--ignore-out-of-sync-trunk");
|
|
125
|
-
if (p.view) args.push("--view");
|
|
126
|
-
|
|
127
|
-
const label = `gt ${args.join(" ")}`;
|
|
128
|
-
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
129
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
130
|
-
|
|
131
|
-
const blocks: string[] = [renderText(label, f)];
|
|
132
|
-
|
|
133
|
-
let metadataNote: string | undefined;
|
|
134
|
-
if (wantsCustomMetadata) {
|
|
135
|
-
const ghParts: string[] = ["gh", "pr", "edit"];
|
|
136
|
-
if (p.branch) ghParts.push(p.branch);
|
|
137
|
-
if (p.title != null) ghParts.push("--title", shellQuote(p.title));
|
|
138
|
-
if (p.body != null) ghParts.push("--body", shellQuote(p.body));
|
|
139
|
-
const ghCmd = ghParts.join(" ");
|
|
140
|
-
|
|
141
|
-
metadataNote = [
|
|
142
|
-
"## metadata note",
|
|
143
|
-
"`gt submit` has no flag to set PR title/body inline.",
|
|
144
|
-
"To apply the title/body you supplied, run the following via the bash tool" +
|
|
145
|
-
" (gt does not run gh for you; this keeps the tool composable):",
|
|
146
|
-
ghCmd,
|
|
147
|
-
p.stack === true || (!p.branch && p.stack !== false)
|
|
148
|
-
? "If this submit covered multiple PRs, repeat `gh pr edit <branch>` for each PR that needs metadata."
|
|
149
|
-
: undefined,
|
|
150
|
-
]
|
|
151
|
-
.filter((x): x is string => Boolean(x))
|
|
152
|
-
.join("\n");
|
|
153
|
-
|
|
154
|
-
blocks.push(metadataNote);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
content: [{ type: "text", text: blocks.join("\n\n") }],
|
|
159
|
-
details: {
|
|
160
|
-
apply,
|
|
161
|
-
editMode,
|
|
162
|
-
wantsCustomMetadata,
|
|
163
|
-
metadataNote,
|
|
164
|
-
result: f,
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/* ----------------------------- pr_lifecycle ----------------------------- */
|
|
172
|
-
|
|
173
|
-
export function registerPrLifecycle(pi: ExtensionAPI) {
|
|
174
|
-
pi.registerTool({
|
|
175
|
-
name: "graphite_pr_lifecycle",
|
|
176
|
-
label: "Graphite: PR lifecycle",
|
|
177
|
-
description:
|
|
178
|
-
"PR lifecycle actions: open the PR/stack page in a browser, merge the stack via Graphite, or unlink a branch from its PR.",
|
|
179
|
-
promptSnippet:
|
|
180
|
-
"graphite_pr_lifecycle: open_url | merge | unlink for a PR/branch",
|
|
181
|
-
parameters: Type.Object({
|
|
182
|
-
cwd: CwdParam,
|
|
183
|
-
action: StringEnum(["open_url", "merge", "unlink"] as const),
|
|
184
|
-
branch: Type.Optional(Type.String({ description: "Branch name or PR number." })),
|
|
185
|
-
stack: Type.Optional(
|
|
186
|
-
Type.Boolean({ description: "action=open_url: open stack page (--stack)." }),
|
|
187
|
-
),
|
|
188
|
-
apply: Type.Optional(
|
|
189
|
-
Type.Boolean({
|
|
190
|
-
description: "action=merge: false (default) => --dry-run; true => actually merge (requires confirmRemote).",
|
|
191
|
-
}),
|
|
192
|
-
),
|
|
193
|
-
confirm: Type.Optional(
|
|
194
|
-
Type.Boolean({
|
|
195
|
-
description: "action=merge: pass --confirm so gt prompts before merging each branch.",
|
|
196
|
-
}),
|
|
197
|
-
),
|
|
198
|
-
confirmRemote: Type.Optional(Type.Boolean()),
|
|
199
|
-
}),
|
|
200
|
-
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
201
|
-
let args: string[];
|
|
202
|
-
if (p.action === "open_url") {
|
|
203
|
-
args = ["pr"];
|
|
204
|
-
if (p.branch) args.push(p.branch);
|
|
205
|
-
if (p.stack) args.push("--stack");
|
|
206
|
-
} else if (p.action === "merge") {
|
|
207
|
-
const apply = p.apply === true;
|
|
208
|
-
if (apply) requireConfirm(p.confirmRemote, "gt merge (merges PRs on GitHub)");
|
|
209
|
-
args = ["merge"];
|
|
210
|
-
if (!apply) args.push("--dry-run");
|
|
211
|
-
if (p.confirm) args.push("--confirm");
|
|
212
|
-
} else {
|
|
213
|
-
args = ["unlink"];
|
|
214
|
-
if (p.branch) args.push(p.branch);
|
|
215
|
-
}
|
|
216
|
-
const label = `gt ${args.join(" ")}`;
|
|
217
|
-
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
218
|
-
const f = await ensureSuccess(label, r, p.cwd);
|
|
219
|
-
return {
|
|
220
|
-
content: [{ type: "text", text: renderText(label, f) }],
|
|
221
|
-
details: { action: p.action, result: f },
|
|
222
|
-
};
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
}
|