claude-code-workflow 6.3.24 → 6.3.26

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 (75) hide show
  1. package/.claude/commands/issue/discover-by-prompt.md +764 -0
  2. package/.claude/skills/text-formatter/SKILL.md +196 -0
  3. package/.claude/skills/text-formatter/phases/01-input-collection.md +111 -0
  4. package/.claude/skills/text-formatter/phases/02-content-analysis.md +248 -0
  5. package/.claude/skills/text-formatter/phases/03-format-transform.md +245 -0
  6. package/.claude/skills/text-formatter/phases/04-output-preview.md +183 -0
  7. package/.claude/skills/text-formatter/specs/callout-types.md +293 -0
  8. package/.claude/skills/text-formatter/specs/element-mapping.md +226 -0
  9. package/.claude/skills/text-formatter/specs/format-rules.md +273 -0
  10. package/.claude/skills/text-formatter/templates/bbcode-template.md +350 -0
  11. package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
  12. package/ccw/dist/core/routes/help-routes.js +43 -7
  13. package/ccw/dist/core/routes/help-routes.js.map +1 -1
  14. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
  15. package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
  16. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
  17. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  18. package/ccw/dist/core/routes/memory-routes.js +73 -0
  19. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  20. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  21. package/ccw/dist/core/routes/status-routes.js +36 -4
  22. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  23. package/ccw/dist/core/server.d.ts.map +1 -1
  24. package/ccw/dist/core/server.js +58 -0
  25. package/ccw/dist/core/server.js.map +1 -1
  26. package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
  27. package/ccw/dist/core/services/api-key-tester.js +8 -3
  28. package/ccw/dist/core/services/api-key-tester.js.map +1 -1
  29. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
  30. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  31. package/ccw/dist/tools/claude-cli-tools.js +11 -1
  32. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  33. package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
  34. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  35. package/ccw/dist/tools/cli-executor-core.js +89 -2
  36. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  37. package/ccw/dist/tools/codex-lens.d.ts +2 -1
  38. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  39. package/ccw/dist/tools/codex-lens.js +51 -8
  40. package/ccw/dist/tools/codex-lens.js.map +1 -1
  41. package/ccw/dist/tools/index.d.ts.map +1 -1
  42. package/ccw/dist/tools/index.js +2 -0
  43. package/ccw/dist/tools/index.js.map +1 -1
  44. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  45. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  46. package/ccw/dist/tools/litellm-client.js +22 -1
  47. package/ccw/dist/tools/litellm-client.js.map +1 -1
  48. package/ccw/dist/tools/litellm-executor.js +2 -2
  49. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  50. package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
  51. package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
  52. package/ccw/dist/tools/memory-update-queue.js +431 -0
  53. package/ccw/dist/tools/memory-update-queue.js.map +1 -0
  54. package/ccw/src/core/routes/help-routes.ts +46 -7
  55. package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
  56. package/ccw/src/core/routes/memory-routes.ts +84 -0
  57. package/ccw/src/core/routes/status-routes.ts +39 -4
  58. package/ccw/src/core/server.ts +62 -0
  59. package/ccw/src/core/services/api-key-tester.ts +9 -3
  60. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
  61. package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
  62. package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
  63. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
  64. package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
  65. package/ccw/src/templates/dashboard-js/i18n.js +26 -0
  66. package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
  67. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
  68. package/ccw/src/tools/claude-cli-tools.ts +17 -1
  69. package/ccw/src/tools/cli-executor-core.ts +103 -2
  70. package/ccw/src/tools/codex-lens.ts +63 -8
  71. package/ccw/src/tools/index.ts +2 -0
  72. package/ccw/src/tools/litellm-client.ts +25 -3
  73. package/ccw/src/tools/litellm-executor.ts +2 -2
  74. package/ccw/src/tools/memory-update-queue.js +499 -0
  75. package/package.json +91 -91
@@ -9,6 +9,15 @@ import { getCliToolsStatus } from '../../tools/cli-executor.js';
9
9
  import { checkVenvStatus, checkSemanticStatus } from '../../tools/codex-lens.js';
10
10
  import type { RouteContext } from './types.js';
11
11
 
12
+ // Performance logging helper
13
+ const PERF_LOG_ENABLED = process.env.CCW_PERF_LOG === '1' || true; // Enable by default for debugging
14
+ function perfLog(label: string, startTime: number, extra?: Record<string, unknown>): void {
15
+ if (!PERF_LOG_ENABLED) return;
16
+ const duration = Date.now() - startTime;
17
+ const extraStr = extra ? ` | ${JSON.stringify(extra)}` : '';
18
+ console.log(`[PERF][Status] ${label}: ${duration}ms${extraStr}`);
19
+ }
20
+
12
21
  /**
13
22
  * Check CCW installation status
14
23
  * Verifies that required workflow files are installed in user's home directory
@@ -62,16 +71,39 @@ export async function handleStatusRoutes(ctx: RouteContext): Promise<boolean> {
62
71
 
63
72
  // API: Aggregated Status (all statuses in one call)
64
73
  if (pathname === '/api/status/all') {
74
+ const totalStart = Date.now();
75
+ console.log('[PERF][Status] === /api/status/all START ===');
76
+
65
77
  try {
66
78
  // Check CCW installation status (sync, fast)
79
+ const ccwStart = Date.now();
67
80
  const ccwInstallStatus = checkCcwInstallStatus();
81
+ perfLog('checkCcwInstallStatus', ccwStart);
82
+
83
+ // Execute all status checks in parallel with individual timing
84
+ const cliStart = Date.now();
85
+ const codexStart = Date.now();
86
+ const semanticStart = Date.now();
68
87
 
69
- // Execute all status checks in parallel
70
88
  const [cliStatus, codexLensStatus, semanticStatus] = await Promise.all([
71
- getCliToolsStatus(),
72
- checkVenvStatus(),
89
+ getCliToolsStatus().then(result => {
90
+ perfLog('getCliToolsStatus', cliStart, { toolCount: Object.keys(result).length });
91
+ return result;
92
+ }),
93
+ checkVenvStatus().then(result => {
94
+ perfLog('checkVenvStatus', codexStart, { ready: result.ready });
95
+ return result;
96
+ }),
73
97
  // Always check semantic status (will return available: false if CodexLens not ready)
74
- checkSemanticStatus().catch(() => ({ available: false, backend: null }))
98
+ checkSemanticStatus()
99
+ .then(result => {
100
+ perfLog('checkSemanticStatus', semanticStart, { available: result.available });
101
+ return result;
102
+ })
103
+ .catch(() => {
104
+ perfLog('checkSemanticStatus (error)', semanticStart);
105
+ return { available: false, backend: null };
106
+ })
75
107
  ]);
76
108
 
77
109
  const response = {
@@ -82,10 +114,13 @@ export async function handleStatusRoutes(ctx: RouteContext): Promise<boolean> {
82
114
  timestamp: new Date().toISOString()
83
115
  };
84
116
 
117
+ perfLog('=== /api/status/all TOTAL ===', totalStart);
118
+
85
119
  res.writeHead(200, { 'Content-Type': 'application/json' });
86
120
  res.end(JSON.stringify(response));
87
121
  return true;
88
122
  } catch (error) {
123
+ perfLog('=== /api/status/all ERROR ===', totalStart);
89
124
  console.error('[Status Routes] Error fetching aggregated status:', error);
90
125
  res.writeHead(500, { 'Content-Type': 'application/json' });
91
126
  res.end(JSON.stringify({ error: (error as Error).message }));
@@ -42,6 +42,10 @@ import { randomBytes } from 'crypto';
42
42
  // Import health check service
43
43
  import { getHealthCheckService } from './services/health-check-service.js';
44
44
 
45
+ // Import status check functions for warmup
46
+ import { checkSemanticStatus, checkVenvStatus } from '../tools/codex-lens.js';
47
+ import { getCliToolsStatus } from '../tools/cli-executor.js';
48
+
45
49
  import type { ServerConfig } from '../types/config.js';
46
50
  import type { PostRequestHandler } from './routes/types.js';
47
51
 
@@ -290,6 +294,56 @@ function setCsrfCookie(res: http.ServerResponse, token: string, maxAgeSeconds: n
290
294
  appendSetCookie(res, attributes.join('; '));
291
295
  }
292
296
 
297
+ /**
298
+ * Warmup function to pre-populate caches on server startup
299
+ * This runs asynchronously and non-blocking after the server starts
300
+ */
301
+ async function warmupCaches(initialPath: string): Promise<void> {
302
+ console.log('[WARMUP] Starting cache warmup...');
303
+ const startTime = Date.now();
304
+
305
+ // Run all warmup tasks in parallel for faster startup
306
+ const warmupTasks = [
307
+ // Warmup semantic status cache (Python process startup - can be slow first time)
308
+ (async () => {
309
+ const taskStart = Date.now();
310
+ try {
311
+ const semanticStatus = await checkSemanticStatus();
312
+ console.log(`[WARMUP] Semantic status: ${semanticStatus.available ? 'available' : 'not available'} (${Date.now() - taskStart}ms)`);
313
+ } catch (err) {
314
+ console.warn(`[WARMUP] Semantic status check failed: ${(err as Error).message}`);
315
+ }
316
+ })(),
317
+
318
+ // Warmup venv status cache
319
+ (async () => {
320
+ const taskStart = Date.now();
321
+ try {
322
+ const venvStatus = await checkVenvStatus();
323
+ console.log(`[WARMUP] Venv status: ${venvStatus.ready ? 'ready' : 'not ready'} (${Date.now() - taskStart}ms)`);
324
+ } catch (err) {
325
+ console.warn(`[WARMUP] Venv status check failed: ${(err as Error).message}`);
326
+ }
327
+ })(),
328
+
329
+ // Warmup CLI tools status cache
330
+ (async () => {
331
+ const taskStart = Date.now();
332
+ try {
333
+ const cliStatus = await getCliToolsStatus();
334
+ const availableCount = Object.values(cliStatus).filter(s => s.available).length;
335
+ const totalCount = Object.keys(cliStatus).length;
336
+ console.log(`[WARMUP] CLI tools status: ${availableCount}/${totalCount} available (${Date.now() - taskStart}ms)`);
337
+ } catch (err) {
338
+ console.warn(`[WARMUP] CLI tools status check failed: ${(err as Error).message}`);
339
+ }
340
+ })()
341
+ ];
342
+
343
+ await Promise.allSettled(warmupTasks);
344
+ console.log(`[WARMUP] Cache warmup complete (${Date.now() - startTime}ms total)`);
345
+ }
346
+
293
347
  /**
294
348
  * Generate dashboard HTML with embedded CSS and JS
295
349
  */
@@ -650,6 +704,14 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
650
704
  console.warn('[Server] Failed to start health check service:', err);
651
705
  }
652
706
 
707
+ // Start cache warmup asynchronously (non-blocking)
708
+ // Uses setImmediate to not delay server startup response
709
+ setImmediate(() => {
710
+ warmupCaches(initialPath).catch((err) => {
711
+ console.warn('[WARMUP] Cache warmup failed:', err);
712
+ });
713
+ });
714
+
653
715
  resolve(server);
654
716
  });
655
717
  server.on('error', reject);
@@ -72,6 +72,10 @@ export async function testApiKeyConnection(
72
72
  return { valid: false, error: urlValidation.error };
73
73
  }
74
74
 
75
+ // Normalize apiBase: remove trailing slashes to prevent URL construction issues
76
+ // e.g., "https://api.openai.com/v1/" -> "https://api.openai.com/v1"
77
+ const normalizedApiBase = apiBase.replace(/\/+$/, '');
78
+
75
79
  const controller = new AbortController();
76
80
  const timeoutId = setTimeout(() => controller.abort(), timeout);
77
81
  const startTime = Date.now();
@@ -80,7 +84,7 @@ export async function testApiKeyConnection(
80
84
  if (providerType === 'anthropic') {
81
85
  // Anthropic format: Use /v1/models endpoint (no cost, no model dependency)
82
86
  // This validates the API key without making a billable request
83
- const response = await fetch(`${apiBase}/models`, {
87
+ const response = await fetch(`${normalizedApiBase}/models`, {
84
88
  method: 'GET',
85
89
  headers: {
86
90
  'x-api-key': apiKey,
@@ -114,8 +118,10 @@ export async function testApiKeyConnection(
114
118
 
115
119
  return { valid: false, error: errorMessage };
116
120
  } else {
117
- // OpenAI-compatible format: GET /v1/models
118
- const modelsUrl = apiBase.endsWith('/v1') ? `${apiBase}/models` : `${apiBase}/v1/models`;
121
+ // OpenAI-compatible format: GET /v{N}/models
122
+ // Detect if URL already ends with a version pattern like /v1, /v2, /v4, etc.
123
+ const hasVersionSuffix = /\/v\d+$/.test(normalizedApiBase);
124
+ const modelsUrl = hasVersionSuffix ? `${normalizedApiBase}/models` : `${normalizedApiBase}/v1/models`;
119
125
  const response = await fetch(modelsUrl, {
120
126
  method: 'GET',
121
127
  headers: {
@@ -304,6 +304,51 @@
304
304
  margin-top: 0;
305
305
  }
306
306
 
307
+ /* Environment File Input Group */
308
+ .env-file-input-group {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 0.5rem;
312
+ }
313
+
314
+ .env-file-input-row {
315
+ display: flex;
316
+ gap: 0.5rem;
317
+ align-items: center;
318
+ }
319
+
320
+ .env-file-input-row .tool-config-input {
321
+ flex: 1;
322
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
323
+ font-size: 0.8125rem;
324
+ margin-top: 0;
325
+ }
326
+
327
+ .env-file-input-row .btn-sm {
328
+ flex-shrink: 0;
329
+ display: inline-flex;
330
+ align-items: center;
331
+ gap: 0.25rem;
332
+ padding: 0.5rem 0.75rem;
333
+ font-size: 0.8125rem;
334
+ white-space: nowrap;
335
+ }
336
+
337
+ .env-file-hint {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 0.375rem;
341
+ font-size: 0.75rem;
342
+ color: hsl(var(--muted-foreground));
343
+ margin: 0;
344
+ padding: 0;
345
+ }
346
+
347
+ .env-file-hint i {
348
+ flex-shrink: 0;
349
+ opacity: 0.7;
350
+ }
351
+
307
352
  .btn-ghost.text-destructive:hover {
308
353
  background: hsl(var(--destructive) / 0.1);
309
354
  }
@@ -33,11 +33,14 @@ function initCliStatus() {
33
33
  * Load all statuses using aggregated endpoint (single API call)
34
34
  */
35
35
  async function loadAllStatuses() {
36
+ const totalStart = performance.now();
37
+ console.log('[PERF][Frontend] loadAllStatuses START');
38
+
36
39
  // 1. 尝试从缓存获取(预加载的数据)
37
40
  if (window.cacheManager) {
38
41
  const cached = window.cacheManager.get('all-status');
39
42
  if (cached) {
40
- console.log('[CLI Status] Loaded all statuses from cache');
43
+ console.log(`[PERF][Frontend] Cache hit: ${(performance.now() - totalStart).toFixed(1)}ms`);
41
44
  // 应用缓存数据
42
45
  cliToolStatus = cached.cli || {};
43
46
  codexLensStatus = cached.codexLens || { ready: false };
@@ -45,25 +48,32 @@ async function loadAllStatuses() {
45
48
  ccwInstallStatus = cached.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
46
49
 
47
50
  // Load CLI tools config, API endpoints, and CLI Settings(这些有自己的缓存)
51
+ const configStart = performance.now();
48
52
  await Promise.all([
49
53
  loadCliToolsConfig(),
50
54
  loadApiEndpoints(),
51
55
  loadCliSettingsEndpoints()
52
56
  ]);
57
+ console.log(`[PERF][Frontend] Config/Endpoints load: ${(performance.now() - configStart).toFixed(1)}ms`);
53
58
 
54
59
  // Update badges
55
60
  updateCliBadge();
56
61
  updateCodexLensBadge();
57
62
  updateCcwInstallBadge();
63
+
64
+ console.log(`[PERF][Frontend] loadAllStatuses TOTAL (cached): ${(performance.now() - totalStart).toFixed(1)}ms`);
58
65
  return cached;
59
66
  }
60
67
  }
61
68
 
62
69
  // 2. 缓存未命中,从服务器获取
63
70
  try {
71
+ const fetchStart = performance.now();
72
+ console.log('[PERF][Frontend] Fetching /api/status/all...');
64
73
  const response = await fetch('/api/status/all');
65
74
  if (!response.ok) throw new Error('Failed to load status');
66
75
  const data = await response.json();
76
+ console.log(`[PERF][Frontend] /api/status/all fetch: ${(performance.now() - fetchStart).toFixed(1)}ms`);
67
77
 
68
78
  // 存入缓存
69
79
  if (window.cacheManager) {
@@ -77,10 +87,11 @@ async function loadAllStatuses() {
77
87
  ccwInstallStatus = data.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
78
88
 
79
89
  // Load CLI tools config, API endpoints, and CLI Settings
80
- await Promise.all([
81
- loadCliToolsConfig(),
82
- loadApiEndpoints(),
83
- loadCliSettingsEndpoints()
90
+ const configStart = performance.now();
91
+ const [configResult, endpointsResult, settingsResult] = await Promise.all([
92
+ loadCliToolsConfig().then(r => { console.log(`[PERF][Frontend] loadCliToolsConfig: ${(performance.now() - configStart).toFixed(1)}ms`); return r; }),
93
+ loadApiEndpoints().then(r => { console.log(`[PERF][Frontend] loadApiEndpoints: ${(performance.now() - configStart).toFixed(1)}ms`); return r; }),
94
+ loadCliSettingsEndpoints().then(r => { console.log(`[PERF][Frontend] loadCliSettingsEndpoints: ${(performance.now() - configStart).toFixed(1)}ms`); return r; })
84
95
  ]);
85
96
 
86
97
  // Update badges
@@ -88,9 +99,11 @@ async function loadAllStatuses() {
88
99
  updateCodexLensBadge();
89
100
  updateCcwInstallBadge();
90
101
 
102
+ console.log(`[PERF][Frontend] loadAllStatuses TOTAL: ${(performance.now() - totalStart).toFixed(1)}ms`);
91
103
  return data;
92
104
  } catch (err) {
93
105
  console.error('Failed to load aggregated status:', err);
106
+ console.log(`[PERF][Frontend] loadAllStatuses ERROR after: ${(performance.now() - totalStart).toFixed(1)}ms`);
94
107
  // Fallback to individual calls if aggregated endpoint fails
95
108
  return await loadAllStatusesFallback();
96
109
  }
@@ -1034,6 +1047,15 @@ async function startCodexLensInstall() {
1034
1047
  progressBar.style.width = '100%';
1035
1048
  statusText.textContent = 'Installation complete!';
1036
1049
 
1050
+ // 清理缓存以确保刷新后获取最新状态
1051
+ if (window.cacheManager) {
1052
+ window.cacheManager.invalidate('all-status');
1053
+ window.cacheManager.invalidate('dashboard-init');
1054
+ }
1055
+ if (typeof window.invalidateCodexLensCache === 'function') {
1056
+ window.invalidateCodexLensCache();
1057
+ }
1058
+
1037
1059
  setTimeout(() => {
1038
1060
  closeCodexLensInstallWizard();
1039
1061
  showRefreshToast('CodexLens installed successfully!', 'success');
@@ -1184,6 +1206,15 @@ async function startCodexLensUninstall() {
1184
1206
  progressBar.style.width = '100%';
1185
1207
  statusText.textContent = 'Uninstallation complete!';
1186
1208
 
1209
+ // 清理缓存以确保刷新后获取最新状态
1210
+ if (window.cacheManager) {
1211
+ window.cacheManager.invalidate('all-status');
1212
+ window.cacheManager.invalidate('dashboard-init');
1213
+ }
1214
+ if (typeof window.invalidateCodexLensCache === 'function') {
1215
+ window.invalidateCodexLensCache();
1216
+ }
1217
+
1187
1218
  setTimeout(() => {
1188
1219
  closeCodexLensUninstallWizard();
1189
1220
  showRefreshToast('CodexLens uninstalled successfully!', 'success');
@@ -49,43 +49,17 @@ const HOOK_TEMPLATES = {
49
49
  description: 'Auto-update code index when files are written or edited',
50
50
  category: 'indexing'
51
51
  },
52
- 'memory-update-related': {
52
+ 'memory-update-queue': {
53
53
  event: 'Stop',
54
54
  matcher: '',
55
55
  command: 'bash',
56
- args: ['-c', 'ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\''],
57
- description: 'Update CLAUDE.md for changed modules when session ends',
56
+ args: ['-c', 'ccw tool exec memory_queue "{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"'],
57
+ description: 'Queue CLAUDE.md update when session ends (batched by threshold/timeout)',
58
58
  category: 'memory',
59
59
  configurable: true,
60
60
  config: {
61
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
62
- strategy: { type: 'select', options: ['related', 'single-layer'], default: 'related', label: 'Strategy' }
63
- }
64
- },
65
- 'memory-update-periodic': {
66
- event: 'PostToolUse',
67
- matcher: 'Write|Edit',
68
- command: 'bash',
69
- args: ['-c', 'INTERVAL=300; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
70
- description: 'Periodically update CLAUDE.md (default: 5 min interval)',
71
- category: 'memory',
72
- configurable: true,
73
- config: {
74
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
75
- interval: { type: 'number', default: 300, min: 60, max: 3600, label: 'Interval (seconds)', step: 60 }
76
- }
77
- },
78
- 'memory-update-count-based': {
79
- event: 'PostToolUse',
80
- matcher: 'Write|Edit',
81
- command: 'bash',
82
- args: ['-c', 'THRESHOLD=10; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
83
- description: 'Update CLAUDE.md when file changes reach threshold (default: 10 files)',
84
- category: 'memory',
85
- configurable: true,
86
- config: {
87
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
88
- threshold: { type: 'number', default: 10, min: 3, max: 50, label: 'File count threshold', step: 1 }
61
+ threshold: { type: 'number', default: 5, min: 1, max: 20, label: 'Threshold (paths)', step: 1 },
62
+ timeout: { type: 'number', default: 300, min: 60, max: 1800, label: 'Timeout (seconds)', step: 60 }
89
63
  }
90
64
  },
91
65
  // SKILL Context Loader templates
@@ -210,33 +184,19 @@ const HOOK_TEMPLATES = {
210
184
  const WIZARD_TEMPLATES = {
211
185
  'memory-update': {
212
186
  name: 'Memory Update Hook',
213
- description: 'Automatically update CLAUDE.md documentation based on code changes',
187
+ description: 'Queue-based CLAUDE.md updates with configurable threshold and timeout',
214
188
  icon: 'brain',
215
189
  options: [
216
190
  {
217
- id: 'on-stop',
218
- name: 'On Session End',
219
- description: 'Update documentation when Claude session ends',
220
- templateId: 'memory-update-related'
221
- },
222
- {
223
- id: 'periodic',
224
- name: 'Periodic Update',
225
- description: 'Update documentation at regular intervals during session',
226
- templateId: 'memory-update-periodic'
227
- },
228
- {
229
- id: 'count-based',
230
- name: 'Count-Based Update',
231
- description: 'Update documentation when file changes reach threshold',
232
- templateId: 'memory-update-count-based'
191
+ id: 'queue',
192
+ name: 'Queue-Based Update',
193
+ description: 'Batch updates when threshold reached or timeout expires',
194
+ templateId: 'memory-update-queue'
233
195
  }
234
196
  ],
235
197
  configFields: [
236
- { key: 'tool', type: 'select', label: 'CLI Tool', options: ['gemini', 'qwen', 'codex'], default: 'gemini', description: 'Tool for documentation generation' },
237
- { key: 'interval', type: 'number', label: 'Interval (seconds)', default: 300, min: 60, max: 3600, step: 60, showFor: ['periodic'], description: 'Time between updates' },
238
- { key: 'threshold', type: 'number', label: 'File Count Threshold', default: 10, min: 3, max: 50, step: 1, showFor: ['count-based'], description: 'Number of file changes to trigger update' },
239
- { key: 'strategy', type: 'select', label: 'Update Strategy', options: ['related', 'single-layer'], default: 'related', description: 'Related: changed modules, Single-layer: current directory' }
198
+ { key: 'threshold', type: 'number', label: 'Threshold (paths)', default: 5, min: 1, max: 20, step: 1, description: 'Number of paths to trigger batch update' },
199
+ { key: 'timeout', type: 'number', label: 'Timeout (seconds)', default: 300, min: 60, max: 1800, step: 60, description: 'Auto-flush queue after this time' }
240
200
  ]
241
201
  },
242
202
  'skill-context': {
@@ -730,9 +690,7 @@ function renderWizardModalContent() {
730
690
  // Helper to get translated option names
731
691
  const getOptionName = (optId) => {
732
692
  if (wizardId === 'memory-update') {
733
- if (optId === 'on-stop') return t('hook.wizard.onSessionEnd');
734
- if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
735
- if (optId === 'count-based') return t('hook.wizard.countBasedUpdate');
693
+ if (optId === 'queue') return t('hook.wizard.queueBasedUpdate') || 'Queue-Based Update';
736
694
  }
737
695
  if (wizardId === 'memory-setup') {
738
696
  if (optId === 'file-read') return t('hook.wizard.fileReadTracker');
@@ -748,9 +706,7 @@ function renderWizardModalContent() {
748
706
 
749
707
  const getOptionDesc = (optId) => {
750
708
  if (wizardId === 'memory-update') {
751
- if (optId === 'on-stop') return t('hook.wizard.onSessionEndDesc');
752
- if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
753
- if (optId === 'count-based') return t('hook.wizard.countBasedUpdateDesc');
709
+ if (optId === 'queue') return t('hook.wizard.queueBasedUpdateDesc') || 'Batch updates when threshold reached or timeout expires';
754
710
  }
755
711
  if (wizardId === 'memory-setup') {
756
712
  if (optId === 'file-read') return t('hook.wizard.fileReadTrackerDesc');
@@ -767,20 +723,16 @@ function renderWizardModalContent() {
767
723
  // Helper to get translated field labels
768
724
  const getFieldLabel = (fieldKey) => {
769
725
  const labels = {
770
- 'tool': t('hook.wizard.cliTool'),
771
- 'interval': t('hook.wizard.intervalSeconds'),
772
- 'threshold': t('hook.wizard.fileCountThreshold'),
773
- 'strategy': t('hook.wizard.updateStrategy')
726
+ 'threshold': t('hook.wizard.thresholdPaths') || 'Threshold (paths)',
727
+ 'timeout': t('hook.wizard.timeoutSeconds') || 'Timeout (seconds)'
774
728
  };
775
729
  return labels[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.label || fieldKey;
776
730
  };
777
731
 
778
732
  const getFieldDesc = (fieldKey) => {
779
733
  const descs = {
780
- 'tool': t('hook.wizard.toolForDocGen'),
781
- 'interval': t('hook.wizard.timeBetweenUpdates'),
782
- 'threshold': t('hook.wizard.fileCountThresholdDesc'),
783
- 'strategy': t('hook.wizard.relatedStrategy')
734
+ 'threshold': t('hook.wizard.thresholdPathsDesc') || 'Number of paths to trigger batch update',
735
+ 'timeout': t('hook.wizard.timeoutSecondsDesc') || 'Auto-flush queue after this time'
784
736
  };
785
737
  return descs[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.description || '';
786
738
  };
@@ -1154,21 +1106,10 @@ function generateWizardCommand() {
1154
1106
  }
1155
1107
 
1156
1108
  // Handle memory-update wizard (default)
1157
- const tool = wizardConfig.tool || 'gemini';
1158
- const strategy = wizardConfig.strategy || 'related';
1159
- const interval = wizardConfig.interval || 300;
1160
- const threshold = wizardConfig.threshold || 10;
1161
-
1162
- // Build the ccw tool command based on configuration
1163
- const params = JSON.stringify({ strategy, tool });
1164
-
1165
- if (triggerType === 'periodic') {
1166
- return `INTERVAL=${interval}; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1167
- } else if (triggerType === 'count-based') {
1168
- return `THRESHOLD=${threshold}; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1169
- } else {
1170
- return `ccw tool exec update_module_claude '${params}'`;
1171
- }
1109
+ // Now uses memory_queue for batched updates with configurable threshold/timeout
1110
+ // The command adds to queue, configuration is applied separately via submitHookWizard
1111
+ const params = `"{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"`;
1112
+ return `ccw tool exec memory_queue ${params}`;
1172
1113
  }
1173
1114
 
1174
1115
  async function submitHookWizard() {
@@ -1263,6 +1204,26 @@ async function submitHookWizard() {
1263
1204
  }
1264
1205
 
1265
1206
  await saveHook(scope, baseTemplate.event, hookData);
1207
+
1208
+ // For memory-update wizard, also configure queue settings
1209
+ if (wizard.id === 'memory-update') {
1210
+ const threshold = wizardConfig.threshold || 5;
1211
+ const timeout = wizardConfig.timeout || 300;
1212
+ try {
1213
+ const configParams = JSON.stringify({ action: 'configure', threshold, timeout });
1214
+ const response = await fetch('/api/tools/execute', {
1215
+ method: 'POST',
1216
+ headers: { 'Content-Type': 'application/json' },
1217
+ body: JSON.stringify({ tool: 'memory_queue', params: configParams })
1218
+ });
1219
+ if (response.ok) {
1220
+ showRefreshToast(`Queue configured: threshold=${threshold}, timeout=${timeout}s`, 'success');
1221
+ }
1222
+ } catch (e) {
1223
+ console.warn('Failed to configure memory queue:', e);
1224
+ }
1225
+ }
1226
+
1266
1227
  closeHookWizardModal();
1267
1228
  }
1268
1229