claude-code-workflow 6.3.7 → 6.3.8
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/.claude/agents/issue-plan-agent.md +136 -760
- package/.claude/agents/issue-queue-agent.md +149 -616
- package/.claude/commands/issue/execute.md +55 -26
- package/.claude/commands/issue/manage.md +37 -789
- package/.claude/commands/issue/new.md +9 -42
- package/.claude/commands/issue/plan.md +86 -278
- package/.claude/commands/issue/queue.md +99 -159
- package/.claude/skills/issue-manage/SKILL.md +244 -0
- package/.codex/prompts/issue-execute.md +11 -11
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +1 -0
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +1 -0
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +86 -70
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/core/routes/issue-routes.d.ts +3 -1
- package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/issue-routes.js +30 -18
- package/ccw/dist/core/routes/issue-routes.js.map +1 -1
- package/ccw/src/cli.ts +1 -0
- package/ccw/src/commands/issue.ts +111 -76
- package/ccw/src/core/routes/issue-routes.ts +34 -18
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +14 -14
- package/package.json +1 -1
|
@@ -20,17 +20,23 @@ Interactive menu-driven interface for issue management using `ccw issue` CLI end
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
# Core endpoints (ccw issue)
|
|
23
|
-
ccw issue list
|
|
24
|
-
ccw issue list <id> --json
|
|
25
|
-
ccw issue status <id>
|
|
26
|
-
ccw issue init <id> --title "..."
|
|
27
|
-
ccw issue task <id> --title "..."
|
|
23
|
+
ccw issue list # List all issues
|
|
24
|
+
ccw issue list <id> --json # Get issue details
|
|
25
|
+
ccw issue status <id> # Detailed status
|
|
26
|
+
ccw issue init <id> --title "..." # Create issue
|
|
27
|
+
ccw issue task <id> --title "..." # Add task
|
|
28
|
+
ccw issue bind <id> <solution-id> # Bind solution
|
|
28
29
|
|
|
29
30
|
# Queue management
|
|
30
|
-
ccw issue queue
|
|
31
|
-
ccw issue queue add <id>
|
|
32
|
-
ccw issue
|
|
33
|
-
ccw issue
|
|
31
|
+
ccw issue queue # List current queue
|
|
32
|
+
ccw issue queue add <id> # Add to queue
|
|
33
|
+
ccw issue queue list # Queue history
|
|
34
|
+
ccw issue queue switch <queue-id> # Switch queue
|
|
35
|
+
ccw issue queue archive # Archive queue
|
|
36
|
+
ccw issue queue delete <queue-id> # Delete queue
|
|
37
|
+
ccw issue next # Get next task
|
|
38
|
+
ccw issue done <queue-id> # Mark completed
|
|
39
|
+
ccw issue complete <item-id> # (legacy alias for done)
|
|
34
40
|
```
|
|
35
41
|
|
|
36
42
|
## Usage
|
|
@@ -49,7 +55,9 @@ ccw issue done <queue-id> # Complete task
|
|
|
49
55
|
|
|
50
56
|
## Implementation
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
This command delegates to the `issue-manage` skill for detailed implementation.
|
|
59
|
+
|
|
60
|
+
### Entry Point
|
|
53
61
|
|
|
54
62
|
```javascript
|
|
55
63
|
const issueId = parseIssueId(userInput);
|
|
@@ -63,787 +71,30 @@ if (!action) {
|
|
|
63
71
|
}
|
|
64
72
|
```
|
|
65
73
|
|
|
66
|
-
###
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
async function showMainMenu(preselectedIssue = null) {
|
|
70
|
-
// Fetch current issues summary
|
|
71
|
-
const issuesResult = Bash('ccw issue list --json 2>/dev/null || echo "[]"');
|
|
72
|
-
const issues = JSON.parse(issuesResult) || [];
|
|
73
|
-
|
|
74
|
-
const queueResult = Bash('ccw issue status --json 2>/dev/null');
|
|
75
|
-
const queueStatus = JSON.parse(queueResult || '{}');
|
|
76
|
-
|
|
77
|
-
console.log(`
|
|
78
|
-
## Issue Management Dashboard
|
|
79
|
-
|
|
80
|
-
**Total Issues**: ${issues.length}
|
|
81
|
-
**Queue Status**: ${queueStatus.queue?.total_tasks || 0} tasks (${queueStatus.queue?.pending_count || 0} pending)
|
|
82
|
-
|
|
83
|
-
### Quick Stats
|
|
84
|
-
- Registered: ${issues.filter(i => i.status === 'registered').length}
|
|
85
|
-
- Planned: ${issues.filter(i => i.status === 'planned').length}
|
|
86
|
-
- Executing: ${issues.filter(i => i.status === 'executing').length}
|
|
87
|
-
- Completed: ${issues.filter(i => i.status === 'completed').length}
|
|
88
|
-
`);
|
|
89
|
-
|
|
90
|
-
const answer = AskUserQuestion({
|
|
91
|
-
questions: [{
|
|
92
|
-
question: 'What would you like to do?',
|
|
93
|
-
header: 'Action',
|
|
94
|
-
multiSelect: false,
|
|
95
|
-
options: [
|
|
96
|
-
{ label: 'List Issues', description: 'Browse all issues with filters' },
|
|
97
|
-
{ label: 'View Issue', description: 'Detailed view of specific issue' },
|
|
98
|
-
{ label: 'Create Issue', description: 'Add new issue from text or GitHub' },
|
|
99
|
-
{ label: 'Edit Issue', description: 'Modify issue fields' },
|
|
100
|
-
{ label: 'Delete Issue', description: 'Remove issue(s)' },
|
|
101
|
-
{ label: 'Bulk Operations', description: 'Batch actions on multiple issues' }
|
|
102
|
-
]
|
|
103
|
-
}]
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const selected = parseAnswer(answer);
|
|
107
|
-
|
|
108
|
-
switch (selected) {
|
|
109
|
-
case 'List Issues':
|
|
110
|
-
await listIssuesInteractive();
|
|
111
|
-
break;
|
|
112
|
-
case 'View Issue':
|
|
113
|
-
await viewIssueInteractive(preselectedIssue);
|
|
114
|
-
break;
|
|
115
|
-
case 'Create Issue':
|
|
116
|
-
await createIssueInteractive();
|
|
117
|
-
break;
|
|
118
|
-
case 'Edit Issue':
|
|
119
|
-
await editIssueInteractive(preselectedIssue);
|
|
120
|
-
break;
|
|
121
|
-
case 'Delete Issue':
|
|
122
|
-
await deleteIssueInteractive(preselectedIssue);
|
|
123
|
-
break;
|
|
124
|
-
case 'Bulk Operations':
|
|
125
|
-
await bulkOperationsInteractive();
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Phase 3: List Issues
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
async function listIssuesInteractive() {
|
|
135
|
-
// Ask for filter
|
|
136
|
-
const filterAnswer = AskUserQuestion({
|
|
137
|
-
questions: [{
|
|
138
|
-
question: 'Filter issues by status?',
|
|
139
|
-
header: 'Filter',
|
|
140
|
-
multiSelect: true,
|
|
141
|
-
options: [
|
|
142
|
-
{ label: 'All', description: 'Show all issues' },
|
|
143
|
-
{ label: 'Registered', description: 'New, unplanned issues' },
|
|
144
|
-
{ label: 'Planned', description: 'Issues with bound solutions' },
|
|
145
|
-
{ label: 'Queued', description: 'In execution queue' },
|
|
146
|
-
{ label: 'Executing', description: 'Currently being worked on' },
|
|
147
|
-
{ label: 'Completed', description: 'Finished issues' },
|
|
148
|
-
{ label: 'Failed', description: 'Failed issues' }
|
|
149
|
-
]
|
|
150
|
-
}]
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const filters = parseMultiAnswer(filterAnswer);
|
|
154
|
-
|
|
155
|
-
// Fetch and filter issues
|
|
156
|
-
const result = Bash('ccw issue list --json');
|
|
157
|
-
let issues = JSON.parse(result) || [];
|
|
158
|
-
|
|
159
|
-
if (!filters.includes('All')) {
|
|
160
|
-
const statusMap = {
|
|
161
|
-
'Registered': 'registered',
|
|
162
|
-
'Planned': 'planned',
|
|
163
|
-
'Queued': 'queued',
|
|
164
|
-
'Executing': 'executing',
|
|
165
|
-
'Completed': 'completed',
|
|
166
|
-
'Failed': 'failed'
|
|
167
|
-
};
|
|
168
|
-
const allowedStatuses = filters.map(f => statusMap[f]).filter(Boolean);
|
|
169
|
-
issues = issues.filter(i => allowedStatuses.includes(i.status));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (issues.length === 0) {
|
|
173
|
-
console.log('No issues found matching filters.');
|
|
174
|
-
return showMainMenu();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Display issues table
|
|
178
|
-
console.log(`
|
|
179
|
-
## Issues (${issues.length})
|
|
180
|
-
|
|
181
|
-
| ID | Status | Priority | Title |
|
|
182
|
-
|----|--------|----------|-------|
|
|
183
|
-
${issues.map(i => `| ${i.id} | ${i.status} | P${i.priority} | ${i.title.substring(0, 40)} |`).join('\n')}
|
|
184
|
-
`);
|
|
185
|
-
|
|
186
|
-
// Ask for action on issue
|
|
187
|
-
const actionAnswer = AskUserQuestion({
|
|
188
|
-
questions: [{
|
|
189
|
-
question: 'Select an issue to view/edit, or return to menu:',
|
|
190
|
-
header: 'Select',
|
|
191
|
-
multiSelect: false,
|
|
192
|
-
options: [
|
|
193
|
-
...issues.slice(0, 10).map(i => ({
|
|
194
|
-
label: i.id,
|
|
195
|
-
description: i.title.substring(0, 50)
|
|
196
|
-
})),
|
|
197
|
-
{ label: 'Back to Menu', description: 'Return to main menu' }
|
|
198
|
-
]
|
|
199
|
-
}]
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const selected = parseAnswer(actionAnswer);
|
|
203
|
-
|
|
204
|
-
if (selected === 'Back to Menu') {
|
|
205
|
-
return showMainMenu();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// View selected issue
|
|
209
|
-
await viewIssueInteractive(selected);
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Phase 4: View Issue
|
|
74
|
+
### Main Menu Flow
|
|
214
75
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const issues = JSON.parse(Bash('ccw issue list --json') || '[]');
|
|
220
|
-
|
|
221
|
-
const idAnswer = AskUserQuestion({
|
|
222
|
-
questions: [{
|
|
223
|
-
question: 'Select issue to view:',
|
|
224
|
-
header: 'Issue',
|
|
225
|
-
multiSelect: false,
|
|
226
|
-
options: issues.slice(0, 10).map(i => ({
|
|
227
|
-
label: i.id,
|
|
228
|
-
description: `${i.status} - ${i.title.substring(0, 40)}`
|
|
229
|
-
}))
|
|
230
|
-
}]
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
issueId = parseAnswer(idAnswer);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Fetch detailed status
|
|
237
|
-
const result = Bash(`ccw issue status ${issueId} --json`);
|
|
238
|
-
const data = JSON.parse(result);
|
|
239
|
-
|
|
240
|
-
const issue = data.issue;
|
|
241
|
-
const solutions = data.solutions || [];
|
|
242
|
-
const bound = data.bound;
|
|
243
|
-
|
|
244
|
-
console.log(`
|
|
245
|
-
## Issue: ${issue.id}
|
|
76
|
+
1. **Dashboard**: Fetch issues summary via `ccw issue list --json`
|
|
77
|
+
2. **Menu**: Present action options via AskUserQuestion
|
|
78
|
+
3. **Route**: Execute selected action (List/View/Edit/Delete/Bulk)
|
|
79
|
+
4. **Loop**: Return to menu after each action
|
|
246
80
|
|
|
247
|
-
|
|
248
|
-
**Status**: ${issue.status}
|
|
249
|
-
**Priority**: P${issue.priority}
|
|
250
|
-
**Created**: ${issue.created_at}
|
|
251
|
-
**Updated**: ${issue.updated_at}
|
|
81
|
+
### Available Actions
|
|
252
82
|
|
|
253
|
-
|
|
254
|
-
|
|
83
|
+
| Action | Description | CLI Command |
|
|
84
|
+
|--------|-------------|-------------|
|
|
85
|
+
| List | Browse with filters | `ccw issue list --json` |
|
|
86
|
+
| View | Detail view | `ccw issue status <id> --json` |
|
|
87
|
+
| Edit | Modify fields | Update `issues.jsonl` |
|
|
88
|
+
| Delete | Remove issue | Clean up all related files |
|
|
89
|
+
| Bulk | Batch operations | Multi-select + batch update |
|
|
255
90
|
|
|
256
|
-
|
|
257
|
-
${solutions.length === 0 ? 'No solutions registered' :
|
|
258
|
-
solutions.map(s => `- ${s.is_bound ? '◉' : '○'} ${s.id}: ${s.tasks?.length || 0} tasks`).join('\n')}
|
|
259
|
-
|
|
260
|
-
${bound ? `### Bound Solution: ${bound.id}\n**Tasks**: ${bound.tasks?.length || 0}` : ''}
|
|
261
|
-
`);
|
|
262
|
-
|
|
263
|
-
// Show tasks if bound solution exists
|
|
264
|
-
if (bound?.tasks?.length > 0) {
|
|
265
|
-
console.log(`
|
|
266
|
-
### Tasks
|
|
267
|
-
| ID | Action | Scope | Title |
|
|
268
|
-
|----|--------|-------|-------|
|
|
269
|
-
${bound.tasks.map(t => `| ${t.id} | ${t.action} | ${t.scope?.substring(0, 20) || '-'} | ${t.title.substring(0, 30)} |`).join('\n')}
|
|
270
|
-
`);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Action menu
|
|
274
|
-
const actionAnswer = AskUserQuestion({
|
|
275
|
-
questions: [{
|
|
276
|
-
question: 'What would you like to do?',
|
|
277
|
-
header: 'Action',
|
|
278
|
-
multiSelect: false,
|
|
279
|
-
options: [
|
|
280
|
-
{ label: 'Edit Issue', description: 'Modify issue fields' },
|
|
281
|
-
{ label: 'Plan Issue', description: 'Generate solution (/issue:plan)' },
|
|
282
|
-
{ label: 'Add to Queue', description: 'Queue bound solution tasks' },
|
|
283
|
-
{ label: 'View Queue', description: 'See queue status' },
|
|
284
|
-
{ label: 'Delete Issue', description: 'Remove this issue' },
|
|
285
|
-
{ label: 'Back to Menu', description: 'Return to main menu' }
|
|
286
|
-
]
|
|
287
|
-
}]
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const action = parseAnswer(actionAnswer);
|
|
291
|
-
|
|
292
|
-
switch (action) {
|
|
293
|
-
case 'Edit Issue':
|
|
294
|
-
await editIssueInteractive(issueId);
|
|
295
|
-
break;
|
|
296
|
-
case 'Plan Issue':
|
|
297
|
-
console.log(`Running: /issue:plan ${issueId}`);
|
|
298
|
-
// Invoke plan skill
|
|
299
|
-
break;
|
|
300
|
-
case 'Add to Queue':
|
|
301
|
-
Bash(`ccw issue queue add ${issueId}`);
|
|
302
|
-
console.log(`✓ Added ${issueId} tasks to queue`);
|
|
303
|
-
break;
|
|
304
|
-
case 'View Queue':
|
|
305
|
-
const queueOutput = Bash('ccw issue queue');
|
|
306
|
-
console.log(queueOutput);
|
|
307
|
-
break;
|
|
308
|
-
case 'Delete Issue':
|
|
309
|
-
await deleteIssueInteractive(issueId);
|
|
310
|
-
break;
|
|
311
|
-
default:
|
|
312
|
-
return showMainMenu();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Phase 5: Edit Issue
|
|
318
|
-
|
|
319
|
-
```javascript
|
|
320
|
-
async function editIssueInteractive(issueId) {
|
|
321
|
-
if (!issueId) {
|
|
322
|
-
const issues = JSON.parse(Bash('ccw issue list --json') || '[]');
|
|
323
|
-
const idAnswer = AskUserQuestion({
|
|
324
|
-
questions: [{
|
|
325
|
-
question: 'Select issue to edit:',
|
|
326
|
-
header: 'Issue',
|
|
327
|
-
multiSelect: false,
|
|
328
|
-
options: issues.slice(0, 10).map(i => ({
|
|
329
|
-
label: i.id,
|
|
330
|
-
description: `${i.status} - ${i.title.substring(0, 40)}`
|
|
331
|
-
}))
|
|
332
|
-
}]
|
|
333
|
-
});
|
|
334
|
-
issueId = parseAnswer(idAnswer);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Get current issue data
|
|
338
|
-
const result = Bash(`ccw issue list ${issueId} --json`);
|
|
339
|
-
const issueData = JSON.parse(result);
|
|
340
|
-
const issue = issueData.issue || issueData;
|
|
341
|
-
|
|
342
|
-
// Ask which field to edit
|
|
343
|
-
const fieldAnswer = AskUserQuestion({
|
|
344
|
-
questions: [{
|
|
345
|
-
question: 'Which field to edit?',
|
|
346
|
-
header: 'Field',
|
|
347
|
-
multiSelect: false,
|
|
348
|
-
options: [
|
|
349
|
-
{ label: 'Title', description: `Current: ${issue.title?.substring(0, 40)}` },
|
|
350
|
-
{ label: 'Priority', description: `Current: P${issue.priority}` },
|
|
351
|
-
{ label: 'Status', description: `Current: ${issue.status}` },
|
|
352
|
-
{ label: 'Context', description: 'Edit problem description' },
|
|
353
|
-
{ label: 'Labels', description: `Current: ${issue.labels?.join(', ') || 'none'}` },
|
|
354
|
-
{ label: 'Back', description: 'Return without changes' }
|
|
355
|
-
]
|
|
356
|
-
}]
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const field = parseAnswer(fieldAnswer);
|
|
360
|
-
|
|
361
|
-
if (field === 'Back') {
|
|
362
|
-
return viewIssueInteractive(issueId);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
let updatePayload = {};
|
|
366
|
-
|
|
367
|
-
switch (field) {
|
|
368
|
-
case 'Title':
|
|
369
|
-
const titleAnswer = AskUserQuestion({
|
|
370
|
-
questions: [{
|
|
371
|
-
question: 'Enter new title (or select current to keep):',
|
|
372
|
-
header: 'Title',
|
|
373
|
-
multiSelect: false,
|
|
374
|
-
options: [
|
|
375
|
-
{ label: issue.title.substring(0, 50), description: 'Keep current title' }
|
|
376
|
-
]
|
|
377
|
-
}]
|
|
378
|
-
});
|
|
379
|
-
const newTitle = parseAnswer(titleAnswer);
|
|
380
|
-
if (newTitle && newTitle !== issue.title.substring(0, 50)) {
|
|
381
|
-
updatePayload.title = newTitle;
|
|
382
|
-
}
|
|
383
|
-
break;
|
|
384
|
-
|
|
385
|
-
case 'Priority':
|
|
386
|
-
const priorityAnswer = AskUserQuestion({
|
|
387
|
-
questions: [{
|
|
388
|
-
question: 'Select priority:',
|
|
389
|
-
header: 'Priority',
|
|
390
|
-
multiSelect: false,
|
|
391
|
-
options: [
|
|
392
|
-
{ label: 'P1 - Critical', description: 'Production blocking' },
|
|
393
|
-
{ label: 'P2 - High', description: 'Major functionality' },
|
|
394
|
-
{ label: 'P3 - Medium', description: 'Normal priority (default)' },
|
|
395
|
-
{ label: 'P4 - Low', description: 'Minor issues' },
|
|
396
|
-
{ label: 'P5 - Trivial', description: 'Nice to have' }
|
|
397
|
-
]
|
|
398
|
-
}]
|
|
399
|
-
});
|
|
400
|
-
const priorityStr = parseAnswer(priorityAnswer);
|
|
401
|
-
updatePayload.priority = parseInt(priorityStr.charAt(1));
|
|
402
|
-
break;
|
|
403
|
-
|
|
404
|
-
case 'Status':
|
|
405
|
-
const statusAnswer = AskUserQuestion({
|
|
406
|
-
questions: [{
|
|
407
|
-
question: 'Select status:',
|
|
408
|
-
header: 'Status',
|
|
409
|
-
multiSelect: false,
|
|
410
|
-
options: [
|
|
411
|
-
{ label: 'registered', description: 'New issue, not yet planned' },
|
|
412
|
-
{ label: 'planning', description: 'Solution being generated' },
|
|
413
|
-
{ label: 'planned', description: 'Solution bound, ready for queue' },
|
|
414
|
-
{ label: 'queued', description: 'In execution queue' },
|
|
415
|
-
{ label: 'executing', description: 'Currently being worked on' },
|
|
416
|
-
{ label: 'completed', description: 'All tasks finished' },
|
|
417
|
-
{ label: 'failed', description: 'Execution failed' },
|
|
418
|
-
{ label: 'paused', description: 'Temporarily on hold' }
|
|
419
|
-
]
|
|
420
|
-
}]
|
|
421
|
-
});
|
|
422
|
-
updatePayload.status = parseAnswer(statusAnswer);
|
|
423
|
-
break;
|
|
424
|
-
|
|
425
|
-
case 'Context':
|
|
426
|
-
console.log(`Current context:\n${issue.context || '(empty)'}\n`);
|
|
427
|
-
const contextAnswer = AskUserQuestion({
|
|
428
|
-
questions: [{
|
|
429
|
-
question: 'Enter new context (problem description):',
|
|
430
|
-
header: 'Context',
|
|
431
|
-
multiSelect: false,
|
|
432
|
-
options: [
|
|
433
|
-
{ label: 'Keep current', description: 'No changes' }
|
|
434
|
-
]
|
|
435
|
-
}]
|
|
436
|
-
});
|
|
437
|
-
const newContext = parseAnswer(contextAnswer);
|
|
438
|
-
if (newContext && newContext !== 'Keep current') {
|
|
439
|
-
updatePayload.context = newContext;
|
|
440
|
-
}
|
|
441
|
-
break;
|
|
442
|
-
|
|
443
|
-
case 'Labels':
|
|
444
|
-
const labelsAnswer = AskUserQuestion({
|
|
445
|
-
questions: [{
|
|
446
|
-
question: 'Enter labels (comma-separated):',
|
|
447
|
-
header: 'Labels',
|
|
448
|
-
multiSelect: false,
|
|
449
|
-
options: [
|
|
450
|
-
{ label: issue.labels?.join(',') || '', description: 'Keep current labels' }
|
|
451
|
-
]
|
|
452
|
-
}]
|
|
453
|
-
});
|
|
454
|
-
const labelsStr = parseAnswer(labelsAnswer);
|
|
455
|
-
if (labelsStr) {
|
|
456
|
-
updatePayload.labels = labelsStr.split(',').map(l => l.trim());
|
|
457
|
-
}
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Apply update if any
|
|
462
|
-
if (Object.keys(updatePayload).length > 0) {
|
|
463
|
-
// Read, update, write issues.jsonl
|
|
464
|
-
const issuesPath = '.workflow/issues/issues.jsonl';
|
|
465
|
-
const allIssues = Bash(`cat "${issuesPath}"`)
|
|
466
|
-
.split('\n')
|
|
467
|
-
.filter(line => line.trim())
|
|
468
|
-
.map(line => JSON.parse(line));
|
|
469
|
-
|
|
470
|
-
const idx = allIssues.findIndex(i => i.id === issueId);
|
|
471
|
-
if (idx !== -1) {
|
|
472
|
-
allIssues[idx] = {
|
|
473
|
-
...allIssues[idx],
|
|
474
|
-
...updatePayload,
|
|
475
|
-
updated_at: new Date().toISOString()
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
Write(issuesPath, allIssues.map(i => JSON.stringify(i)).join('\n'));
|
|
479
|
-
console.log(`✓ Updated ${issueId}: ${Object.keys(updatePayload).join(', ')}`);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Continue editing or return
|
|
484
|
-
const continueAnswer = AskUserQuestion({
|
|
485
|
-
questions: [{
|
|
486
|
-
question: 'Continue editing?',
|
|
487
|
-
header: 'Continue',
|
|
488
|
-
multiSelect: false,
|
|
489
|
-
options: [
|
|
490
|
-
{ label: 'Edit Another Field', description: 'Continue editing this issue' },
|
|
491
|
-
{ label: 'View Issue', description: 'See updated issue' },
|
|
492
|
-
{ label: 'Back to Menu', description: 'Return to main menu' }
|
|
493
|
-
]
|
|
494
|
-
}]
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
const cont = parseAnswer(continueAnswer);
|
|
498
|
-
if (cont === 'Edit Another Field') {
|
|
499
|
-
await editIssueInteractive(issueId);
|
|
500
|
-
} else if (cont === 'View Issue') {
|
|
501
|
-
await viewIssueInteractive(issueId);
|
|
502
|
-
} else {
|
|
503
|
-
return showMainMenu();
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
```
|
|
91
|
+
## Data Files
|
|
507
92
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const issues = JSON.parse(Bash('ccw issue list --json') || '[]');
|
|
514
|
-
const idAnswer = AskUserQuestion({
|
|
515
|
-
questions: [{
|
|
516
|
-
question: 'Select issue to delete:',
|
|
517
|
-
header: 'Delete',
|
|
518
|
-
multiSelect: false,
|
|
519
|
-
options: issues.slice(0, 10).map(i => ({
|
|
520
|
-
label: i.id,
|
|
521
|
-
description: `${i.status} - ${i.title.substring(0, 40)}`
|
|
522
|
-
}))
|
|
523
|
-
}]
|
|
524
|
-
});
|
|
525
|
-
issueId = parseAnswer(idAnswer);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Confirm deletion
|
|
529
|
-
const confirmAnswer = AskUserQuestion({
|
|
530
|
-
questions: [{
|
|
531
|
-
question: `Delete issue ${issueId}? This will also remove associated solutions.`,
|
|
532
|
-
header: 'Confirm',
|
|
533
|
-
multiSelect: false,
|
|
534
|
-
options: [
|
|
535
|
-
{ label: 'Delete', description: 'Permanently remove issue and solutions' },
|
|
536
|
-
{ label: 'Cancel', description: 'Keep issue' }
|
|
537
|
-
]
|
|
538
|
-
}]
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
if (parseAnswer(confirmAnswer) !== 'Delete') {
|
|
542
|
-
console.log('Deletion cancelled.');
|
|
543
|
-
return showMainMenu();
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Remove from issues.jsonl
|
|
547
|
-
const issuesPath = '.workflow/issues/issues.jsonl';
|
|
548
|
-
const allIssues = Bash(`cat "${issuesPath}"`)
|
|
549
|
-
.split('\n')
|
|
550
|
-
.filter(line => line.trim())
|
|
551
|
-
.map(line => JSON.parse(line));
|
|
552
|
-
|
|
553
|
-
const filtered = allIssues.filter(i => i.id !== issueId);
|
|
554
|
-
Write(issuesPath, filtered.map(i => JSON.stringify(i)).join('\n'));
|
|
555
|
-
|
|
556
|
-
// Remove solutions file if exists
|
|
557
|
-
const solPath = `.workflow/issues/solutions/${issueId}.jsonl`;
|
|
558
|
-
Bash(`rm -f "${solPath}" 2>/dev/null || true`);
|
|
559
|
-
|
|
560
|
-
// Remove from queue if present
|
|
561
|
-
const queuePath = '.workflow/issues/queue.json';
|
|
562
|
-
if (Bash(`test -f "${queuePath}" && echo exists`) === 'exists') {
|
|
563
|
-
const queue = JSON.parse(Bash(`cat "${queuePath}"`));
|
|
564
|
-
queue.queue = queue.queue.filter(q => q.issue_id !== issueId);
|
|
565
|
-
Write(queuePath, JSON.stringify(queue, null, 2));
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
console.log(`✓ Deleted issue ${issueId}`);
|
|
569
|
-
return showMainMenu();
|
|
570
|
-
}
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
### Phase 7: Bulk Operations
|
|
574
|
-
|
|
575
|
-
```javascript
|
|
576
|
-
async function bulkOperationsInteractive() {
|
|
577
|
-
const bulkAnswer = AskUserQuestion({
|
|
578
|
-
questions: [{
|
|
579
|
-
question: 'Select bulk operation:',
|
|
580
|
-
header: 'Bulk',
|
|
581
|
-
multiSelect: false,
|
|
582
|
-
options: [
|
|
583
|
-
{ label: 'Update Status', description: 'Change status of multiple issues' },
|
|
584
|
-
{ label: 'Update Priority', description: 'Change priority of multiple issues' },
|
|
585
|
-
{ label: 'Add Labels', description: 'Add labels to multiple issues' },
|
|
586
|
-
{ label: 'Delete Multiple', description: 'Remove multiple issues' },
|
|
587
|
-
{ label: 'Queue All Planned', description: 'Add all planned issues to queue' },
|
|
588
|
-
{ label: 'Retry All Failed', description: 'Reset all failed tasks to pending' },
|
|
589
|
-
{ label: 'Back', description: 'Return to main menu' }
|
|
590
|
-
]
|
|
591
|
-
}]
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
const operation = parseAnswer(bulkAnswer);
|
|
595
|
-
|
|
596
|
-
if (operation === 'Back') {
|
|
597
|
-
return showMainMenu();
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Get issues for selection
|
|
601
|
-
const allIssues = JSON.parse(Bash('ccw issue list --json') || '[]');
|
|
602
|
-
|
|
603
|
-
if (operation === 'Queue All Planned') {
|
|
604
|
-
const planned = allIssues.filter(i => i.status === 'planned' && i.bound_solution_id);
|
|
605
|
-
for (const issue of planned) {
|
|
606
|
-
Bash(`ccw issue queue add ${issue.id}`);
|
|
607
|
-
console.log(`✓ Queued ${issue.id}`);
|
|
608
|
-
}
|
|
609
|
-
console.log(`\n✓ Queued ${planned.length} issues`);
|
|
610
|
-
return showMainMenu();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (operation === 'Retry All Failed') {
|
|
614
|
-
Bash('ccw issue retry');
|
|
615
|
-
console.log('✓ Reset all failed tasks to pending');
|
|
616
|
-
return showMainMenu();
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Multi-select issues
|
|
620
|
-
const selectAnswer = AskUserQuestion({
|
|
621
|
-
questions: [{
|
|
622
|
-
question: 'Select issues (multi-select):',
|
|
623
|
-
header: 'Select',
|
|
624
|
-
multiSelect: true,
|
|
625
|
-
options: allIssues.slice(0, 15).map(i => ({
|
|
626
|
-
label: i.id,
|
|
627
|
-
description: `${i.status} - ${i.title.substring(0, 30)}`
|
|
628
|
-
}))
|
|
629
|
-
}]
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
const selectedIds = parseMultiAnswer(selectAnswer);
|
|
633
|
-
|
|
634
|
-
if (selectedIds.length === 0) {
|
|
635
|
-
console.log('No issues selected.');
|
|
636
|
-
return showMainMenu();
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Execute bulk operation
|
|
640
|
-
const issuesPath = '.workflow/issues/issues.jsonl';
|
|
641
|
-
let issues = Bash(`cat "${issuesPath}"`)
|
|
642
|
-
.split('\n')
|
|
643
|
-
.filter(line => line.trim())
|
|
644
|
-
.map(line => JSON.parse(line));
|
|
645
|
-
|
|
646
|
-
switch (operation) {
|
|
647
|
-
case 'Update Status':
|
|
648
|
-
const statusAnswer = AskUserQuestion({
|
|
649
|
-
questions: [{
|
|
650
|
-
question: 'Select new status:',
|
|
651
|
-
header: 'Status',
|
|
652
|
-
multiSelect: false,
|
|
653
|
-
options: [
|
|
654
|
-
{ label: 'registered', description: 'Reset to registered' },
|
|
655
|
-
{ label: 'paused', description: 'Pause issues' },
|
|
656
|
-
{ label: 'completed', description: 'Mark completed' }
|
|
657
|
-
]
|
|
658
|
-
}]
|
|
659
|
-
});
|
|
660
|
-
const newStatus = parseAnswer(statusAnswer);
|
|
661
|
-
issues = issues.map(i =>
|
|
662
|
-
selectedIds.includes(i.id)
|
|
663
|
-
? { ...i, status: newStatus, updated_at: new Date().toISOString() }
|
|
664
|
-
: i
|
|
665
|
-
);
|
|
666
|
-
break;
|
|
667
|
-
|
|
668
|
-
case 'Update Priority':
|
|
669
|
-
const prioAnswer = AskUserQuestion({
|
|
670
|
-
questions: [{
|
|
671
|
-
question: 'Select new priority:',
|
|
672
|
-
header: 'Priority',
|
|
673
|
-
multiSelect: false,
|
|
674
|
-
options: [
|
|
675
|
-
{ label: 'P1', description: 'Critical' },
|
|
676
|
-
{ label: 'P2', description: 'High' },
|
|
677
|
-
{ label: 'P3', description: 'Medium' },
|
|
678
|
-
{ label: 'P4', description: 'Low' },
|
|
679
|
-
{ label: 'P5', description: 'Trivial' }
|
|
680
|
-
]
|
|
681
|
-
}]
|
|
682
|
-
});
|
|
683
|
-
const newPrio = parseInt(parseAnswer(prioAnswer).charAt(1));
|
|
684
|
-
issues = issues.map(i =>
|
|
685
|
-
selectedIds.includes(i.id)
|
|
686
|
-
? { ...i, priority: newPrio, updated_at: new Date().toISOString() }
|
|
687
|
-
: i
|
|
688
|
-
);
|
|
689
|
-
break;
|
|
690
|
-
|
|
691
|
-
case 'Add Labels':
|
|
692
|
-
const labelAnswer = AskUserQuestion({
|
|
693
|
-
questions: [{
|
|
694
|
-
question: 'Enter labels to add (comma-separated):',
|
|
695
|
-
header: 'Labels',
|
|
696
|
-
multiSelect: false,
|
|
697
|
-
options: [
|
|
698
|
-
{ label: 'bug', description: 'Bug fix' },
|
|
699
|
-
{ label: 'feature', description: 'New feature' },
|
|
700
|
-
{ label: 'urgent', description: 'Urgent priority' }
|
|
701
|
-
]
|
|
702
|
-
}]
|
|
703
|
-
});
|
|
704
|
-
const newLabels = parseAnswer(labelAnswer).split(',').map(l => l.trim());
|
|
705
|
-
issues = issues.map(i =>
|
|
706
|
-
selectedIds.includes(i.id)
|
|
707
|
-
? {
|
|
708
|
-
...i,
|
|
709
|
-
labels: [...new Set([...(i.labels || []), ...newLabels])],
|
|
710
|
-
updated_at: new Date().toISOString()
|
|
711
|
-
}
|
|
712
|
-
: i
|
|
713
|
-
);
|
|
714
|
-
break;
|
|
715
|
-
|
|
716
|
-
case 'Delete Multiple':
|
|
717
|
-
const confirmDelete = AskUserQuestion({
|
|
718
|
-
questions: [{
|
|
719
|
-
question: `Delete ${selectedIds.length} issues permanently?`,
|
|
720
|
-
header: 'Confirm',
|
|
721
|
-
multiSelect: false,
|
|
722
|
-
options: [
|
|
723
|
-
{ label: 'Delete All', description: 'Remove selected issues' },
|
|
724
|
-
{ label: 'Cancel', description: 'Keep issues' }
|
|
725
|
-
]
|
|
726
|
-
}]
|
|
727
|
-
});
|
|
728
|
-
if (parseAnswer(confirmDelete) === 'Delete All') {
|
|
729
|
-
issues = issues.filter(i => !selectedIds.includes(i.id));
|
|
730
|
-
// Clean up solutions
|
|
731
|
-
for (const id of selectedIds) {
|
|
732
|
-
Bash(`rm -f ".workflow/issues/solutions/${id}.jsonl" 2>/dev/null || true`);
|
|
733
|
-
}
|
|
734
|
-
} else {
|
|
735
|
-
console.log('Deletion cancelled.');
|
|
736
|
-
return showMainMenu();
|
|
737
|
-
}
|
|
738
|
-
break;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
Write(issuesPath, issues.map(i => JSON.stringify(i)).join('\n'));
|
|
742
|
-
console.log(`✓ Updated ${selectedIds.length} issues`);
|
|
743
|
-
return showMainMenu();
|
|
744
|
-
}
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### Phase 8: Create Issue (Redirect)
|
|
748
|
-
|
|
749
|
-
```javascript
|
|
750
|
-
async function createIssueInteractive() {
|
|
751
|
-
const typeAnswer = AskUserQuestion({
|
|
752
|
-
questions: [{
|
|
753
|
-
question: 'Create issue from:',
|
|
754
|
-
header: 'Source',
|
|
755
|
-
multiSelect: false,
|
|
756
|
-
options: [
|
|
757
|
-
{ label: 'GitHub URL', description: 'Import from GitHub issue' },
|
|
758
|
-
{ label: 'Text Description', description: 'Enter problem description' },
|
|
759
|
-
{ label: 'Quick Create', description: 'Just title and priority' }
|
|
760
|
-
]
|
|
761
|
-
}]
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
const type = parseAnswer(typeAnswer);
|
|
765
|
-
|
|
766
|
-
if (type === 'GitHub URL' || type === 'Text Description') {
|
|
767
|
-
console.log('Use /issue:new for structured issue creation');
|
|
768
|
-
console.log('Example: /issue:new https://github.com/org/repo/issues/123');
|
|
769
|
-
return showMainMenu();
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// Quick create
|
|
773
|
-
const titleAnswer = AskUserQuestion({
|
|
774
|
-
questions: [{
|
|
775
|
-
question: 'Enter issue title:',
|
|
776
|
-
header: 'Title',
|
|
777
|
-
multiSelect: false,
|
|
778
|
-
options: [
|
|
779
|
-
{ label: 'Authentication Bug', description: 'Example title' }
|
|
780
|
-
]
|
|
781
|
-
}]
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
const title = parseAnswer(titleAnswer);
|
|
785
|
-
|
|
786
|
-
const prioAnswer = AskUserQuestion({
|
|
787
|
-
questions: [{
|
|
788
|
-
question: 'Select priority:',
|
|
789
|
-
header: 'Priority',
|
|
790
|
-
multiSelect: false,
|
|
791
|
-
options: [
|
|
792
|
-
{ label: 'P3 - Medium (Recommended)', description: 'Normal priority' },
|
|
793
|
-
{ label: 'P1 - Critical', description: 'Production blocking' },
|
|
794
|
-
{ label: 'P2 - High', description: 'Major functionality' }
|
|
795
|
-
]
|
|
796
|
-
}]
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
const priority = parseInt(parseAnswer(prioAnswer).charAt(1));
|
|
800
|
-
|
|
801
|
-
// Generate ID and create
|
|
802
|
-
const id = `ISS-${Date.now()}`;
|
|
803
|
-
Bash(`ccw issue init ${id} --title "${title}" --priority ${priority}`);
|
|
804
|
-
|
|
805
|
-
console.log(`✓ Created issue ${id}`);
|
|
806
|
-
await viewIssueInteractive(id);
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
## Helper Functions
|
|
811
|
-
|
|
812
|
-
```javascript
|
|
813
|
-
function parseAnswer(answer) {
|
|
814
|
-
// Extract selected option from AskUserQuestion response
|
|
815
|
-
if (typeof answer === 'string') return answer;
|
|
816
|
-
if (answer.answers) {
|
|
817
|
-
const values = Object.values(answer.answers);
|
|
818
|
-
return values[0] || '';
|
|
819
|
-
}
|
|
820
|
-
return '';
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function parseMultiAnswer(answer) {
|
|
824
|
-
// Extract multiple selections
|
|
825
|
-
if (typeof answer === 'string') return answer.split(',').map(s => s.trim());
|
|
826
|
-
if (answer.answers) {
|
|
827
|
-
const values = Object.values(answer.answers);
|
|
828
|
-
return values.flatMap(v => v.split(',').map(s => s.trim()));
|
|
829
|
-
}
|
|
830
|
-
return [];
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
function parseFlags(input) {
|
|
834
|
-
const flags = {};
|
|
835
|
-
const matches = input.matchAll(/--(\w+)\s+([^\s-]+)/g);
|
|
836
|
-
for (const match of matches) {
|
|
837
|
-
flags[match[1]] = match[2];
|
|
838
|
-
}
|
|
839
|
-
return flags;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function parseIssueId(input) {
|
|
843
|
-
const match = input.match(/^([A-Z]+-\d+|ISS-\d+|GH-\d+)/i);
|
|
844
|
-
return match ? match[1] : null;
|
|
845
|
-
}
|
|
846
|
-
```
|
|
93
|
+
| File | Purpose |
|
|
94
|
+
|------|---------|
|
|
95
|
+
| `.workflow/issues/issues.jsonl` | Issue records |
|
|
96
|
+
| `.workflow/issues/solutions/<id>.jsonl` | Solutions per issue |
|
|
97
|
+
| `.workflow/issues/queue.json` | Execution queue |
|
|
847
98
|
|
|
848
99
|
## Error Handling
|
|
849
100
|
|
|
@@ -853,7 +104,6 @@ function parseIssueId(input) {
|
|
|
853
104
|
| Issue not found | Show available issues, ask for correction |
|
|
854
105
|
| Invalid selection | Show error, re-prompt |
|
|
855
106
|
| Write failure | Check permissions, show error |
|
|
856
|
-
| Queue operation fails | Show ccw issue error, suggest fix |
|
|
857
107
|
|
|
858
108
|
## Related Commands
|
|
859
109
|
|
|
@@ -861,5 +111,3 @@ function parseIssueId(input) {
|
|
|
861
111
|
- `/issue:plan` - Plan solution for issue
|
|
862
112
|
- `/issue:queue` - Form execution queue
|
|
863
113
|
- `/issue:execute` - Execute queued tasks
|
|
864
|
-
- `ccw issue list` - CLI list command
|
|
865
|
-
- `ccw issue status` - CLI status command
|