claude-code-workflow 6.3.2 → 6.3.4
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/CLAUDE.md +9 -1
- package/.claude/commands/workflow/lite-plan.md +1 -1
- package/.claude/workflows/cli-tools-usage.md +515 -516
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +6 -1
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +1 -1
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +71 -7
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +19 -7
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/cli-history-store.d.ts +33 -0
- package/ccw/dist/tools/cli-history-store.d.ts.map +1 -1
- package/ccw/dist/tools/cli-history-store.js +89 -5
- package/ccw/dist/tools/cli-history-store.js.map +1 -1
- package/ccw/src/cli.ts +263 -258
- package/ccw/src/commands/cli.ts +967 -884
- package/ccw/src/tools/cli-executor.ts +20 -7
- package/ccw/src/tools/cli-history-store.ts +125 -5
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +3 -0
- 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 +71 -1
- package/codex-lens/src/codexlens/search/ranking.py +274 -274
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/__pycache__/global_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/dir_index.py +1888 -1850
- package/codex-lens/src/codexlens/storage/global_index.py +365 -0
- package/codex-lens/src/codexlens/storage/index_tree.py +83 -10
- package/package.json +1 -1
|
@@ -73,6 +73,7 @@ const ParamsSchema = z.object({
|
|
|
73
73
|
noNative: z.boolean().optional(), // Force prompt concatenation instead of native resume
|
|
74
74
|
category: z.enum(['user', 'internal', 'insight']).default('user'), // Execution category for tracking
|
|
75
75
|
parentExecutionId: z.string().optional(), // Parent execution ID for fork/retry scenarios
|
|
76
|
+
stream: z.boolean().default(false), // false = cache full output (default), true = stream output via callback
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
// Execution category types
|
|
@@ -863,24 +864,36 @@ async function executeCliTool(
|
|
|
863
864
|
const endTime = Date.now();
|
|
864
865
|
const duration = endTime - startTime;
|
|
865
866
|
|
|
866
|
-
// Determine status
|
|
867
|
+
// Determine status - prioritize output content over exit code
|
|
867
868
|
let status: 'success' | 'error' | 'timeout' = 'success';
|
|
868
869
|
if (timedOut) {
|
|
869
870
|
status = 'timeout';
|
|
870
871
|
} else if (code !== 0) {
|
|
871
|
-
//
|
|
872
|
-
if
|
|
872
|
+
// Non-zero exit code doesn't always mean failure
|
|
873
|
+
// Check if there's valid output (AI response) - treat as success
|
|
874
|
+
const hasValidOutput = stdout.trim().length > 0;
|
|
875
|
+
const hasFatalError = stderr.includes('FATAL') ||
|
|
876
|
+
stderr.includes('Authentication failed') ||
|
|
877
|
+
stderr.includes('API key') ||
|
|
878
|
+
stderr.includes('rate limit exceeded');
|
|
879
|
+
|
|
880
|
+
if (hasValidOutput && !hasFatalError) {
|
|
881
|
+
// Has output and no fatal errors - treat as success despite exit code
|
|
873
882
|
status = 'success';
|
|
874
883
|
} else {
|
|
875
884
|
status = 'error';
|
|
876
885
|
}
|
|
877
886
|
}
|
|
878
887
|
|
|
879
|
-
// Create new turn
|
|
888
|
+
// Create new turn - cache full output when not streaming (default)
|
|
889
|
+
const shouldCache = !parsed.data.stream;
|
|
880
890
|
const newTurnOutput = {
|
|
881
|
-
stdout: stdout.substring(0, 10240), // Truncate to 10KB
|
|
882
|
-
stderr: stderr.substring(0, 2048), // Truncate to 2KB
|
|
883
|
-
truncated: stdout.length > 10240 || stderr.length > 2048
|
|
891
|
+
stdout: stdout.substring(0, 10240), // Truncate preview to 10KB
|
|
892
|
+
stderr: stderr.substring(0, 2048), // Truncate preview to 2KB
|
|
893
|
+
truncated: stdout.length > 10240 || stderr.length > 2048,
|
|
894
|
+
cached: shouldCache,
|
|
895
|
+
stdout_full: shouldCache ? stdout : undefined,
|
|
896
|
+
stderr_full: shouldCache ? stderr : undefined
|
|
884
897
|
};
|
|
885
898
|
|
|
886
899
|
// Determine base turn number for merge scenarios
|
|
@@ -23,6 +23,9 @@ export interface ConversationTurn {
|
|
|
23
23
|
stdout: string;
|
|
24
24
|
stderr: string;
|
|
25
25
|
truncated: boolean;
|
|
26
|
+
cached?: boolean;
|
|
27
|
+
stdout_full?: string;
|
|
28
|
+
stderr_full?: string;
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -315,6 +318,28 @@ export class CliHistoryStore {
|
|
|
315
318
|
} catch (indexErr) {
|
|
316
319
|
console.warn('[CLI History] Turns timestamp index creation warning:', (indexErr as Error).message);
|
|
317
320
|
}
|
|
321
|
+
|
|
322
|
+
// Add cached output columns to turns table for non-streaming mode
|
|
323
|
+
const turnsInfo = this.db.prepare('PRAGMA table_info(turns)').all() as Array<{ name: string }>;
|
|
324
|
+
const hasCached = turnsInfo.some(col => col.name === 'cached');
|
|
325
|
+
const hasStdoutFull = turnsInfo.some(col => col.name === 'stdout_full');
|
|
326
|
+
const hasStderrFull = turnsInfo.some(col => col.name === 'stderr_full');
|
|
327
|
+
|
|
328
|
+
if (!hasCached) {
|
|
329
|
+
console.log('[CLI History] Migrating database: adding cached column to turns table...');
|
|
330
|
+
this.db.exec('ALTER TABLE turns ADD COLUMN cached INTEGER DEFAULT 0;');
|
|
331
|
+
console.log('[CLI History] Migration complete: cached column added');
|
|
332
|
+
}
|
|
333
|
+
if (!hasStdoutFull) {
|
|
334
|
+
console.log('[CLI History] Migrating database: adding stdout_full column to turns table...');
|
|
335
|
+
this.db.exec('ALTER TABLE turns ADD COLUMN stdout_full TEXT;');
|
|
336
|
+
console.log('[CLI History] Migration complete: stdout_full column added');
|
|
337
|
+
}
|
|
338
|
+
if (!hasStderrFull) {
|
|
339
|
+
console.log('[CLI History] Migrating database: adding stderr_full column to turns table...');
|
|
340
|
+
this.db.exec('ALTER TABLE turns ADD COLUMN stderr_full TEXT;');
|
|
341
|
+
console.log('[CLI History] Migration complete: stderr_full column added');
|
|
342
|
+
}
|
|
318
343
|
} catch (err) {
|
|
319
344
|
console.error('[CLI History] Migration error:', (err as Error).message);
|
|
320
345
|
// Don't throw - allow the store to continue working with existing schema
|
|
@@ -421,8 +446,8 @@ export class CliHistoryStore {
|
|
|
421
446
|
`);
|
|
422
447
|
|
|
423
448
|
const upsertTurn = this.db.prepare(`
|
|
424
|
-
INSERT INTO turns (conversation_id, turn_number, timestamp, prompt, duration_ms, status, exit_code, stdout, stderr, truncated)
|
|
425
|
-
VALUES (@conversation_id, @turn_number, @timestamp, @prompt, @duration_ms, @status, @exit_code, @stdout, @stderr, @truncated)
|
|
449
|
+
INSERT INTO turns (conversation_id, turn_number, timestamp, prompt, duration_ms, status, exit_code, stdout, stderr, truncated, cached, stdout_full, stderr_full)
|
|
450
|
+
VALUES (@conversation_id, @turn_number, @timestamp, @prompt, @duration_ms, @status, @exit_code, @stdout, @stderr, @truncated, @cached, @stdout_full, @stderr_full)
|
|
426
451
|
ON CONFLICT(conversation_id, turn_number) DO UPDATE SET
|
|
427
452
|
timestamp = @timestamp,
|
|
428
453
|
prompt = @prompt,
|
|
@@ -431,7 +456,10 @@ export class CliHistoryStore {
|
|
|
431
456
|
exit_code = @exit_code,
|
|
432
457
|
stdout = @stdout,
|
|
433
458
|
stderr = @stderr,
|
|
434
|
-
truncated = @truncated
|
|
459
|
+
truncated = @truncated,
|
|
460
|
+
cached = @cached,
|
|
461
|
+
stdout_full = @stdout_full,
|
|
462
|
+
stderr_full = @stderr_full
|
|
435
463
|
`);
|
|
436
464
|
|
|
437
465
|
const transaction = this.db.transaction(() => {
|
|
@@ -463,7 +491,10 @@ export class CliHistoryStore {
|
|
|
463
491
|
exit_code: turn.exit_code,
|
|
464
492
|
stdout: turn.output.stdout,
|
|
465
493
|
stderr: turn.output.stderr,
|
|
466
|
-
truncated: turn.output.truncated ? 1 : 0
|
|
494
|
+
truncated: turn.output.truncated ? 1 : 0,
|
|
495
|
+
cached: turn.output.cached ? 1 : 0,
|
|
496
|
+
stdout_full: turn.output.stdout_full || null,
|
|
497
|
+
stderr_full: turn.output.stderr_full || null
|
|
467
498
|
});
|
|
468
499
|
}
|
|
469
500
|
});
|
|
@@ -507,7 +538,10 @@ export class CliHistoryStore {
|
|
|
507
538
|
output: {
|
|
508
539
|
stdout: t.stdout || '',
|
|
509
540
|
stderr: t.stderr || '',
|
|
510
|
-
truncated: !!t.truncated
|
|
541
|
+
truncated: !!t.truncated,
|
|
542
|
+
cached: !!t.cached,
|
|
543
|
+
stdout_full: t.stdout_full || undefined,
|
|
544
|
+
stderr_full: t.stderr_full || undefined
|
|
511
545
|
}
|
|
512
546
|
}))
|
|
513
547
|
};
|
|
@@ -533,6 +567,92 @@ export class CliHistoryStore {
|
|
|
533
567
|
};
|
|
534
568
|
}
|
|
535
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Get paginated cached output for a conversation turn
|
|
572
|
+
* @param conversationId - Conversation ID
|
|
573
|
+
* @param turnNumber - Turn number (default: latest turn)
|
|
574
|
+
* @param options - Pagination options
|
|
575
|
+
*/
|
|
576
|
+
getCachedOutput(
|
|
577
|
+
conversationId: string,
|
|
578
|
+
turnNumber?: number,
|
|
579
|
+
options: {
|
|
580
|
+
offset?: number; // Character offset (default: 0)
|
|
581
|
+
limit?: number; // Max characters to return (default: 10000)
|
|
582
|
+
outputType?: 'stdout' | 'stderr' | 'both'; // Which output to fetch
|
|
583
|
+
} = {}
|
|
584
|
+
): {
|
|
585
|
+
conversationId: string;
|
|
586
|
+
turnNumber: number;
|
|
587
|
+
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
|
588
|
+
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
|
589
|
+
cached: boolean;
|
|
590
|
+
prompt: string;
|
|
591
|
+
status: string;
|
|
592
|
+
timestamp: string;
|
|
593
|
+
} | null {
|
|
594
|
+
const { offset = 0, limit = 10000, outputType = 'both' } = options;
|
|
595
|
+
|
|
596
|
+
// Get turn (latest if not specified)
|
|
597
|
+
let turn;
|
|
598
|
+
if (turnNumber !== undefined) {
|
|
599
|
+
turn = this.db.prepare(`
|
|
600
|
+
SELECT * FROM turns WHERE conversation_id = ? AND turn_number = ?
|
|
601
|
+
`).get(conversationId, turnNumber) as any;
|
|
602
|
+
} else {
|
|
603
|
+
turn = this.db.prepare(`
|
|
604
|
+
SELECT * FROM turns WHERE conversation_id = ? ORDER BY turn_number DESC LIMIT 1
|
|
605
|
+
`).get(conversationId) as any;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!turn) return null;
|
|
609
|
+
|
|
610
|
+
const result: {
|
|
611
|
+
conversationId: string;
|
|
612
|
+
turnNumber: number;
|
|
613
|
+
stdout?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
|
614
|
+
stderr?: { content: string; totalBytes: number; offset: number; hasMore: boolean };
|
|
615
|
+
cached: boolean;
|
|
616
|
+
prompt: string;
|
|
617
|
+
status: string;
|
|
618
|
+
timestamp: string;
|
|
619
|
+
} = {
|
|
620
|
+
conversationId,
|
|
621
|
+
turnNumber: turn.turn_number,
|
|
622
|
+
cached: !!turn.cached,
|
|
623
|
+
prompt: turn.prompt,
|
|
624
|
+
status: turn.status,
|
|
625
|
+
timestamp: turn.timestamp
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// Use full output if cached, otherwise use truncated
|
|
629
|
+
if (outputType === 'stdout' || outputType === 'both') {
|
|
630
|
+
const fullStdout = turn.cached ? (turn.stdout_full || '') : (turn.stdout || '');
|
|
631
|
+
const totalBytes = fullStdout.length;
|
|
632
|
+
const content = fullStdout.substring(offset, offset + limit);
|
|
633
|
+
result.stdout = {
|
|
634
|
+
content,
|
|
635
|
+
totalBytes,
|
|
636
|
+
offset,
|
|
637
|
+
hasMore: offset + limit < totalBytes
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (outputType === 'stderr' || outputType === 'both') {
|
|
642
|
+
const fullStderr = turn.cached ? (turn.stderr_full || '') : (turn.stderr || '');
|
|
643
|
+
const totalBytes = fullStderr.length;
|
|
644
|
+
const content = fullStderr.substring(offset, offset + limit);
|
|
645
|
+
result.stderr = {
|
|
646
|
+
content,
|
|
647
|
+
totalBytes,
|
|
648
|
+
offset,
|
|
649
|
+
hasMore: offset + limit < totalBytes
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
|
|
536
656
|
/**
|
|
537
657
|
* Query execution history
|
|
538
658
|
*/
|
|
Binary file
|
|
@@ -100,6 +100,9 @@ class Config:
|
|
|
100
100
|
# For litellm: model name from config (e.g., "qwen3-embedding")
|
|
101
101
|
embedding_use_gpu: bool = True # For fastembed: whether to use GPU acceleration
|
|
102
102
|
|
|
103
|
+
# Indexing/search optimizations
|
|
104
|
+
global_symbol_index_enabled: bool = True # Enable project-wide symbol index fast path
|
|
105
|
+
|
|
103
106
|
# Multi-endpoint configuration for litellm backend
|
|
104
107
|
embedding_endpoints: List[Dict[str, Any]] = field(default_factory=list)
|
|
105
108
|
# List of endpoint configs: [{"model": "...", "api_key": "...", "api_base": "...", "weight": 1.0}]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -11,11 +11,14 @@ from dataclasses import dataclass, field
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import List, Optional, Dict, Any
|
|
13
13
|
import logging
|
|
14
|
+
import os
|
|
14
15
|
import time
|
|
15
16
|
|
|
16
17
|
from codexlens.entities import SearchResult, Symbol
|
|
18
|
+
from codexlens.config import Config
|
|
17
19
|
from codexlens.storage.registry import RegistryStore, DirMapping
|
|
18
20
|
from codexlens.storage.dir_index import DirIndexStore, SubdirLink
|
|
21
|
+
from codexlens.storage.global_index import GlobalSymbolIndex
|
|
19
22
|
from codexlens.storage.path_mapper import PathMapper
|
|
20
23
|
from codexlens.storage.sqlite_store import SQLiteStore
|
|
21
24
|
from codexlens.search.hybrid_search import HybridSearchEngine
|
|
@@ -107,7 +110,8 @@ class ChainSearchEngine:
|
|
|
107
110
|
def __init__(self,
|
|
108
111
|
registry: RegistryStore,
|
|
109
112
|
mapper: PathMapper,
|
|
110
|
-
max_workers: int = 8
|
|
113
|
+
max_workers: int = 8,
|
|
114
|
+
config: Config | None = None):
|
|
111
115
|
"""Initialize chain search engine.
|
|
112
116
|
|
|
113
117
|
Args:
|
|
@@ -120,6 +124,7 @@ class ChainSearchEngine:
|
|
|
120
124
|
self.logger = logging.getLogger(__name__)
|
|
121
125
|
self._max_workers = max_workers
|
|
122
126
|
self._executor: Optional[ThreadPoolExecutor] = None
|
|
127
|
+
self._config = config
|
|
123
128
|
|
|
124
129
|
def _get_executor(self, max_workers: Optional[int] = None) -> ThreadPoolExecutor:
|
|
125
130
|
"""Get or create the shared thread pool executor.
|
|
@@ -294,6 +299,71 @@ class ChainSearchEngine:
|
|
|
294
299
|
self.logger.warning(f"No index found for {source_path}")
|
|
295
300
|
return []
|
|
296
301
|
|
|
302
|
+
# Fast path: project-wide global symbol index (avoids chain traversal).
|
|
303
|
+
if self._config is None or getattr(self._config, "global_symbol_index_enabled", True):
|
|
304
|
+
try:
|
|
305
|
+
# Avoid relying on index_to_source() here; use the same logic as _find_start_index
|
|
306
|
+
# to determine the effective search root directory.
|
|
307
|
+
search_root = source_path.resolve()
|
|
308
|
+
exact_index = self.mapper.source_to_index_db(search_root)
|
|
309
|
+
if not exact_index.exists():
|
|
310
|
+
nearest = self.registry.find_nearest_index(search_root)
|
|
311
|
+
if nearest:
|
|
312
|
+
search_root = nearest.source_path
|
|
313
|
+
|
|
314
|
+
project = self.registry.find_by_source_path(str(search_root))
|
|
315
|
+
if project:
|
|
316
|
+
global_db_path = Path(project["index_root"]) / GlobalSymbolIndex.DEFAULT_DB_NAME
|
|
317
|
+
if global_db_path.exists():
|
|
318
|
+
query_limit = max(int(options.total_limit) * 10, int(options.total_limit))
|
|
319
|
+
with GlobalSymbolIndex(global_db_path, project_id=int(project["id"])) as global_index:
|
|
320
|
+
candidates = global_index.search(name=name, kind=kind, limit=query_limit)
|
|
321
|
+
|
|
322
|
+
# Apply depth constraint relative to the start index directory.
|
|
323
|
+
filtered: List[Symbol] = []
|
|
324
|
+
for sym in candidates:
|
|
325
|
+
if not sym.file:
|
|
326
|
+
continue
|
|
327
|
+
try:
|
|
328
|
+
root_str = str(search_root)
|
|
329
|
+
file_dir_str = str(Path(sym.file).parent)
|
|
330
|
+
|
|
331
|
+
# Normalize Windows long-path prefix (\\?\) if present.
|
|
332
|
+
if root_str.startswith("\\\\?\\"):
|
|
333
|
+
root_str = root_str[4:]
|
|
334
|
+
if file_dir_str.startswith("\\\\?\\"):
|
|
335
|
+
file_dir_str = file_dir_str[4:]
|
|
336
|
+
|
|
337
|
+
root_cmp = root_str.lower().rstrip("\\/")
|
|
338
|
+
dir_cmp = file_dir_str.lower().rstrip("\\/")
|
|
339
|
+
|
|
340
|
+
if os.path.commonpath([root_cmp, dir_cmp]) != root_cmp:
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
rel = os.path.relpath(dir_cmp, root_cmp)
|
|
344
|
+
rel_depth = 0 if rel == "." else len(rel.split(os.sep))
|
|
345
|
+
except Exception:
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
if options.depth >= 0 and rel_depth > options.depth:
|
|
349
|
+
continue
|
|
350
|
+
filtered.append(sym)
|
|
351
|
+
|
|
352
|
+
if filtered:
|
|
353
|
+
# Match existing semantics: dedupe by (name, kind, range), sort by name.
|
|
354
|
+
seen = set()
|
|
355
|
+
unique_symbols: List[Symbol] = []
|
|
356
|
+
for sym in filtered:
|
|
357
|
+
key = (sym.name, sym.kind, sym.range)
|
|
358
|
+
if key in seen:
|
|
359
|
+
continue
|
|
360
|
+
seen.add(key)
|
|
361
|
+
unique_symbols.append(sym)
|
|
362
|
+
unique_symbols.sort(key=lambda s: s.name)
|
|
363
|
+
return unique_symbols[: options.total_limit]
|
|
364
|
+
except Exception as exc:
|
|
365
|
+
self.logger.debug("Global symbol index fast path failed: %s", exc)
|
|
366
|
+
|
|
297
367
|
index_paths = self._collect_index_paths(start_index, options.depth)
|
|
298
368
|
if not index_paths:
|
|
299
369
|
return []
|