claude-code-workflow 6.3.13 → 6.3.15
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 +57 -103
- package/.claude/agents/issue-queue-agent.md +69 -120
- package/.claude/commands/issue/new.md +217 -473
- package/.claude/commands/issue/plan.md +76 -154
- package/.claude/commands/issue/queue.md +208 -259
- package/.claude/skills/issue-manage/SKILL.md +63 -22
- package/.claude/workflows/cli-templates/schemas/discovery-finding-schema.json +3 -3
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +3 -3
- package/.claude/workflows/cli-templates/schemas/queue-schema.json +0 -5
- package/.codex/prompts/issue-plan.md +16 -19
- package/.codex/prompts/issue-queue.md +0 -1
- package/README.md +1 -0
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +3 -1
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +45 -3
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +3 -1
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +383 -30
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/issue-routes.js +77 -16
- package/ccw/dist/core/routes/issue-routes.js.map +1 -1
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +117 -4
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/litellm-executor.d.ts +4 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-executor.js +54 -1
- package/ccw/dist/tools/litellm-executor.js.map +1 -1
- package/ccw/dist/tools/ui-generate-preview.d.ts +18 -0
- package/ccw/dist/tools/ui-generate-preview.d.ts.map +1 -1
- package/ccw/dist/tools/ui-generate-preview.js +26 -10
- package/ccw/dist/tools/ui-generate-preview.js.map +1 -1
- package/ccw/src/cli.ts +3 -1
- package/ccw/src/commands/cli.ts +47 -3
- package/ccw/src/commands/issue.ts +442 -34
- package/ccw/src/core/routes/issue-routes.ts +82 -16
- package/ccw/src/tools/cli-executor.ts +125 -4
- package/ccw/src/tools/litellm-executor.ts +107 -24
- package/ccw/src/tools/ui-generate-preview.js +60 -37
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +25 -2
- package/codex-lens/src/codexlens/entities.py +5 -1
- package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -243
- package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/parsers/factory.py +256 -256
- package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -335
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +30 -1
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/reranker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/embedder.py +6 -9
- package/codex-lens/src/codexlens/semantic/vector_store.py +271 -200
- package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/sqlite_store.py +184 -108
- package/package.json +6 -1
- package/.claude/commands/issue/manage.md +0 -113
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/commands/issue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/commands/issue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoLH,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9B;AA0rDD,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAuGf"}
|
|
@@ -52,11 +52,61 @@ function readIssues() {
|
|
|
52
52
|
function writeIssues(issues) {
|
|
53
53
|
ensureIssuesDir();
|
|
54
54
|
const path = join(getIssuesDir(), 'issues.jsonl');
|
|
55
|
-
|
|
55
|
+
// Always add trailing newline for proper JSONL format
|
|
56
|
+
const content = issues.map(i => JSON.stringify(i)).join('\n');
|
|
57
|
+
writeFileSync(path, content ? content + '\n' : '', 'utf-8');
|
|
56
58
|
}
|
|
57
59
|
function findIssue(issueId) {
|
|
58
60
|
return readIssues().find(i => i.id === issueId);
|
|
59
61
|
}
|
|
62
|
+
// ============ Issue History JSONL ============
|
|
63
|
+
function readIssueHistory() {
|
|
64
|
+
const path = join(getIssuesDir(), 'issue-history.jsonl');
|
|
65
|
+
if (!existsSync(path))
|
|
66
|
+
return [];
|
|
67
|
+
try {
|
|
68
|
+
return readFileSync(path, 'utf-8')
|
|
69
|
+
.split('\n')
|
|
70
|
+
.filter(line => line.trim())
|
|
71
|
+
.map(line => JSON.parse(line));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function appendIssueHistory(issue) {
|
|
78
|
+
ensureIssuesDir();
|
|
79
|
+
const path = join(getIssuesDir(), 'issue-history.jsonl');
|
|
80
|
+
const line = JSON.stringify(issue) + '\n';
|
|
81
|
+
// Append to history file
|
|
82
|
+
if (existsSync(path)) {
|
|
83
|
+
const content = readFileSync(path, 'utf-8');
|
|
84
|
+
// Ensure proper newline before appending
|
|
85
|
+
const needsNewline = content.length > 0 && !content.endsWith('\n');
|
|
86
|
+
writeFileSync(path, (needsNewline ? '\n' : '') + line, { flag: 'a' });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
writeFileSync(path, line, 'utf-8');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Move completed issue from issues.jsonl to issue-history.jsonl
|
|
94
|
+
*/
|
|
95
|
+
function moveIssueToHistory(issueId) {
|
|
96
|
+
const issues = readIssues();
|
|
97
|
+
const idx = issues.findIndex(i => i.id === issueId);
|
|
98
|
+
if (idx === -1)
|
|
99
|
+
return false;
|
|
100
|
+
const issue = issues[idx];
|
|
101
|
+
if (issue.status !== 'completed')
|
|
102
|
+
return false;
|
|
103
|
+
// Append to history
|
|
104
|
+
appendIssueHistory(issue);
|
|
105
|
+
// Remove from active issues
|
|
106
|
+
issues.splice(idx, 1);
|
|
107
|
+
writeIssues(issues);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
60
110
|
function updateIssue(issueId, updates) {
|
|
61
111
|
const issues = readIssues();
|
|
62
112
|
const idx = issues.findIndex(i => i.id === issueId);
|
|
@@ -64,8 +114,59 @@ function updateIssue(issueId, updates) {
|
|
|
64
114
|
return false;
|
|
65
115
|
issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
|
|
66
116
|
writeIssues(issues);
|
|
117
|
+
// Auto-move to history when completed
|
|
118
|
+
if (updates.status === 'completed') {
|
|
119
|
+
moveIssueToHistory(issueId);
|
|
120
|
+
}
|
|
67
121
|
return true;
|
|
68
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate auto-increment issue ID: ISS-YYYYMMDD-NNN
|
|
125
|
+
*/
|
|
126
|
+
function generateIssueId(existingIssues = []) {
|
|
127
|
+
const today = new Date();
|
|
128
|
+
const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
|
|
129
|
+
const prefix = `ISS-${dateStr}-`;
|
|
130
|
+
const todayPattern = new RegExp(`^ISS-${dateStr}-(\\d{3})$`);
|
|
131
|
+
let maxSeq = 0;
|
|
132
|
+
for (const issue of existingIssues) {
|
|
133
|
+
const match = issue.id.match(todayPattern);
|
|
134
|
+
if (match)
|
|
135
|
+
maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
|
|
136
|
+
}
|
|
137
|
+
return `${prefix}${String(maxSeq + 1).padStart(3, '0')}`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create a new issue with proper JSONL handling
|
|
141
|
+
* Auto-generates ID if not provided
|
|
142
|
+
*/
|
|
143
|
+
function createIssue(data) {
|
|
144
|
+
const issues = readIssues();
|
|
145
|
+
const issueId = data.id || generateIssueId(issues);
|
|
146
|
+
if (issues.some(i => i.id === issueId)) {
|
|
147
|
+
throw new Error(`Issue "${issueId}" already exists`);
|
|
148
|
+
}
|
|
149
|
+
const newIssue = {
|
|
150
|
+
id: issueId,
|
|
151
|
+
title: data.title || issueId,
|
|
152
|
+
status: data.status || 'registered',
|
|
153
|
+
priority: data.priority || 3,
|
|
154
|
+
context: data.context || '',
|
|
155
|
+
source: data.source,
|
|
156
|
+
source_url: data.source_url,
|
|
157
|
+
tags: data.tags,
|
|
158
|
+
expected_behavior: data.expected_behavior,
|
|
159
|
+
actual_behavior: data.actual_behavior,
|
|
160
|
+
affected_components: data.affected_components,
|
|
161
|
+
feedback: data.feedback,
|
|
162
|
+
bound_solution_id: data.bound_solution_id || null,
|
|
163
|
+
created_at: new Date().toISOString(),
|
|
164
|
+
updated_at: new Date().toISOString()
|
|
165
|
+
};
|
|
166
|
+
issues.push(newIssue);
|
|
167
|
+
writeIssues(issues);
|
|
168
|
+
return newIssue;
|
|
169
|
+
}
|
|
69
170
|
// ============ Solutions JSONL ============
|
|
70
171
|
function getSolutionsPath(issueId) {
|
|
71
172
|
return join(getIssuesDir(), 'solutions', `${issueId}.jsonl`);
|
|
@@ -88,7 +189,9 @@ function writeSolutions(issueId, solutions) {
|
|
|
88
189
|
const dir = join(getIssuesDir(), 'solutions');
|
|
89
190
|
if (!existsSync(dir))
|
|
90
191
|
mkdirSync(dir, { recursive: true });
|
|
91
|
-
|
|
192
|
+
// Always add trailing newline for proper JSONL format
|
|
193
|
+
const content = solutions.map(s => JSON.stringify(s)).join('\n');
|
|
194
|
+
writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
|
|
92
195
|
}
|
|
93
196
|
function findSolution(issueId, solutionId) {
|
|
94
197
|
return readSolutions(issueId).find(s => s.id === solutionId);
|
|
@@ -96,9 +199,52 @@ function findSolution(issueId, solutionId) {
|
|
|
96
199
|
function getBoundSolution(issueId) {
|
|
97
200
|
return readSolutions(issueId).find(s => s.is_bound);
|
|
98
201
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Generate solution ID in format: SOL-{issue-id}-{seq}
|
|
204
|
+
* @param issueId - The issue ID to include in the solution ID
|
|
205
|
+
* @param existingSolutions - Existing solutions to calculate next sequence number
|
|
206
|
+
* @returns Solution ID like "SOL-GH-123-1" or "SOL-ISS-20251229-001-2"
|
|
207
|
+
*/
|
|
208
|
+
function generateSolutionId(issueId, existingSolutions = []) {
|
|
209
|
+
// Find the highest existing sequence number for this issue
|
|
210
|
+
const pattern = new RegExp(`^SOL-${issueId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)$`);
|
|
211
|
+
let maxSeq = 0;
|
|
212
|
+
for (const sol of existingSolutions) {
|
|
213
|
+
const match = sol.id.match(pattern);
|
|
214
|
+
if (match) {
|
|
215
|
+
maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return `SOL-${issueId}-${maxSeq + 1}`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Create a new solution with proper JSONL handling
|
|
222
|
+
* Auto-generates ID if not provided
|
|
223
|
+
*/
|
|
224
|
+
function createSolution(issueId, data) {
|
|
225
|
+
const issue = findIssue(issueId);
|
|
226
|
+
if (!issue) {
|
|
227
|
+
throw new Error(`Issue "${issueId}" not found`);
|
|
228
|
+
}
|
|
229
|
+
const solutions = readSolutions(issueId);
|
|
230
|
+
const solutionId = data.id || generateSolutionId(issueId, solutions);
|
|
231
|
+
if (solutions.some(s => s.id === solutionId)) {
|
|
232
|
+
throw new Error(`Solution "${solutionId}" already exists`);
|
|
233
|
+
}
|
|
234
|
+
const newSolution = {
|
|
235
|
+
id: solutionId,
|
|
236
|
+
description: data.description || '',
|
|
237
|
+
approach: data.approach || '',
|
|
238
|
+
tasks: data.tasks || [],
|
|
239
|
+
exploration_context: data.exploration_context,
|
|
240
|
+
analysis: data.analysis,
|
|
241
|
+
score: data.score,
|
|
242
|
+
is_bound: false,
|
|
243
|
+
created_at: new Date().toISOString()
|
|
244
|
+
};
|
|
245
|
+
solutions.push(newSolution);
|
|
246
|
+
writeSolutions(issueId, solutions);
|
|
247
|
+
return newSolution;
|
|
102
248
|
}
|
|
103
249
|
// ============ Queue Management (Multi-Queue) ============
|
|
104
250
|
function getQueuesDir() {
|
|
@@ -235,7 +381,54 @@ function generateQueueItemId(queue, level = 'solution') {
|
|
|
235
381
|
}
|
|
236
382
|
// ============ Commands ============
|
|
237
383
|
/**
|
|
238
|
-
*
|
|
384
|
+
* create - Create issue from JSON data
|
|
385
|
+
* Usage: ccw issue create --data '{"title":"...", "context":"..."}'
|
|
386
|
+
* Output: JSON with created issue (includes auto-generated ID)
|
|
387
|
+
*/
|
|
388
|
+
async function createAction(options) {
|
|
389
|
+
if (!options.data) {
|
|
390
|
+
console.error(chalk.red('JSON data required'));
|
|
391
|
+
console.error(chalk.gray('Usage: ccw issue create --data \'{"title":"...", "context":"..."}\''));
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const data = JSON.parse(options.data);
|
|
396
|
+
const issue = createIssue(data);
|
|
397
|
+
console.log(JSON.stringify(issue, null, 2));
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
console.error(chalk.red(err.message));
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* solution - Create solution from JSON data
|
|
406
|
+
* Usage: ccw issue solution <issue-id> --data '{"tasks":[...]}'
|
|
407
|
+
* Output: JSON with created solution (includes auto-generated ID)
|
|
408
|
+
*/
|
|
409
|
+
async function solutionAction(issueId, options) {
|
|
410
|
+
if (!issueId) {
|
|
411
|
+
console.error(chalk.red('Issue ID required'));
|
|
412
|
+
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
if (!options.data) {
|
|
416
|
+
console.error(chalk.red('JSON data required'));
|
|
417
|
+
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const data = JSON.parse(options.data);
|
|
422
|
+
const solution = createSolution(issueId, data);
|
|
423
|
+
console.log(JSON.stringify(solution, null, 2));
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
console.error(chalk.red(err.message));
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* init - Initialize a new issue (manual ID)
|
|
239
432
|
*/
|
|
240
433
|
async function initAction(issueId, options) {
|
|
241
434
|
if (!issueId) {
|
|
@@ -257,7 +450,6 @@ async function initAction(issueId, options) {
|
|
|
257
450
|
priority: options.priority ? parseInt(options.priority) : 3,
|
|
258
451
|
context: options.description || '',
|
|
259
452
|
bound_solution_id: null,
|
|
260
|
-
solution_count: 0,
|
|
261
453
|
created_at: new Date().toISOString(),
|
|
262
454
|
updated_at: new Date().toISOString()
|
|
263
455
|
};
|
|
@@ -278,9 +470,17 @@ async function listAction(issueId, options) {
|
|
|
278
470
|
const statuses = options.status.split(',').map(s => s.trim());
|
|
279
471
|
issues = issues.filter(i => statuses.includes(i.status));
|
|
280
472
|
}
|
|
281
|
-
//
|
|
282
|
-
if (options.
|
|
283
|
-
issues.
|
|
473
|
+
// Brief mode: minimal fields only (id, title, status, priority, tags, bound_solution_id)
|
|
474
|
+
if (options.brief) {
|
|
475
|
+
const briefIssues = issues.map(i => ({
|
|
476
|
+
id: i.id,
|
|
477
|
+
title: i.title,
|
|
478
|
+
status: i.status,
|
|
479
|
+
priority: i.priority,
|
|
480
|
+
tags: i.tags || [],
|
|
481
|
+
bound_solution_id: i.bound_solution_id
|
|
482
|
+
}));
|
|
483
|
+
console.log(JSON.stringify(briefIssues, null, 2));
|
|
284
484
|
return;
|
|
285
485
|
}
|
|
286
486
|
if (options.json) {
|
|
@@ -306,7 +506,8 @@ async function listAction(issueId, options) {
|
|
|
306
506
|
'failed': chalk.red,
|
|
307
507
|
'paused': chalk.magenta
|
|
308
508
|
}[issue.status] || chalk.white;
|
|
309
|
-
const
|
|
509
|
+
const solutionCount = readSolutions(issue.id).length;
|
|
510
|
+
const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
|
|
310
511
|
console.log(issue.id.padEnd(20) +
|
|
311
512
|
statusColor(issue.status.padEnd(15)) +
|
|
312
513
|
bound.padEnd(12) +
|
|
@@ -344,6 +545,43 @@ async function listAction(issueId, options) {
|
|
|
344
545
|
task.title.substring(0, 30));
|
|
345
546
|
}
|
|
346
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* history - List completed issues from history
|
|
550
|
+
*/
|
|
551
|
+
async function historyAction(options) {
|
|
552
|
+
const history = readIssueHistory();
|
|
553
|
+
// Brief mode: minimal fields only
|
|
554
|
+
if (options.brief) {
|
|
555
|
+
const briefHistory = history.map(i => ({
|
|
556
|
+
id: i.id,
|
|
557
|
+
title: i.title,
|
|
558
|
+
status: i.status,
|
|
559
|
+
completed_at: i.completed_at
|
|
560
|
+
}));
|
|
561
|
+
console.log(JSON.stringify(briefHistory, null, 2));
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (options.json) {
|
|
565
|
+
console.log(JSON.stringify(history, null, 2));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (history.length === 0) {
|
|
569
|
+
console.log(chalk.yellow('No completed issues in history'));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
console.log(chalk.bold.cyan('\nIssue History (Completed)\n'));
|
|
573
|
+
console.log(chalk.gray('ID'.padEnd(25) + 'Completed At'.padEnd(22) + 'Title'));
|
|
574
|
+
console.log(chalk.gray('-'.repeat(80)));
|
|
575
|
+
for (const issue of history) {
|
|
576
|
+
const completedAt = issue.completed_at
|
|
577
|
+
? new Date(issue.completed_at).toLocaleString()
|
|
578
|
+
: 'N/A';
|
|
579
|
+
console.log(chalk.green(issue.id.padEnd(25)) +
|
|
580
|
+
completedAt.padEnd(22) +
|
|
581
|
+
(issue.title || '').substring(0, 35));
|
|
582
|
+
}
|
|
583
|
+
console.log(chalk.gray(`\nTotal: ${history.length} completed issues`));
|
|
584
|
+
}
|
|
347
585
|
/**
|
|
348
586
|
* status - Show detailed status
|
|
349
587
|
*/
|
|
@@ -416,7 +654,7 @@ async function taskAction(issueId, taskId, options) {
|
|
|
416
654
|
// Create default solution if none bound
|
|
417
655
|
if (boundIdx === -1) {
|
|
418
656
|
const newSol = {
|
|
419
|
-
id: generateSolutionId(),
|
|
657
|
+
id: generateSolutionId(issueId, solutions),
|
|
420
658
|
description: 'Manual tasks',
|
|
421
659
|
tasks: [],
|
|
422
660
|
is_bound: true,
|
|
@@ -479,11 +717,82 @@ async function taskAction(issueId, taskId, options) {
|
|
|
479
717
|
}
|
|
480
718
|
/**
|
|
481
719
|
* update - Update issue fields (status, priority, title, etc.)
|
|
720
|
+
* --from-queue: Sync statuses from active queue (auto-update queued issues)
|
|
482
721
|
*/
|
|
483
722
|
async function updateAction(issueId, options) {
|
|
723
|
+
// Handle --from-queue: Sync statuses from queue
|
|
724
|
+
if (options.fromQueue) {
|
|
725
|
+
// Determine queue ID: string value = specific queue, true = active queue
|
|
726
|
+
const queueId = typeof options.fromQueue === 'string' ? options.fromQueue : undefined;
|
|
727
|
+
const queue = queueId ? readQueue(queueId) : readActiveQueue();
|
|
728
|
+
if (!queue) {
|
|
729
|
+
if (options.json) {
|
|
730
|
+
console.log(JSON.stringify({ success: false, message: `Queue not found: ${queueId}`, queued: [], unplanned: [] }));
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
console.log(chalk.red(`Queue not found: ${queueId}`));
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const items = queue.solutions || queue.tasks || [];
|
|
738
|
+
const allIssues = readIssues();
|
|
739
|
+
if (!queue.id || items.length === 0) {
|
|
740
|
+
if (options.json) {
|
|
741
|
+
console.log(JSON.stringify({ success: false, message: 'No active queue', queued: [], unplanned: [] }));
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
console.log(chalk.yellow('No active queue to sync from'));
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
// Get issue IDs from queue
|
|
749
|
+
const queuedIssueIds = new Set(items.map(item => item.issue_id));
|
|
750
|
+
const now = new Date().toISOString();
|
|
751
|
+
// Track updates
|
|
752
|
+
const updated = [];
|
|
753
|
+
const unplanned = [];
|
|
754
|
+
// Update queued issues
|
|
755
|
+
for (const issueId of queuedIssueIds) {
|
|
756
|
+
const issue = allIssues.find(i => i.id === issueId);
|
|
757
|
+
if (issue && issue.status !== 'queued' && issue.status !== 'executing' && issue.status !== 'completed') {
|
|
758
|
+
updateIssue(issueId, { status: 'queued', queued_at: now });
|
|
759
|
+
updated.push(issueId);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Find planned issues NOT in queue
|
|
763
|
+
for (const issue of allIssues) {
|
|
764
|
+
if (issue.status === 'planned' && issue.bound_solution_id && !queuedIssueIds.has(issue.id)) {
|
|
765
|
+
unplanned.push(issue.id);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (options.json) {
|
|
769
|
+
console.log(JSON.stringify({
|
|
770
|
+
success: true,
|
|
771
|
+
queue_id: queue.id,
|
|
772
|
+
queued: updated,
|
|
773
|
+
queued_count: updated.length,
|
|
774
|
+
unplanned: unplanned,
|
|
775
|
+
unplanned_count: unplanned.length
|
|
776
|
+
}, null, 2));
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
console.log(chalk.green(`✓ Synced from queue ${queue.id}`));
|
|
780
|
+
console.log(chalk.gray(` Updated to 'queued': ${updated.length} issues`));
|
|
781
|
+
if (updated.length > 0) {
|
|
782
|
+
updated.forEach(id => console.log(chalk.gray(` - ${id}`)));
|
|
783
|
+
}
|
|
784
|
+
if (unplanned.length > 0) {
|
|
785
|
+
console.log(chalk.yellow(` Planned but NOT in queue: ${unplanned.length} issues`));
|
|
786
|
+
unplanned.forEach(id => console.log(chalk.yellow(` - ${id}`)));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
// Standard single-issue update
|
|
484
792
|
if (!issueId) {
|
|
485
793
|
console.error(chalk.red('Issue ID is required'));
|
|
486
|
-
console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>
|
|
794
|
+
console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>'));
|
|
795
|
+
console.error(chalk.gray(' ccw issue update --from-queue [queue-id] (sync from queue)'));
|
|
487
796
|
process.exit(1);
|
|
488
797
|
}
|
|
489
798
|
const issue = findIssue(issueId);
|
|
@@ -553,8 +862,9 @@ async function bindAction(issueId, solutionId, options) {
|
|
|
553
862
|
try {
|
|
554
863
|
const content = readFileSync(options.solution, 'utf-8');
|
|
555
864
|
const data = JSON.parse(content);
|
|
865
|
+
// Priority: CLI arg > file content ID > generate new (SOL-{issue-id}-{seq})
|
|
556
866
|
const newSol = {
|
|
557
|
-
id: solutionId || generateSolutionId(),
|
|
867
|
+
id: solutionId || data.id || generateSolutionId(issueId, solutions),
|
|
558
868
|
description: data.description || data.approach_name || 'Imported solution',
|
|
559
869
|
tasks: data.tasks || [],
|
|
560
870
|
exploration_context: data.exploration_context,
|
|
@@ -599,7 +909,6 @@ async function bindAction(issueId, solutionId, options) {
|
|
|
599
909
|
writeSolutions(issueId, solutions);
|
|
600
910
|
updateIssue(issueId, {
|
|
601
911
|
bound_solution_id: solutionId,
|
|
602
|
-
solution_count: solutions.length,
|
|
603
912
|
status: 'planned',
|
|
604
913
|
planned_at: new Date().toISOString()
|
|
605
914
|
});
|
|
@@ -612,6 +921,21 @@ async function queueAction(subAction, issueId, options) {
|
|
|
612
921
|
// List all queues (history)
|
|
613
922
|
if (subAction === 'list' || subAction === 'history') {
|
|
614
923
|
const index = readQueueIndex();
|
|
924
|
+
// Brief mode: minimal queue index info
|
|
925
|
+
if (options.brief) {
|
|
926
|
+
const briefIndex = {
|
|
927
|
+
active_queue_id: index.active_queue_id,
|
|
928
|
+
queues: index.queues.map(q => ({
|
|
929
|
+
id: q.id,
|
|
930
|
+
status: q.status,
|
|
931
|
+
issue_ids: q.issue_ids,
|
|
932
|
+
total_solutions: q.total_solutions,
|
|
933
|
+
completed_solutions: q.completed_solutions
|
|
934
|
+
}))
|
|
935
|
+
};
|
|
936
|
+
console.log(JSON.stringify(briefIndex, null, 2));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
615
939
|
if (options.json) {
|
|
616
940
|
console.log(JSON.stringify(index, null, 2));
|
|
617
941
|
return;
|
|
@@ -673,7 +997,6 @@ async function queueAction(subAction, issueId, options) {
|
|
|
673
997
|
issue_id: item.issue_id,
|
|
674
998
|
solution_id: item.solution_id,
|
|
675
999
|
status: item.status,
|
|
676
|
-
executor: item.assigned_executor,
|
|
677
1000
|
priority: item.semantic_priority,
|
|
678
1001
|
depends_on: item.depends_on || [],
|
|
679
1002
|
task_count: item.task_count || 1,
|
|
@@ -860,7 +1183,6 @@ async function queueAction(subAction, issueId, options) {
|
|
|
860
1183
|
execution_group: 'P1',
|
|
861
1184
|
depends_on: [],
|
|
862
1185
|
semantic_priority: 0.5,
|
|
863
|
-
assigned_executor: 'codex',
|
|
864
1186
|
task_count: solution.tasks?.length || 0,
|
|
865
1187
|
files_touched: Array.from(filesTouched)
|
|
866
1188
|
});
|
|
@@ -874,6 +1196,27 @@ async function queueAction(subAction, issueId, options) {
|
|
|
874
1196
|
}
|
|
875
1197
|
// Show current queue
|
|
876
1198
|
const queue = readActiveQueue();
|
|
1199
|
+
// Brief mode: minimal queue info (id, issue_ids, item summaries)
|
|
1200
|
+
if (options.brief) {
|
|
1201
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1202
|
+
const briefQueue = {
|
|
1203
|
+
id: queue.id,
|
|
1204
|
+
issue_ids: queue.issue_ids || [],
|
|
1205
|
+
total: items.length,
|
|
1206
|
+
pending: items.filter(i => i.status === 'pending').length,
|
|
1207
|
+
executing: items.filter(i => i.status === 'executing').length,
|
|
1208
|
+
completed: items.filter(i => i.status === 'completed').length,
|
|
1209
|
+
items: items.map(i => ({
|
|
1210
|
+
item_id: i.item_id,
|
|
1211
|
+
issue_id: i.issue_id,
|
|
1212
|
+
solution_id: i.solution_id,
|
|
1213
|
+
status: i.status,
|
|
1214
|
+
task_count: i.task_count
|
|
1215
|
+
}))
|
|
1216
|
+
};
|
|
1217
|
+
console.log(JSON.stringify(briefQueue, null, 2));
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
877
1220
|
if (options.json) {
|
|
878
1221
|
console.log(JSON.stringify(queue, null, 2));
|
|
879
1222
|
return;
|
|
@@ -893,12 +1236,12 @@ async function queueAction(subAction, issueId, options) {
|
|
|
893
1236
|
console.log(chalk.gray(`Total: ${items.length} | Pending: ${items.filter(i => i.status === 'pending').length} | Executing: ${items.filter(i => i.status === 'executing').length} | Completed: ${items.filter(i => i.status === 'completed').length}`));
|
|
894
1237
|
console.log();
|
|
895
1238
|
if (isSolutionLevel) {
|
|
896
|
-
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'
|
|
1239
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'));
|
|
897
1240
|
}
|
|
898
1241
|
else {
|
|
899
|
-
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'
|
|
1242
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'));
|
|
900
1243
|
}
|
|
901
|
-
console.log(chalk.gray('-'.repeat(
|
|
1244
|
+
console.log(chalk.gray('-'.repeat(48)));
|
|
902
1245
|
for (const item of items) {
|
|
903
1246
|
const statusColor = {
|
|
904
1247
|
'pending': chalk.gray,
|
|
@@ -914,8 +1257,7 @@ async function queueAction(subAction, issueId, options) {
|
|
|
914
1257
|
console.log(item.item_id.padEnd(10) +
|
|
915
1258
|
item.issue_id.substring(0, 13).padEnd(15) +
|
|
916
1259
|
thirdCol +
|
|
917
|
-
statusColor(item.status
|
|
918
|
-
item.assigned_executor);
|
|
1260
|
+
statusColor(item.status));
|
|
919
1261
|
}
|
|
920
1262
|
}
|
|
921
1263
|
/**
|
|
@@ -1015,7 +1357,6 @@ async function nextAction(itemId, options) {
|
|
|
1015
1357
|
resumed: isResume,
|
|
1016
1358
|
resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
|
|
1017
1359
|
execution_hints: {
|
|
1018
|
-
executor: nextItem.assigned_executor,
|
|
1019
1360
|
task_count: solution.tasks?.length || 0,
|
|
1020
1361
|
estimated_minutes: totalMinutes
|
|
1021
1362
|
},
|
|
@@ -1066,7 +1407,6 @@ async function detailAction(itemId, options) {
|
|
|
1066
1407
|
exploration_context: solution.exploration_context || {}
|
|
1067
1408
|
},
|
|
1068
1409
|
execution_hints: {
|
|
1069
|
-
executor: queueItem.assigned_executor,
|
|
1070
1410
|
task_count: solution.tasks?.length || 0,
|
|
1071
1411
|
estimated_minutes: totalMinutes
|
|
1072
1412
|
}
|
|
@@ -1183,12 +1523,21 @@ async function retryAction(issueId, options) {
|
|
|
1183
1523
|
export async function issueCommand(subcommand, args, options) {
|
|
1184
1524
|
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
|
|
1185
1525
|
switch (subcommand) {
|
|
1526
|
+
case 'create':
|
|
1527
|
+
await createAction(options);
|
|
1528
|
+
break;
|
|
1529
|
+
case 'solution':
|
|
1530
|
+
await solutionAction(argsArray[0], options);
|
|
1531
|
+
break;
|
|
1186
1532
|
case 'init':
|
|
1187
1533
|
await initAction(argsArray[0], options);
|
|
1188
1534
|
break;
|
|
1189
1535
|
case 'list':
|
|
1190
1536
|
await listAction(argsArray[0], options);
|
|
1191
1537
|
break;
|
|
1538
|
+
case 'history':
|
|
1539
|
+
await historyAction(options);
|
|
1540
|
+
break;
|
|
1192
1541
|
case 'status':
|
|
1193
1542
|
await statusAction(argsArray[0], options);
|
|
1194
1543
|
break;
|
|
@@ -1230,12 +1579,15 @@ export async function issueCommand(subcommand, args, options) {
|
|
|
1230
1579
|
default:
|
|
1231
1580
|
console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
|
|
1232
1581
|
console.log(chalk.bold('Core Commands:'));
|
|
1233
|
-
console.log(chalk.gray('
|
|
1582
|
+
console.log(chalk.gray(' create --data \'{"title":"..."}\' Create issue (auto-generates ID)'));
|
|
1583
|
+
console.log(chalk.gray(' init <issue-id> Initialize new issue (manual ID)'));
|
|
1234
1584
|
console.log(chalk.gray(' list [issue-id] List issues or tasks'));
|
|
1585
|
+
console.log(chalk.gray(' history List completed issues (from history)'));
|
|
1235
1586
|
console.log(chalk.gray(' status [issue-id] Show detailed status'));
|
|
1236
|
-
console.log(chalk.gray('
|
|
1237
|
-
console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution
|
|
1238
|
-
console.log(chalk.gray(' update <issue-id>
|
|
1587
|
+
console.log(chalk.gray(' solution <id> --data \'{...}\' Create solution (auto-generates ID)'));
|
|
1588
|
+
console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution'));
|
|
1589
|
+
console.log(chalk.gray(' update <issue-id> --status <s> Update issue status'));
|
|
1590
|
+
console.log(chalk.gray(' update --from-queue [queue-id] Sync statuses from queue (default: active)'));
|
|
1239
1591
|
console.log();
|
|
1240
1592
|
console.log(chalk.bold('Queue Commands:'));
|
|
1241
1593
|
console.log(chalk.gray(' queue Show active queue'));
|
|
@@ -1256,7 +1608,7 @@ export async function issueCommand(subcommand, args, options) {
|
|
|
1256
1608
|
console.log(chalk.bold('Options:'));
|
|
1257
1609
|
console.log(chalk.gray(' --title <title> Issue/task title'));
|
|
1258
1610
|
console.log(chalk.gray(' --status <status> Filter by status (comma-separated)'));
|
|
1259
|
-
console.log(chalk.gray(' --
|
|
1611
|
+
console.log(chalk.gray(' --brief Brief JSON output (minimal fields)'));
|
|
1260
1612
|
console.log(chalk.gray(' --solution <path> Solution JSON file'));
|
|
1261
1613
|
console.log(chalk.gray(' --result <json> Execution result'));
|
|
1262
1614
|
console.log(chalk.gray(' --reason <text> Failure reason'));
|
|
@@ -1264,7 +1616,8 @@ export async function issueCommand(subcommand, args, options) {
|
|
|
1264
1616
|
console.log(chalk.gray(' --force Force operation'));
|
|
1265
1617
|
console.log();
|
|
1266
1618
|
console.log(chalk.bold('Storage:'));
|
|
1267
|
-
console.log(chalk.gray(' .workflow/issues/issues.jsonl
|
|
1619
|
+
console.log(chalk.gray(' .workflow/issues/issues.jsonl Active issues'));
|
|
1620
|
+
console.log(chalk.gray(' .workflow/issues/issue-history.jsonl Completed issues'));
|
|
1268
1621
|
console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
|
|
1269
1622
|
console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
|
|
1270
1623
|
console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));
|