claude-code-workflow 6.3.2 → 6.3.5

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.
Files changed (80) hide show
  1. package/.claude/CLAUDE.md +9 -1
  2. package/.claude/commands/{clean.md → workflow/clean.md} +5 -5
  3. package/.claude/commands/workflow/docs/analyze.md +1467 -0
  4. package/.claude/commands/workflow/docs/copyright.md +1265 -0
  5. package/.claude/commands/workflow/lite-plan.md +1 -1
  6. package/.claude/commands/workflow/tools/conflict-resolution.md +76 -240
  7. package/.claude/commands/workflow/tools/task-generate-agent.md +81 -8
  8. package/.claude/skills/_shared/mermaid-utils.md +584 -0
  9. package/.claude/skills/copyright-docs/SKILL.md +132 -0
  10. package/.claude/skills/copyright-docs/phases/01-metadata-collection.md +78 -0
  11. package/.claude/skills/copyright-docs/phases/02-deep-analysis.md +454 -0
  12. package/.claude/skills/copyright-docs/phases/02.5-consolidation.md +192 -0
  13. package/.claude/skills/copyright-docs/phases/04-document-assembly.md +261 -0
  14. package/.claude/skills/copyright-docs/phases/05-compliance-refinement.md +192 -0
  15. package/.claude/skills/copyright-docs/specs/cpcc-requirements.md +121 -0
  16. package/.claude/skills/copyright-docs/templates/agent-base.md +200 -0
  17. package/.claude/skills/project-analyze/SKILL.md +162 -0
  18. package/.claude/skills/project-analyze/phases/01-requirements-discovery.md +79 -0
  19. package/.claude/skills/project-analyze/phases/02-project-exploration.md +75 -0
  20. package/.claude/skills/project-analyze/phases/03-deep-analysis.md +640 -0
  21. package/.claude/skills/project-analyze/phases/03.5-consolidation.md +208 -0
  22. package/.claude/skills/project-analyze/phases/04-report-generation.md +217 -0
  23. package/.claude/skills/project-analyze/phases/05-iterative-refinement.md +124 -0
  24. package/.claude/skills/project-analyze/specs/quality-standards.md +115 -0
  25. package/.claude/skills/project-analyze/specs/writing-style.md +152 -0
  26. package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +79 -65
  27. package/.claude/workflows/cli-tools-usage.md +515 -516
  28. package/README.md +11 -1
  29. package/ccw/dist/cli.d.ts.map +1 -1
  30. package/ccw/dist/cli.js +7 -1
  31. package/ccw/dist/cli.js.map +1 -1
  32. package/ccw/dist/commands/cli.d.ts +1 -1
  33. package/ccw/dist/commands/cli.d.ts.map +1 -1
  34. package/ccw/dist/commands/cli.js +116 -14
  35. package/ccw/dist/commands/cli.js.map +1 -1
  36. package/ccw/dist/core/routes/cli-routes.js +2 -2
  37. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  38. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -3
  39. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  40. package/ccw/dist/tools/claude-cli-tools.js +31 -17
  41. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  42. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  43. package/ccw/dist/tools/cli-executor.js +19 -7
  44. package/ccw/dist/tools/cli-executor.js.map +1 -1
  45. package/ccw/dist/tools/cli-history-store.d.ts +33 -0
  46. package/ccw/dist/tools/cli-history-store.d.ts.map +1 -1
  47. package/ccw/dist/tools/cli-history-store.js +89 -5
  48. package/ccw/dist/tools/cli-history-store.js.map +1 -1
  49. package/ccw/dist/tools/smart-search.d.ts +25 -0
  50. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  51. package/ccw/dist/tools/smart-search.js +121 -17
  52. package/ccw/dist/tools/smart-search.js.map +1 -1
  53. package/ccw/src/cli.ts +264 -258
  54. package/ccw/src/commands/cli.ts +1009 -884
  55. package/ccw/src/core/routes/cli-routes.ts +3 -3
  56. package/ccw/src/templates/dashboard-js/components/cli-history.js +40 -13
  57. package/ccw/src/templates/dashboard-js/components/cli-status.js +26 -2
  58. package/ccw/src/templates/dashboard-js/views/cli-manager.js +5 -0
  59. package/ccw/src/templates/dashboard-js/views/history.js +19 -4
  60. package/ccw/src/tools/claude-cli-tools.ts +37 -20
  61. package/ccw/src/tools/cli-executor.ts +20 -7
  62. package/ccw/src/tools/cli-history-store.ts +125 -5
  63. package/ccw/src/tools/smart-search.ts +157 -16
  64. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  65. package/codex-lens/src/codexlens/config.py +8 -0
  66. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  67. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  68. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  69. package/codex-lens/src/codexlens/search/chain_search.py +71 -1
  70. package/codex-lens/src/codexlens/search/hybrid_search.py +144 -11
  71. package/codex-lens/src/codexlens/search/ranking.py +540 -274
  72. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  73. package/codex-lens/src/codexlens/semantic/chunker.py +55 -10
  74. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  75. package/codex-lens/src/codexlens/storage/__pycache__/global_index.cpython-313.pyc +0 -0
  76. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  77. package/codex-lens/src/codexlens/storage/dir_index.py +1888 -1850
  78. package/codex-lens/src/codexlens/storage/global_index.py +365 -0
  79. package/codex-lens/src/codexlens/storage/index_tree.py +83 -10
  80. package/package.json +2 -2
@@ -769,9 +769,9 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
769
769
  if (pathname === '/api/cli/code-index-mcp' && req.method === 'PUT') {
770
770
  handlePostRequest(req, res, async (body: unknown) => {
771
771
  try {
772
- const { provider } = body as { provider: 'codexlens' | 'ace' };
773
- if (!provider || !['codexlens', 'ace'].includes(provider)) {
774
- return { error: 'Invalid provider. Must be "codexlens" or "ace"', status: 400 };
772
+ const { provider } = body as { provider: 'codexlens' | 'ace' | 'none' };
773
+ if (!provider || !['codexlens', 'ace', 'none'].includes(provider)) {
774
+ return { error: 'Invalid provider. Must be "codexlens", "ace", or "none"', status: 400 };
775
775
  }
776
776
 
777
777
  const result = updateCodeIndexMcp(initialPath, provider);
@@ -15,7 +15,9 @@ async function loadCliHistory(options = {}) {
15
15
  const { limit = cliHistoryLimit, tool = cliHistoryFilter, status = null } = options;
16
16
 
17
17
  // Use history-native endpoint to get native session info
18
- let url = `/api/cli/history-native?path=${encodeURIComponent(projectPath)}&limit=${limit}`;
18
+ // Use recursiveQueryEnabled setting (from cli-status.js) to control recursive query
19
+ const recursive = typeof recursiveQueryEnabled !== 'undefined' ? recursiveQueryEnabled : true;
20
+ let url = `/api/cli/history-native?path=${encodeURIComponent(projectPath)}&limit=${limit}&recursive=${recursive}`;
19
21
  if (tool) url += `&tool=${tool}`;
20
22
  if (status) url += `&status=${status}`;
21
23
  if (cliHistorySearch) url += `&search=${encodeURIComponent(cliHistorySearch)}`;
@@ -36,9 +38,12 @@ async function loadCliHistory(options = {}) {
36
38
  async function loadNativeSessionContent(executionId, sourceDir) {
37
39
  try {
38
40
  // If sourceDir provided, use it to build the correct path
39
- const basePath = sourceDir && sourceDir !== '.'
40
- ? projectPath + '/' + sourceDir
41
- : projectPath;
41
+ // Check if sourceDir is absolute path (contains : or starts with /)
42
+ let basePath = projectPath;
43
+ if (sourceDir && sourceDir !== '.') {
44
+ const isAbsolute = sourceDir.includes(':') || sourceDir.startsWith('/');
45
+ basePath = isAbsolute ? sourceDir : projectPath + '/' + sourceDir;
46
+ }
42
47
  const url = `/api/cli/native-session?path=${encodeURIComponent(basePath)}&id=${encodeURIComponent(executionId)}`;
43
48
  const response = await fetch(url);
44
49
  if (!response.ok) return null;
@@ -65,9 +70,12 @@ async function loadEnrichedConversation(executionId) {
65
70
  async function loadExecutionDetail(executionId, sourceDir) {
66
71
  try {
67
72
  // If sourceDir provided, use it to build the correct path
68
- const basePath = sourceDir && sourceDir !== '.'
69
- ? projectPath + '/' + sourceDir
70
- : projectPath;
73
+ // Check if sourceDir is absolute path (contains : or starts with /)
74
+ let basePath = projectPath;
75
+ if (sourceDir && sourceDir !== '.') {
76
+ const isAbsolute = sourceDir.includes(':') || sourceDir.startsWith('/');
77
+ basePath = isAbsolute ? sourceDir : projectPath + '/' + sourceDir;
78
+ }
71
79
  const url = `/api/cli/execution?path=${encodeURIComponent(basePath)}&id=${encodeURIComponent(executionId)}`;
72
80
  const response = await fetch(url);
73
81
  if (!response.ok) throw new Error('Execution not found');
@@ -137,8 +145,9 @@ function renderCliHistory() {
137
145
  </span>`
138
146
  : '';
139
147
 
140
- // Escape sourceDir for use in onclick
141
- const sourceDirEscaped = exec.sourceDir ? exec.sourceDir.replace(/'/g, "\\'") : '';
148
+ // Normalize and escape sourceDir for use in onclick
149
+ // Convert backslashes to forward slashes to prevent JS escape issues in onclick
150
+ const sourceDirEscaped = exec.sourceDir ? exec.sourceDir.replace(/\\/g, '/').replace(/'/g, "\\'") : '';
142
151
 
143
152
  return `
144
153
  <div class="cli-history-item ${hasNative ? 'has-native' : ''}">
@@ -155,11 +164,14 @@ function renderCliHistory() {
155
164
  <div class="cli-history-meta">
156
165
  <span><i data-lucide="clock" class="w-3 h-3"></i> ${timeAgo}</span>
157
166
  <span><i data-lucide="timer" class="w-3 h-3"></i> ${duration}</span>
158
- <span><i data-lucide="hash" class="w-3 h-3"></i> ${exec.id.split('-')[0]}</span>
167
+ <span title="${exec.id}"><i data-lucide="hash" class="w-3 h-3"></i> ${exec.id.substring(0, 13)}...${exec.id.split('-').pop()}</span>
159
168
  ${turnBadge}
160
169
  </div>
161
170
  </div>
162
171
  <div class="cli-history-actions">
172
+ <button class="btn-icon" onclick="event.stopPropagation(); copyCliExecutionId('${exec.id}')" title="Copy ID">
173
+ <i data-lucide="copy" class="w-3.5 h-3.5"></i>
174
+ </button>
163
175
  ${hasNative ? `
164
176
  <button class="btn-icon" onclick="event.stopPropagation(); showNativeSessionDetail('${exec.id}', '${sourceDirEscaped}')" title="View Native Session">
165
177
  <i data-lucide="file-json" class="w-3.5 h-3.5"></i>
@@ -431,9 +443,12 @@ function confirmDeleteExecution(executionId, sourceDir) {
431
443
  async function deleteExecution(executionId, sourceDir) {
432
444
  try {
433
445
  // Build correct path - use sourceDir if provided for recursive items
434
- const basePath = sourceDir && sourceDir !== '.'
435
- ? projectPath + '/' + sourceDir
436
- : projectPath;
446
+ // Check if sourceDir is absolute path (contains : or starts with /)
447
+ let basePath = projectPath;
448
+ if (sourceDir && sourceDir !== '.') {
449
+ const isAbsolute = sourceDir.includes(':') || sourceDir.startsWith('/');
450
+ basePath = isAbsolute ? sourceDir : projectPath + '/' + sourceDir;
451
+ }
437
452
 
438
453
  const response = await fetch(`/api/cli/execution?path=${encodeURIComponent(basePath)}&id=${encodeURIComponent(executionId)}`, {
439
454
  method: 'DELETE'
@@ -461,6 +476,18 @@ async function deleteExecution(executionId, sourceDir) {
461
476
  }
462
477
 
463
478
  // ========== Copy Functions ==========
479
+ async function copyCliExecutionId(executionId) {
480
+ if (navigator.clipboard) {
481
+ try {
482
+ await navigator.clipboard.writeText(executionId);
483
+ showRefreshToast('ID copied: ' + executionId, 'success');
484
+ } catch (err) {
485
+ console.error('Failed to copy ID:', err);
486
+ showRefreshToast('Failed to copy ID', 'error');
487
+ }
488
+ }
489
+ }
490
+
464
491
  async function copyExecutionPrompt(executionId) {
465
492
  const detail = await loadExecutionDetail(executionId);
466
493
  if (!detail) {
@@ -21,9 +21,24 @@ let nativeResumeEnabled = localStorage.getItem('ccw-native-resume') !== 'false';
21
21
  // Recursive Query settings (for hierarchical storage aggregation)
22
22
  let recursiveQueryEnabled = localStorage.getItem('ccw-recursive-query') !== 'false'; // default true
23
23
 
24
- // Code Index MCP provider (codexlens or ace)
24
+ // Code Index MCP provider (codexlens, ace, or none)
25
25
  let codeIndexMcpProvider = 'codexlens';
26
26
 
27
+ // ========== Helper Functions ==========
28
+ /**
29
+ * Get the context-tools filename based on provider
30
+ */
31
+ function getContextToolsFileName(provider) {
32
+ switch (provider) {
33
+ case 'ace':
34
+ return 'context-tools-ace.md';
35
+ case 'none':
36
+ return 'context-tools-none.md';
37
+ default:
38
+ return 'context-tools.md';
39
+ }
40
+ }
41
+
27
42
  // ========== Initialization ==========
28
43
  function initCliStatus() {
29
44
  // Load all statuses in one call using aggregated endpoint
@@ -637,9 +652,17 @@ function renderCliStatus() {
637
652
  onclick="setCodeIndexMcpProvider('ace')">
638
653
  ACE
639
654
  </button>
655
+ <button class="code-mcp-btn px-3 py-1.5 text-xs font-medium rounded-md transition-all ${codeIndexMcpProvider === 'none' ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}"
656
+ onclick="setCodeIndexMcpProvider('none')">
657
+ None
658
+ </button>
640
659
  </div>
641
660
  </div>
642
661
  <p class="cli-setting-desc">Code search provider (updates CLAUDE.md context-tools reference)</p>
662
+ <p class="cli-setting-desc text-xs text-muted-foreground mt-1">
663
+ <i data-lucide="file-text" class="w-3 h-3 inline-block mr-1"></i>
664
+ Current: <code class="bg-muted px-1 rounded">${getContextToolsFileName(codeIndexMcpProvider)}</code>
665
+ </p>
643
666
  </div>
644
667
  </div>
645
668
  </div>
@@ -775,7 +798,8 @@ async function setCodeIndexMcpProvider(provider) {
775
798
  if (window.claudeCliToolsConfig && window.claudeCliToolsConfig.settings) {
776
799
  window.claudeCliToolsConfig.settings.codeIndexMcp = provider;
777
800
  }
778
- showRefreshToast(`Code Index MCP switched to ${provider === 'ace' ? 'ACE (Augment)' : 'CodexLens'}`, 'success');
801
+ const providerName = provider === 'ace' ? 'ACE (Augment)' : provider === 'none' ? 'None (Built-in only)' : 'CodexLens';
802
+ showRefreshToast(`Code Index MCP switched to ${providerName}`, 'success');
779
803
  // Re-render both CLI status and settings section
780
804
  if (typeof renderCliStatus === 'function') renderCliStatus();
781
805
  if (typeof renderCliSettingsSection === 'function') renderCliSettingsSection();
@@ -996,9 +996,14 @@ function renderCliSettingsSection() {
996
996
  '<select class="cli-setting-select" onchange="setCodeIndexMcpProvider(this.value)">' +
997
997
  '<option value="codexlens"' + (codeIndexMcpProvider === 'codexlens' ? ' selected' : '') + '>CodexLens</option>' +
998
998
  '<option value="ace"' + (codeIndexMcpProvider === 'ace' ? ' selected' : '') + '>ACE (Augment)</option>' +
999
+ '<option value="none"' + (codeIndexMcpProvider === 'none' ? ' selected' : '') + '>None (Built-in)</option>' +
999
1000
  '</select>' +
1000
1001
  '</div>' +
1001
1002
  '<p class="cli-setting-desc">' + t('cli.codeIndexMcpDesc') + '</p>' +
1003
+ '<p class="cli-setting-desc text-xs text-muted-foreground">' +
1004
+ '<i data-lucide="file-text" class="w-3 h-3 inline-block mr-1"></i>' +
1005
+ 'Current: <code class="bg-muted px-1 rounded">' + getContextToolsFileName(codeIndexMcpProvider) + '</code>' +
1006
+ '</p>' +
1002
1007
  '</div>' +
1003
1008
  '</div>';
1004
1009
 
@@ -69,8 +69,10 @@ async function renderCliHistoryView() {
69
69
  '</div>'
70
70
  : '';
71
71
 
72
+ // Normalize sourceDir: convert backslashes to forward slashes for safe onclick handling
73
+ var normalizedSourceDir = (exec.sourceDir || '').replace(/\\/g, '/');
72
74
  historyHtml += '<div class="history-item' + (isSelected ? ' history-item-selected' : '') + '" ' +
73
- 'onclick="' + (isMultiSelectMode ? 'toggleExecutionSelection(\'' + exec.id + '\')' : 'showExecutionDetail(\'' + exec.id + (exec.sourceDir ? '\',\'' + escapeHtml(exec.sourceDir) : '') + '\')') + '">' +
75
+ 'onclick="' + (isMultiSelectMode ? 'toggleExecutionSelection(\'' + exec.id + '\')' : 'showExecutionDetail(\'' + exec.id + '\', \'' + normalizedSourceDir.replace(/'/g, "\\'") + '\')') + '">' +
74
76
  checkboxHtml +
75
77
  '<div class="history-item-main">' +
76
78
  '<div class="history-item-header">' +
@@ -87,14 +89,17 @@ async function renderCliHistoryView() {
87
89
  '<div class="history-item-meta">' +
88
90
  '<span class="history-time"><i data-lucide="clock" class="w-3 h-3"></i> ' + timeAgo + '</span>' +
89
91
  '<span class="history-duration"><i data-lucide="timer" class="w-3 h-3"></i> ' + duration + '</span>' +
90
- '<span class="history-id"><i data-lucide="hash" class="w-3 h-3"></i> ' + exec.id.split('-')[0] + '</span>' +
92
+ '<span class="history-id" title="' + exec.id + '"><i data-lucide="hash" class="w-3 h-3"></i> ' + exec.id.substring(0, 13) + '...' + exec.id.split('-').pop() + '</span>' +
91
93
  '</div>' +
92
94
  '</div>' +
93
95
  '<div class="history-item-actions">' +
94
- '<button class="btn-icon" onclick="event.stopPropagation(); showExecutionDetail(\'' + exec.id + '\')" title="View Details">' +
96
+ '<button class="btn-icon" onclick="event.stopPropagation(); copyExecutionId(\'' + exec.id + '\')" title="Copy ID">' +
97
+ '<i data-lucide="copy" class="w-4 h-4"></i>' +
98
+ '</button>' +
99
+ '<button class="btn-icon" onclick="event.stopPropagation(); showExecutionDetail(\'' + exec.id + '\', \'' + normalizedSourceDir.replace(/'/g, "\\'") + '\')" title="View Details">' +
95
100
  '<i data-lucide="eye" class="w-4 h-4"></i>' +
96
101
  '</button>' +
97
- '<button class="btn-icon btn-danger" onclick="event.stopPropagation(); confirmDeleteExecution(\'' + exec.id + (exec.sourceDir ? '\',\'' + escapeHtml(exec.sourceDir) : '') + '\')" title="Delete">' +
102
+ '<button class="btn-icon btn-danger" onclick="event.stopPropagation(); confirmDeleteExecution(\'' + exec.id + '\', \'' + normalizedSourceDir.replace(/'/g, "\\'") + '\')" title="Delete">' +
98
103
  '<i data-lucide="trash-2" class="w-4 h-4"></i>' +
99
104
  '</button>' +
100
105
  '</div>' +
@@ -179,6 +184,16 @@ async function renderCliHistoryView() {
179
184
  }
180
185
 
181
186
  // ========== Actions ==========
187
+ async function copyExecutionId(executionId) {
188
+ try {
189
+ await navigator.clipboard.writeText(executionId);
190
+ showRefreshToast('ID copied: ' + executionId, 'success');
191
+ } catch (err) {
192
+ console.error('Failed to copy ID:', err);
193
+ showRefreshToast('Failed to copy ID', 'error');
194
+ }
195
+ }
196
+
182
197
  async function filterCliHistoryView(tool) {
183
198
  cliHistoryFilter = tool || null;
184
199
  await loadCliHistory();
@@ -42,7 +42,7 @@ export interface ClaudeCliToolsConfig {
42
42
  nativeResume: boolean;
43
43
  recursiveQuery: boolean;
44
44
  cache: ClaudeCacheSettings;
45
- codeIndexMcp: 'codexlens' | 'ace'; // Code Index MCP provider
45
+ codeIndexMcp: 'codexlens' | 'ace' | 'none'; // Code Index MCP provider
46
46
  };
47
47
  }
48
48
 
@@ -308,7 +308,7 @@ export function getClaudeCliToolsInfo(projectDir: string): {
308
308
  */
309
309
  export function updateCodeIndexMcp(
310
310
  projectDir: string,
311
- provider: 'codexlens' | 'ace'
311
+ provider: 'codexlens' | 'ace' | 'none'
312
312
  ): { success: boolean; error?: string; config?: ClaudeCliToolsConfig } {
313
313
  try {
314
314
  // Update config
@@ -319,21 +319,28 @@ export function updateCodeIndexMcp(
319
319
  // Only update global CLAUDE.md (consistent with Chinese response / Windows platform)
320
320
  const globalClaudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
321
321
 
322
+ // Define patterns for all formats
323
+ const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g;
324
+ const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g;
325
+ const nonePattern = /@~\/\.claude\/workflows\/context-tools-none\.md/g;
326
+
327
+ // Determine target file based on provider
328
+ const targetFile = provider === 'ace'
329
+ ? '@~/.claude/workflows/context-tools-ace.md'
330
+ : provider === 'none'
331
+ ? '@~/.claude/workflows/context-tools-none.md'
332
+ : '@~/.claude/workflows/context-tools.md';
333
+
322
334
  if (!fs.existsSync(globalClaudeMdPath)) {
323
335
  // If global CLAUDE.md doesn't exist, check project-level
324
336
  const projectClaudeMdPath = path.join(projectDir, '.claude', 'CLAUDE.md');
325
337
  if (fs.existsSync(projectClaudeMdPath)) {
326
338
  let content = fs.readFileSync(projectClaudeMdPath, 'utf-8');
327
339
 
328
- // Define patterns for both formats
329
- const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g;
330
- const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g;
331
-
332
- if (provider === 'ace') {
333
- content = content.replace(codexlensPattern, '@~/.claude/workflows/context-tools-ace.md');
334
- } else {
335
- content = content.replace(acePattern, '@~/.claude/workflows/context-tools.md');
336
- }
340
+ // Replace any existing pattern with the target
341
+ content = content.replace(codexlensPattern, targetFile);
342
+ content = content.replace(acePattern, targetFile);
343
+ content = content.replace(nonePattern, targetFile);
337
344
 
338
345
  fs.writeFileSync(projectClaudeMdPath, content, 'utf-8');
339
346
  console.log(`[claude-cli-tools] Updated project CLAUDE.md to use ${provider} (no global CLAUDE.md found)`);
@@ -342,14 +349,10 @@ export function updateCodeIndexMcp(
342
349
  // Update global CLAUDE.md (primary target)
343
350
  let content = fs.readFileSync(globalClaudeMdPath, 'utf-8');
344
351
 
345
- const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g;
346
- const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g;
347
-
348
- if (provider === 'ace') {
349
- content = content.replace(codexlensPattern, '@~/.claude/workflows/context-tools-ace.md');
350
- } else {
351
- content = content.replace(acePattern, '@~/.claude/workflows/context-tools.md');
352
- }
352
+ // Replace any existing pattern with the target
353
+ content = content.replace(codexlensPattern, targetFile);
354
+ content = content.replace(acePattern, targetFile);
355
+ content = content.replace(nonePattern, targetFile);
353
356
 
354
357
  fs.writeFileSync(globalClaudeMdPath, content, 'utf-8');
355
358
  console.log(`[claude-cli-tools] Updated global CLAUDE.md to use ${provider}`);
@@ -365,7 +368,21 @@ export function updateCodeIndexMcp(
365
368
  /**
366
369
  * Get current Code Index MCP provider
367
370
  */
368
- export function getCodeIndexMcp(projectDir: string): 'codexlens' | 'ace' {
371
+ export function getCodeIndexMcp(projectDir: string): 'codexlens' | 'ace' | 'none' {
369
372
  const config = loadClaudeCliTools(projectDir);
370
373
  return config.settings.codeIndexMcp || 'codexlens';
371
374
  }
375
+
376
+ /**
377
+ * Get the context-tools file path based on provider
378
+ */
379
+ export function getContextToolsPath(provider: 'codexlens' | 'ace' | 'none'): string {
380
+ switch (provider) {
381
+ case 'ace':
382
+ return 'context-tools-ace.md';
383
+ case 'none':
384
+ return 'context-tools-none.md';
385
+ default:
386
+ return 'context-tools.md';
387
+ }
388
+ }
@@ -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
- // Check if HTTP 429 but results exist (Gemini quirk)
872
- if (stderr.includes('429') && stdout.trim()) {
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
  */