@alejandrochaves/devflow-cli 1.3.2 → 1.5.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 (63) hide show
  1. package/dist/commands/amend.d.ts.map +1 -1
  2. package/dist/commands/amend.js +139 -68
  3. package/dist/commands/amend.js.map +1 -1
  4. package/dist/commands/branch.d.ts.map +1 -1
  5. package/dist/commands/branch.js +267 -103
  6. package/dist/commands/branch.js.map +1 -1
  7. package/dist/commands/changelog.d.ts.map +1 -1
  8. package/dist/commands/changelog.js +62 -32
  9. package/dist/commands/changelog.js.map +1 -1
  10. package/dist/commands/cleanup.d.ts.map +1 -1
  11. package/dist/commands/cleanup.js +10 -3
  12. package/dist/commands/cleanup.js.map +1 -1
  13. package/dist/commands/commit.d.ts.map +1 -1
  14. package/dist/commands/commit.js +343 -175
  15. package/dist/commands/commit.js.map +1 -1
  16. package/dist/commands/fixup.d.ts.map +1 -1
  17. package/dist/commands/fixup.js +129 -81
  18. package/dist/commands/fixup.js.map +1 -1
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js +456 -110
  21. package/dist/commands/init.js.map +1 -1
  22. package/dist/commands/issue.d.ts.map +1 -1
  23. package/dist/commands/issue.js +353 -85
  24. package/dist/commands/issue.js.map +1 -1
  25. package/dist/commands/issues.d.ts.map +1 -1
  26. package/dist/commands/issues.js +166 -77
  27. package/dist/commands/issues.js.map +1 -1
  28. package/dist/commands/log.d.ts.map +1 -1
  29. package/dist/commands/log.js +81 -46
  30. package/dist/commands/log.js.map +1 -1
  31. package/dist/commands/merge.d.ts.map +1 -1
  32. package/dist/commands/merge.js +78 -38
  33. package/dist/commands/merge.js.map +1 -1
  34. package/dist/commands/pr.d.ts.map +1 -1
  35. package/dist/commands/pr.js +110 -42
  36. package/dist/commands/pr.js.map +1 -1
  37. package/dist/commands/release.d.ts.map +1 -1
  38. package/dist/commands/release.js +70 -37
  39. package/dist/commands/release.js.map +1 -1
  40. package/dist/commands/review.d.ts.map +1 -1
  41. package/dist/commands/review.js +144 -84
  42. package/dist/commands/review.js.map +1 -1
  43. package/dist/commands/stash.d.ts.map +1 -1
  44. package/dist/commands/stash.js +164 -94
  45. package/dist/commands/stash.js.map +1 -1
  46. package/dist/commands/test-plan.d.ts.map +1 -1
  47. package/dist/commands/test-plan.js +86 -42
  48. package/dist/commands/test-plan.js.map +1 -1
  49. package/dist/commands/update.d.ts.map +1 -1
  50. package/dist/commands/update.js +9 -3
  51. package/dist/commands/update.js.map +1 -1
  52. package/dist/commands/worktree.d.ts.map +1 -1
  53. package/dist/commands/worktree.js +168 -96
  54. package/dist/commands/worktree.js.map +1 -1
  55. package/dist/prompts.d.ts +62 -0
  56. package/dist/prompts.d.ts.map +1 -0
  57. package/dist/prompts.js +130 -0
  58. package/dist/prompts.js.map +1 -0
  59. package/dist/providers/tickets.d.ts +7 -0
  60. package/dist/providers/tickets.d.ts.map +1 -1
  61. package/dist/providers/tickets.js +29 -2
  62. package/dist/providers/tickets.js.map +1 -1
  63. package/package.json +3 -2
@@ -4,6 +4,7 @@ import { confirm, input, select } from "@inquirer/prompts";
4
4
  import { execSync } from "child_process";
5
5
  import { PRESETS } from "../config.js";
6
6
  import { writeVersionInfo, getCliVersion } from "../devflow-version.js";
7
+ import { selectWithBack, inputWithBack, confirmWithBack, BACK_VALUE, } from "../prompts.js";
7
8
  const DEFAULT_CHECKLIST = [
8
9
  "Code follows project conventions",
9
10
  "Self-reviewed the changes",
@@ -42,6 +43,95 @@ function getGitHubIssuesUrl() {
42
43
  }
43
44
  return undefined;
44
45
  }
46
+ function getGitHubRepo() {
47
+ try {
48
+ const remoteUrl = execSync("git remote get-url origin", {
49
+ encoding: "utf-8",
50
+ stdio: ["pipe", "pipe", "pipe"],
51
+ }).trim();
52
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
53
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
54
+ const match = sshMatch || httpsMatch;
55
+ if (match) {
56
+ return { owner: match[1], name: match[2] };
57
+ }
58
+ }
59
+ catch {
60
+ // Not a git repo or no remote
61
+ }
62
+ return undefined;
63
+ }
64
+ function hasProjectScopes() {
65
+ try {
66
+ // Try to list projects - if it fails with scope error, we don't have permissions
67
+ execSync("gh project list --owner @me --limit 1", {
68
+ encoding: "utf-8",
69
+ stdio: ["pipe", "pipe", "pipe"],
70
+ });
71
+ return true;
72
+ }
73
+ catch (error) {
74
+ const errorMessage = error.message || "";
75
+ return !errorMessage.includes("missing required scopes") && !errorMessage.includes("read:project");
76
+ }
77
+ }
78
+ function getRepoLinkedProjects(repo) {
79
+ try {
80
+ const query = `{
81
+ repository(owner: "${repo.owner}", name: "${repo.name}") {
82
+ projectsV2(first: 20) {
83
+ nodes { number title }
84
+ }
85
+ }
86
+ }`;
87
+ const result = execSync(`gh api graphql -f query='${query}'`, {
88
+ encoding: "utf-8",
89
+ stdio: ["pipe", "pipe", "pipe"],
90
+ }).trim();
91
+ if (!result)
92
+ return [];
93
+ const data = JSON.parse(result);
94
+ const nodes = data?.data?.repository?.projectsV2?.nodes || [];
95
+ return nodes.map((p) => ({
96
+ number: p.number,
97
+ title: p.title,
98
+ }));
99
+ }
100
+ catch {
101
+ return [];
102
+ }
103
+ }
104
+ function refreshProjectScopes() {
105
+ try {
106
+ execSync("gh auth refresh -s project", {
107
+ stdio: "inherit",
108
+ });
109
+ return true;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ function createAndLinkProject(repo, title) {
116
+ try {
117
+ // Create the project
118
+ const createResult = execSync(`gh project create --owner ${repo.owner} --title "${title}" --format json`, {
119
+ encoding: "utf-8",
120
+ stdio: ["pipe", "pipe", "pipe"],
121
+ }).trim();
122
+ const project = JSON.parse(createResult);
123
+ const projectNumber = project.number;
124
+ // Link it to the repo
125
+ execSync(`gh project link ${projectNumber} --owner ${repo.owner} --repo ${repo.name}`, {
126
+ encoding: "utf-8",
127
+ stdio: ["pipe", "pipe", "pipe"],
128
+ });
129
+ return { number: projectNumber, title };
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
45
135
  function writePackageJson(cwd, pkg) {
46
136
  const pkgPath = resolve(cwd, "package.json");
47
137
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
@@ -52,133 +142,376 @@ export async function initCommand() {
52
142
  const devflowDir = resolve(cwd, ".devflow");
53
143
  const configPath = resolve(devflowDir, "config.json");
54
144
  if (existsSync(configPath)) {
55
- const overwrite = await confirm({
145
+ const overwrite = await confirmWithBack({
56
146
  message: "Devflow config already exists. Overwrite?",
57
147
  default: false,
148
+ showBack: false,
58
149
  });
59
- if (!overwrite) {
150
+ if (overwrite !== true) {
60
151
  console.log("Aborted.");
61
152
  process.exit(0);
62
153
  }
63
154
  }
64
155
  console.log("\n devflow setup\n");
65
- // 0. Preset selection
66
- const preset = await select({
67
- message: "Select a workflow preset:",
68
- choices: [
69
- { value: "scrum", name: "Scrum - User stories, sprints, acceptance criteria" },
70
- { value: "kanban", name: "Kanban - Simple flow-based workflow" },
71
- { value: "simple", name: "Simple - Minimal configuration, no ticket required" },
72
- { value: "custom", name: "Custom - Configure everything manually" },
73
- ],
74
- });
75
- const presetConfig = PRESETS[preset];
76
- console.log("");
77
- // 1. Ticket base URL (skip for simple preset since no tickets)
78
- let ticketBaseUrl = "";
79
- let useGitHubIssues = false;
80
- if (preset !== "simple") {
81
- const detectedIssuesUrl = getGitHubIssuesUrl();
82
- ticketBaseUrl = await input({
83
- message: "Ticket base URL (e.g., https://github.com/org/repo/issues):",
84
- default: detectedIssuesUrl,
85
- });
86
- // 1b. GitHub Issues integration
87
- useGitHubIssues = await confirm({
88
- message: "Use GitHub Issues for ticket tracking? (enables issue picker in branch command)",
89
- default: ticketBaseUrl.includes("github.com"),
90
- });
91
- }
92
- // 2. Scopes
93
- const scopes = [];
94
- const scopeChoice = await select({
95
- message: "How would you like to configure commit scopes?",
96
- choices: [
97
- { value: "defaults", name: "Use defaults (core, ui, api, config, deps, ci)" },
98
- { value: "custom", name: "Define custom scopes" },
99
- { value: "skip", name: "Skip - configure later" },
100
- ],
101
- });
102
- if (scopeChoice === "defaults") {
103
- scopes.push({ value: "core", description: "Core functionality" }, { value: "ui", description: "UI components" }, { value: "api", description: "API layer" }, { value: "config", description: "Configuration" }, { value: "deps", description: "Dependencies" }, { value: "ci", description: "CI/CD" });
104
- }
105
- else if (scopeChoice === "custom") {
106
- console.log("\nAdd scopes one at a time. Press Enter with empty name to finish.\n");
107
- let addingScopes = true;
108
- while (addingScopes) {
109
- const value = await input({
110
- message: `Scope name${scopes.length > 0 ? " (blank to finish)" : ""}:`,
111
- });
112
- if (!value.trim()) {
113
- addingScopes = false;
156
+ const state = {
157
+ preset: "scrum",
158
+ ticketBaseUrl: "",
159
+ useGitHubIssues: false,
160
+ projectEnabled: false,
161
+ projectNumber: null,
162
+ scopeChoice: "",
163
+ scopes: [],
164
+ checklistChoice: "",
165
+ checklist: [...DEFAULT_CHECKLIST],
166
+ customizeFormats: false,
167
+ branchFormat: "",
168
+ commitFormat: "{type}[{ticket}]{breaking}({scope}): {message}",
169
+ };
170
+ let currentStep = "preset";
171
+ while (currentStep !== "done") {
172
+ switch (currentStep) {
173
+ case "preset": {
174
+ const result = await selectWithBack({
175
+ message: "Select a workflow preset:",
176
+ choices: [
177
+ { value: "scrum", name: "Scrum - User stories, sprints, acceptance criteria" },
178
+ { value: "kanban", name: "Kanban - Simple flow-based workflow" },
179
+ { value: "simple", name: "Simple - Minimal configuration, no ticket required" },
180
+ { value: "custom", name: "Custom - Configure everything manually" },
181
+ ],
182
+ default: state.preset,
183
+ showBack: false, // First step
184
+ });
185
+ if (result === BACK_VALUE) {
186
+ // Can't go back from first step
187
+ }
188
+ else {
189
+ state.preset = result;
190
+ state.branchFormat = PRESETS[state.preset].branchFormat;
191
+ console.log("");
192
+ // Skip ticket URL for simple preset
193
+ currentStep = state.preset === "simple" ? "scopes" : "ticketUrl";
194
+ }
195
+ break;
114
196
  }
115
- else {
116
- const description = await input({
117
- message: `Description for "${value.trim()}":`,
197
+ case "ticketUrl": {
198
+ const detectedIssuesUrl = getGitHubIssuesUrl();
199
+ const result = await inputWithBack({
200
+ message: "Ticket base URL (e.g., https://github.com/org/repo/issues):",
201
+ default: state.ticketBaseUrl || detectedIssuesUrl,
202
+ showBack: true,
118
203
  });
119
- scopes.push({
120
- value: value.trim().toLowerCase(),
121
- description: description.trim() || value.trim(),
204
+ if (result === BACK_VALUE) {
205
+ currentStep = "preset";
206
+ }
207
+ else {
208
+ state.ticketBaseUrl = result;
209
+ currentStep = "githubIssues";
210
+ }
211
+ break;
212
+ }
213
+ case "githubIssues": {
214
+ const result = await confirmWithBack({
215
+ message: "Use GitHub Issues for ticket tracking? (enables issue picker in branch command)",
216
+ default: state.useGitHubIssues || state.ticketBaseUrl.includes("github.com"),
217
+ showBack: true,
122
218
  });
219
+ if (result === BACK_VALUE) {
220
+ currentStep = "ticketUrl";
221
+ }
222
+ else {
223
+ state.useGitHubIssues = result === true;
224
+ // If using GitHub issues, offer project integration
225
+ currentStep = result === true ? "projectSetup" : "scopes";
226
+ }
227
+ break;
123
228
  }
124
- }
125
- }
126
- // 3. Checklist (shown in PR descriptions)
127
- let checklist = [...DEFAULT_CHECKLIST];
128
- const checklistChoice = await select({
129
- message: "PR checklist items (shown in pull request descriptions):",
130
- choices: [
131
- {
132
- value: "defaults",
133
- name: `Use defaults (${DEFAULT_CHECKLIST.length} items: conventions, self-review, no warnings)`,
134
- },
135
- { value: "custom", name: "Define custom checklist" },
136
- { value: "skip", name: "Skip - no checklist" },
137
- ],
138
- });
139
- if (checklistChoice === "custom") {
140
- console.log("\nAdd checklist items one at a time. Press Enter with empty text to finish.\n");
141
- checklist = [];
142
- let addingChecklist = true;
143
- while (addingChecklist) {
144
- const item = await input({
145
- message: `Checklist item${checklist.length > 0 ? " (blank to finish)" : ""}:`,
146
- });
147
- if (!item.trim()) {
148
- if (checklist.length === 0) {
149
- checklist = [...DEFAULT_CHECKLIST];
150
- console.log("Using default checklist.");
229
+ case "projectSetup": {
230
+ // Check if we have project scopes
231
+ const hasScopes = hasProjectScopes();
232
+ if (!hasScopes) {
233
+ console.log("\n GitHub Projects Integration\n");
234
+ console.log(" Connecting to a GitHub Project enables:");
235
+ console.log(" • Auto-move issues to 'In Progress' when you start a branch");
236
+ console.log(" • Auto-move issues to 'In Review' when you open a PR");
237
+ console.log(" • View project issues with `devflow issues`\n");
238
+ console.log(" This requires the 'project' OAuth scope for your GitHub CLI.");
239
+ console.log(" If you skip this, you can still use GitHub Issues without board automation.\n");
240
+ const addScopes = await confirmWithBack({
241
+ message: "Authorize project access?",
242
+ default: true,
243
+ showBack: true,
244
+ });
245
+ if (addScopes === BACK_VALUE) {
246
+ currentStep = "githubIssues";
247
+ break;
248
+ }
249
+ if (addScopes === true) {
250
+ console.log("\nOpening GitHub to authorize project permissions...\n");
251
+ const success = refreshProjectScopes();
252
+ if (!success) {
253
+ console.log("⚠ Failed to add project permissions. Skipping project integration.");
254
+ currentStep = "scopes";
255
+ break;
256
+ }
257
+ console.log("✓ Project permissions added\n");
258
+ }
259
+ else {
260
+ // Skip project integration
261
+ state.projectEnabled = false;
262
+ currentStep = "scopes";
263
+ break;
264
+ }
265
+ }
266
+ // Now we have scopes, list available projects
267
+ const repo = getGitHubRepo();
268
+ if (!repo) {
269
+ console.log("⚠ Could not determine repository. Skipping project integration.");
270
+ currentStep = "scopes";
271
+ break;
272
+ }
273
+ // First, try to get projects linked to this repo
274
+ const linkedProjects = getRepoLinkedProjects(repo);
275
+ if (linkedProjects.length > 0) {
276
+ // Show only linked projects
277
+ const projectResult = await selectWithBack({
278
+ message: "Select a GitHub Project for issue tracking:",
279
+ choices: [
280
+ ...linkedProjects.map((p) => ({
281
+ value: p.number,
282
+ name: `#${p.number} ${p.title}`,
283
+ })),
284
+ { value: -1, name: "Skip - don't use project integration" },
285
+ ],
286
+ showBack: true,
287
+ });
288
+ if (projectResult === BACK_VALUE) {
289
+ currentStep = "githubIssues";
290
+ }
291
+ else if (projectResult === -1) {
292
+ state.projectEnabled = false;
293
+ currentStep = "scopes";
294
+ }
295
+ else {
296
+ state.projectEnabled = true;
297
+ state.projectNumber = projectResult;
298
+ console.log(`✓ Will use project #${projectResult}\n`);
299
+ currentStep = "scopes";
300
+ }
301
+ break;
151
302
  }
152
- addingChecklist = false;
303
+ // No linked projects - offer to create one
304
+ console.log(`\n No GitHub Projects are linked to ${repo.owner}/${repo.name}.`);
305
+ const createChoice = await selectWithBack({
306
+ message: "What would you like to do?",
307
+ choices: [
308
+ { value: "create", name: "Create a new project" },
309
+ { value: "skip", name: "Skip project integration for now" },
310
+ ],
311
+ showBack: true,
312
+ });
313
+ if (createChoice === BACK_VALUE) {
314
+ currentStep = "githubIssues";
315
+ break;
316
+ }
317
+ if (createChoice === "skip") {
318
+ state.projectEnabled = false;
319
+ currentStep = "scopes";
320
+ break;
321
+ }
322
+ // Ask for project name
323
+ const defaultProjectName = `${repo.name} Board`;
324
+ const projectName = await inputWithBack({
325
+ message: "Project name:",
326
+ default: defaultProjectName,
327
+ showBack: true,
328
+ });
329
+ if (projectName === BACK_VALUE) {
330
+ // Go back to the create/skip choice - stay on this step
331
+ break;
332
+ }
333
+ // Create and link a new project
334
+ console.log(`\nCreating project "${projectName}"...`);
335
+ const newProject = createAndLinkProject(repo, projectName);
336
+ if (newProject) {
337
+ console.log(`✓ Created and linked project #${newProject.number}\n`);
338
+ state.projectEnabled = true;
339
+ state.projectNumber = newProject.number;
340
+ }
341
+ else {
342
+ console.log("⚠ Failed to create project. Skipping project integration.");
343
+ state.projectEnabled = false;
344
+ }
345
+ currentStep = "scopes";
346
+ break;
153
347
  }
154
- else {
155
- checklist.push(item.trim());
348
+ case "scopes": {
349
+ const result = await selectWithBack({
350
+ message: "How would you like to configure commit scopes?",
351
+ choices: [
352
+ { value: "defaults", name: "Use defaults (core, ui, api, config, deps, ci)" },
353
+ { value: "custom", name: "Define custom scopes" },
354
+ { value: "skip", name: "Skip - configure later" },
355
+ ],
356
+ default: state.scopeChoice || undefined,
357
+ showBack: true,
358
+ });
359
+ if (result === BACK_VALUE) {
360
+ // Go back to appropriate step based on config
361
+ if (state.preset === "simple") {
362
+ currentStep = "preset";
363
+ }
364
+ else if (state.useGitHubIssues) {
365
+ currentStep = "projectSetup";
366
+ }
367
+ else {
368
+ currentStep = "githubIssues";
369
+ }
370
+ }
371
+ else {
372
+ state.scopeChoice = result;
373
+ state.scopes = [];
374
+ if (result === "defaults") {
375
+ state.scopes = [
376
+ { value: "core", description: "Core functionality" },
377
+ { value: "ui", description: "UI components" },
378
+ { value: "api", description: "API layer" },
379
+ { value: "config", description: "Configuration" },
380
+ { value: "deps", description: "Dependencies" },
381
+ { value: "ci", description: "CI/CD" },
382
+ ];
383
+ currentStep = "checklist";
384
+ }
385
+ else if (result === "custom") {
386
+ console.log("\nAdd scopes one at a time. Press Enter with empty name to finish.\n");
387
+ let addingScopes = true;
388
+ while (addingScopes) {
389
+ const scopeValue = await input({
390
+ message: `Scope name${state.scopes.length > 0 ? " (blank to finish)" : ""}:`,
391
+ });
392
+ if (!scopeValue.trim()) {
393
+ addingScopes = false;
394
+ }
395
+ else {
396
+ const description = await input({
397
+ message: `Description for "${scopeValue.trim()}":`,
398
+ });
399
+ state.scopes.push({
400
+ value: scopeValue.trim().toLowerCase(),
401
+ description: description.trim() || scopeValue.trim(),
402
+ });
403
+ }
404
+ }
405
+ currentStep = "checklist";
406
+ }
407
+ else {
408
+ currentStep = "checklist";
409
+ }
410
+ }
411
+ break;
412
+ }
413
+ case "checklist": {
414
+ const result = await selectWithBack({
415
+ message: "PR checklist items (shown in pull request descriptions):",
416
+ choices: [
417
+ {
418
+ value: "defaults",
419
+ name: `Use defaults (${DEFAULT_CHECKLIST.length} items: conventions, self-review, no warnings)`,
420
+ },
421
+ { value: "custom", name: "Define custom checklist" },
422
+ { value: "skip", name: "Skip - no checklist" },
423
+ ],
424
+ default: state.checklistChoice || undefined,
425
+ showBack: true,
426
+ });
427
+ if (result === BACK_VALUE) {
428
+ currentStep = "scopes";
429
+ }
430
+ else {
431
+ state.checklistChoice = result;
432
+ if (result === "custom") {
433
+ console.log("\nAdd checklist items one at a time. Press Enter with empty text to finish.\n");
434
+ state.checklist = [];
435
+ let addingChecklist = true;
436
+ while (addingChecklist) {
437
+ const item = await input({
438
+ message: `Checklist item${state.checklist.length > 0 ? " (blank to finish)" : ""}:`,
439
+ });
440
+ if (!item.trim()) {
441
+ if (state.checklist.length === 0) {
442
+ state.checklist = [...DEFAULT_CHECKLIST];
443
+ console.log("Using default checklist.");
444
+ }
445
+ addingChecklist = false;
446
+ }
447
+ else {
448
+ state.checklist.push(item.trim());
449
+ }
450
+ }
451
+ }
452
+ else if (result === "skip") {
453
+ state.checklist = [];
454
+ }
455
+ else {
456
+ state.checklist = [...DEFAULT_CHECKLIST];
457
+ }
458
+ currentStep = "formats";
459
+ }
460
+ break;
156
461
  }
462
+ case "formats": {
463
+ const result = await confirmWithBack({
464
+ message: "Customize branch/commit formats? (advanced)",
465
+ default: state.customizeFormats,
466
+ showBack: true,
467
+ });
468
+ if (result === BACK_VALUE) {
469
+ currentStep = "checklist";
470
+ }
471
+ else {
472
+ state.customizeFormats = result === true;
473
+ if (state.customizeFormats) {
474
+ console.log("\nAvailable placeholders:");
475
+ console.log(" Branch: {type}, {ticket}, {description}");
476
+ console.log(" Commit: {type}, {ticket}, {scope}, {message}, {breaking}\n");
477
+ const branchResult = await inputWithBack({
478
+ message: "Branch format:",
479
+ default: state.branchFormat,
480
+ showBack: true,
481
+ });
482
+ if (branchResult === BACK_VALUE) {
483
+ // Stay on formats step
484
+ break;
485
+ }
486
+ state.branchFormat = branchResult;
487
+ const commitResult = await inputWithBack({
488
+ message: "Commit format:",
489
+ default: state.commitFormat,
490
+ showBack: true,
491
+ });
492
+ if (commitResult === BACK_VALUE) {
493
+ // Stay on formats step
494
+ break;
495
+ }
496
+ state.commitFormat = commitResult;
497
+ }
498
+ currentStep = "done";
499
+ }
500
+ break;
501
+ }
502
+ default:
503
+ currentStep = "done";
157
504
  }
158
505
  }
159
- else if (checklistChoice === "skip") {
160
- checklist = [];
161
- }
162
- // 4. Format customization
163
- let branchFormat = presetConfig.branchFormat;
164
- let commitFormat = "{type}[{ticket}]{breaking}({scope}): {message}";
165
- const customizeFormats = await confirm({
166
- message: "Customize branch/commit formats? (advanced)",
167
- default: false,
168
- });
169
- if (customizeFormats) {
170
- console.log("\nAvailable placeholders:");
171
- console.log(" Branch: {type}, {ticket}, {description}");
172
- console.log(" Commit: {type}, {ticket}, {scope}, {message}, {breaking}\n");
173
- branchFormat = await input({
174
- message: "Branch format:",
175
- default: branchFormat,
176
- });
177
- commitFormat = await input({
178
- message: "Commit format:",
179
- default: commitFormat,
180
- });
181
- }
506
+ // Extract configuration from state
507
+ const preset = state.preset;
508
+ const presetConfig = PRESETS[preset];
509
+ const ticketBaseUrl = state.ticketBaseUrl;
510
+ const useGitHubIssues = state.useGitHubIssues;
511
+ const scopes = state.scopes;
512
+ const checklist = state.checklist;
513
+ const branchFormat = state.branchFormat || presetConfig.branchFormat;
514
+ const commitFormat = state.commitFormat;
182
515
  // 5. Commitlint setup (validates commit messages on git commit)
183
516
  const commitlintChoice = await select({
184
517
  message: "Set up commitlint? (rejects commits that don't match the format)",
@@ -416,6 +749,19 @@ devflow amend # If you need to add more changes
416
749
  if (useGitHubIssues) {
417
750
  config.ticketProvider = { type: "github" };
418
751
  }
752
+ if (state.projectEnabled && state.projectNumber) {
753
+ config.project = {
754
+ enabled: true,
755
+ number: state.projectNumber,
756
+ statusField: "Status",
757
+ statuses: {
758
+ todo: "Todo",
759
+ inProgress: "In Progress",
760
+ inReview: "In Review",
761
+ done: "Done",
762
+ },
763
+ };
764
+ }
419
765
  if (preset === "custom") {
420
766
  config.issueTypes = presetConfig.issueTypes;
421
767
  config.prTemplate = presetConfig.prTemplate;