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.
- package/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- 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
|
+
}
|