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
|
@@ -18,14 +18,34 @@ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
|
|
|
18
18
|
|
|
19
19
|
// ============ Interfaces ============
|
|
20
20
|
|
|
21
|
+
interface IssueFeedback {
|
|
22
|
+
type: 'failure' | 'clarification' | 'rejection';
|
|
23
|
+
stage: string; // new/plan/execute
|
|
24
|
+
content: string;
|
|
25
|
+
created_at: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
interface Issue {
|
|
22
29
|
id: string;
|
|
23
30
|
title: string;
|
|
24
31
|
status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
|
|
25
32
|
priority: number;
|
|
26
|
-
context: string;
|
|
33
|
+
context: string; // Problem description (single source of truth)
|
|
34
|
+
source?: 'github' | 'text' | 'discovery';
|
|
35
|
+
source_url?: string;
|
|
36
|
+
tags?: string[];
|
|
37
|
+
|
|
38
|
+
// Optional structured fields
|
|
39
|
+
expected_behavior?: string;
|
|
40
|
+
actual_behavior?: string;
|
|
41
|
+
affected_components?: string[];
|
|
42
|
+
|
|
43
|
+
// Feedback history (failures + human clarifications)
|
|
44
|
+
feedback?: IssueFeedback[];
|
|
45
|
+
|
|
46
|
+
// Solution binding
|
|
27
47
|
bound_solution_id: string | null;
|
|
28
|
-
|
|
48
|
+
|
|
29
49
|
// Timestamps
|
|
30
50
|
created_at: string;
|
|
31
51
|
updated_at: string;
|
|
@@ -98,7 +118,6 @@ interface QueueItem {
|
|
|
98
118
|
execution_group: string;
|
|
99
119
|
depends_on: string[];
|
|
100
120
|
semantic_priority: number;
|
|
101
|
-
assigned_executor: 'codex' | 'gemini' | 'agent';
|
|
102
121
|
task_count?: number; // For solution-level queues
|
|
103
122
|
files_touched?: string[]; // For solution-level queues
|
|
104
123
|
queued_at?: string;
|
|
@@ -175,7 +194,9 @@ interface IssueOptions {
|
|
|
175
194
|
json?: boolean;
|
|
176
195
|
force?: boolean;
|
|
177
196
|
fail?: boolean;
|
|
178
|
-
|
|
197
|
+
brief?: boolean; // List brief info only (id, title, status, priority, tags) - JSON format
|
|
198
|
+
data?: string; // JSON data for create
|
|
199
|
+
fromQueue?: boolean | string; // Sync statuses from queue (true=active, string=specific queue ID)
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
const ISSUES_DIR = '.workflow/issues';
|
|
@@ -222,22 +243,132 @@ function readIssues(): Issue[] {
|
|
|
222
243
|
function writeIssues(issues: Issue[]): void {
|
|
223
244
|
ensureIssuesDir();
|
|
224
245
|
const path = join(getIssuesDir(), 'issues.jsonl');
|
|
225
|
-
|
|
246
|
+
// Always add trailing newline for proper JSONL format
|
|
247
|
+
const content = issues.map(i => JSON.stringify(i)).join('\n');
|
|
248
|
+
writeFileSync(path, content ? content + '\n' : '', 'utf-8');
|
|
226
249
|
}
|
|
227
250
|
|
|
228
251
|
function findIssue(issueId: string): Issue | undefined {
|
|
229
252
|
return readIssues().find(i => i.id === issueId);
|
|
230
253
|
}
|
|
231
254
|
|
|
255
|
+
// ============ Issue History JSONL ============
|
|
256
|
+
|
|
257
|
+
function readIssueHistory(): Issue[] {
|
|
258
|
+
const path = join(getIssuesDir(), 'issue-history.jsonl');
|
|
259
|
+
if (!existsSync(path)) return [];
|
|
260
|
+
try {
|
|
261
|
+
return readFileSync(path, 'utf-8')
|
|
262
|
+
.split('\n')
|
|
263
|
+
.filter(line => line.trim())
|
|
264
|
+
.map(line => JSON.parse(line));
|
|
265
|
+
} catch {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function appendIssueHistory(issue: Issue): void {
|
|
271
|
+
ensureIssuesDir();
|
|
272
|
+
const path = join(getIssuesDir(), 'issue-history.jsonl');
|
|
273
|
+
const line = JSON.stringify(issue) + '\n';
|
|
274
|
+
// Append to history file
|
|
275
|
+
if (existsSync(path)) {
|
|
276
|
+
const content = readFileSync(path, 'utf-8');
|
|
277
|
+
// Ensure proper newline before appending
|
|
278
|
+
const needsNewline = content.length > 0 && !content.endsWith('\n');
|
|
279
|
+
writeFileSync(path, (needsNewline ? '\n' : '') + line, { flag: 'a' });
|
|
280
|
+
} else {
|
|
281
|
+
writeFileSync(path, line, 'utf-8');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Move completed issue from issues.jsonl to issue-history.jsonl
|
|
287
|
+
*/
|
|
288
|
+
function moveIssueToHistory(issueId: string): boolean {
|
|
289
|
+
const issues = readIssues();
|
|
290
|
+
const idx = issues.findIndex(i => i.id === issueId);
|
|
291
|
+
if (idx === -1) return false;
|
|
292
|
+
|
|
293
|
+
const issue = issues[idx];
|
|
294
|
+
if (issue.status !== 'completed') return false;
|
|
295
|
+
|
|
296
|
+
// Append to history
|
|
297
|
+
appendIssueHistory(issue);
|
|
298
|
+
|
|
299
|
+
// Remove from active issues
|
|
300
|
+
issues.splice(idx, 1);
|
|
301
|
+
writeIssues(issues);
|
|
302
|
+
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
232
306
|
function updateIssue(issueId: string, updates: Partial<Issue>): boolean {
|
|
233
307
|
const issues = readIssues();
|
|
234
308
|
const idx = issues.findIndex(i => i.id === issueId);
|
|
235
309
|
if (idx === -1) return false;
|
|
236
310
|
issues[idx] = { ...issues[idx], ...updates, updated_at: new Date().toISOString() };
|
|
237
311
|
writeIssues(issues);
|
|
312
|
+
|
|
313
|
+
// Auto-move to history when completed
|
|
314
|
+
if (updates.status === 'completed') {
|
|
315
|
+
moveIssueToHistory(issueId);
|
|
316
|
+
}
|
|
317
|
+
|
|
238
318
|
return true;
|
|
239
319
|
}
|
|
240
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Generate auto-increment issue ID: ISS-YYYYMMDD-NNN
|
|
323
|
+
*/
|
|
324
|
+
function generateIssueId(existingIssues: Issue[] = []): string {
|
|
325
|
+
const today = new Date();
|
|
326
|
+
const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
|
|
327
|
+
const prefix = `ISS-${dateStr}-`;
|
|
328
|
+
const todayPattern = new RegExp(`^ISS-${dateStr}-(\\d{3})$`);
|
|
329
|
+
let maxSeq = 0;
|
|
330
|
+
for (const issue of existingIssues) {
|
|
331
|
+
const match = issue.id.match(todayPattern);
|
|
332
|
+
if (match) maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
|
|
333
|
+
}
|
|
334
|
+
return `${prefix}${String(maxSeq + 1).padStart(3, '0')}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Create a new issue with proper JSONL handling
|
|
339
|
+
* Auto-generates ID if not provided
|
|
340
|
+
*/
|
|
341
|
+
function createIssue(data: Partial<Issue>): Issue {
|
|
342
|
+
const issues = readIssues();
|
|
343
|
+
const issueId = data.id || generateIssueId(issues);
|
|
344
|
+
|
|
345
|
+
if (issues.some(i => i.id === issueId)) {
|
|
346
|
+
throw new Error(`Issue "${issueId}" already exists`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const newIssue: Issue = {
|
|
350
|
+
id: issueId,
|
|
351
|
+
title: data.title || issueId,
|
|
352
|
+
status: data.status || 'registered',
|
|
353
|
+
priority: data.priority || 3,
|
|
354
|
+
context: data.context || '',
|
|
355
|
+
source: data.source,
|
|
356
|
+
source_url: data.source_url,
|
|
357
|
+
tags: data.tags,
|
|
358
|
+
expected_behavior: data.expected_behavior,
|
|
359
|
+
actual_behavior: data.actual_behavior,
|
|
360
|
+
affected_components: data.affected_components,
|
|
361
|
+
feedback: data.feedback,
|
|
362
|
+
bound_solution_id: data.bound_solution_id || null,
|
|
363
|
+
created_at: new Date().toISOString(),
|
|
364
|
+
updated_at: new Date().toISOString()
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
issues.push(newIssue);
|
|
368
|
+
writeIssues(issues);
|
|
369
|
+
return newIssue;
|
|
370
|
+
}
|
|
371
|
+
|
|
241
372
|
// ============ Solutions JSONL ============
|
|
242
373
|
|
|
243
374
|
function getSolutionsPath(issueId: string): string {
|
|
@@ -260,7 +391,9 @@ function readSolutions(issueId: string): Solution[] {
|
|
|
260
391
|
function writeSolutions(issueId: string, solutions: Solution[]): void {
|
|
261
392
|
const dir = join(getIssuesDir(), 'solutions');
|
|
262
393
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
263
|
-
|
|
394
|
+
// Always add trailing newline for proper JSONL format
|
|
395
|
+
const content = solutions.map(s => JSON.stringify(s)).join('\n');
|
|
396
|
+
writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
|
|
264
397
|
}
|
|
265
398
|
|
|
266
399
|
function findSolution(issueId: string, solutionId: string): Solution | undefined {
|
|
@@ -271,9 +404,57 @@ function getBoundSolution(issueId: string): Solution | undefined {
|
|
|
271
404
|
return readSolutions(issueId).find(s => s.is_bound);
|
|
272
405
|
}
|
|
273
406
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Generate solution ID in format: SOL-{issue-id}-{seq}
|
|
409
|
+
* @param issueId - The issue ID to include in the solution ID
|
|
410
|
+
* @param existingSolutions - Existing solutions to calculate next sequence number
|
|
411
|
+
* @returns Solution ID like "SOL-GH-123-1" or "SOL-ISS-20251229-001-2"
|
|
412
|
+
*/
|
|
413
|
+
function generateSolutionId(issueId: string, existingSolutions: Solution[] = []): string {
|
|
414
|
+
// Find the highest existing sequence number for this issue
|
|
415
|
+
const pattern = new RegExp(`^SOL-${issueId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)$`);
|
|
416
|
+
let maxSeq = 0;
|
|
417
|
+
for (const sol of existingSolutions) {
|
|
418
|
+
const match = sol.id.match(pattern);
|
|
419
|
+
if (match) {
|
|
420
|
+
maxSeq = Math.max(maxSeq, parseInt(match[1], 10));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return `SOL-${issueId}-${maxSeq + 1}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Create a new solution with proper JSONL handling
|
|
428
|
+
* Auto-generates ID if not provided
|
|
429
|
+
*/
|
|
430
|
+
function createSolution(issueId: string, data: Partial<Solution>): Solution {
|
|
431
|
+
const issue = findIssue(issueId);
|
|
432
|
+
if (!issue) {
|
|
433
|
+
throw new Error(`Issue "${issueId}" not found`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const solutions = readSolutions(issueId);
|
|
437
|
+
const solutionId = data.id || generateSolutionId(issueId, solutions);
|
|
438
|
+
|
|
439
|
+
if (solutions.some(s => s.id === solutionId)) {
|
|
440
|
+
throw new Error(`Solution "${solutionId}" already exists`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const newSolution: Solution = {
|
|
444
|
+
id: solutionId,
|
|
445
|
+
description: data.description || '',
|
|
446
|
+
approach: data.approach || '',
|
|
447
|
+
tasks: data.tasks || [],
|
|
448
|
+
exploration_context: data.exploration_context,
|
|
449
|
+
analysis: data.analysis,
|
|
450
|
+
score: data.score,
|
|
451
|
+
is_bound: false,
|
|
452
|
+
created_at: new Date().toISOString()
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
solutions.push(newSolution);
|
|
456
|
+
writeSolutions(issueId, solutions);
|
|
457
|
+
return newSolution;
|
|
277
458
|
}
|
|
278
459
|
|
|
279
460
|
// ============ Queue Management (Multi-Queue) ============
|
|
@@ -434,7 +615,57 @@ function generateQueueItemId(queue: Queue, level: 'solution' | 'task' = 'solutio
|
|
|
434
615
|
// ============ Commands ============
|
|
435
616
|
|
|
436
617
|
/**
|
|
437
|
-
*
|
|
618
|
+
* create - Create issue from JSON data
|
|
619
|
+
* Usage: ccw issue create --data '{"title":"...", "context":"..."}'
|
|
620
|
+
* Output: JSON with created issue (includes auto-generated ID)
|
|
621
|
+
*/
|
|
622
|
+
async function createAction(options: IssueOptions): Promise<void> {
|
|
623
|
+
if (!options.data) {
|
|
624
|
+
console.error(chalk.red('JSON data required'));
|
|
625
|
+
console.error(chalk.gray('Usage: ccw issue create --data \'{"title":"...", "context":"..."}\''));
|
|
626
|
+
process.exit(1);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const data = JSON.parse(options.data);
|
|
631
|
+
const issue = createIssue(data);
|
|
632
|
+
console.log(JSON.stringify(issue, null, 2));
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.error(chalk.red((err as Error).message));
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* solution - Create solution from JSON data
|
|
641
|
+
* Usage: ccw issue solution <issue-id> --data '{"tasks":[...]}'
|
|
642
|
+
* Output: JSON with created solution (includes auto-generated ID)
|
|
643
|
+
*/
|
|
644
|
+
async function solutionAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
|
|
645
|
+
if (!issueId) {
|
|
646
|
+
console.error(chalk.red('Issue ID required'));
|
|
647
|
+
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (!options.data) {
|
|
652
|
+
console.error(chalk.red('JSON data required'));
|
|
653
|
+
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const data = JSON.parse(options.data);
|
|
659
|
+
const solution = createSolution(issueId, data);
|
|
660
|
+
console.log(JSON.stringify(solution, null, 2));
|
|
661
|
+
} catch (err) {
|
|
662
|
+
console.error(chalk.red((err as Error).message));
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* init - Initialize a new issue (manual ID)
|
|
438
669
|
*/
|
|
439
670
|
async function initAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
|
|
440
671
|
if (!issueId) {
|
|
@@ -458,7 +689,6 @@ async function initAction(issueId: string | undefined, options: IssueOptions): P
|
|
|
458
689
|
priority: options.priority ? parseInt(options.priority) : 3,
|
|
459
690
|
context: options.description || '',
|
|
460
691
|
bound_solution_id: null,
|
|
461
|
-
solution_count: 0,
|
|
462
692
|
created_at: new Date().toISOString(),
|
|
463
693
|
updated_at: new Date().toISOString()
|
|
464
694
|
};
|
|
@@ -484,9 +714,17 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
|
|
|
484
714
|
issues = issues.filter(i => statuses.includes(i.status));
|
|
485
715
|
}
|
|
486
716
|
|
|
487
|
-
//
|
|
488
|
-
if (options.
|
|
489
|
-
issues.
|
|
717
|
+
// Brief mode: minimal fields only (id, title, status, priority, tags, bound_solution_id)
|
|
718
|
+
if (options.brief) {
|
|
719
|
+
const briefIssues = issues.map(i => ({
|
|
720
|
+
id: i.id,
|
|
721
|
+
title: i.title,
|
|
722
|
+
status: i.status,
|
|
723
|
+
priority: i.priority,
|
|
724
|
+
tags: i.tags || [],
|
|
725
|
+
bound_solution_id: i.bound_solution_id
|
|
726
|
+
}));
|
|
727
|
+
console.log(JSON.stringify(briefIssues, null, 2));
|
|
490
728
|
return;
|
|
491
729
|
}
|
|
492
730
|
|
|
@@ -517,7 +755,8 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
|
|
|
517
755
|
'paused': chalk.magenta
|
|
518
756
|
}[issue.status] || chalk.white;
|
|
519
757
|
|
|
520
|
-
const
|
|
758
|
+
const solutionCount = readSolutions(issue.id).length;
|
|
759
|
+
const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
|
|
521
760
|
console.log(
|
|
522
761
|
issue.id.padEnd(20) +
|
|
523
762
|
statusColor(issue.status.padEnd(15)) +
|
|
@@ -567,6 +806,52 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
|
|
|
567
806
|
}
|
|
568
807
|
}
|
|
569
808
|
|
|
809
|
+
/**
|
|
810
|
+
* history - List completed issues from history
|
|
811
|
+
*/
|
|
812
|
+
async function historyAction(options: IssueOptions): Promise<void> {
|
|
813
|
+
const history = readIssueHistory();
|
|
814
|
+
|
|
815
|
+
// Brief mode: minimal fields only
|
|
816
|
+
if (options.brief) {
|
|
817
|
+
const briefHistory = history.map(i => ({
|
|
818
|
+
id: i.id,
|
|
819
|
+
title: i.title,
|
|
820
|
+
status: i.status,
|
|
821
|
+
completed_at: i.completed_at
|
|
822
|
+
}));
|
|
823
|
+
console.log(JSON.stringify(briefHistory, null, 2));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (options.json) {
|
|
828
|
+
console.log(JSON.stringify(history, null, 2));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (history.length === 0) {
|
|
833
|
+
console.log(chalk.yellow('No completed issues in history'));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
console.log(chalk.bold.cyan('\nIssue History (Completed)\n'));
|
|
838
|
+
console.log(chalk.gray('ID'.padEnd(25) + 'Completed At'.padEnd(22) + 'Title'));
|
|
839
|
+
console.log(chalk.gray('-'.repeat(80)));
|
|
840
|
+
|
|
841
|
+
for (const issue of history) {
|
|
842
|
+
const completedAt = issue.completed_at
|
|
843
|
+
? new Date(issue.completed_at).toLocaleString()
|
|
844
|
+
: 'N/A';
|
|
845
|
+
console.log(
|
|
846
|
+
chalk.green(issue.id.padEnd(25)) +
|
|
847
|
+
completedAt.padEnd(22) +
|
|
848
|
+
(issue.title || '').substring(0, 35)
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
console.log(chalk.gray(`\nTotal: ${history.length} completed issues`));
|
|
853
|
+
}
|
|
854
|
+
|
|
570
855
|
/**
|
|
571
856
|
* status - Show detailed status
|
|
572
857
|
*/
|
|
@@ -651,7 +936,7 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
|
|
|
651
936
|
// Create default solution if none bound
|
|
652
937
|
if (boundIdx === -1) {
|
|
653
938
|
const newSol: Solution = {
|
|
654
|
-
id: generateSolutionId(),
|
|
939
|
+
id: generateSolutionId(issueId, solutions),
|
|
655
940
|
description: 'Manual tasks',
|
|
656
941
|
tasks: [],
|
|
657
942
|
is_bound: true,
|
|
@@ -718,11 +1003,88 @@ async function taskAction(issueId: string | undefined, taskId: string | undefine
|
|
|
718
1003
|
|
|
719
1004
|
/**
|
|
720
1005
|
* update - Update issue fields (status, priority, title, etc.)
|
|
1006
|
+
* --from-queue: Sync statuses from active queue (auto-update queued issues)
|
|
721
1007
|
*/
|
|
722
1008
|
async function updateAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
|
|
1009
|
+
// Handle --from-queue: Sync statuses from queue
|
|
1010
|
+
if (options.fromQueue) {
|
|
1011
|
+
// Determine queue ID: string value = specific queue, true = active queue
|
|
1012
|
+
const queueId = typeof options.fromQueue === 'string' ? options.fromQueue : undefined;
|
|
1013
|
+
const queue = queueId ? readQueue(queueId) : readActiveQueue();
|
|
1014
|
+
|
|
1015
|
+
if (!queue) {
|
|
1016
|
+
if (options.json) {
|
|
1017
|
+
console.log(JSON.stringify({ success: false, message: `Queue not found: ${queueId}`, queued: [], unplanned: [] }));
|
|
1018
|
+
} else {
|
|
1019
|
+
console.log(chalk.red(`Queue not found: ${queueId}`));
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1025
|
+
const allIssues = readIssues();
|
|
1026
|
+
|
|
1027
|
+
if (!queue.id || items.length === 0) {
|
|
1028
|
+
if (options.json) {
|
|
1029
|
+
console.log(JSON.stringify({ success: false, message: 'No active queue', queued: [], unplanned: [] }));
|
|
1030
|
+
} else {
|
|
1031
|
+
console.log(chalk.yellow('No active queue to sync from'));
|
|
1032
|
+
}
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Get issue IDs from queue
|
|
1037
|
+
const queuedIssueIds = new Set(items.map(item => item.issue_id));
|
|
1038
|
+
const now = new Date().toISOString();
|
|
1039
|
+
|
|
1040
|
+
// Track updates
|
|
1041
|
+
const updated: string[] = [];
|
|
1042
|
+
const unplanned: string[] = [];
|
|
1043
|
+
|
|
1044
|
+
// Update queued issues
|
|
1045
|
+
for (const issueId of queuedIssueIds) {
|
|
1046
|
+
const issue = allIssues.find(i => i.id === issueId);
|
|
1047
|
+
if (issue && issue.status !== 'queued' && issue.status !== 'executing' && issue.status !== 'completed') {
|
|
1048
|
+
updateIssue(issueId, { status: 'queued', queued_at: now });
|
|
1049
|
+
updated.push(issueId);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Find planned issues NOT in queue
|
|
1054
|
+
for (const issue of allIssues) {
|
|
1055
|
+
if (issue.status === 'planned' && issue.bound_solution_id && !queuedIssueIds.has(issue.id)) {
|
|
1056
|
+
unplanned.push(issue.id);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (options.json) {
|
|
1061
|
+
console.log(JSON.stringify({
|
|
1062
|
+
success: true,
|
|
1063
|
+
queue_id: queue.id,
|
|
1064
|
+
queued: updated,
|
|
1065
|
+
queued_count: updated.length,
|
|
1066
|
+
unplanned: unplanned,
|
|
1067
|
+
unplanned_count: unplanned.length
|
|
1068
|
+
}, null, 2));
|
|
1069
|
+
} else {
|
|
1070
|
+
console.log(chalk.green(`✓ Synced from queue ${queue.id}`));
|
|
1071
|
+
console.log(chalk.gray(` Updated to 'queued': ${updated.length} issues`));
|
|
1072
|
+
if (updated.length > 0) {
|
|
1073
|
+
updated.forEach(id => console.log(chalk.gray(` - ${id}`)));
|
|
1074
|
+
}
|
|
1075
|
+
if (unplanned.length > 0) {
|
|
1076
|
+
console.log(chalk.yellow(` Planned but NOT in queue: ${unplanned.length} issues`));
|
|
1077
|
+
unplanned.forEach(id => console.log(chalk.yellow(` - ${id}`)));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Standard single-issue update
|
|
723
1084
|
if (!issueId) {
|
|
724
1085
|
console.error(chalk.red('Issue ID is required'));
|
|
725
|
-
console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>
|
|
1086
|
+
console.error(chalk.gray('Usage: ccw issue update <issue-id> --status <status>'));
|
|
1087
|
+
console.error(chalk.gray(' ccw issue update --from-queue [queue-id] (sync from queue)'));
|
|
726
1088
|
process.exit(1);
|
|
727
1089
|
}
|
|
728
1090
|
|
|
@@ -802,8 +1164,9 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
|
|
|
802
1164
|
try {
|
|
803
1165
|
const content = readFileSync(options.solution, 'utf-8');
|
|
804
1166
|
const data = JSON.parse(content);
|
|
1167
|
+
// Priority: CLI arg > file content ID > generate new (SOL-{issue-id}-{seq})
|
|
805
1168
|
const newSol: Solution = {
|
|
806
|
-
id: solutionId || generateSolutionId(),
|
|
1169
|
+
id: solutionId || data.id || generateSolutionId(issueId, solutions),
|
|
807
1170
|
description: data.description || data.approach_name || 'Imported solution',
|
|
808
1171
|
tasks: data.tasks || [],
|
|
809
1172
|
exploration_context: data.exploration_context,
|
|
@@ -852,7 +1215,6 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
|
|
|
852
1215
|
writeSolutions(issueId, solutions);
|
|
853
1216
|
updateIssue(issueId, {
|
|
854
1217
|
bound_solution_id: solutionId,
|
|
855
|
-
solution_count: solutions.length,
|
|
856
1218
|
status: 'planned',
|
|
857
1219
|
planned_at: new Date().toISOString()
|
|
858
1220
|
});
|
|
@@ -868,6 +1230,22 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
868
1230
|
if (subAction === 'list' || subAction === 'history') {
|
|
869
1231
|
const index = readQueueIndex();
|
|
870
1232
|
|
|
1233
|
+
// Brief mode: minimal queue index info
|
|
1234
|
+
if (options.brief) {
|
|
1235
|
+
const briefIndex = {
|
|
1236
|
+
active_queue_id: index.active_queue_id,
|
|
1237
|
+
queues: index.queues.map(q => ({
|
|
1238
|
+
id: q.id,
|
|
1239
|
+
status: q.status,
|
|
1240
|
+
issue_ids: q.issue_ids,
|
|
1241
|
+
total_solutions: q.total_solutions,
|
|
1242
|
+
completed_solutions: q.completed_solutions
|
|
1243
|
+
}))
|
|
1244
|
+
};
|
|
1245
|
+
console.log(JSON.stringify(briefIndex, null, 2));
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
871
1249
|
if (options.json) {
|
|
872
1250
|
console.log(JSON.stringify(index, null, 2));
|
|
873
1251
|
return;
|
|
@@ -944,7 +1322,6 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
944
1322
|
issue_id: item.issue_id,
|
|
945
1323
|
solution_id: item.solution_id,
|
|
946
1324
|
status: item.status,
|
|
947
|
-
executor: item.assigned_executor,
|
|
948
1325
|
priority: item.semantic_priority,
|
|
949
1326
|
depends_on: item.depends_on || [],
|
|
950
1327
|
task_count: item.task_count || 1,
|
|
@@ -1161,7 +1538,6 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
1161
1538
|
execution_group: 'P1',
|
|
1162
1539
|
depends_on: [],
|
|
1163
1540
|
semantic_priority: 0.5,
|
|
1164
|
-
assigned_executor: 'codex',
|
|
1165
1541
|
task_count: solution.tasks?.length || 0,
|
|
1166
1542
|
files_touched: Array.from(filesTouched)
|
|
1167
1543
|
});
|
|
@@ -1179,6 +1555,28 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
1179
1555
|
// Show current queue
|
|
1180
1556
|
const queue = readActiveQueue();
|
|
1181
1557
|
|
|
1558
|
+
// Brief mode: minimal queue info (id, issue_ids, item summaries)
|
|
1559
|
+
if (options.brief) {
|
|
1560
|
+
const items = queue.solutions || queue.tasks || [];
|
|
1561
|
+
const briefQueue = {
|
|
1562
|
+
id: queue.id,
|
|
1563
|
+
issue_ids: queue.issue_ids || [],
|
|
1564
|
+
total: items.length,
|
|
1565
|
+
pending: items.filter(i => i.status === 'pending').length,
|
|
1566
|
+
executing: items.filter(i => i.status === 'executing').length,
|
|
1567
|
+
completed: items.filter(i => i.status === 'completed').length,
|
|
1568
|
+
items: items.map(i => ({
|
|
1569
|
+
item_id: i.item_id,
|
|
1570
|
+
issue_id: i.issue_id,
|
|
1571
|
+
solution_id: i.solution_id,
|
|
1572
|
+
status: i.status,
|
|
1573
|
+
task_count: i.task_count
|
|
1574
|
+
}))
|
|
1575
|
+
};
|
|
1576
|
+
console.log(JSON.stringify(briefQueue, null, 2));
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1182
1580
|
if (options.json) {
|
|
1183
1581
|
console.log(JSON.stringify(queue, null, 2));
|
|
1184
1582
|
return;
|
|
@@ -1203,11 +1601,11 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
1203
1601
|
console.log();
|
|
1204
1602
|
|
|
1205
1603
|
if (isSolutionLevel) {
|
|
1206
|
-
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'
|
|
1604
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Tasks'.padEnd(8) + 'Status'));
|
|
1207
1605
|
} else {
|
|
1208
|
-
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'
|
|
1606
|
+
console.log(chalk.gray('ItemID'.padEnd(10) + 'Issue'.padEnd(15) + 'Task'.padEnd(8) + 'Status'));
|
|
1209
1607
|
}
|
|
1210
|
-
console.log(chalk.gray('-'.repeat(
|
|
1608
|
+
console.log(chalk.gray('-'.repeat(48)));
|
|
1211
1609
|
|
|
1212
1610
|
for (const item of items) {
|
|
1213
1611
|
const statusColor = {
|
|
@@ -1227,8 +1625,7 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
|
|
1227
1625
|
item.item_id.padEnd(10) +
|
|
1228
1626
|
item.issue_id.substring(0, 13).padEnd(15) +
|
|
1229
1627
|
thirdCol +
|
|
1230
|
-
statusColor(item.status
|
|
1231
|
-
item.assigned_executor
|
|
1628
|
+
statusColor(item.status)
|
|
1232
1629
|
);
|
|
1233
1630
|
}
|
|
1234
1631
|
}
|
|
@@ -1337,7 +1734,6 @@ async function nextAction(itemId: string | undefined, options: IssueOptions): Pr
|
|
|
1337
1734
|
resumed: isResume,
|
|
1338
1735
|
resume_note: isResume ? `Resuming interrupted item (started: ${nextItem.started_at})` : undefined,
|
|
1339
1736
|
execution_hints: {
|
|
1340
|
-
executor: nextItem.assigned_executor,
|
|
1341
1737
|
task_count: solution.tasks?.length || 0,
|
|
1342
1738
|
estimated_minutes: totalMinutes
|
|
1343
1739
|
},
|
|
@@ -1395,7 +1791,6 @@ async function detailAction(itemId: string | undefined, options: IssueOptions):
|
|
|
1395
1791
|
exploration_context: solution.exploration_context || {}
|
|
1396
1792
|
},
|
|
1397
1793
|
execution_hints: {
|
|
1398
|
-
executor: queueItem.assigned_executor,
|
|
1399
1794
|
task_count: solution.tasks?.length || 0,
|
|
1400
1795
|
estimated_minutes: totalMinutes
|
|
1401
1796
|
}
|
|
@@ -1532,12 +1927,21 @@ export async function issueCommand(
|
|
|
1532
1927
|
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
|
|
1533
1928
|
|
|
1534
1929
|
switch (subcommand) {
|
|
1930
|
+
case 'create':
|
|
1931
|
+
await createAction(options);
|
|
1932
|
+
break;
|
|
1933
|
+
case 'solution':
|
|
1934
|
+
await solutionAction(argsArray[0], options);
|
|
1935
|
+
break;
|
|
1535
1936
|
case 'init':
|
|
1536
1937
|
await initAction(argsArray[0], options);
|
|
1537
1938
|
break;
|
|
1538
1939
|
case 'list':
|
|
1539
1940
|
await listAction(argsArray[0], options);
|
|
1540
1941
|
break;
|
|
1942
|
+
case 'history':
|
|
1943
|
+
await historyAction(options);
|
|
1944
|
+
break;
|
|
1541
1945
|
case 'status':
|
|
1542
1946
|
await statusAction(argsArray[0], options);
|
|
1543
1947
|
break;
|
|
@@ -1579,12 +1983,15 @@ export async function issueCommand(
|
|
|
1579
1983
|
default:
|
|
1580
1984
|
console.log(chalk.bold.cyan('\nCCW Issue Management (v3.0 - Multi-Queue + Lifecycle)\n'));
|
|
1581
1985
|
console.log(chalk.bold('Core Commands:'));
|
|
1582
|
-
console.log(chalk.gray('
|
|
1986
|
+
console.log(chalk.gray(' create --data \'{"title":"..."}\' Create issue (auto-generates ID)'));
|
|
1987
|
+
console.log(chalk.gray(' init <issue-id> Initialize new issue (manual ID)'));
|
|
1583
1988
|
console.log(chalk.gray(' list [issue-id] List issues or tasks'));
|
|
1989
|
+
console.log(chalk.gray(' history List completed issues (from history)'));
|
|
1584
1990
|
console.log(chalk.gray(' status [issue-id] Show detailed status'));
|
|
1585
|
-
console.log(chalk.gray('
|
|
1586
|
-
console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution
|
|
1587
|
-
console.log(chalk.gray(' update <issue-id>
|
|
1991
|
+
console.log(chalk.gray(' solution <id> --data \'{...}\' Create solution (auto-generates ID)'));
|
|
1992
|
+
console.log(chalk.gray(' bind <issue-id> [sol-id] Bind solution'));
|
|
1993
|
+
console.log(chalk.gray(' update <issue-id> --status <s> Update issue status'));
|
|
1994
|
+
console.log(chalk.gray(' update --from-queue [queue-id] Sync statuses from queue (default: active)'));
|
|
1588
1995
|
console.log();
|
|
1589
1996
|
console.log(chalk.bold('Queue Commands:'));
|
|
1590
1997
|
console.log(chalk.gray(' queue Show active queue'));
|
|
@@ -1605,7 +2012,7 @@ export async function issueCommand(
|
|
|
1605
2012
|
console.log(chalk.bold('Options:'));
|
|
1606
2013
|
console.log(chalk.gray(' --title <title> Issue/task title'));
|
|
1607
2014
|
console.log(chalk.gray(' --status <status> Filter by status (comma-separated)'));
|
|
1608
|
-
console.log(chalk.gray(' --
|
|
2015
|
+
console.log(chalk.gray(' --brief Brief JSON output (minimal fields)'));
|
|
1609
2016
|
console.log(chalk.gray(' --solution <path> Solution JSON file'));
|
|
1610
2017
|
console.log(chalk.gray(' --result <json> Execution result'));
|
|
1611
2018
|
console.log(chalk.gray(' --reason <text> Failure reason'));
|
|
@@ -1613,7 +2020,8 @@ export async function issueCommand(
|
|
|
1613
2020
|
console.log(chalk.gray(' --force Force operation'));
|
|
1614
2021
|
console.log();
|
|
1615
2022
|
console.log(chalk.bold('Storage:'));
|
|
1616
|
-
console.log(chalk.gray(' .workflow/issues/issues.jsonl
|
|
2023
|
+
console.log(chalk.gray(' .workflow/issues/issues.jsonl Active issues'));
|
|
2024
|
+
console.log(chalk.gray(' .workflow/issues/issue-history.jsonl Completed issues'));
|
|
1617
2025
|
console.log(chalk.gray(' .workflow/issues/solutions/*.jsonl Solutions per issue'));
|
|
1618
2026
|
console.log(chalk.gray(' .workflow/issues/queues/ Queue files (multi-queue)'));
|
|
1619
2027
|
console.log(chalk.gray(' .workflow/issues/queues/index.json Queue index'));
|