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,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decompose Command
|
|
3
|
+
*
|
|
4
|
+
* Break down a GitHub issue into granular, actionable tasks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
|
|
11
|
+
import { loadConfigSync } from '../utils.js';
|
|
12
|
+
import { getIssue, addIssueComment } from '../github/client.js';
|
|
13
|
+
import { parseIssueBody, toClaudeTaskList, toMarkdownChecklist, getCompletionStats } from '../analysis/checklist-parser.js';
|
|
14
|
+
import { analyzeForIssue, searchFiles, findDefinitions } from '../analysis/codebase.js';
|
|
15
|
+
import { saveTaskState, loadTaskState } from './sync.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run the decompose command
|
|
19
|
+
*/
|
|
20
|
+
export async function runDecompose(options) {
|
|
21
|
+
showHeader('Decompose GitHub Issue');
|
|
22
|
+
|
|
23
|
+
// Load config
|
|
24
|
+
const { config } = loadConfigSync();
|
|
25
|
+
if (!config?.project_board?.owner) {
|
|
26
|
+
showError('Not configured', 'Run "gtask setup" first.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { owner, repo } = config.project_board;
|
|
31
|
+
|
|
32
|
+
// Get issue number
|
|
33
|
+
let issueNumber = options.issue;
|
|
34
|
+
if (!issueNumber) {
|
|
35
|
+
const { num } = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: 'input',
|
|
38
|
+
name: 'num',
|
|
39
|
+
message: 'Issue number to decompose:',
|
|
40
|
+
validate: (input) => {
|
|
41
|
+
const n = parseInt(input, 10);
|
|
42
|
+
return n > 0 ? true : 'Enter a valid issue number';
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
issueNumber = parseInt(num, 10);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Fetch issue
|
|
50
|
+
const spinner = ora(`Fetching issue #${issueNumber}...`).start();
|
|
51
|
+
const issue = getIssue(owner, repo, issueNumber);
|
|
52
|
+
|
|
53
|
+
if (!issue) {
|
|
54
|
+
spinner.fail(`Issue #${issueNumber} not found`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
spinner.succeed(`Fetched: ${issue.title}`);
|
|
59
|
+
console.log('');
|
|
60
|
+
|
|
61
|
+
// Parse existing issue body
|
|
62
|
+
const parseSpinner = ora('Parsing issue structure...').start();
|
|
63
|
+
const parsed = parseIssueBody(issue.body);
|
|
64
|
+
parseSpinner.stop();
|
|
65
|
+
|
|
66
|
+
// Show what we found
|
|
67
|
+
console.log(chalk.dim('Found in issue:'));
|
|
68
|
+
console.log(chalk.dim(` - ${parsed.todoList.length} existing todo items`));
|
|
69
|
+
console.log(chalk.dim(` - ${parsed.acceptanceCriteria.length} acceptance criteria`));
|
|
70
|
+
console.log(chalk.dim(` - ${parsed.testScenarios.length} test scenarios`));
|
|
71
|
+
console.log(chalk.dim(` - ${parsed.codeAnalysis.files.length} file references`));
|
|
72
|
+
console.log('');
|
|
73
|
+
|
|
74
|
+
// Decide decomposition strategy
|
|
75
|
+
const { strategy } = await inquirer.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'list',
|
|
78
|
+
name: 'strategy',
|
|
79
|
+
message: 'How should we decompose this issue?',
|
|
80
|
+
choices: [
|
|
81
|
+
{
|
|
82
|
+
name: 'Use existing checklist + enhance with codebase analysis',
|
|
83
|
+
value: 'enhance',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'Create new detailed breakdown (ignore existing)',
|
|
87
|
+
value: 'new',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Merge: combine existing + new analysis',
|
|
91
|
+
value: 'merge',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
let tasks = [];
|
|
98
|
+
|
|
99
|
+
if (strategy === 'enhance' || strategy === 'merge') {
|
|
100
|
+
// Start with existing todos
|
|
101
|
+
tasks = toClaudeTaskList(parsed);
|
|
102
|
+
console.log(chalk.green(`✓ Loaded ${tasks.length} existing tasks`));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (strategy === 'new' || strategy === 'merge') {
|
|
106
|
+
// Perform codebase analysis
|
|
107
|
+
const analyzeSpinner = ora('Analyzing codebase...').start();
|
|
108
|
+
|
|
109
|
+
// Extract keywords from issue
|
|
110
|
+
const keywords = extractKeywords(issue.title, parsed.problemStatement);
|
|
111
|
+
const analysis = await analyzeForIssue(keywords, { maxFiles: 8 });
|
|
112
|
+
|
|
113
|
+
analyzeSpinner.succeed(
|
|
114
|
+
`Found ${analysis.relevantFiles.length} relevant files`
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Generate new tasks from analysis
|
|
118
|
+
const newTasks = generateTasksFromAnalysis(analysis, parsed, issue);
|
|
119
|
+
|
|
120
|
+
if (strategy === 'merge') {
|
|
121
|
+
// Deduplicate and merge
|
|
122
|
+
tasks = mergeTasks(tasks, newTasks);
|
|
123
|
+
console.log(chalk.green(`✓ Merged to ${tasks.length} total tasks`));
|
|
124
|
+
} else {
|
|
125
|
+
tasks = newTasks;
|
|
126
|
+
console.log(chalk.green(`✓ Generated ${tasks.length} new tasks`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add standard tasks if missing
|
|
131
|
+
tasks = ensureStandardTasks(tasks);
|
|
132
|
+
|
|
133
|
+
// Show task preview
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(chalk.cyan('─'.repeat(60)));
|
|
136
|
+
console.log(chalk.bold('Task Breakdown Preview:'));
|
|
137
|
+
console.log(chalk.cyan('─'.repeat(60)));
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
140
|
+
const task = tasks[i];
|
|
141
|
+
const status = task.status === 'completed' ? chalk.green('✓') : chalk.dim('○');
|
|
142
|
+
const num = chalk.dim(`${i + 1}.`);
|
|
143
|
+
console.log(`${status} ${num} ${task.content}`);
|
|
144
|
+
|
|
145
|
+
if (task.metadata?.fileRefs?.length > 0) {
|
|
146
|
+
const refs = task.metadata.fileRefs
|
|
147
|
+
.slice(0, 2)
|
|
148
|
+
.map((r) => `${r.file}${r.line ? ':' + r.line : ''}`)
|
|
149
|
+
.join(', ');
|
|
150
|
+
console.log(chalk.dim(` Files: ${refs}`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(chalk.cyan('─'.repeat(60)));
|
|
155
|
+
console.log('');
|
|
156
|
+
|
|
157
|
+
// Confirm and choose output
|
|
158
|
+
const { outputChoice } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'list',
|
|
161
|
+
name: 'outputChoice',
|
|
162
|
+
message: 'What would you like to do with this breakdown?',
|
|
163
|
+
choices: [
|
|
164
|
+
{
|
|
165
|
+
name: 'Save locally (for gtask sync later)',
|
|
166
|
+
value: 'local',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'Post as comment on GitHub issue',
|
|
170
|
+
value: 'comment',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'Both: save locally + post comment',
|
|
174
|
+
value: 'both',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'Cancel',
|
|
178
|
+
value: 'cancel',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
if (outputChoice === 'cancel') {
|
|
185
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Save locally
|
|
190
|
+
if (outputChoice === 'local' || outputChoice === 'both') {
|
|
191
|
+
const state = {
|
|
192
|
+
issueNumber,
|
|
193
|
+
issueTitle: issue.title,
|
|
194
|
+
owner,
|
|
195
|
+
repo,
|
|
196
|
+
tasks,
|
|
197
|
+
createdAt: new Date().toISOString(),
|
|
198
|
+
lastSyncedAt: null,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
saveTaskState(state);
|
|
202
|
+
console.log(chalk.green('✓ Saved locally'));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Post to GitHub
|
|
206
|
+
if (outputChoice === 'comment' || outputChoice === 'both') {
|
|
207
|
+
const commentSpinner = ora('Posting to GitHub...').start();
|
|
208
|
+
|
|
209
|
+
const commentBody = generateDecomposeComment(tasks, strategy);
|
|
210
|
+
const success = addIssueComment(owner, repo, issueNumber, commentBody);
|
|
211
|
+
|
|
212
|
+
if (success) {
|
|
213
|
+
commentSpinner.succeed('Posted task breakdown to GitHub');
|
|
214
|
+
} else {
|
|
215
|
+
commentSpinner.fail('Failed to post comment');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Summary
|
|
220
|
+
const stats = getCompletionStats(tasks);
|
|
221
|
+
showSuccess('Decomposition Complete', [
|
|
222
|
+
`Issue: #${issueNumber} - ${issue.title}`,
|
|
223
|
+
`Tasks: ${stats.total} (${stats.completed} completed, ${stats.pending} pending)`,
|
|
224
|
+
'',
|
|
225
|
+
'Next steps:',
|
|
226
|
+
` gtask sync ${issueNumber} - Sync progress to GitHub`,
|
|
227
|
+
` gtask sync watch ${issueNumber} - Auto-sync on changes`,
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
return { issueNumber, tasks };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extract keywords from title and description
|
|
235
|
+
*/
|
|
236
|
+
function extractKeywords(title, description) {
|
|
237
|
+
const text = `${title} ${description || ''}`.toLowerCase();
|
|
238
|
+
const stopWords = new Set([
|
|
239
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has',
|
|
240
|
+
'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might',
|
|
241
|
+
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'and',
|
|
242
|
+
'but', 'or', 'not', 'this', 'that', 'it', 'bug', 'fix', 'issue', 'error',
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
return text
|
|
246
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
247
|
+
.split(/\s+/)
|
|
248
|
+
.filter((w) => w.length > 2 && !stopWords.has(w))
|
|
249
|
+
.slice(0, 10);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Generate tasks from codebase analysis
|
|
254
|
+
*/
|
|
255
|
+
function generateTasksFromAnalysis(analysis, parsed, issue) {
|
|
256
|
+
const tasks = [];
|
|
257
|
+
|
|
258
|
+
// Task 1: Understand the problem
|
|
259
|
+
tasks.push({
|
|
260
|
+
content: 'Review issue and understand the problem',
|
|
261
|
+
activeForm: 'Reviewing issue and understanding the problem',
|
|
262
|
+
status: 'pending',
|
|
263
|
+
metadata: { type: 'research' },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Tasks for each relevant file
|
|
267
|
+
for (const fileInfo of analysis.relevantFiles.slice(0, 5)) {
|
|
268
|
+
const file = fileInfo.file;
|
|
269
|
+
const mainFunc = fileInfo.definitions?.[0];
|
|
270
|
+
|
|
271
|
+
if (mainFunc) {
|
|
272
|
+
tasks.push({
|
|
273
|
+
content: `Review ${mainFunc.name}() in ${file}`,
|
|
274
|
+
activeForm: `Reviewing ${mainFunc.name}() in ${file}`,
|
|
275
|
+
status: 'pending',
|
|
276
|
+
metadata: {
|
|
277
|
+
type: 'review',
|
|
278
|
+
fileRefs: [{ file, line: mainFunc.line }],
|
|
279
|
+
functionRefs: [mainFunc.name],
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
tasks.push({
|
|
284
|
+
content: `Analyze ${file} for relevant code`,
|
|
285
|
+
activeForm: `Analyzing ${file}`,
|
|
286
|
+
status: 'pending',
|
|
287
|
+
metadata: {
|
|
288
|
+
type: 'review',
|
|
289
|
+
fileRefs: [{ file, line: null }],
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Task for implementation
|
|
296
|
+
tasks.push({
|
|
297
|
+
content: 'Implement the fix/feature',
|
|
298
|
+
activeForm: 'Implementing the fix/feature',
|
|
299
|
+
status: 'pending',
|
|
300
|
+
metadata: { type: 'implementation' },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Tasks from acceptance criteria
|
|
304
|
+
for (const criterion of parsed.acceptanceCriteria.slice(0, 3)) {
|
|
305
|
+
tasks.push({
|
|
306
|
+
content: `Verify: ${criterion.text}`,
|
|
307
|
+
activeForm: `Verifying: ${criterion.text}`,
|
|
308
|
+
status: criterion.completed ? 'completed' : 'pending',
|
|
309
|
+
metadata: { type: 'verification' },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return tasks;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Merge two task lists, avoiding duplicates
|
|
318
|
+
*/
|
|
319
|
+
function mergeTasks(existing, newTasks) {
|
|
320
|
+
const merged = [...existing];
|
|
321
|
+
const existingTexts = new Set(
|
|
322
|
+
existing.map((t) => t.content.toLowerCase())
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
for (const task of newTasks) {
|
|
326
|
+
const textLower = task.content.toLowerCase();
|
|
327
|
+
// Check for similar tasks
|
|
328
|
+
const isDuplicate = [...existingTexts].some(
|
|
329
|
+
(t) => t.includes(textLower) || textLower.includes(t)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
if (!isDuplicate) {
|
|
333
|
+
merged.push(task);
|
|
334
|
+
existingTexts.add(textLower);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return merged;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Ensure standard tasks are present
|
|
343
|
+
*/
|
|
344
|
+
function ensureStandardTasks(tasks) {
|
|
345
|
+
const hasTest = tasks.some(
|
|
346
|
+
(t) => t.content.toLowerCase().includes('test')
|
|
347
|
+
);
|
|
348
|
+
const hasCommit = tasks.some(
|
|
349
|
+
(t) => t.content.toLowerCase().includes('commit')
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if (!hasTest) {
|
|
353
|
+
tasks.push({
|
|
354
|
+
content: 'Test changes locally',
|
|
355
|
+
activeForm: 'Testing changes locally',
|
|
356
|
+
status: 'pending',
|
|
357
|
+
metadata: { type: 'testing' },
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!hasCommit) {
|
|
362
|
+
tasks.push({
|
|
363
|
+
content: 'Commit changes with descriptive message',
|
|
364
|
+
activeForm: 'Committing changes',
|
|
365
|
+
status: 'pending',
|
|
366
|
+
metadata: { type: 'commit' },
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return tasks;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Generate comment body for GitHub
|
|
375
|
+
*/
|
|
376
|
+
function generateDecomposeComment(tasks, strategy) {
|
|
377
|
+
const stats = getCompletionStats(tasks);
|
|
378
|
+
const checklist = toMarkdownChecklist(tasks);
|
|
379
|
+
|
|
380
|
+
return `## Task Breakdown
|
|
381
|
+
|
|
382
|
+
**Strategy**: ${strategy === 'enhance' ? 'Enhanced from existing checklist' : strategy === 'new' ? 'New analysis' : 'Merged existing + new'}
|
|
383
|
+
**Tasks**: ${stats.total} total (${stats.completed} done, ${stats.pending} pending)
|
|
384
|
+
**Progress**: ${stats.percentage}%
|
|
385
|
+
|
|
386
|
+
### Detailed Tasks
|
|
387
|
+
|
|
388
|
+
${checklist}
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
*Generated by GitHub Task Kit - Use \`gtask sync ${stats.total > 0 ? 'watch' : ''}\` to track progress*`;
|
|
392
|
+
}
|