pi-graphite 0.0.1 → 0.1.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 +74 -1
- package/package.json +22 -11
- package/src/index.ts +65 -0
- package/src/lib/exec.ts +134 -0
- package/src/lib/result.ts +80 -0
- package/src/lib/schema.ts +50 -0
- package/src/tools/branch.ts +352 -0
- package/src/tools/pr.ts +179 -0
- package/src/tools/recovery.ts +52 -0
- package/src/tools/remote.ts +107 -0
- package/src/tools/repo.ts +121 -0
- package/src/tools/stack.ts +176 -0
- package/index.js +0 -3
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { runGt } from "../lib/exec";
|
|
3
|
+
import { formatResult, renderText } from "../lib/result";
|
|
4
|
+
import {
|
|
5
|
+
CwdParam,
|
|
6
|
+
StageMode,
|
|
7
|
+
StringEnum,
|
|
8
|
+
Type,
|
|
9
|
+
requireConfirm,
|
|
10
|
+
stageArgs,
|
|
11
|
+
type ToolReturn,
|
|
12
|
+
} from "../lib/schema";
|
|
13
|
+
|
|
14
|
+
/* --------------------------- branch_inspect --------------------------- */
|
|
15
|
+
|
|
16
|
+
export function registerBranchInspect(pi: ExtensionAPI) {
|
|
17
|
+
pi.registerTool({
|
|
18
|
+
name: "graphite_branch_inspect",
|
|
19
|
+
label: "Graphite: branch inspect",
|
|
20
|
+
description:
|
|
21
|
+
"Inspect a branch: parent, children, PR body, diff, and diffstat. Read-only.",
|
|
22
|
+
promptSnippet:
|
|
23
|
+
"graphite_branch_inspect: read-only branch metadata + diff/patch/stat",
|
|
24
|
+
parameters: Type.Object({
|
|
25
|
+
cwd: CwdParam,
|
|
26
|
+
branch: Type.Optional(Type.String({ description: "Branch to inspect (default current)." })),
|
|
27
|
+
include: Type.Optional(
|
|
28
|
+
Type.Array(StringEnum(["body", "diff", "patch", "stat"] as const), {
|
|
29
|
+
description:
|
|
30
|
+
"Pass to `gt info`. `diff` and `patch` are mutually exclusive (diff wins). `stat` modifies diff/patch.",
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
withParentChildren: Type.Optional(
|
|
34
|
+
Type.Boolean({ description: "Also run `gt parent` and `gt children`." }),
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
38
|
+
const infoArgs = ["info"];
|
|
39
|
+
if (p.branch) infoArgs.push(p.branch);
|
|
40
|
+
const inc = new Set(p.include ?? []);
|
|
41
|
+
if (inc.has("body")) infoArgs.push("--body");
|
|
42
|
+
if (inc.has("diff")) infoArgs.push("--diff");
|
|
43
|
+
else if (inc.has("patch")) infoArgs.push("--patch");
|
|
44
|
+
if (inc.has("stat")) infoArgs.push("--stat");
|
|
45
|
+
|
|
46
|
+
const tasks: Promise<unknown>[] = [runGt(infoArgs, { cwd: p.cwd, signal })];
|
|
47
|
+
if (p.withParentChildren) {
|
|
48
|
+
tasks.push(runGt(["parent"], { cwd: p.cwd, signal }));
|
|
49
|
+
tasks.push(runGt(["children"], { cwd: p.cwd, signal }));
|
|
50
|
+
}
|
|
51
|
+
const [info, parent, children] = (await Promise.all(tasks)) as Awaited<
|
|
52
|
+
ReturnType<typeof runGt>
|
|
53
|
+
>[];
|
|
54
|
+
|
|
55
|
+
const blocks: string[] = [renderText(`gt ${infoArgs.join(" ")}`, formatResult(info))];
|
|
56
|
+
if (parent) blocks.push(renderText("gt parent", formatResult(parent)));
|
|
57
|
+
if (children) blocks.push(renderText("gt children", formatResult(children)));
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: blocks.join("\n\n") }],
|
|
61
|
+
details: {
|
|
62
|
+
info: formatResult(info),
|
|
63
|
+
parent: parent ? formatResult(parent) : undefined,
|
|
64
|
+
children: children ? formatResult(children) : undefined,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* --------------------------- branch_create --------------------------- */
|
|
72
|
+
|
|
73
|
+
export function registerBranchCreate(pi: ExtensionAPI) {
|
|
74
|
+
pi.registerTool({
|
|
75
|
+
name: "graphite_branch_create",
|
|
76
|
+
label: "Graphite: branch create",
|
|
77
|
+
description:
|
|
78
|
+
"Create a new branch stacked on top of the current (or `onto`) branch and commit staged changes. Local mutation.",
|
|
79
|
+
promptSnippet:
|
|
80
|
+
"graphite_branch_create: `gt create` with explicit message + stage mode",
|
|
81
|
+
promptGuidelines: [
|
|
82
|
+
"When using graphite_branch_create, always provide `message` unless the user explicitly asked for AI-generated metadata.",
|
|
83
|
+
],
|
|
84
|
+
parameters: Type.Object({
|
|
85
|
+
cwd: CwdParam,
|
|
86
|
+
message: Type.Optional(
|
|
87
|
+
Type.String({ description: "Commit message. Required unless ai=true." }),
|
|
88
|
+
),
|
|
89
|
+
name: Type.Optional(Type.String({ description: "Branch name (default generated)." })),
|
|
90
|
+
stage: Type.Optional(StageMode),
|
|
91
|
+
onto: Type.Optional(Type.String({ description: "Create on top of this branch instead of HEAD (--onto)." })),
|
|
92
|
+
insert: Type.Optional(
|
|
93
|
+
Type.Boolean({ description: "Insert between current branch and its child (--insert)." }),
|
|
94
|
+
),
|
|
95
|
+
ai: Type.Optional(
|
|
96
|
+
Type.Boolean({ description: "Use AI to generate branch name + message (--ai). Default false (--no-ai)." }),
|
|
97
|
+
),
|
|
98
|
+
}),
|
|
99
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
100
|
+
if (!p.ai && !p.message) {
|
|
101
|
+
throw new Error("graphite_branch_create requires `message` unless ai=true.");
|
|
102
|
+
}
|
|
103
|
+
const args = ["create"];
|
|
104
|
+
if (p.name) args.push(p.name);
|
|
105
|
+
if (p.message) args.push("--message", p.message);
|
|
106
|
+
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
107
|
+
if (p.onto) args.push("--onto", p.onto);
|
|
108
|
+
if (p.insert) args.push("--insert");
|
|
109
|
+
args.push(p.ai ? "--ai" : "--no-ai");
|
|
110
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
111
|
+
const f = formatResult(r);
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
114
|
+
details: { result: f },
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* --------------------------- branch_update --------------------------- */
|
|
121
|
+
|
|
122
|
+
export function registerBranchUpdate(pi: ExtensionAPI) {
|
|
123
|
+
pi.registerTool({
|
|
124
|
+
name: "graphite_branch_update",
|
|
125
|
+
label: "Graphite: branch update",
|
|
126
|
+
description:
|
|
127
|
+
"Mutate the current (or named) branch: amend, add a new commit, absorb staged hunks, squash, pop, rename, or delete. Auto-restacks descendants where applicable.",
|
|
128
|
+
promptSnippet:
|
|
129
|
+
"graphite_branch_update: amend/new_commit/absorb/squash/pop/rename/delete on a branch",
|
|
130
|
+
promptGuidelines: [
|
|
131
|
+
"Use graphite_branch_update with action=absorb (dryRun:true first) to distribute staged hunks across downstack commits.",
|
|
132
|
+
"graphite_branch_update with action=delete and close:true requires confirmRemote.",
|
|
133
|
+
],
|
|
134
|
+
parameters: Type.Object({
|
|
135
|
+
cwd: CwdParam,
|
|
136
|
+
action: StringEnum([
|
|
137
|
+
"amend",
|
|
138
|
+
"new_commit",
|
|
139
|
+
"absorb",
|
|
140
|
+
"squash",
|
|
141
|
+
"pop",
|
|
142
|
+
"rename",
|
|
143
|
+
"delete",
|
|
144
|
+
] as const),
|
|
145
|
+
message: Type.Optional(Type.String()),
|
|
146
|
+
stage: Type.Optional(StageMode),
|
|
147
|
+
into: Type.Optional(
|
|
148
|
+
Type.String({ description: "action=amend: amend into a downstack branch (`gt modify --into`)." }),
|
|
149
|
+
),
|
|
150
|
+
edit: Type.Optional(Type.Boolean({ description: "Open editor for commit message." })),
|
|
151
|
+
resetAuthor: Type.Optional(Type.Boolean({ description: "action=amend: reset commit author." })),
|
|
152
|
+
newName: Type.Optional(Type.String({ description: "action=rename: new branch name." })),
|
|
153
|
+
branch: Type.Optional(Type.String({ description: "action=delete: branch name to delete." })),
|
|
154
|
+
force: Type.Optional(Type.Boolean({ description: "action=delete or rename: force." })),
|
|
155
|
+
close: Type.Optional(
|
|
156
|
+
Type.Boolean({ description: "action=delete: also close associated PR (requires confirmRemote)." }),
|
|
157
|
+
),
|
|
158
|
+
downstack: Type.Optional(Type.Boolean({ description: "action=delete: also delete ancestors." })),
|
|
159
|
+
upstack: Type.Optional(Type.Boolean({ description: "action=delete: also delete descendants." })),
|
|
160
|
+
dryRun: Type.Optional(
|
|
161
|
+
Type.Boolean({ description: "action=absorb: dry-run only (default true)." }),
|
|
162
|
+
),
|
|
163
|
+
patch: Type.Optional(Type.Boolean({ description: "action=absorb: pick hunks (--patch)." })),
|
|
164
|
+
confirmRemote: Type.Optional(Type.Boolean()),
|
|
165
|
+
}),
|
|
166
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
167
|
+
let args: string[];
|
|
168
|
+
|
|
169
|
+
switch (p.action) {
|
|
170
|
+
case "amend": {
|
|
171
|
+
args = ["modify"];
|
|
172
|
+
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
173
|
+
if (p.message) args.push("--message", p.message);
|
|
174
|
+
if (p.edit) args.push("--edit");
|
|
175
|
+
if (p.resetAuthor) args.push("--reset-author");
|
|
176
|
+
if (p.into) args.push("--into", p.into);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "new_commit": {
|
|
180
|
+
if (!p.message && !p.edit) {
|
|
181
|
+
throw new Error("action=new_commit requires `message` or edit=true.");
|
|
182
|
+
}
|
|
183
|
+
args = ["modify", "--commit"];
|
|
184
|
+
args.push(...stageArgs((p.stage ?? "none") as "none" | "all" | "update" | "patch"));
|
|
185
|
+
if (p.message) args.push("--message", p.message);
|
|
186
|
+
if (p.edit) args.push("--edit");
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "absorb": {
|
|
190
|
+
const dryRun = p.dryRun !== false; // default true
|
|
191
|
+
args = ["absorb"];
|
|
192
|
+
if (dryRun) args.push("--dry-run");
|
|
193
|
+
else args.push("--force");
|
|
194
|
+
const stage = (p.stage ?? "none") as "none" | "all" | "update" | "patch";
|
|
195
|
+
if (stage === "all") args.push("--all");
|
|
196
|
+
if (p.patch) args.push("--patch");
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "squash": {
|
|
200
|
+
args = ["squash"];
|
|
201
|
+
if (p.message) args.push("--message", p.message);
|
|
202
|
+
if (p.edit) args.push("--edit");
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "pop": {
|
|
206
|
+
args = ["pop"];
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "rename": {
|
|
210
|
+
if (!p.newName) throw new Error("action=rename requires `newName`.");
|
|
211
|
+
args = ["rename", p.newName];
|
|
212
|
+
if (p.force) args.push("--force");
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case "delete": {
|
|
216
|
+
if (p.close) requireConfirm(p.confirmRemote, "delete --close (closes PR on GitHub)");
|
|
217
|
+
args = ["delete"];
|
|
218
|
+
if (p.branch) args.push(p.branch);
|
|
219
|
+
if (p.force) args.push("--force");
|
|
220
|
+
if (p.close) args.push("--close");
|
|
221
|
+
if (p.downstack) args.push("--downstack");
|
|
222
|
+
if (p.upstack) args.push("--upstack");
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
228
|
+
const f = formatResult(r);
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
231
|
+
details: { action: p.action, result: f },
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* --------------------------- branch_tracking --------------------------- */
|
|
238
|
+
|
|
239
|
+
export function registerBranchTracking(pi: ExtensionAPI) {
|
|
240
|
+
pi.registerTool({
|
|
241
|
+
name: "graphite_branch_tracking",
|
|
242
|
+
label: "Graphite: branch tracking",
|
|
243
|
+
description:
|
|
244
|
+
"Track / untrack a branch with Graphite, or freeze / unfreeze to prevent local modifications.",
|
|
245
|
+
promptSnippet:
|
|
246
|
+
"graphite_branch_tracking: track/untrack/freeze/unfreeze a branch",
|
|
247
|
+
parameters: Type.Object({
|
|
248
|
+
cwd: CwdParam,
|
|
249
|
+
action: StringEnum(["track", "untrack", "freeze", "unfreeze"] as const),
|
|
250
|
+
branch: Type.Optional(Type.String()),
|
|
251
|
+
parent: Type.Optional(
|
|
252
|
+
Type.String({ description: "action=track: explicit parent branch." }),
|
|
253
|
+
),
|
|
254
|
+
force: Type.Optional(Type.Boolean()),
|
|
255
|
+
}),
|
|
256
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
257
|
+
let args: string[];
|
|
258
|
+
switch (p.action) {
|
|
259
|
+
case "track":
|
|
260
|
+
args = ["track"];
|
|
261
|
+
if (p.branch) args.push(p.branch);
|
|
262
|
+
if (p.parent) args.push("--parent", p.parent);
|
|
263
|
+
if (p.force) args.push("--force");
|
|
264
|
+
break;
|
|
265
|
+
case "untrack":
|
|
266
|
+
args = ["untrack"];
|
|
267
|
+
if (p.branch) args.push(p.branch);
|
|
268
|
+
if (p.force) args.push("--force");
|
|
269
|
+
break;
|
|
270
|
+
case "freeze":
|
|
271
|
+
args = ["freeze"];
|
|
272
|
+
if (p.branch) args.push(p.branch);
|
|
273
|
+
break;
|
|
274
|
+
case "unfreeze":
|
|
275
|
+
args = ["unfreeze"];
|
|
276
|
+
if (p.branch) args.push(p.branch);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
280
|
+
const f = formatResult(r);
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
283
|
+
details: { action: p.action, result: f },
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* --------------------------- branch_navigate --------------------------- */
|
|
290
|
+
|
|
291
|
+
export function registerBranchNavigate(pi: ExtensionAPI) {
|
|
292
|
+
pi.registerTool({
|
|
293
|
+
name: "graphite_branch_navigate",
|
|
294
|
+
label: "Graphite: navigate",
|
|
295
|
+
description:
|
|
296
|
+
"Navigate the current stack: checkout a branch, move up/down N levels, or jump to top/bottom.",
|
|
297
|
+
promptSnippet:
|
|
298
|
+
"graphite_branch_navigate: checkout/up/down/top/bottom across the stack",
|
|
299
|
+
parameters: Type.Object({
|
|
300
|
+
cwd: CwdParam,
|
|
301
|
+
action: StringEnum(["checkout", "up", "down", "top", "bottom"] as const),
|
|
302
|
+
branch: Type.Optional(Type.String({ description: "action=checkout: branch to checkout." })),
|
|
303
|
+
steps: Type.Optional(Type.Integer({ minimum: 1, description: "action=up|down step count." })),
|
|
304
|
+
to: Type.Optional(
|
|
305
|
+
Type.String({ description: "action=up: target descendant when multiple children exist (--to)." }),
|
|
306
|
+
),
|
|
307
|
+
showAllTrunks: Type.Optional(Type.Boolean({ description: "action=checkout: --all." })),
|
|
308
|
+
showUntracked: Type.Optional(Type.Boolean({ description: "action=checkout: --show-untracked." })),
|
|
309
|
+
stackOnly: Type.Optional(Type.Boolean({ description: "action=checkout: only stack branches (--stack)." })),
|
|
310
|
+
checkoutTrunk: Type.Optional(Type.Boolean({ description: "action=checkout: jump to trunk (--trunk)." })),
|
|
311
|
+
}),
|
|
312
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
313
|
+
let args: string[];
|
|
314
|
+
switch (p.action) {
|
|
315
|
+
case "checkout":
|
|
316
|
+
args = ["checkout"];
|
|
317
|
+
if (p.branch) args.push(p.branch);
|
|
318
|
+
if (p.showAllTrunks) args.push("--all");
|
|
319
|
+
if (p.showUntracked) args.push("--show-untracked");
|
|
320
|
+
if (p.stackOnly) args.push("--stack");
|
|
321
|
+
if (p.checkoutTrunk) args.push("--trunk");
|
|
322
|
+
if (!p.branch && !p.checkoutTrunk) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
"action=checkout requires `branch` or `checkoutTrunk:true` (interactive selector disabled).",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
case "up":
|
|
329
|
+
args = ["up"];
|
|
330
|
+
if (p.steps != null) args.push("--steps", String(p.steps));
|
|
331
|
+
if (p.to) args.push("--to", p.to);
|
|
332
|
+
break;
|
|
333
|
+
case "down":
|
|
334
|
+
args = ["down"];
|
|
335
|
+
if (p.steps != null) args.push("--steps", String(p.steps));
|
|
336
|
+
break;
|
|
337
|
+
case "top":
|
|
338
|
+
args = ["top"];
|
|
339
|
+
break;
|
|
340
|
+
case "bottom":
|
|
341
|
+
args = ["bottom"];
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
345
|
+
const f = formatResult(r);
|
|
346
|
+
return {
|
|
347
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
348
|
+
details: { action: p.action, result: f },
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
package/src/tools/pr.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { runGt } from "../lib/exec";
|
|
3
|
+
import { formatResult, 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
|
+
export function registerPrSubmit(pi: ExtensionAPI) {
|
|
15
|
+
pi.registerTool({
|
|
16
|
+
name: "graphite_pr_submit",
|
|
17
|
+
label: "Graphite: PR submit",
|
|
18
|
+
description:
|
|
19
|
+
"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.",
|
|
20
|
+
promptSnippet:
|
|
21
|
+
"graphite_pr_submit: plan or apply `gt submit` for a branch or stack",
|
|
22
|
+
promptGuidelines: [
|
|
23
|
+
"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.",
|
|
24
|
+
],
|
|
25
|
+
parameters: Type.Object({
|
|
26
|
+
cwd: CwdParam,
|
|
27
|
+
apply: Type.Optional(
|
|
28
|
+
Type.Boolean({
|
|
29
|
+
description: "false => --dry-run (default). true => actually push (requires confirmRemote).",
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
stack: Type.Optional(
|
|
33
|
+
Type.Boolean({
|
|
34
|
+
description:
|
|
35
|
+
"true => --stack (include descendants). false => --no-stack. Omitted => default (gt prompts based on config).",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
branch: Type.Optional(Type.String({ description: "Run from this branch (--branch)." })),
|
|
39
|
+
updateOnly: Type.Optional(Type.Boolean({ description: "--update-only" })),
|
|
40
|
+
draft: Type.Optional(Type.Boolean({ description: "--draft for new PRs" })),
|
|
41
|
+
publish: Type.Optional(Type.Boolean({ description: "--publish all PRs" })),
|
|
42
|
+
mergeWhenReady: Type.Optional(Type.Boolean({ description: "--merge-when-ready" })),
|
|
43
|
+
rerequestReview: Type.Optional(Type.Boolean()),
|
|
44
|
+
reviewers: Type.Optional(Type.Array(Type.String(), { description: "User reviewers (--reviewers)." })),
|
|
45
|
+
teamReviewers: Type.Optional(Type.Array(Type.String())),
|
|
46
|
+
comment: Type.Optional(Type.String({ description: "--comment <msg>" })),
|
|
47
|
+
targetTrunk: Type.Optional(Type.String()),
|
|
48
|
+
editMode: Type.Optional(
|
|
49
|
+
StringEnum(["none", "cli", "web"] as const, {
|
|
50
|
+
description:
|
|
51
|
+
"none (default) => --no-edit; cli => --edit --cli; web => --web. Affects PR metadata prompting.",
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
ai: Type.Optional(
|
|
55
|
+
Type.Boolean({
|
|
56
|
+
description: "true => --ai (let gt generate PR title/body). Default false (--no-ai).",
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
forcePush: Type.Optional(
|
|
60
|
+
Type.Boolean({ description: "--force (instead of default --force-with-lease). Requires confirmRemote." }),
|
|
61
|
+
),
|
|
62
|
+
ignoreOutOfSyncTrunk: Type.Optional(Type.Boolean()),
|
|
63
|
+
view: Type.Optional(Type.Boolean({ description: "--view (open PR in browser after submit)." })),
|
|
64
|
+
confirmRemote: Type.Optional(Type.Boolean()),
|
|
65
|
+
}),
|
|
66
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
67
|
+
const apply = p.apply === true;
|
|
68
|
+
if (apply) requireConfirm(p.confirmRemote, "gt submit (push branches + create/update PRs)");
|
|
69
|
+
if (p.forcePush) requireConfirm(p.confirmRemote, "gt submit --force");
|
|
70
|
+
|
|
71
|
+
const args: string[] = ["submit"];
|
|
72
|
+
if (!apply) args.push("--dry-run");
|
|
73
|
+
|
|
74
|
+
if (p.stack === true) args.push("--stack");
|
|
75
|
+
else if (p.stack === false) args.push("--no-stack");
|
|
76
|
+
|
|
77
|
+
if (p.branch) args.push("--branch", p.branch);
|
|
78
|
+
if (p.updateOnly) args.push("--update-only");
|
|
79
|
+
if (p.draft) args.push("--draft");
|
|
80
|
+
if (p.publish) args.push("--publish");
|
|
81
|
+
if (p.mergeWhenReady) args.push("--merge-when-ready");
|
|
82
|
+
if (p.rerequestReview) args.push("--rerequest-review");
|
|
83
|
+
|
|
84
|
+
if (p.reviewers && p.reviewers.length)
|
|
85
|
+
args.push("--reviewers", p.reviewers.join(","));
|
|
86
|
+
if (p.teamReviewers && p.teamReviewers.length)
|
|
87
|
+
args.push("--team-reviewers", p.teamReviewers.join(","));
|
|
88
|
+
if (p.comment) args.push("--comment", p.comment);
|
|
89
|
+
if (p.targetTrunk) args.push("--target-trunk", p.targetTrunk);
|
|
90
|
+
|
|
91
|
+
const editMode = p.editMode ?? "none";
|
|
92
|
+
if (editMode === "none") args.push("--no-edit");
|
|
93
|
+
else if (editMode === "cli") args.push("--edit", "--cli");
|
|
94
|
+
else if (editMode === "web") args.push("--web");
|
|
95
|
+
|
|
96
|
+
args.push(p.ai ? "--ai" : "--no-ai");
|
|
97
|
+
|
|
98
|
+
if (p.forcePush) args.push("--force");
|
|
99
|
+
if (p.ignoreOutOfSyncTrunk) args.push("--ignore-out-of-sync-trunk");
|
|
100
|
+
if (p.view) args.push("--view");
|
|
101
|
+
|
|
102
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
103
|
+
const f = formatResult(r);
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
106
|
+
details: { apply, result: f },
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ----------------------------- pr_lifecycle ----------------------------- */
|
|
113
|
+
|
|
114
|
+
export function registerPrLifecycle(pi: ExtensionAPI) {
|
|
115
|
+
pi.registerTool({
|
|
116
|
+
name: "graphite_pr_lifecycle",
|
|
117
|
+
label: "Graphite: PR lifecycle",
|
|
118
|
+
description:
|
|
119
|
+
"PR lifecycle actions: open the PR/stack page in a browser, merge the stack via Graphite, or unlink a branch from its PR.",
|
|
120
|
+
promptSnippet:
|
|
121
|
+
"graphite_pr_lifecycle: open_url | merge | unlink for a PR/branch",
|
|
122
|
+
parameters: Type.Object({
|
|
123
|
+
cwd: CwdParam,
|
|
124
|
+
action: StringEnum(["open_url", "merge", "unlink"] as const),
|
|
125
|
+
branch: Type.Optional(Type.String({ description: "Branch name or PR number." })),
|
|
126
|
+
stack: Type.Optional(
|
|
127
|
+
Type.Boolean({ description: "action=open_url: open stack page (--stack)." }),
|
|
128
|
+
),
|
|
129
|
+
apply: Type.Optional(
|
|
130
|
+
Type.Boolean({
|
|
131
|
+
description: "action=merge: false (default) => --dry-run; true => actually merge (requires confirmRemote).",
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
confirm: Type.Optional(
|
|
135
|
+
Type.Boolean({
|
|
136
|
+
description: "action=merge: pass --confirm so gt prompts before merging each branch.",
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
139
|
+
confirmRemote: Type.Optional(Type.Boolean()),
|
|
140
|
+
}),
|
|
141
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
142
|
+
if (p.action === "open_url") {
|
|
143
|
+
const args = ["pr"];
|
|
144
|
+
if (p.branch) args.push(p.branch);
|
|
145
|
+
if (p.stack) args.push("--stack");
|
|
146
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
147
|
+
const f = formatResult(r);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
150
|
+
details: { result: f },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (p.action === "merge") {
|
|
155
|
+
const apply = p.apply === true;
|
|
156
|
+
if (apply) requireConfirm(p.confirmRemote, "gt merge (merges PRs on GitHub)");
|
|
157
|
+
const args = ["merge"];
|
|
158
|
+
if (!apply) args.push("--dry-run");
|
|
159
|
+
if (p.confirm) args.push("--confirm");
|
|
160
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
161
|
+
const f = formatResult(r);
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
164
|
+
details: { apply, result: f },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// unlink
|
|
169
|
+
const args = ["unlink"];
|
|
170
|
+
if (p.branch) args.push(p.branch);
|
|
171
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
172
|
+
const f = formatResult(r);
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
175
|
+
details: { result: f },
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { runGt } from "../lib/exec";
|
|
3
|
+
import { formatResult, renderText } from "../lib/result";
|
|
4
|
+
import { CwdParam, StringEnum, Type, type ToolReturn } from "../lib/schema";
|
|
5
|
+
|
|
6
|
+
export function registerRecovery(pi: ExtensionAPI) {
|
|
7
|
+
pi.registerTool({
|
|
8
|
+
name: "graphite_recovery",
|
|
9
|
+
label: "Graphite: recovery",
|
|
10
|
+
description:
|
|
11
|
+
"Recover from conflicts or mistakes: continue a halted command, abort it, or undo the most recent Graphite mutation in this worktree.",
|
|
12
|
+
promptSnippet:
|
|
13
|
+
"graphite_recovery: continue / abort / undo Graphite commands",
|
|
14
|
+
promptGuidelines: [
|
|
15
|
+
"After resolving a rebase conflict, run graphite_recovery action=continue to resume the original gt command.",
|
|
16
|
+
"graphite_recovery action=undo only undoes commands run from the current worktree.",
|
|
17
|
+
],
|
|
18
|
+
parameters: Type.Object({
|
|
19
|
+
cwd: CwdParam,
|
|
20
|
+
action: StringEnum(["continue", "abort", "undo"] as const),
|
|
21
|
+
stageAll: Type.Optional(
|
|
22
|
+
Type.Boolean({ description: "action=continue: stage all changes first (--all)." }),
|
|
23
|
+
),
|
|
24
|
+
force: Type.Optional(
|
|
25
|
+
Type.Boolean({ description: "action=abort|undo: skip confirmation prompt (--force)." }),
|
|
26
|
+
),
|
|
27
|
+
}),
|
|
28
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
29
|
+
let args: string[];
|
|
30
|
+
switch (p.action) {
|
|
31
|
+
case "continue":
|
|
32
|
+
args = ["continue"];
|
|
33
|
+
if (p.stageAll) args.push("--all");
|
|
34
|
+
break;
|
|
35
|
+
case "abort":
|
|
36
|
+
args = ["abort"];
|
|
37
|
+
if (p.force) args.push("--force");
|
|
38
|
+
break;
|
|
39
|
+
case "undo":
|
|
40
|
+
args = ["undo"];
|
|
41
|
+
if (p.force) args.push("--force");
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
45
|
+
const f = formatResult(r);
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
48
|
+
details: { action: p.action, result: f },
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { runGt } from "../lib/exec";
|
|
3
|
+
import { formatResult, renderText } from "../lib/result";
|
|
4
|
+
import {
|
|
5
|
+
CwdParam,
|
|
6
|
+
StringEnum,
|
|
7
|
+
Type,
|
|
8
|
+
requireConfirm,
|
|
9
|
+
type ToolReturn,
|
|
10
|
+
} from "../lib/schema";
|
|
11
|
+
|
|
12
|
+
export function registerRemoteSync(pi: ExtensionAPI) {
|
|
13
|
+
pi.registerTool({
|
|
14
|
+
name: "graphite_remote_sync",
|
|
15
|
+
label: "Graphite: remote sync",
|
|
16
|
+
description:
|
|
17
|
+
"Sync local branches with remote: `gt sync` (pull trunk + restack + cleanup) or `gt get` (fetch a branch / PR locally).",
|
|
18
|
+
promptSnippet:
|
|
19
|
+
"graphite_remote_sync: `gt sync` for cleanup+restack, or `gt get` to import a branch/PR",
|
|
20
|
+
promptGuidelines: [
|
|
21
|
+
"Run graphite_remote_sync action=sync at the start of a session to update trunk and restack open stacks.",
|
|
22
|
+
"graphite_remote_sync action=sync with force=true or deleteAll=true is destructive; pass confirmDestructive:true.",
|
|
23
|
+
],
|
|
24
|
+
parameters: Type.Object({
|
|
25
|
+
cwd: CwdParam,
|
|
26
|
+
action: StringEnum(["sync", "get"] as const),
|
|
27
|
+
|
|
28
|
+
// sync
|
|
29
|
+
allTrunks: Type.Optional(Type.Boolean({ description: "sync: --all" })),
|
|
30
|
+
deleteAll: Type.Optional(
|
|
31
|
+
Type.Boolean({
|
|
32
|
+
description:
|
|
33
|
+
"sync|get: delete all merged/closed branches without prompting (--delete-all). Requires confirmDestructive.",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
force: Type.Optional(
|
|
37
|
+
Type.Boolean({
|
|
38
|
+
description:
|
|
39
|
+
"sync|get: overwrite local branches with remote (--force). Requires confirmDestructive.",
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
restack: Type.Optional(
|
|
43
|
+
Type.Boolean({
|
|
44
|
+
description: "sync|get: restack after fetching (default true; pass false for --no-restack).",
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
|
|
48
|
+
// get
|
|
49
|
+
target: Type.Optional(
|
|
50
|
+
Type.String({ description: "get: branch name or PR number to fetch." }),
|
|
51
|
+
),
|
|
52
|
+
downstack: Type.Optional(
|
|
53
|
+
Type.Boolean({ description: "get: --downstack (don't sync upstack)." }),
|
|
54
|
+
),
|
|
55
|
+
remoteUpstack: Type.Optional(
|
|
56
|
+
Type.Boolean({ description: "get: --remote-upstack (include remote-only upstack)." }),
|
|
57
|
+
),
|
|
58
|
+
checkout: Type.Optional(
|
|
59
|
+
Type.Boolean({ description: "get: check out target after sync (default true; false => --no-checkout)." }),
|
|
60
|
+
),
|
|
61
|
+
unfrozen: Type.Optional(
|
|
62
|
+
Type.Boolean({ description: "get: --unfrozen (new branches editable)." }),
|
|
63
|
+
),
|
|
64
|
+
|
|
65
|
+
confirmDestructive: Type.Optional(Type.Boolean()),
|
|
66
|
+
}),
|
|
67
|
+
async execute(_id, p, signal): Promise<ToolReturn> {
|
|
68
|
+
const args: string[] = [p.action];
|
|
69
|
+
|
|
70
|
+
if (p.action === "sync") {
|
|
71
|
+
if (p.force || p.deleteAll) {
|
|
72
|
+
requireConfirm(
|
|
73
|
+
p.confirmDestructive,
|
|
74
|
+
"gt sync with --force/--delete-all (may overwrite branches)",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (p.allTrunks) args.push("--all");
|
|
78
|
+
if (p.deleteAll) args.push("--delete-all");
|
|
79
|
+
if (p.force) args.push("--force");
|
|
80
|
+
if (p.restack === false) args.push("--no-restack");
|
|
81
|
+
} else {
|
|
82
|
+
if (!p.target) throw new Error("action=get requires `target` (branch name or PR number).");
|
|
83
|
+
args.push(p.target);
|
|
84
|
+
if (p.downstack) args.push("--downstack");
|
|
85
|
+
if (p.remoteUpstack) args.push("--remote-upstack");
|
|
86
|
+
if (p.force) {
|
|
87
|
+
requireConfirm(p.confirmDestructive, "gt get --force (overwrites local branches)");
|
|
88
|
+
args.push("--force");
|
|
89
|
+
}
|
|
90
|
+
if (p.deleteAll) {
|
|
91
|
+
requireConfirm(p.confirmDestructive, "gt get --delete-all");
|
|
92
|
+
args.push("--delete-all");
|
|
93
|
+
}
|
|
94
|
+
if (p.checkout === false) args.push("--no-checkout");
|
|
95
|
+
if (p.restack === false) args.push("--no-restack");
|
|
96
|
+
if (p.unfrozen) args.push("--unfrozen");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const r = await runGt(args, { cwd: p.cwd, signal });
|
|
100
|
+
const f = formatResult(r);
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: renderText(`gt ${args.join(" ")}`, f) }],
|
|
103
|
+
details: { action: p.action, result: f },
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|