gflows 0.1.18 → 1.0.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.
Files changed (49) hide show
  1. package/AGENTS.md +78 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +279 -505
  4. package/llms.txt +22 -0
  5. package/package.json +29 -23
  6. package/src/cli.ts +21 -367
  7. package/src/commands/abort.ts +39 -0
  8. package/src/commands/bump.ts +10 -10
  9. package/src/commands/completion.ts +14 -4
  10. package/src/commands/config.ts +123 -0
  11. package/src/commands/continue.ts +40 -0
  12. package/src/commands/delete.ts +4 -4
  13. package/src/commands/doctor.ts +143 -0
  14. package/src/commands/finish.ts +363 -137
  15. package/src/commands/help.ts +29 -21
  16. package/src/commands/init.ts +99 -18
  17. package/src/commands/list.ts +6 -1
  18. package/src/commands/mcp.ts +239 -0
  19. package/src/commands/pr.ts +120 -0
  20. package/src/commands/schema.ts +99 -0
  21. package/src/commands/start.ts +33 -31
  22. package/src/commands/status.ts +70 -51
  23. package/src/commands/switch.ts +14 -19
  24. package/src/commands/sync.ts +160 -0
  25. package/src/commands/undo.ts +78 -0
  26. package/src/commands/version.ts +3 -15
  27. package/src/commands/viz.ts +18 -0
  28. package/src/dispatch.ts +21 -0
  29. package/src/errors.ts +55 -12
  30. package/src/flow.ts +157 -0
  31. package/src/git.ts +135 -8
  32. package/src/index.ts +24 -1
  33. package/src/interactive.ts +209 -0
  34. package/src/out.ts +11 -4
  35. package/src/package-scripts.ts +73 -0
  36. package/src/parse.ts +430 -0
  37. package/src/prompts.ts +89 -0
  38. package/src/recommend.ts +132 -0
  39. package/src/run-state.ts +165 -0
  40. package/src/tui/HubHome.tsx +391 -0
  41. package/src/tui/HubShell.tsx +193 -0
  42. package/src/tui/flows.tsx +229 -0
  43. package/src/tui/hub.ts +114 -0
  44. package/src/tui/prompts.tsx +207 -0
  45. package/src/tui/slash.ts +63 -0
  46. package/src/types.ts +62 -3
  47. package/src/ui.ts +132 -0
  48. package/src/version.ts +24 -0
  49. package/src/viz.ts +294 -0
package/llms.txt ADDED
@@ -0,0 +1,22 @@
1
+ # gflows
2
+
3
+ > Lightweight Bun/TypeScript CLI for main+dev Git branching workflows (feature, bugfix, chore, release, hotfix, spike).
4
+
5
+ ## Docs
6
+
7
+ - [AGENTS.md](AGENTS.md): instructions for coding agents (recipes, exit codes, MCP)
8
+ - [README.md](README.md): human quick start, command reference, flowcharts
9
+ - Machine catalog: run `gflows schema` (JSON)
10
+
11
+ ## Key commands
12
+
13
+ - `gflows` (TTY): Ink fullscreen hub (frame, tips, actions, `/` slash commands, status bar); Clack prompts
14
+ - `gflows viz`: scrollback status panel — lifecycle, branches, recommended next step
15
+ - `gflows init` / `start` / `finish` / `sync` / `pr`
16
+ - `gflows continue` / `undo` / `abort` — recovery after conflicts
17
+ - `gflows doctor` / `config` / `status --json` / `mcp`
18
+
19
+ ## Package
20
+
21
+ - npm: `gflows`
22
+ - JSR: `@alialnaghmoush/gflows`
package/package.json CHANGED
@@ -1,13 +1,34 @@
1
1
  {
2
2
  "name": "gflows",
3
- "version": "0.1.18",
4
- "description": "A lightweight CLI for consistent Git branching workflows (main + dev, feature/bugfix/chore/release/hotfix).",
5
- "license": "MIT",
6
- "type": "module",
3
+ "version": "1.0.1",
4
+ "author": "Ali AlNaghmoush (https://github.com/alialnaghmoush)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/alialnaghmoush/gflows.git"
8
+ },
7
9
  "module": "src/index.ts",
10
+ "devDependencies": {
11
+ "@biomejs/biome": "^2.4.4",
12
+ "@types/bun": "latest",
13
+ "@types/react": "^19.2.17",
14
+ "typescript": "^5"
15
+ },
8
16
  "bin": {
9
17
  "gflows": "src/cli.ts"
10
18
  },
19
+ "description": "A lightweight CLI for consistent Git branching workflows (main + dev, feature/bugfix/chore/release/hotfix).",
20
+ "engines": {
21
+ "bun": ">=1.0.0"
22
+ },
23
+ "files": [
24
+ "src",
25
+ "README.md",
26
+ "LICENSE",
27
+ "AGENTS.md",
28
+ "llms.txt",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "license": "MIT",
11
32
  "scripts": {
12
33
  "test": "bun test",
13
34
  "typecheck": "tsc --noEmit",
@@ -20,25 +41,10 @@
20
41
  "publish:npm": "bun run scripts/publish.ts -- --npm-only",
21
42
  "publish:jsr": "bun run scripts/publish.ts -- --jsr-only"
22
43
  },
23
- "files": [
24
- "src",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- "engines": {
29
- "bun": ">=1.0.0"
30
- },
31
- "author": "Ali AlNaghmoush (https://github.com/alialnaghmoush)",
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/alialnaghmoush/gflows.git"
35
- },
44
+ "type": "module",
36
45
  "dependencies": {
37
- "@inquirer/prompts": "^8"
38
- },
39
- "devDependencies": {
40
- "@biomejs/biome": "^2.4.4",
41
- "@types/bun": "latest",
42
- "typescript": "^5"
46
+ "@clack/prompts": "^1.7.0",
47
+ "ink": "^7.1.1",
48
+ "react": "^19.2.8"
43
49
  }
44
50
  }
package/src/cli.ts CHANGED
@@ -6,405 +6,59 @@
6
6
  * @module cli
7
7
  */
8
8
 
9
- import { existsSync, statSync } from "node:fs";
10
- import { resolve } from "node:path";
11
- import { parseArgs } from "node:util";
12
9
  import { EXIT_GIT, EXIT_OK, EXIT_USER } from "./constants.js";
13
- import { exitCodeForError } from "./errors.js";
14
- import type { BranchType, Command, ParsedArgs } from "./types.js";
10
+ import { exitCodeForError, printError } from "./errors.js";
11
+ import { parse } from "./parse.js";
12
+ import type { ParsedArgs } from "./types.js";
15
13
 
16
14
  /** Last parsed args, set at start of run(); used by catch/rejection to respect -v for stack trace. */
17
15
  let lastParsedArgs: ParsedArgs | null = null;
18
16
 
19
- const COMMANDS: Command[] = [
20
- "init",
21
- "start",
22
- "finish",
23
- "switch",
24
- "delete",
25
- "list",
26
- "bump",
27
- "completion",
28
- "status",
29
- "help",
30
- "version",
31
- ];
17
+ export { parse } from "./parse.js";
32
18
 
33
- const BRANCH_TYPES: BranchType[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
34
-
35
- /** Short flag → command (when used as main command). */
36
- const SHORT_TO_COMMAND: Record<string, Command> = {
37
- I: "init",
38
- S: "start",
39
- F: "finish",
40
- W: "switch",
41
- L: "delete",
42
- l: "list",
43
- t: "status",
44
- h: "help",
45
- V: "version",
46
- };
47
-
48
- function buildParseArgsOptions() {
49
- return {
50
- args: Bun.argv.slice(2),
51
- strict: false,
52
- allowPositionals: true,
53
- options: {
54
- // -C / --path (must be first conceptually for cwd resolution)
55
- path: { type: "string" as const, short: "C" },
56
- // Command shorts
57
- init: { type: "boolean" as const, short: "I" },
58
- start: { type: "boolean" as const, short: "S" },
59
- finish: { type: "boolean" as const, short: "F" },
60
- switch: { type: "boolean" as const, short: "W" },
61
- delete: { type: "boolean" as const, short: "L" },
62
- list: { type: "boolean" as const, short: "l" },
63
- status: { type: "boolean" as const, short: "t" },
64
- help: { type: "boolean" as const, short: "h" },
65
- version: { type: "boolean" as const, short: "V" },
66
- // Type shorts (-r is context-dependent: list → include-remote, start/finish → release)
67
- feature: { type: "boolean" as const, short: "f" },
68
- bugfix: { type: "boolean" as const, short: "b" },
69
- chore: { type: "boolean" as const, short: "c" },
70
- release: { type: "boolean" as const, short: "r" },
71
- hotfix: { type: "boolean" as const, short: "x" },
72
- spike: { type: "boolean" as const, short: "e" },
73
- // Common
74
- push: { type: "boolean" as const, short: "p" },
75
- noPush: { type: "boolean" as const, short: "P" },
76
- "no-push": { type: "boolean" as const },
77
- main: { type: "string" as const },
78
- dev: { type: "string" as const },
79
- remote: { type: "string" as const, short: "R" },
80
- from: { type: "string" as const, short: "o" },
81
- branch: { type: "string" as const, short: "B" },
82
- yes: { type: "boolean" as const, short: "y" },
83
- dryRun: { type: "boolean" as const, short: "d" },
84
- verbose: { type: "boolean" as const, short: "v" },
85
- quiet: { type: "boolean" as const, short: "q" },
86
- force: { type: "boolean" as const },
87
- // finish (-D/--delete-branch, -N/--no-delete)
88
- noFf: { type: "boolean" as const },
89
- deleteBranch: { type: "boolean" as const, short: "D" },
90
- noDelete: { type: "boolean" as const, short: "N" },
91
- sign: { type: "boolean" as const, short: "s" },
92
- noTag: { type: "boolean" as const, short: "T" },
93
- tagMessage: { type: "string" as const, short: "M" },
94
- message: { type: "string" as const, short: "m" },
95
- // list (-r is context-dependent: list → include-remote; start/finish → release)
96
- includeRemote: { type: "boolean" as const },
97
- "include-remote": { type: "boolean" as const },
98
- // switch: explicit mode (--restore, --clean, --cancel, --move, --destroy)
99
- restore: { type: "boolean" as const },
100
- clean: { type: "boolean" as const },
101
- cancel: { type: "boolean" as const },
102
- move: { type: "boolean" as const },
103
- destroy: { type: "boolean" as const },
104
- },
105
- };
106
- }
107
-
108
- /** Resolve -C/--path to absolute directory; validate it exists and is a directory. */
109
- function resolveCwd(pathFlag: string | undefined): string {
110
- if (!pathFlag || pathFlag.trim() === "") {
111
- return process.cwd();
112
- }
113
- const absolute = resolve(process.cwd(), pathFlag.trim());
114
- if (!existsSync(absolute)) {
115
- console.error(`gflows: path does not exist: ${absolute}`);
116
- process.exit(EXIT_USER);
117
- }
118
- const stat = statSync(absolute, { throwIfNoEntry: false });
119
- if (!stat || !stat.isDirectory()) {
120
- console.error(`gflows: path is not a directory: ${absolute}`);
121
- process.exit(EXIT_USER);
122
- }
123
- return absolute;
124
- }
125
-
126
- /**
127
- * Returns the command name closest to `input` by edit distance, or undefined if no close match.
128
- * Used for "did you mean?" when the user mistypes a command.
129
- */
130
- function closestCommand(input: string): Command | undefined {
131
- if (!input || input.length < 2) return undefined;
132
- const target = input.toLowerCase();
133
- let best: Command | undefined;
134
- let bestDistance = 3; // only suggest if within 2 edits
135
- for (const cmd of COMMANDS) {
136
- const d = editDistance(target, cmd);
137
- if (d < bestDistance) {
138
- bestDistance = d;
139
- best = cmd as Command;
140
- }
141
- }
142
- return best;
143
- }
144
-
145
- /** Levenshtein edit distance between two strings. */
146
- function editDistance(a: string, b: string): number {
147
- const m = a.length;
148
- const n = b.length;
149
- const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
150
- for (let i = 0; i <= m; i++) {
151
- const row = dp[i];
152
- if (row) row[0] = i;
153
- }
154
- for (let j = 0; j <= n; j++) {
155
- const row = dp[0];
156
- if (row) row[j] = j;
157
- }
158
- for (let i = 1; i <= m; i++) {
159
- for (let j = 1; j <= n; j++) {
160
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
161
- const v1 = dp[i - 1]?.[j] ?? 0;
162
- const v2 = dp[i]?.[j - 1] ?? 0;
163
- const v3 = dp[i - 1]?.[j - 1] ?? 0;
164
- const rowI = dp[i];
165
- if (rowI) rowI[j] = Math.min(v1 + 1, v2 + 1, v3 + cost);
166
- }
167
- }
168
- return dp[m]?.[n] ?? 0;
169
- }
170
-
171
- /** Resolve command from positionals and short flags. Short wins if both present. */
172
- function resolveCommand(
173
- positionals: string[],
174
- values: Record<string, string | boolean | undefined>,
175
- ): Command | undefined {
176
- for (const [_short, cmd] of Object.entries(SHORT_TO_COMMAND)) {
177
- const key = cmd === "delete" ? "delete" : cmd;
178
- if (values[key] === true) return cmd as Command;
179
- }
180
- const first = positionals[0];
181
- if (first && COMMANDS.includes(first as Command)) {
182
- return first as Command;
183
- }
184
- return undefined;
185
- }
186
-
187
- /** Resolve branch type for start/finish/list. -r means release for start/finish; for list, -r means include-remote (handled in flags). */
188
- function resolveType(
189
- command: Command,
190
- positionals: string[],
191
- values: Record<string, string | boolean | undefined>,
192
- ): BranchType | undefined {
193
- if (command !== "start" && command !== "finish" && command !== "list") {
194
- return undefined;
195
- }
196
- // Type from short flags (for start/finish, -r => release)
197
- if (command === "list") {
198
- // For list, -r is include-remote only (see includeRemote below); type from other shorts or positional (not -r)
199
- if (values.feature === true) return "feature";
200
- if (values.bugfix === true) return "bugfix";
201
- if (values.chore === true) return "chore";
202
- if (values.hotfix === true) return "hotfix";
203
- if (values.spike === true) return "spike";
204
- // release type for list only via positional, e.g. "gflows list release"
205
- } else {
206
- // start / finish: -r => release
207
- if (values.release === true) return "release";
208
- if (values.feature === true) return "feature";
209
- if (values.bugfix === true) return "bugfix";
210
- if (values.chore === true) return "chore";
211
- if (values.hotfix === true) return "hotfix";
212
- if (values.spike === true) return "spike";
213
- }
214
- // Type from second positional
215
- const idx = positionals[0] && COMMANDS.includes(positionals[0] as Command) ? 1 : 0;
216
- const pos = positionals[idx];
217
- if (pos && BRANCH_TYPES.includes(pos as BranchType)) {
218
- return pos as BranchType;
219
- }
220
- return undefined;
221
- }
222
-
223
- /** Resolve name (third positional for start; -B for finish; first positional for completion). */
224
- function resolveName(
225
- command: Command,
226
- positionals: string[],
227
- values: Record<string, string | boolean | undefined>,
228
- ): string | undefined {
229
- const branch = values.branch;
230
- if (typeof branch === "string" && branch.trim() !== "") {
231
- return branch.trim();
232
- }
233
- const skip = positionals[0] && COMMANDS.includes(positionals[0] as Command) ? 1 : 0;
234
- if (command === "start") {
235
- const typeFromPos = positionals[skip] && BRANCH_TYPES.includes(positionals[skip] as BranchType);
236
- if (typeFromPos) {
237
- return positionals[skip + 1];
238
- }
239
- return positionals[skip];
240
- }
241
- if (command === "completion") {
242
- const shell = positionals[skip];
243
- if (shell === "bash" || shell === "zsh" || shell === "fish") return shell;
244
- return undefined;
245
- }
246
- if (command === "bump") {
247
- const dir = positionals[skip];
248
- const _typ = positionals[skip + 1];
249
- if (dir === "up" || dir === "down") {
250
- return dir;
251
- }
252
- return undefined;
253
- }
254
- if (command === "switch") {
255
- return positionals[skip];
256
- }
257
- return undefined;
258
- }
259
-
260
- /** Resolve bump direction and type from positionals (bump [up|down] [patch|minor|major]). */
261
- function resolveBump(
262
- positionals: string[],
263
- _values: Record<string, string | boolean | undefined>,
264
- ): { direction?: "up" | "down"; type?: "patch" | "minor" | "major" } {
265
- const skip = positionals[0] === "bump" ? 1 : 0;
266
- const a = positionals[skip];
267
- const b = positionals[skip + 1];
268
- const direction = a === "up" || a === "down" ? a : undefined;
269
- const type = b === "patch" || b === "minor" || b === "major" ? b : undefined;
270
- return { direction, type };
271
- }
272
-
273
- /** Parse raw argv into ParsedArgs. Resolves -C first, then command/type/name and flags. */
274
- export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
275
- const config = {
276
- ...buildParseArgsOptions(),
277
- args: argv,
278
- };
279
- const { values, positionals } = parseArgs(config);
280
- const v = values as Record<string, string | boolean | undefined>;
281
-
282
- const pathRaw = v.path;
283
- const pathStr = typeof pathRaw === "string" ? pathRaw : undefined;
284
- const cwd = resolveCwd(pathStr);
285
-
286
- const command = resolveCommand(positionals, v);
287
- if (!command) {
288
- const first = positionals[0];
289
- const suggestion = typeof first === "string" ? closestCommand(first) : undefined;
290
- if (suggestion) {
291
- console.error(`gflows: unknown command '${first}'. Did you mean '${suggestion}'?`);
292
- } else {
293
- console.error("gflows: missing command. Use 'gflows help' for usage.");
19
+ /** Run the CLI: parse, dispatch, set exit code. */
20
+ async function run(): Promise<void> {
21
+ const rawArgv = Bun.argv.slice(2);
22
+ const isTTY = Boolean(process.stdin.isTTY);
23
+
24
+ if (rawArgv.length === 0) {
25
+ if (isTTY) {
26
+ const { runHub } = await import("./interactive.js");
27
+ await runHub(process.cwd());
28
+ return;
294
29
  }
30
+ console.error("gflows: missing command. Use 'gflows help' for usage.");
295
31
  process.exit(EXIT_USER);
296
32
  }
297
33
 
298
- const type = resolveType(command, positionals, v);
299
- const name = resolveName(command, positionals, v);
300
- const { direction: bumpDirection, type: bumpType } =
301
- command === "bump" ? resolveBump(positionals, v) : { direction: undefined, type: undefined };
302
-
303
- const branchNames =
304
- command === "delete"
305
- ? positionals[0] && COMMANDS.includes(positionals[0] as Command)
306
- ? positionals.slice(1)
307
- : positionals
308
- : undefined;
309
-
310
- // -r context: for list → includeRemote; for start/finish → already used as type release
311
- const includeRemote =
312
- command === "list"
313
- ? v.includeRemote === true || v["include-remote"] === true || v.release === true
314
- : false;
315
-
316
- let completionShell: "bash" | "zsh" | "fish" | undefined;
317
- if (command === "completion" && name === "bash") completionShell = "bash";
318
- else if (command === "completion" && name === "zsh") completionShell = "zsh";
319
- else if (command === "completion" && name === "fish") completionShell = "fish";
320
-
321
- let switchMode: "restore" | "clean" | "cancel" | "move" | "destroy" | undefined;
322
- if (command === "switch") {
323
- const restore = v.restore === true;
324
- const clean = v.clean === true;
325
- const cancel = v.cancel === true;
326
- const move = v.move === true;
327
- const destroy = v.destroy === true;
328
- const count = [restore, clean, cancel, move, destroy].filter(Boolean).length;
329
- if (count > 1) {
330
- console.error(
331
- "gflows switch: only one of --restore, --clean, --cancel, --move, or --destroy may be used at a time.",
332
- );
333
- process.exit(EXIT_USER);
334
- }
335
- if (restore) switchMode = "restore";
336
- else if (clean) switchMode = "clean";
337
- else if (cancel) switchMode = "cancel";
338
- else if (move) switchMode = "move";
339
- else if (destroy) switchMode = "destroy";
340
- }
341
-
342
- return {
343
- command,
344
- cwd,
345
- type,
346
- name,
347
- completionShell,
348
- branchNames,
349
- bumpDirection,
350
- bumpType,
351
- push: v.push === true,
352
- noPush: v.noPush === true || v["no-push"] === true,
353
- main: typeof v.main === "string" && v.main.trim() !== "" ? v.main.trim() : undefined,
354
- dev: typeof v.dev === "string" && v.dev.trim() !== "" ? v.dev.trim() : undefined,
355
- remote: typeof v.remote === "string" ? v.remote : undefined,
356
- branch: typeof v.branch === "string" ? v.branch : undefined,
357
- yes: v.yes === true,
358
- dryRun: v.dryRun === true,
359
- verbose: v.verbose === true,
360
- quiet: v.quiet === true,
361
- force: v.force === true,
362
- path: pathStr,
363
- fromMain: v.from === "main",
364
- noFf: v.noFf === true,
365
- deleteAfterFinish: v.deleteBranch === true,
366
- noDeleteAfterFinish: v.noDelete === true,
367
- signTag: v.sign === true,
368
- noTag: v.noTag === true,
369
- tagMessage: typeof v.tagMessage === "string" ? v.tagMessage : undefined,
370
- message: typeof v.message === "string" ? v.message : undefined,
371
- includeRemote,
372
- switchMode,
373
- };
374
- }
375
-
376
- /** Run the CLI: parse, dispatch, set exit code. */
377
- async function run(): Promise<void> {
378
34
  const args = parse();
379
35
  lastParsedArgs = args;
380
- const { command } = args;
381
36
 
382
- if (command === "help") {
37
+ if (args.command === "help") {
383
38
  const { run: runHelp } = await import("./commands/help.js");
384
39
  await runHelp(args);
385
40
  return;
386
41
  }
387
- if (command === "version") {
42
+ if (args.command === "version") {
388
43
  const { run: runVersion } = await import("./commands/version.js");
389
44
  await runVersion(args);
390
45
  return;
391
46
  }
392
47
 
393
- const mod = await import(`./commands/${command}.js`).catch(() => null);
48
+ const mod = await import(`./commands/${args.command}.js`).catch(() => null);
394
49
  if (!mod || typeof mod.run !== "function") {
395
- console.error(`gflows: command '${command}' is not implemented.`);
50
+ console.error(`gflows: command '${args.command}' is not implemented.`);
396
51
  process.exit(EXIT_GIT);
397
52
  }
398
53
  await mod.run(args);
399
54
  }
400
55
 
401
- // Top-level try/catch and unhandledRejection so exit code is always set
402
56
  function main(): void {
403
57
  let exitCode: number | null = null;
404
58
 
405
59
  const handleRejection = (reason: unknown): void => {
406
60
  if (exitCode !== null) return;
407
- console.error("gflows:", reason instanceof Error ? reason.message : String(reason));
61
+ printError(reason);
408
62
  const verbose = lastParsedArgs?.verbose ?? !!process.env.GFLOWS_VERBOSE;
409
63
  if (verbose && reason instanceof Error && reason.stack) {
410
64
  console.error(reason.stack);
@@ -422,7 +76,7 @@ function main(): void {
422
76
  })
423
77
  .catch((err: unknown) => {
424
78
  if (exitCode !== null) return;
425
- console.error("gflows:", err instanceof Error ? err.message : String(err));
79
+ printError(err);
426
80
  const verbose = lastParsedArgs?.verbose ?? !!process.env.GFLOWS_VERBOSE;
427
81
  if (verbose && err instanceof Error && err.stack) {
428
82
  console.error(err.stack);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Abort a suspended gflows operation and clear merge/rebase state when present.
3
+ * @module commands/abort
4
+ */
5
+
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { isRebaseOrMergeInProgress, mergeAbort, rebaseAbort, resolveRepoRoot } from "../git.js";
9
+ import { hint, success } from "../out.js";
10
+ import { clearActiveRun, readActiveRun } from "../run-state.js";
11
+ import type { ParsedArgs } from "../types.js";
12
+
13
+ /**
14
+ * Aborts the suspended run and any in-progress git merge/rebase.
15
+ */
16
+ export async function run(args: ParsedArgs): Promise<void> {
17
+ const repoRoot = await resolveRepoRoot(args.cwd);
18
+ const state = readActiveRun(repoRoot);
19
+ const opts = { dryRun: args.dryRun, verbose: args.verbose };
20
+
21
+ if (isRebaseOrMergeInProgress(repoRoot)) {
22
+ const root = join(repoRoot, ".git");
23
+ if (existsSync(join(root, "MERGE_HEAD"))) {
24
+ await mergeAbort(repoRoot, opts);
25
+ } else if (existsSync(join(root, "rebase-merge")) || existsSync(join(root, "rebase-apply"))) {
26
+ await rebaseAbort(repoRoot, opts);
27
+ }
28
+ }
29
+
30
+ if (state) {
31
+ clearActiveRun(repoRoot);
32
+ if (!args.quiet) {
33
+ success(`gflows: aborted suspended '${state.command}' operation.`);
34
+ }
35
+ } else if (!args.quiet) {
36
+ success("gflows: no suspended operation; cleared any merge/rebase in progress.");
37
+ }
38
+ hint("Repository should be ready for a new gflows command.");
39
+ }
@@ -195,20 +195,20 @@ export async function run(args: ParsedArgs): Promise<void> {
195
195
  );
196
196
  process.exit(EXIT_USER);
197
197
  } else {
198
- const { select } = await import("@inquirer/prompts");
199
- direction = await select({
198
+ const { selectPrompt } = await import("../prompts.js");
199
+ direction = await selectPrompt<"up" | "down">({
200
200
  message: "Direction",
201
- choices: [
202
- { name: "Up (bump)", value: "up" as const },
203
- { name: "Down (rollback)", value: "down" as const },
201
+ options: [
202
+ { label: "Up (bump)", value: "up" },
203
+ { label: "Down (rollback)", value: "down" },
204
204
  ],
205
205
  });
206
- type = await select({
206
+ type = await selectPrompt<"patch" | "minor" | "major">({
207
207
  message: "Type",
208
- choices: [
209
- { name: "patch (x.y.Z)", value: "patch" as const },
210
- { name: "minor (x.Y.0)", value: "minor" as const },
211
- { name: "major (X.0.0)", value: "major" as const },
208
+ options: [
209
+ { label: "patch (x.y.Z)", value: "patch" },
210
+ { label: "minor (x.Y.0)", value: "minor" },
211
+ { label: "major (X.0.0)", value: "major" },
212
212
  ],
213
213
  });
214
214
  }
@@ -12,10 +12,20 @@ const COMMANDS = [
12
12
  "init",
13
13
  "start",
14
14
  "finish",
15
+ "sync",
16
+ "pr",
17
+ "viz",
15
18
  "switch",
16
19
  "delete",
17
20
  "list",
18
21
  "bump",
22
+ "doctor",
23
+ "config",
24
+ "schema",
25
+ "continue",
26
+ "undo",
27
+ "abort",
28
+ "mcp",
19
29
  "completion",
20
30
  "status",
21
31
  "help",
@@ -66,9 +76,9 @@ _gflows() {
66
76
  fi
67
77
  done
68
78
  if [[ -n "$path" ]]; then
69
- gflows -C "$path" list 2>/dev/null
79
+ gflows -C "$path" list -q 2>/dev/null
70
80
  else
71
- gflows list 2>/dev/null
81
+ gflows list -q 2>/dev/null
72
82
  fi
73
83
  }
74
84
 
@@ -145,7 +155,7 @@ _gflows_list_branches() {
145
155
  fi
146
156
  (( i++ ))
147
157
  done
148
- (( ${D}#path[@]} > 0 )) && gflows "${D}path[@]}" list 2>/dev/null || gflows list 2>/dev/null
158
+ (( ${D}#path[@]} > 0 )) && gflows "${D}path[@]}" list -q 2>/dev/null || gflows list -q 2>/dev/null
149
159
  }
150
160
 
151
161
  _gflows() {
@@ -231,7 +241,7 @@ function __gflows_list_branches
231
241
  if test -n "$path"
232
242
  gflows -C "$path" list 2>/dev/null
233
243
  else
234
- gflows list 2>/dev/null
244
+ gflows list -q 2>/dev/null
235
245
  end
236
246
  end
237
247