gflows 0.1.17 → 1.0.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.
Files changed (48) hide show
  1. package/AGENTS.md +78 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +279 -502
  4. package/llms.txt +22 -0
  5. package/package.json +30 -23
  6. package/src/cli.ts +21 -364
  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 -20
  16. package/src/commands/init.ts +99 -18
  17. package/src/commands/list.ts +6 -1
  18. package/src/commands/mcp.ts +238 -0
  19. package/src/commands/pr.ts +120 -0
  20. package/src/commands/schema.ts +98 -0
  21. package/src/commands/start.ts +33 -31
  22. package/src/commands/status.ts +70 -51
  23. package/src/commands/switch.ts +50 -20
  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 +343 -0
  41. package/src/tui/HubShell.tsx +186 -0
  42. package/src/tui/flows.tsx +140 -0
  43. package/src/tui/hub.ts +89 -0
  44. package/src/tui/prompts.tsx +207 -0
  45. package/src/types.ts +63 -4
  46. package/src/ui.ts +132 -0
  47. package/src/version.ts +24 -0
  48. 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,43 +1,50 @@
1
1
  {
2
2
  "name": "gflows",
3
- "version": "0.1.17",
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.0",
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",
14
35
  "lint": "biome check .",
15
36
  "lint:fix": "biome check . --write",
16
37
  "format": "biome format . --write",
38
+ "check": "bun run test && bun run typecheck && bun run lint",
17
39
  "gflows": "bun run src/cli.ts",
18
40
  "publish:all": "bun run scripts/publish.ts",
19
41
  "publish:npm": "bun run scripts/publish.ts -- --npm-only",
20
42
  "publish:jsr": "bun run scripts/publish.ts -- --jsr-only"
21
43
  },
22
- "files": [
23
- "src",
24
- "README.md",
25
- "LICENSE"
26
- ],
27
- "engines": {
28
- "bun": ">=1.0.0"
29
- },
30
- "author": "Ali AlNaghmoush (https://github.com/alialnaghmoush)",
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/alialnaghmoush/gflows.git"
34
- },
44
+ "type": "module",
35
45
  "dependencies": {
36
- "@inquirer/prompts": "^8"
37
- },
38
- "devDependencies": {
39
- "@biomejs/biome": "^2.4.4",
40
- "@types/bun": "latest",
41
- "typescript": "^5"
46
+ "@clack/prompts": "^1.7.0",
47
+ "ink": "^7.1.1",
48
+ "react": "^19.2.8"
42
49
  }
43
50
  }
package/src/cli.ts CHANGED
@@ -6,402 +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)
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
- },
104
- };
105
- }
106
-
107
- /** Resolve -C/--path to absolute directory; validate it exists and is a directory. */
108
- function resolveCwd(pathFlag: string | undefined): string {
109
- if (!pathFlag || pathFlag.trim() === "") {
110
- return process.cwd();
111
- }
112
- const absolute = resolve(process.cwd(), pathFlag.trim());
113
- if (!existsSync(absolute)) {
114
- console.error(`gflows: path does not exist: ${absolute}`);
115
- process.exit(EXIT_USER);
116
- }
117
- const stat = statSync(absolute, { throwIfNoEntry: false });
118
- if (!stat || !stat.isDirectory()) {
119
- console.error(`gflows: path is not a directory: ${absolute}`);
120
- process.exit(EXIT_USER);
121
- }
122
- return absolute;
123
- }
124
-
125
- /**
126
- * Returns the command name closest to `input` by edit distance, or undefined if no close match.
127
- * Used for "did you mean?" when the user mistypes a command.
128
- */
129
- function closestCommand(input: string): Command | undefined {
130
- if (!input || input.length < 2) return undefined;
131
- const target = input.toLowerCase();
132
- let best: Command | undefined;
133
- let bestDistance = 3; // only suggest if within 2 edits
134
- for (const cmd of COMMANDS) {
135
- const d = editDistance(target, cmd);
136
- if (d < bestDistance) {
137
- bestDistance = d;
138
- best = cmd as Command;
139
- }
140
- }
141
- return best;
142
- }
143
-
144
- /** Levenshtein edit distance between two strings. */
145
- function editDistance(a: string, b: string): number {
146
- const m = a.length;
147
- const n = b.length;
148
- const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
149
- for (let i = 0; i <= m; i++) {
150
- const row = dp[i];
151
- if (row) row[0] = i;
152
- }
153
- for (let j = 0; j <= n; j++) {
154
- const row = dp[0];
155
- if (row) row[j] = j;
156
- }
157
- for (let i = 1; i <= m; i++) {
158
- for (let j = 1; j <= n; j++) {
159
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
160
- const v1 = dp[i - 1]?.[j] ?? 0;
161
- const v2 = dp[i]?.[j - 1] ?? 0;
162
- const v3 = dp[i - 1]?.[j - 1] ?? 0;
163
- const rowI = dp[i];
164
- if (rowI) rowI[j] = Math.min(v1 + 1, v2 + 1, v3 + cost);
165
- }
166
- }
167
- return dp[m]?.[n] ?? 0;
168
- }
169
-
170
- /** Resolve command from positionals and short flags. Short wins if both present. */
171
- function resolveCommand(
172
- positionals: string[],
173
- values: Record<string, string | boolean | undefined>,
174
- ): Command | undefined {
175
- for (const [_short, cmd] of Object.entries(SHORT_TO_COMMAND)) {
176
- const key = cmd === "delete" ? "delete" : cmd;
177
- if (values[key] === true) return cmd as Command;
178
- }
179
- const first = positionals[0];
180
- if (first && COMMANDS.includes(first as Command)) {
181
- return first as Command;
182
- }
183
- return undefined;
184
- }
185
-
186
- /** Resolve branch type for start/finish/list. -r means release for start/finish; for list, -r means include-remote (handled in flags). */
187
- function resolveType(
188
- command: Command,
189
- positionals: string[],
190
- values: Record<string, string | boolean | undefined>,
191
- ): BranchType | undefined {
192
- if (command !== "start" && command !== "finish" && command !== "list") {
193
- return undefined;
194
- }
195
- // Type from short flags (for start/finish, -r => release)
196
- if (command === "list") {
197
- // For list, -r is include-remote only (see includeRemote below); type from other shorts or positional (not -r)
198
- if (values.feature === true) return "feature";
199
- if (values.bugfix === true) return "bugfix";
200
- if (values.chore === true) return "chore";
201
- if (values.hotfix === true) return "hotfix";
202
- if (values.spike === true) return "spike";
203
- // release type for list only via positional, e.g. "gflows list release"
204
- } else {
205
- // start / finish: -r => release
206
- if (values.release === true) return "release";
207
- if (values.feature === true) return "feature";
208
- if (values.bugfix === true) return "bugfix";
209
- if (values.chore === true) return "chore";
210
- if (values.hotfix === true) return "hotfix";
211
- if (values.spike === true) return "spike";
212
- }
213
- // Type from second positional
214
- const idx = positionals[0] && COMMANDS.includes(positionals[0] as Command) ? 1 : 0;
215
- const pos = positionals[idx];
216
- if (pos && BRANCH_TYPES.includes(pos as BranchType)) {
217
- return pos as BranchType;
218
- }
219
- return undefined;
220
- }
221
-
222
- /** Resolve name (third positional for start; -B for finish; first positional for completion). */
223
- function resolveName(
224
- command: Command,
225
- positionals: string[],
226
- values: Record<string, string | boolean | undefined>,
227
- ): string | undefined {
228
- const branch = values.branch;
229
- if (typeof branch === "string" && branch.trim() !== "") {
230
- return branch.trim();
231
- }
232
- const skip = positionals[0] && COMMANDS.includes(positionals[0] as Command) ? 1 : 0;
233
- if (command === "start") {
234
- const typeFromPos = positionals[skip] && BRANCH_TYPES.includes(positionals[skip] as BranchType);
235
- if (typeFromPos) {
236
- return positionals[skip + 1];
237
- }
238
- return positionals[skip];
239
- }
240
- if (command === "completion") {
241
- const shell = positionals[skip];
242
- if (shell === "bash" || shell === "zsh" || shell === "fish") return shell;
243
- return undefined;
244
- }
245
- if (command === "bump") {
246
- const dir = positionals[skip];
247
- const _typ = positionals[skip + 1];
248
- if (dir === "up" || dir === "down") {
249
- return dir;
250
- }
251
- return undefined;
252
- }
253
- if (command === "switch") {
254
- return positionals[skip];
255
- }
256
- return undefined;
257
- }
258
-
259
- /** Resolve bump direction and type from positionals (bump [up|down] [patch|minor|major]). */
260
- function resolveBump(
261
- positionals: string[],
262
- _values: Record<string, string | boolean | undefined>,
263
- ): { direction?: "up" | "down"; type?: "patch" | "minor" | "major" } {
264
- const skip = positionals[0] === "bump" ? 1 : 0;
265
- const a = positionals[skip];
266
- const b = positionals[skip + 1];
267
- const direction = a === "up" || a === "down" ? a : undefined;
268
- const type = b === "patch" || b === "minor" || b === "major" ? b : undefined;
269
- return { direction, type };
270
- }
271
-
272
- /** Parse raw argv into ParsedArgs. Resolves -C first, then command/type/name and flags. */
273
- export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
274
- const config = {
275
- ...buildParseArgsOptions(),
276
- args: argv,
277
- };
278
- const { values, positionals } = parseArgs(config);
279
- const v = values as Record<string, string | boolean | undefined>;
280
-
281
- const pathRaw = v.path;
282
- const pathStr = typeof pathRaw === "string" ? pathRaw : undefined;
283
- const cwd = resolveCwd(pathStr);
284
-
285
- const command = resolveCommand(positionals, v);
286
- if (!command) {
287
- const first = positionals[0];
288
- const suggestion = typeof first === "string" ? closestCommand(first) : undefined;
289
- if (suggestion) {
290
- console.error(`gflows: unknown command '${first}'. Did you mean '${suggestion}'?`);
291
- } else {
292
- 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;
293
29
  }
30
+ console.error("gflows: missing command. Use 'gflows help' for usage.");
294
31
  process.exit(EXIT_USER);
295
32
  }
296
33
 
297
- const type = resolveType(command, positionals, v);
298
- const name = resolveName(command, positionals, v);
299
- const { direction: bumpDirection, type: bumpType } =
300
- command === "bump" ? resolveBump(positionals, v) : { direction: undefined, type: undefined };
301
-
302
- const branchNames =
303
- command === "delete"
304
- ? positionals[0] && COMMANDS.includes(positionals[0] as Command)
305
- ? positionals.slice(1)
306
- : positionals
307
- : undefined;
308
-
309
- // -r context: for list → includeRemote; for start/finish → already used as type release
310
- const includeRemote =
311
- command === "list"
312
- ? v.includeRemote === true || v["include-remote"] === true || v.release === true
313
- : false;
314
-
315
- let completionShell: "bash" | "zsh" | "fish" | undefined;
316
- if (command === "completion" && name === "bash") completionShell = "bash";
317
- else if (command === "completion" && name === "zsh") completionShell = "zsh";
318
- else if (command === "completion" && name === "fish") completionShell = "fish";
319
-
320
- let switchMode: "restore" | "clean" | "cancel" | "move" | undefined;
321
- if (command === "switch") {
322
- const restore = v.restore === true;
323
- const clean = v.clean === true;
324
- const cancel = v.cancel === true;
325
- const move = v.move === true;
326
- const count = [restore, clean, cancel, move].filter(Boolean).length;
327
- if (count > 1) {
328
- console.error(
329
- "gflows switch: only one of --restore, --clean, --cancel, or --move may be used at a time.",
330
- );
331
- process.exit(EXIT_USER);
332
- }
333
- if (restore) switchMode = "restore";
334
- else if (clean) switchMode = "clean";
335
- else if (cancel) switchMode = "cancel";
336
- else if (move) switchMode = "move";
337
- }
338
-
339
- return {
340
- command,
341
- cwd,
342
- type,
343
- name,
344
- completionShell,
345
- branchNames,
346
- bumpDirection,
347
- bumpType,
348
- push: v.push === true,
349
- noPush: v.noPush === true || v["no-push"] === true,
350
- main: typeof v.main === "string" && v.main.trim() !== "" ? v.main.trim() : undefined,
351
- dev: typeof v.dev === "string" && v.dev.trim() !== "" ? v.dev.trim() : undefined,
352
- remote: typeof v.remote === "string" ? v.remote : undefined,
353
- branch: typeof v.branch === "string" ? v.branch : undefined,
354
- yes: v.yes === true,
355
- dryRun: v.dryRun === true,
356
- verbose: v.verbose === true,
357
- quiet: v.quiet === true,
358
- force: v.force === true,
359
- path: pathStr,
360
- fromMain: v.from === "main",
361
- noFf: v.noFf === true,
362
- deleteAfterFinish: v.deleteBranch === true,
363
- noDeleteAfterFinish: v.noDelete === true,
364
- signTag: v.sign === true,
365
- noTag: v.noTag === true,
366
- tagMessage: typeof v.tagMessage === "string" ? v.tagMessage : undefined,
367
- message: typeof v.message === "string" ? v.message : undefined,
368
- includeRemote,
369
- switchMode,
370
- };
371
- }
372
-
373
- /** Run the CLI: parse, dispatch, set exit code. */
374
- async function run(): Promise<void> {
375
34
  const args = parse();
376
35
  lastParsedArgs = args;
377
- const { command } = args;
378
36
 
379
- if (command === "help") {
37
+ if (args.command === "help") {
380
38
  const { run: runHelp } = await import("./commands/help.js");
381
39
  await runHelp(args);
382
40
  return;
383
41
  }
384
- if (command === "version") {
42
+ if (args.command === "version") {
385
43
  const { run: runVersion } = await import("./commands/version.js");
386
44
  await runVersion(args);
387
45
  return;
388
46
  }
389
47
 
390
- const mod = await import(`./commands/${command}.js`).catch(() => null);
48
+ const mod = await import(`./commands/${args.command}.js`).catch(() => null);
391
49
  if (!mod || typeof mod.run !== "function") {
392
- console.error(`gflows: command '${command}' is not implemented.`);
50
+ console.error(`gflows: command '${args.command}' is not implemented.`);
393
51
  process.exit(EXIT_GIT);
394
52
  }
395
53
  await mod.run(args);
396
54
  }
397
55
 
398
- // Top-level try/catch and unhandledRejection so exit code is always set
399
56
  function main(): void {
400
57
  let exitCode: number | null = null;
401
58
 
402
59
  const handleRejection = (reason: unknown): void => {
403
60
  if (exitCode !== null) return;
404
- console.error("gflows:", reason instanceof Error ? reason.message : String(reason));
61
+ printError(reason);
405
62
  const verbose = lastParsedArgs?.verbose ?? !!process.env.GFLOWS_VERBOSE;
406
63
  if (verbose && reason instanceof Error && reason.stack) {
407
64
  console.error(reason.stack);
@@ -419,7 +76,7 @@ function main(): void {
419
76
  })
420
77
  .catch((err: unknown) => {
421
78
  if (exitCode !== null) return;
422
- console.error("gflows:", err instanceof Error ? err.message : String(err));
79
+ printError(err);
423
80
  const verbose = lastParsedArgs?.verbose ?? !!process.env.GFLOWS_VERBOSE;
424
81
  if (verbose && err instanceof Error && err.stack) {
425
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