claude-cli-advanced-starter-pack 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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +597 -0
  3. package/README.md +439 -0
  4. package/bin/gtask.js +282 -0
  5. package/bin/postinstall.js +53 -0
  6. package/package.json +69 -0
  7. package/src/agents/phase-dev-templates.js +1011 -0
  8. package/src/agents/templates.js +668 -0
  9. package/src/analysis/checklist-parser.js +414 -0
  10. package/src/analysis/codebase.js +481 -0
  11. package/src/cli/menu.js +958 -0
  12. package/src/commands/claude-audit.js +1482 -0
  13. package/src/commands/claude-settings.js +2243 -0
  14. package/src/commands/create-agent.js +681 -0
  15. package/src/commands/create-command.js +337 -0
  16. package/src/commands/create-hook.js +262 -0
  17. package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
  18. package/src/commands/create-phase-dev/documentation-generator.js +352 -0
  19. package/src/commands/create-phase-dev/post-completion.js +404 -0
  20. package/src/commands/create-phase-dev/scale-calculator.js +344 -0
  21. package/src/commands/create-phase-dev/wizard.js +492 -0
  22. package/src/commands/create-phase-dev.js +481 -0
  23. package/src/commands/create-skill.js +313 -0
  24. package/src/commands/create.js +446 -0
  25. package/src/commands/decompose.js +392 -0
  26. package/src/commands/detect-tech-stack.js +768 -0
  27. package/src/commands/explore-mcp/claude-md-updater.js +252 -0
  28. package/src/commands/explore-mcp/mcp-installer.js +346 -0
  29. package/src/commands/explore-mcp/mcp-registry.js +438 -0
  30. package/src/commands/explore-mcp.js +638 -0
  31. package/src/commands/gtask-init.js +641 -0
  32. package/src/commands/help.js +128 -0
  33. package/src/commands/init.js +1890 -0
  34. package/src/commands/install.js +250 -0
  35. package/src/commands/list.js +116 -0
  36. package/src/commands/roadmap.js +750 -0
  37. package/src/commands/setup-wizard.js +482 -0
  38. package/src/commands/setup.js +351 -0
  39. package/src/commands/sync.js +534 -0
  40. package/src/commands/test-run.js +456 -0
  41. package/src/commands/test-setup.js +456 -0
  42. package/src/commands/validate.js +67 -0
  43. package/src/config/tech-stack.defaults.json +182 -0
  44. package/src/config/tech-stack.schema.json +502 -0
  45. package/src/github/client.js +359 -0
  46. package/src/index.js +84 -0
  47. package/src/templates/claude-command.js +244 -0
  48. package/src/templates/issue-body.js +284 -0
  49. package/src/testing/config.js +411 -0
  50. package/src/utils/template-engine.js +398 -0
  51. package/src/utils/validate-templates.js +223 -0
  52. package/src/utils.js +396 -0
  53. package/templates/commands/ccasp-setup.template.md +113 -0
  54. package/templates/commands/context-audit.template.md +97 -0
  55. package/templates/commands/create-task-list.template.md +382 -0
  56. package/templates/commands/deploy-full.template.md +261 -0
  57. package/templates/commands/github-task-start.template.md +99 -0
  58. package/templates/commands/github-update.template.md +69 -0
  59. package/templates/commands/happy-start.template.md +117 -0
  60. package/templates/commands/phase-track.template.md +142 -0
  61. package/templates/commands/tunnel-start.template.md +127 -0
  62. package/templates/commands/tunnel-stop.template.md +106 -0
  63. package/templates/hooks/context-guardian.template.js +173 -0
  64. package/templates/hooks/deployment-orchestrator.template.js +219 -0
  65. package/templates/hooks/github-progress-hook.template.js +197 -0
  66. package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
  67. package/templates/hooks/phase-dev-enforcer.template.js +183 -0
@@ -0,0 +1,750 @@
1
+ /**
2
+ * Roadmap Integration Command
3
+ *
4
+ * Bridges /create-roadmap with GitHub Project Board:
5
+ * - Import: Create GitHub issues from ROADMAP.json projects
6
+ * - Sync: Update GitHub issues with project completion status
7
+ * - Create: Generate ROADMAP.json from existing GitHub issues
8
+ * - Status: Show sync status between local roadmap and GitHub
9
+ */
10
+
11
+ import chalk from 'chalk';
12
+ import ora from 'ora';
13
+ import inquirer from 'inquirer';
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
15
+ import { join, dirname, basename } from 'path';
16
+ import { execSync } from 'child_process';
17
+ import { showHeader } from '../cli/menu.js';
18
+ import { hasValidConfig, loadConfig } from '../utils.js';
19
+
20
+ /**
21
+ * Priority to label mapping
22
+ */
23
+ const PRIORITY_LABELS = {
24
+ CRITICAL: 'priority-critical',
25
+ HIGH: 'priority-high',
26
+ MEDIUM: 'priority-medium',
27
+ LOW: 'priority-low',
28
+ };
29
+
30
+ /**
31
+ * Run roadmap command
32
+ */
33
+ export async function runRoadmap(options = {}) {
34
+ const { subcommand } = options;
35
+
36
+ switch (subcommand) {
37
+ case 'import':
38
+ return await runRoadmapImport(options);
39
+ case 'sync':
40
+ return await runRoadmapSync(options);
41
+ case 'create':
42
+ return await runRoadmapCreate(options);
43
+ case 'status':
44
+ return await runRoadmapStatus(options);
45
+ default:
46
+ return await showRoadmapMenu();
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Show interactive roadmap menu
52
+ */
53
+ export async function showRoadmapMenu() {
54
+ showHeader('Roadmap Integration');
55
+
56
+ console.log(chalk.dim('Bridge your local roadmaps with GitHub Project Board'));
57
+ console.log('');
58
+
59
+ // Find existing roadmaps
60
+ const roadmaps = findRoadmaps();
61
+
62
+ if (roadmaps.length > 0) {
63
+ console.log(chalk.cyan('Found Roadmaps:'));
64
+ for (const rm of roadmaps) {
65
+ const syncStatus = rm.github_integrated ? chalk.green('✓ Synced') : chalk.yellow('○ Local only');
66
+ console.log(` ${syncStatus} ${rm.roadmap_name} (${rm.total_projects} projects)`);
67
+ }
68
+ console.log('');
69
+ }
70
+
71
+ const { action } = await inquirer.prompt([
72
+ {
73
+ type: 'list',
74
+ name: 'action',
75
+ message: 'What would you like to do?',
76
+ choices: [
77
+ { name: 'Import - Create GitHub issues from ROADMAP.json', value: 'import' },
78
+ { name: 'Sync - Update GitHub with project progress', value: 'sync' },
79
+ { name: 'Create - Generate ROADMAP.json from GitHub issues', value: 'create' },
80
+ { name: 'Status - Show sync status dashboard', value: 'status' },
81
+ new inquirer.Separator(),
82
+ { name: 'Back', value: 'back' },
83
+ ],
84
+ },
85
+ ]);
86
+
87
+ if (action === 'back') return;
88
+
89
+ switch (action) {
90
+ case 'import':
91
+ await runRoadmapImport({});
92
+ break;
93
+ case 'sync':
94
+ await runRoadmapSync({});
95
+ break;
96
+ case 'create':
97
+ await runRoadmapCreate({});
98
+ break;
99
+ case 'status':
100
+ await runRoadmapStatus({});
101
+ break;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Find all ROADMAP.json files in .claude/docs/
107
+ */
108
+ function findRoadmaps(cwd = process.cwd()) {
109
+ const roadmaps = [];
110
+ const docsDir = join(cwd, '.claude', 'docs');
111
+
112
+ if (!existsSync(docsDir)) return roadmaps;
113
+
114
+ try {
115
+ const dirs = readdirSync(docsDir, { withFileTypes: true });
116
+ for (const dir of dirs) {
117
+ if (dir.isDirectory()) {
118
+ const roadmapPath = join(docsDir, dir.name, 'ROADMAP.json');
119
+ if (existsSync(roadmapPath)) {
120
+ try {
121
+ const data = JSON.parse(readFileSync(roadmapPath, 'utf8'));
122
+ roadmaps.push({
123
+ ...data,
124
+ path: roadmapPath,
125
+ dir: dir.name,
126
+ });
127
+ } catch (e) {
128
+ // Skip invalid JSON
129
+ }
130
+ }
131
+ }
132
+ }
133
+ } catch (e) {
134
+ // Ignore errors
135
+ }
136
+
137
+ return roadmaps;
138
+ }
139
+
140
+ /**
141
+ * Import: Create GitHub issues from ROADMAP.json projects
142
+ */
143
+ async function runRoadmapImport(options) {
144
+ showHeader('Import Roadmap to GitHub');
145
+
146
+ // Check gh CLI
147
+ if (!checkGhCli()) return;
148
+
149
+ // Find or select roadmap
150
+ const roadmap = await selectRoadmap(options.file);
151
+ if (!roadmap) return;
152
+
153
+ console.log('');
154
+ console.log(chalk.cyan(`Roadmap: ${roadmap.roadmap_name}`));
155
+ console.log(chalk.dim(`Projects: ${roadmap.total_projects}`));
156
+ console.log(chalk.dim(`Goal: ${roadmap.primary_goal}`));
157
+ console.log('');
158
+
159
+ // Check which projects already have issues
160
+ const projectsToImport = [];
161
+ const projectsWithIssues = [];
162
+
163
+ for (const project of roadmap.projects) {
164
+ if (project.phase_dev_config?.github_issue_number) {
165
+ projectsWithIssues.push(project);
166
+ } else {
167
+ projectsToImport.push(project);
168
+ }
169
+ }
170
+
171
+ if (projectsToImport.length === 0) {
172
+ console.log(chalk.green('✓ All projects already have GitHub issues linked'));
173
+ return;
174
+ }
175
+
176
+ console.log(`${chalk.yellow(projectsToImport.length)} projects need GitHub issues`);
177
+ if (projectsWithIssues.length > 0) {
178
+ console.log(`${chalk.green(projectsWithIssues.length)} projects already linked`);
179
+ }
180
+ console.log('');
181
+
182
+ // Confirm
183
+ const { confirm } = await inquirer.prompt([
184
+ {
185
+ type: 'confirm',
186
+ name: 'confirm',
187
+ message: `Create ${projectsToImport.length} GitHub issues?`,
188
+ default: true,
189
+ },
190
+ ]);
191
+
192
+ if (!confirm) {
193
+ console.log(chalk.dim('Cancelled'));
194
+ return;
195
+ }
196
+
197
+ // Get repo info
198
+ const config = loadConfig();
199
+ const owner = config?.owner || await getRepoOwner();
200
+ const repo = config?.repo || await getRepoName();
201
+
202
+ if (!owner || !repo) {
203
+ console.log(chalk.red('Could not determine repository. Run `gtask setup` first.'));
204
+ return;
205
+ }
206
+
207
+ const spinner = ora('Creating GitHub issues...').start();
208
+ let created = 0;
209
+ let failed = 0;
210
+
211
+ for (const project of projectsToImport) {
212
+ try {
213
+ spinner.text = `Creating issue for: ${project.project_name}`;
214
+
215
+ // Build issue body
216
+ const body = buildIssueBody(project, roadmap);
217
+
218
+ // Build labels
219
+ const labels = buildLabels(project, roadmap);
220
+
221
+ // Create issue via gh CLI
222
+ const result = execSync(
223
+ `gh issue create --repo "${owner}/${repo}" --title "${escapeShell(project.project_name)}" --body "${escapeShell(body)}" --label "${labels.join(',')}"`,
224
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
225
+ );
226
+
227
+ // Extract issue number from URL
228
+ const issueUrl = result.trim();
229
+ const issueNumber = issueUrl.match(/\/issues\/(\d+)/)?.[1];
230
+
231
+ if (issueNumber) {
232
+ // Update project with issue number
233
+ project.phase_dev_config = project.phase_dev_config || {};
234
+ project.phase_dev_config.github_issue_number = parseInt(issueNumber);
235
+ project.phase_dev_config.github_issue_url = issueUrl;
236
+ created++;
237
+ }
238
+ } catch (e) {
239
+ failed++;
240
+ spinner.warn(`Failed to create issue for ${project.project_name}: ${e.message}`);
241
+ }
242
+ }
243
+
244
+ // Save updated roadmap
245
+ roadmap.github_integrated = true;
246
+ roadmap.last_github_sync = new Date().toISOString();
247
+ saveRoadmap(roadmap);
248
+
249
+ spinner.succeed(`Created ${created} GitHub issues${failed > 0 ? `, ${failed} failed` : ''}`);
250
+
251
+ // Show summary
252
+ console.log('');
253
+ console.log(chalk.green.bold('Import Complete'));
254
+ console.log('');
255
+ for (const project of roadmap.projects) {
256
+ if (project.phase_dev_config?.github_issue_number) {
257
+ console.log(` ${chalk.green('✓')} #${project.phase_dev_config.github_issue_number} - ${project.project_name}`);
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Sync: Update GitHub issues with project completion status
264
+ */
265
+ async function runRoadmapSync(options) {
266
+ showHeader('Sync Roadmap with GitHub');
267
+
268
+ if (!checkGhCli()) return;
269
+
270
+ const roadmap = await selectRoadmap(options.file);
271
+ if (!roadmap) return;
272
+
273
+ if (!roadmap.github_integrated) {
274
+ console.log(chalk.yellow('This roadmap has not been imported to GitHub yet.'));
275
+ const { doImport } = await inquirer.prompt([
276
+ {
277
+ type: 'confirm',
278
+ name: 'doImport',
279
+ message: 'Would you like to import it now?',
280
+ default: true,
281
+ },
282
+ ]);
283
+ if (doImport) {
284
+ await runRoadmapImport({ file: roadmap.path });
285
+ }
286
+ return;
287
+ }
288
+
289
+ const config = loadConfig();
290
+ const owner = config?.owner || await getRepoOwner();
291
+ const repo = config?.repo || await getRepoName();
292
+
293
+ const spinner = ora('Syncing with GitHub...').start();
294
+ let synced = 0;
295
+ let closed = 0;
296
+
297
+ for (const project of roadmap.projects) {
298
+ const issueNumber = project.phase_dev_config?.github_issue_number;
299
+ if (!issueNumber) continue;
300
+
301
+ try {
302
+ spinner.text = `Syncing: ${project.project_name}`;
303
+
304
+ // Calculate completion from PROGRESS.json if exists
305
+ const progressPath = project.phase_dev_config?.progress_json_path;
306
+ let completion = 0;
307
+ let completionDetails = '';
308
+
309
+ if (progressPath && existsSync(progressPath)) {
310
+ const progress = JSON.parse(readFileSync(progressPath, 'utf8'));
311
+ completion = progress.completion_percentage || 0;
312
+ completionDetails = `Phase ${progress.current_phase || 1}/${progress.total_phases || 1}`;
313
+ }
314
+
315
+ // Determine status based on project.status
316
+ const isCompleted = project.status === 'completed' || completion >= 100;
317
+
318
+ // Build comment
319
+ const comment = buildSyncComment(project, completion, completionDetails, roadmap);
320
+
321
+ // Post comment to issue
322
+ execSync(
323
+ `gh issue comment ${issueNumber} --repo "${owner}/${repo}" --body "${escapeShell(comment)}"`,
324
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
325
+ );
326
+
327
+ synced++;
328
+
329
+ // Close issue if completed
330
+ if (isCompleted && project.status !== 'completed') {
331
+ execSync(
332
+ `gh issue close ${issueNumber} --repo "${owner}/${repo}" --comment "✅ Project completed via roadmap execution"`,
333
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
334
+ );
335
+ project.status = 'completed';
336
+ closed++;
337
+ }
338
+ } catch (e) {
339
+ spinner.warn(`Failed to sync ${project.project_name}: ${e.message}`);
340
+ }
341
+ }
342
+
343
+ // Save updated roadmap
344
+ roadmap.last_github_sync = new Date().toISOString();
345
+ saveRoadmap(roadmap);
346
+
347
+ spinner.succeed(`Synced ${synced} issues${closed > 0 ? `, closed ${closed}` : ''}`);
348
+ }
349
+
350
+ /**
351
+ * Create: Generate ROADMAP.json from existing GitHub issues
352
+ */
353
+ async function runRoadmapCreate(options) {
354
+ showHeader('Create Roadmap from GitHub Issues');
355
+
356
+ if (!checkGhCli()) return;
357
+
358
+ const config = loadConfig();
359
+ const owner = config?.owner || await getRepoOwner();
360
+ const repo = config?.repo || await getRepoName();
361
+
362
+ if (!owner || !repo) {
363
+ console.log(chalk.red('Could not determine repository. Run `gtask setup` first.'));
364
+ return;
365
+ }
366
+
367
+ // Get roadmap details
368
+ const { roadmapName, primaryGoal, labelFilter } = await inquirer.prompt([
369
+ {
370
+ type: 'input',
371
+ name: 'roadmapName',
372
+ message: 'Roadmap name:',
373
+ default: 'github-import',
374
+ validate: (v) => v.length > 0,
375
+ },
376
+ {
377
+ type: 'input',
378
+ name: 'primaryGoal',
379
+ message: 'Primary goal:',
380
+ default: 'Complete imported GitHub issues',
381
+ },
382
+ {
383
+ type: 'input',
384
+ name: 'labelFilter',
385
+ message: 'Filter by label (optional, e.g., "roadmap"):',
386
+ default: '',
387
+ },
388
+ ]);
389
+
390
+ const spinner = ora('Fetching GitHub issues...').start();
391
+
392
+ try {
393
+ // Fetch issues
394
+ const labelArg = labelFilter ? `--label "${labelFilter}"` : '';
395
+ const result = execSync(
396
+ `gh issue list --repo "${owner}/${repo}" --state open ${labelArg} --json number,title,body,labels,assignees --limit 100`,
397
+ { encoding: 'utf8' }
398
+ );
399
+
400
+ const issues = JSON.parse(result);
401
+
402
+ if (issues.length === 0) {
403
+ spinner.fail('No issues found');
404
+ return;
405
+ }
406
+
407
+ spinner.succeed(`Found ${issues.length} issues`);
408
+
409
+ // Convert to roadmap projects
410
+ const projects = issues.map((issue, index) => ({
411
+ project_id: `gh-${issue.number}`,
412
+ project_name: issue.title,
413
+ description: issue.body?.substring(0, 500) || 'No description',
414
+ priority: extractPriority(issue.labels),
415
+ estimated_effort_hours: '4-8',
416
+ source_files: [],
417
+ target_files: [],
418
+ deliverables: [`Complete issue #${issue.number}`],
419
+ dependencies: [],
420
+ status: 'pending',
421
+ phase_dev_config: {
422
+ github_issue_number: issue.number,
423
+ github_issue_url: `https://github.com/${owner}/${repo}/issues/${issue.number}`,
424
+ scale: 'S',
425
+ },
426
+ }));
427
+
428
+ // Create roadmap structure
429
+ const roadmapSlug = roadmapName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
430
+ const roadmap = {
431
+ roadmap_name: roadmapName,
432
+ roadmap_slug: roadmapSlug,
433
+ primary_goal: primaryGoal,
434
+ total_projects: projects.length,
435
+ completed_projects: 0,
436
+ completion_percentage: 0,
437
+ projects,
438
+ technologies: [],
439
+ github_integrated: true,
440
+ github_repo: `${owner}/${repo}`,
441
+ created_at: new Date().toISOString(),
442
+ last_updated: new Date().toISOString(),
443
+ last_github_sync: new Date().toISOString(),
444
+ };
445
+
446
+ // Save roadmap
447
+ const docsDir = join(process.cwd(), '.claude', 'docs', roadmapSlug);
448
+ mkdirSync(docsDir, { recursive: true });
449
+
450
+ const roadmapPath = join(docsDir, 'ROADMAP.json');
451
+ writeFileSync(roadmapPath, JSON.stringify(roadmap, null, 2));
452
+
453
+ // Create EXECUTION_STATE.json
454
+ const executionState = {
455
+ roadmap_id: roadmapSlug,
456
+ mode: 'paused',
457
+ queue: projects.map((p) => p.project_id),
458
+ current_project: null,
459
+ completed_this_session: [],
460
+ completed_all_time: [],
461
+ metrics: {
462
+ projects_completed_total: 0,
463
+ projects_remaining: projects.length,
464
+ consecutive_failures: 0,
465
+ },
466
+ safety: {
467
+ max_parallel_agents: 2,
468
+ max_context_percent: 80,
469
+ compact_at_context_percent: 70,
470
+ max_consecutive_failures: 3,
471
+ },
472
+ last_updated: new Date().toISOString(),
473
+ };
474
+
475
+ writeFileSync(join(docsDir, 'EXECUTION_STATE.json'), JSON.stringify(executionState, null, 2));
476
+
477
+ console.log('');
478
+ console.log(chalk.green.bold('Roadmap Created'));
479
+ console.log('');
480
+ console.log(` Path: ${roadmapPath}`);
481
+ console.log(` Projects: ${projects.length}`);
482
+ console.log('');
483
+ console.log(chalk.dim('Run `/create-roadmap` to generate execution hooks and slash command'));
484
+ } catch (e) {
485
+ spinner.fail(`Failed to fetch issues: ${e.message}`);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Status: Show sync status dashboard
491
+ */
492
+ async function runRoadmapStatus(options) {
493
+ showHeader('Roadmap Sync Status');
494
+
495
+ const roadmaps = findRoadmaps();
496
+
497
+ if (roadmaps.length === 0) {
498
+ console.log(chalk.yellow('No roadmaps found in .claude/docs/'));
499
+ console.log(chalk.dim('Run /create-roadmap to create one'));
500
+ return;
501
+ }
502
+
503
+ const config = loadConfig();
504
+ const owner = config?.owner;
505
+ const repo = config?.repo;
506
+
507
+ for (const roadmap of roadmaps) {
508
+ console.log('');
509
+ console.log(chalk.cyan.bold(`━━━ ${roadmap.roadmap_name} ━━━`));
510
+ console.log('');
511
+
512
+ // Summary
513
+ const completed = roadmap.projects.filter((p) => p.status === 'completed').length;
514
+ const inProgress = roadmap.projects.filter((p) => p.status === 'in_progress').length;
515
+ const pending = roadmap.projects.filter((p) => p.status === 'pending').length;
516
+
517
+ console.log(` Total: ${roadmap.total_projects} projects`);
518
+ console.log(` ${chalk.green('✓')} Completed: ${completed}`);
519
+ console.log(` ${chalk.yellow('●')} In Progress: ${inProgress}`);
520
+ console.log(` ${chalk.dim('○')} Pending: ${pending}`);
521
+ console.log('');
522
+
523
+ // GitHub integration status
524
+ if (roadmap.github_integrated) {
525
+ console.log(chalk.green(' GitHub Integration: ✓ Enabled'));
526
+ if (roadmap.last_github_sync) {
527
+ console.log(chalk.dim(` Last Sync: ${new Date(roadmap.last_github_sync).toLocaleString()}`));
528
+ }
529
+ console.log('');
530
+
531
+ // Project-level status
532
+ console.log(' Projects:');
533
+ for (const project of roadmap.projects) {
534
+ const issueNum = project.phase_dev_config?.github_issue_number;
535
+ const statusIcon = project.status === 'completed'
536
+ ? chalk.green('✓')
537
+ : project.status === 'in_progress'
538
+ ? chalk.yellow('●')
539
+ : chalk.dim('○');
540
+
541
+ if (issueNum) {
542
+ console.log(` ${statusIcon} #${issueNum} - ${project.project_name}`);
543
+ } else {
544
+ console.log(` ${statusIcon} [no issue] - ${project.project_name}`);
545
+ }
546
+ }
547
+ } else {
548
+ console.log(chalk.yellow(' GitHub Integration: ○ Not linked'));
549
+ console.log(chalk.dim(' Run `gtask roadmap import` to create GitHub issues'));
550
+ }
551
+ }
552
+
553
+ console.log('');
554
+
555
+ // Actions
556
+ const { action } = await inquirer.prompt([
557
+ {
558
+ type: 'list',
559
+ name: 'action',
560
+ message: 'Actions:',
561
+ choices: [
562
+ { name: 'Sync all roadmaps with GitHub', value: 'sync-all' },
563
+ { name: 'Open GitHub Project Board', value: 'open-board' },
564
+ { name: 'Back', value: 'back' },
565
+ ],
566
+ },
567
+ ]);
568
+
569
+ if (action === 'sync-all') {
570
+ for (const roadmap of roadmaps) {
571
+ if (roadmap.github_integrated) {
572
+ await runRoadmapSync({ file: roadmap.path });
573
+ }
574
+ }
575
+ } else if (action === 'open-board') {
576
+ const projectUrl = config?.projectUrl || `https://github.com/users/${owner}/projects`;
577
+ console.log(chalk.dim(`Opening: ${projectUrl}`));
578
+ try {
579
+ execSync(`start "" "${projectUrl}"`, { stdio: 'ignore' });
580
+ } catch (e) {
581
+ console.log(chalk.yellow(`Open manually: ${projectUrl}`));
582
+ }
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Helper: Check gh CLI is available
588
+ */
589
+ function checkGhCli() {
590
+ try {
591
+ execSync('gh --version', { stdio: 'pipe' });
592
+ return true;
593
+ } catch (e) {
594
+ console.log(chalk.red('GitHub CLI (gh) not found'));
595
+ console.log(chalk.dim('Install from: https://cli.github.com/'));
596
+ return false;
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Helper: Get repo owner from git
602
+ */
603
+ function getRepoOwner() {
604
+ try {
605
+ const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
606
+ const match = remote.match(/github\.com[:/]([^/]+)/);
607
+ return match?.[1];
608
+ } catch (e) {
609
+ return null;
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Helper: Get repo name from git
615
+ */
616
+ function getRepoName() {
617
+ try {
618
+ const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
619
+ const match = remote.match(/github\.com[:/][^/]+\/([^/.]+)/);
620
+ return match?.[1];
621
+ } catch (e) {
622
+ return null;
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Helper: Select a roadmap interactively
628
+ */
629
+ async function selectRoadmap(filePath) {
630
+ if (filePath && existsSync(filePath)) {
631
+ return JSON.parse(readFileSync(filePath, 'utf8'));
632
+ }
633
+
634
+ const roadmaps = findRoadmaps();
635
+
636
+ if (roadmaps.length === 0) {
637
+ console.log(chalk.yellow('No roadmaps found in .claude/docs/'));
638
+ console.log(chalk.dim('Run /create-roadmap to create one'));
639
+ return null;
640
+ }
641
+
642
+ if (roadmaps.length === 1) {
643
+ return roadmaps[0];
644
+ }
645
+
646
+ const { selected } = await inquirer.prompt([
647
+ {
648
+ type: 'list',
649
+ name: 'selected',
650
+ message: 'Select a roadmap:',
651
+ choices: roadmaps.map((rm) => ({
652
+ name: `${rm.roadmap_name} (${rm.total_projects} projects)`,
653
+ value: rm,
654
+ })),
655
+ },
656
+ ]);
657
+
658
+ return selected;
659
+ }
660
+
661
+ /**
662
+ * Helper: Save roadmap back to file
663
+ */
664
+ function saveRoadmap(roadmap) {
665
+ const path = roadmap.path;
666
+ if (path) {
667
+ const data = { ...roadmap };
668
+ delete data.path;
669
+ delete data.dir;
670
+ data.last_updated = new Date().toISOString();
671
+ writeFileSync(path, JSON.stringify(data, null, 2));
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Helper: Build issue body from project
677
+ */
678
+ function buildIssueBody(project, roadmap) {
679
+ return `## ${project.project_name}
680
+
681
+ ${project.description}
682
+
683
+ ### Details
684
+
685
+ - **Priority:** ${project.priority}
686
+ - **Estimated Effort:** ${project.estimated_effort_hours} hours
687
+ - **Roadmap:** ${roadmap.roadmap_name}
688
+ - **Project ID:** ${project.project_id}
689
+
690
+ ### Deliverables
691
+
692
+ ${project.deliverables?.map((d) => `- [ ] ${d}`).join('\n') || '- [ ] Complete project'}
693
+
694
+ ### Dependencies
695
+
696
+ ${project.dependencies?.length > 0 ? project.dependencies.map((d) => `- ${d}`).join('\n') : 'None'}
697
+
698
+ ---
699
+ *Generated by gtask roadmap import*`;
700
+ }
701
+
702
+ /**
703
+ * Helper: Build labels for issue
704
+ */
705
+ function buildLabels(project, roadmap) {
706
+ const labels = ['roadmap'];
707
+
708
+ // Priority label
709
+ const priorityLabel = PRIORITY_LABELS[project.priority];
710
+ if (priorityLabel) {
711
+ labels.push(priorityLabel);
712
+ }
713
+
714
+ return labels;
715
+ }
716
+
717
+ /**
718
+ * Helper: Build sync comment
719
+ */
720
+ function buildSyncComment(project, completion, details, roadmap) {
721
+ const statusEmoji = completion >= 100 ? '✅' : completion > 0 ? '🔄' : '⏳';
722
+
723
+ return `${statusEmoji} **Roadmap Sync Update**
724
+
725
+ **Status:** ${project.status}
726
+ **Completion:** ${completion}%
727
+ ${details ? `**Progress:** ${details}` : ''}
728
+
729
+ ---
730
+ *Synced from ${roadmap.roadmap_name} via gtask*`;
731
+ }
732
+
733
+ /**
734
+ * Helper: Extract priority from labels
735
+ */
736
+ function extractPriority(labels) {
737
+ const labelNames = labels.map((l) => l.name?.toLowerCase() || l.toLowerCase());
738
+
739
+ if (labelNames.some((l) => l.includes('critical') || l.includes('p0'))) return 'CRITICAL';
740
+ if (labelNames.some((l) => l.includes('high') || l.includes('p1'))) return 'HIGH';
741
+ if (labelNames.some((l) => l.includes('low') || l.includes('p3'))) return 'LOW';
742
+ return 'MEDIUM';
743
+ }
744
+
745
+ /**
746
+ * Helper: Escape shell string
747
+ */
748
+ function escapeShell(str) {
749
+ return str.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/`/g, '\\`');
750
+ }