claude-code-workflow 6.0.4 → 6.1.0

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 (120) hide show
  1. package/.claude/agents/action-planning-agent.md +1 -1
  2. package/.claude/agents/cli-execution-agent.md +269 -269
  3. package/.claude/agents/cli-explore-agent.md +182 -182
  4. package/.claude/agents/context-search-agent.md +582 -582
  5. package/.claude/agents/memory-bridge.md +93 -93
  6. package/.claude/commands/cli/cli-init.md +1 -1
  7. package/.claude/commands/memory/docs-full-cli.md +471 -471
  8. package/.claude/commands/memory/docs-related-cli.md +386 -386
  9. package/.claude/commands/memory/docs.md +615 -615
  10. package/.claude/commands/memory/load.md +1 -1
  11. package/.claude/commands/memory/update-full.md +332 -332
  12. package/.claude/commands/memory/update-related.md +5 -5
  13. package/.claude/commands/workflow/init.md +1 -1
  14. package/.claude/commands/workflow/lite-fix.md +621 -621
  15. package/.claude/commands/workflow/lite-plan.md +592 -592
  16. package/.claude/commands/workflow/tools/context-gather.md +434 -434
  17. package/.claude/commands/workflow/ui-design/generate.md +504 -504
  18. package/.claude/commands/workflow/ui-design/import-from-code.md +537 -537
  19. package/.claude/scripts/classify-folders.sh +4 -0
  20. package/.claude/scripts/convert_tokens_to_css.sh +4 -0
  21. package/.claude/scripts/detect_changed_modules.sh +5 -1
  22. package/.claude/scripts/discover-design-files.sh +87 -83
  23. package/.claude/scripts/generate_module_docs.sh +717 -713
  24. package/.claude/scripts/get_modules_by_depth.sh +5 -1
  25. package/.claude/scripts/ui-generate-preview.sh +4 -0
  26. package/.claude/scripts/ui-instantiate-prototypes.sh +4 -0
  27. package/.claude/scripts/update_module_claude.sh +4 -0
  28. package/.claude/skills/command-guide/index/all-commands.json +1 -12
  29. package/.claude/skills/command-guide/index/by-category.json +1 -12
  30. package/.claude/skills/command-guide/index/by-use-case.json +1 -12
  31. package/.claude/skills/command-guide/index/essential-commands.json +1 -12
  32. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +127 -71
  33. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +269 -269
  34. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -182
  35. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +18 -38
  36. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +582 -577
  37. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
  38. package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +1 -1
  39. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
  40. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
  41. package/.claude/skills/command-guide/reference/commands/memory/docs.md +615 -610
  42. package/.claude/skills/command-guide/reference/commands/memory/load.md +1 -1
  43. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
  44. package/.claude/skills/command-guide/reference/commands/memory/update-related.md +5 -5
  45. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +299 -451
  46. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +14 -37
  47. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +252 -350
  48. package/.claude/skills/command-guide/reference/commands/workflow/init.md +2 -2
  49. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +52 -0
  50. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +621 -602
  51. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +46 -36
  52. package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +18 -58
  53. package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +22 -52
  54. package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +19 -48
  55. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +25 -5
  56. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +1 -1
  57. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +7 -7
  58. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
  59. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -11
  60. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +4 -4
  61. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +1 -1
  62. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -504
  63. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -537
  64. package/.claude/workflows/context-search-strategy.md +77 -77
  65. package/.claude/workflows/tool-strategy.md +90 -71
  66. package/.claude/workflows/workflow-architecture.md +1 -1
  67. package/ccw/package.json +6 -6
  68. package/ccw/src/cli.js +16 -0
  69. package/ccw/src/commands/stop.js +101 -0
  70. package/ccw/src/commands/tool.js +181 -0
  71. package/ccw/src/core/dashboard-generator.js +18 -3
  72. package/ccw/src/core/lite-scanner.js +35 -11
  73. package/ccw/src/core/server.js +583 -17
  74. package/ccw/src/templates/dashboard-css/01-base.css +161 -0
  75. package/ccw/src/templates/dashboard-css/02-session.css +726 -0
  76. package/ccw/src/templates/dashboard-css/03-tasks.css +512 -0
  77. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +843 -0
  78. package/ccw/src/templates/dashboard-css/05-context.css +2206 -0
  79. package/ccw/src/templates/dashboard-css/06-cards.css +1570 -0
  80. package/ccw/src/templates/dashboard-css/07-managers.css +936 -0
  81. package/ccw/src/templates/dashboard-css/08-review.css +1266 -0
  82. package/ccw/src/templates/dashboard-css/09-explorer.css +1397 -0
  83. package/ccw/src/templates/dashboard-js/components/global-notifications.js +219 -0
  84. package/ccw/src/templates/dashboard-js/components/hook-manager.js +10 -0
  85. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +24 -2
  86. package/ccw/src/templates/dashboard-js/components/navigation.js +11 -5
  87. package/ccw/src/templates/dashboard-js/components/tabs-context.js +20 -20
  88. package/ccw/src/templates/dashboard-js/components/tabs-other.js +11 -11
  89. package/ccw/src/templates/dashboard-js/components/theme.js +29 -1
  90. package/ccw/src/templates/dashboard-js/main.js +4 -0
  91. package/ccw/src/templates/dashboard-js/state.js +5 -0
  92. package/ccw/src/templates/dashboard-js/views/explorer.js +852 -0
  93. package/ccw/src/templates/dashboard-js/views/home.js +13 -9
  94. package/ccw/src/templates/dashboard-js/views/hook-manager.js +8 -5
  95. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +21 -16
  96. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +148 -8
  97. package/ccw/src/templates/dashboard-js/views/project-overview.js +15 -11
  98. package/ccw/src/templates/dashboard-js/views/review-session.js +3 -3
  99. package/ccw/src/templates/dashboard-js/views/session-detail.js +38 -28
  100. package/ccw/src/templates/dashboard.html +129 -28
  101. package/ccw/src/tools/classify-folders.js +204 -0
  102. package/ccw/src/tools/convert-tokens-to-css.js +250 -0
  103. package/ccw/src/tools/detect-changed-modules.js +288 -0
  104. package/ccw/src/tools/discover-design-files.js +134 -0
  105. package/ccw/src/tools/edit-file.js +266 -0
  106. package/ccw/src/tools/generate-module-docs.js +416 -0
  107. package/ccw/src/tools/get-modules-by-depth.js +308 -0
  108. package/ccw/src/tools/index.js +176 -0
  109. package/ccw/src/tools/ui-generate-preview.js +327 -0
  110. package/ccw/src/tools/ui-instantiate-prototypes.js +301 -0
  111. package/ccw/src/tools/update-module-claude.js +380 -0
  112. package/ccw/src/utils/browser-launcher.js +15 -4
  113. package/package.json +1 -1
  114. package/.claude/skills/command-guide/reference/commands/workflow/status.md +0 -352
  115. package/ccw/src/core/server.js.bak +0 -385
  116. package/ccw/src/core/server_original.bak +0 -385
  117. package/ccw/src/templates/dashboard.css +0 -8114
  118. package/ccw/src/templates/dashboard_tailwind.html +0 -42
  119. package/ccw/src/templates/dashboard_test.html +0 -37
  120. package/ccw/src/templates/tailwind-base.css +0 -212
@@ -1,6 +1,6 @@
1
1
  import http from 'http';
2
2
  import { URL } from 'url';
3
- import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, promises as fsPromises } from 'fs';
3
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, statSync, promises as fsPromises } from 'fs';
4
4
  import { join, dirname } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { createHash } from 'crypto';
@@ -8,17 +8,46 @@ import { scanSessions } from './session-scanner.js';
8
8
  import { aggregateData } from './data-aggregator.js';
9
9
  import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
10
10
 
11
- // Claude config file path
11
+ // Claude config file paths
12
12
  const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
13
+ const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
14
+ const CLAUDE_GLOBAL_SETTINGS = join(CLAUDE_SETTINGS_DIR, 'settings.json');
15
+ const CLAUDE_GLOBAL_SETTINGS_LOCAL = join(CLAUDE_SETTINGS_DIR, 'settings.local.json');
16
+
17
+ // Enterprise managed MCP paths (platform-specific)
18
+ function getEnterpriseMcpPath() {
19
+ const platform = process.platform;
20
+ if (platform === 'darwin') {
21
+ return '/Library/Application Support/ClaudeCode/managed-mcp.json';
22
+ } else if (platform === 'win32') {
23
+ return 'C:\\Program Files\\ClaudeCode\\managed-mcp.json';
24
+ } else {
25
+ // Linux and WSL
26
+ return '/etc/claude-code/managed-mcp.json';
27
+ }
28
+ }
13
29
 
14
30
  // WebSocket clients for real-time notifications
15
31
  const wsClients = new Set();
16
32
 
17
33
  const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
18
- const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
34
+ const MODULE_CSS_DIR = join(import.meta.dirname, '../templates/dashboard-css');
19
35
  const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
20
36
  const MODULE_JS_DIR = join(import.meta.dirname, '../templates/dashboard-js');
21
37
 
38
+ // Modular CSS files in load order
39
+ const MODULE_CSS_FILES = [
40
+ '01-base.css',
41
+ '02-session.css',
42
+ '03-tasks.css',
43
+ '04-lite-tasks.css',
44
+ '05-context.css',
45
+ '06-cards.css',
46
+ '07-managers.css',
47
+ '08-review.css',
48
+ '09-explorer.css'
49
+ ];
50
+
22
51
  /**
23
52
  * Handle POST request with JSON body
24
53
  */
@@ -56,6 +85,7 @@ const MODULE_FILES = [
56
85
  'components/sidebar.js',
57
86
  'components/carousel.js',
58
87
  'components/notifications.js',
88
+ 'components/global-notifications.js',
59
89
  'components/mcp-manager.js',
60
90
  'components/hook-manager.js',
61
91
  'components/_exp_helpers.js',
@@ -74,6 +104,7 @@ const MODULE_FILES = [
74
104
  'views/fix-session.js',
75
105
  'views/mcp-manager.js',
76
106
  'views/hook-manager.js',
107
+ 'views/explorer.js',
77
108
  'main.js'
78
109
  ];
79
110
  /**
@@ -160,6 +191,24 @@ export async function startServer(options = {}) {
160
191
  return;
161
192
  }
162
193
 
194
+ // API: Shutdown server (for ccw stop command)
195
+ if (pathname === '/api/shutdown' && req.method === 'POST') {
196
+ res.writeHead(200, { 'Content-Type': 'application/json' });
197
+ res.end(JSON.stringify({ status: 'shutting_down' }));
198
+
199
+ // Graceful shutdown
200
+ console.log('\n Received shutdown signal...');
201
+ setTimeout(() => {
202
+ server.close(() => {
203
+ console.log(' Server stopped.\n');
204
+ process.exit(0);
205
+ });
206
+ // Force exit after 3 seconds if graceful shutdown fails
207
+ setTimeout(() => process.exit(0), 3000);
208
+ }, 100);
209
+ return;
210
+ }
211
+
163
212
  // API: Remove a recent path
164
213
  if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
165
214
  handlePostRequest(req, res, async (body) => {
@@ -353,6 +402,41 @@ export async function startServer(options = {}) {
353
402
  return;
354
403
  }
355
404
 
405
+ // API: List directory files with .gitignore filtering (Explorer view)
406
+ if (pathname === '/api/files') {
407
+ const dirPath = url.searchParams.get('path') || initialPath;
408
+ const filesData = await listDirectoryFiles(dirPath);
409
+ res.writeHead(200, { 'Content-Type': 'application/json' });
410
+ res.end(JSON.stringify(filesData));
411
+ return;
412
+ }
413
+
414
+ // API: Get file content for preview (Explorer view)
415
+ if (pathname === '/api/file-content') {
416
+ const filePath = url.searchParams.get('path');
417
+ if (!filePath) {
418
+ res.writeHead(400, { 'Content-Type': 'application/json' });
419
+ res.end(JSON.stringify({ error: 'File path is required' }));
420
+ return;
421
+ }
422
+ const fileData = await getFileContent(filePath);
423
+ res.writeHead(fileData.error ? 404 : 200, { 'Content-Type': 'application/json' });
424
+ res.end(JSON.stringify(fileData));
425
+ return;
426
+ }
427
+
428
+ // API: Update CLAUDE.md using CLI tools (Explorer view)
429
+ if (pathname === '/api/update-claude-md' && req.method === 'POST') {
430
+ handlePostRequest(req, res, async (body) => {
431
+ const { path: targetPath, tool = 'gemini', strategy = 'single-layer' } = body;
432
+ if (!targetPath) {
433
+ return { error: 'path is required', status: 400 };
434
+ }
435
+ return await triggerUpdateClaudeMd(targetPath, tool, strategy);
436
+ });
437
+ return;
438
+ }
439
+
356
440
  // Serve dashboard HTML
357
441
  if (pathname === '/' || pathname === '/index.html') {
358
442
  const html = generateServerDashboard(initialPath);
@@ -423,9 +507,31 @@ function handleWebSocketUpgrade(req, socket, head) {
423
507
  // Handle incoming messages
424
508
  socket.on('data', (buffer) => {
425
509
  try {
426
- const message = parseWebSocketFrame(buffer);
427
- if (message) {
428
- console.log('[WS] Received:', message);
510
+ const frame = parseWebSocketFrame(buffer);
511
+ if (!frame) return;
512
+
513
+ const { opcode, payload } = frame;
514
+
515
+ switch (opcode) {
516
+ case 0x1: // Text frame
517
+ if (payload) {
518
+ console.log('[WS] Received:', payload);
519
+ }
520
+ break;
521
+ case 0x8: // Close frame
522
+ socket.end();
523
+ break;
524
+ case 0x9: // Ping frame - respond with Pong
525
+ const pongFrame = Buffer.alloc(2);
526
+ pongFrame[0] = 0x8A; // Pong opcode with FIN bit
527
+ pongFrame[1] = 0x00; // No payload
528
+ socket.write(pongFrame);
529
+ break;
530
+ case 0xA: // Pong frame - ignore
531
+ break;
532
+ default:
533
+ // Ignore other frame types (binary, continuation)
534
+ break;
429
535
  }
430
536
  } catch (e) {
431
537
  // Ignore parse errors
@@ -445,10 +551,18 @@ function handleWebSocketUpgrade(req, socket, head) {
445
551
 
446
552
  /**
447
553
  * Parse WebSocket frame (simplified)
554
+ * Returns { opcode, payload } or null
448
555
  */
449
556
  function parseWebSocketFrame(buffer) {
450
557
  if (buffer.length < 2) return null;
451
558
 
559
+ const firstByte = buffer[0];
560
+ const opcode = firstByte & 0x0f; // Extract opcode (bits 0-3)
561
+
562
+ // Opcode types:
563
+ // 0x0 = continuation, 0x1 = text, 0x2 = binary
564
+ // 0x8 = close, 0x9 = ping, 0xA = pong
565
+
452
566
  const secondByte = buffer[1];
453
567
  const isMasked = (secondByte & 0x80) !== 0;
454
568
  let payloadLength = secondByte & 0x7f;
@@ -476,7 +590,7 @@ function parseWebSocketFrame(buffer) {
476
590
  }
477
591
  }
478
592
 
479
- return payload.toString('utf8');
593
+ return { opcode, payload: payload.toString('utf8') };
480
594
  }
481
595
 
482
596
  /**
@@ -931,8 +1045,11 @@ async function updateTaskStatus(sessionPath, taskId, newStatus) {
931
1045
  function generateServerDashboard(initialPath) {
932
1046
  let html = readFileSync(TEMPLATE_PATH, 'utf8');
933
1047
 
934
- // Read CSS file
935
- const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
1048
+ // Read and concatenate modular CSS files in load order
1049
+ const cssContent = MODULE_CSS_FILES.map(file => {
1050
+ const filePath = join(MODULE_CSS_DIR, file);
1051
+ return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
1052
+ }).join('\n\n');
936
1053
 
937
1054
  // Read and concatenate modular JS files in dependency order
938
1055
  let jsContent = MODULE_FILES.map(file => {
@@ -1006,22 +1123,114 @@ async function loadRecentPaths() {
1006
1123
  // ========================================
1007
1124
 
1008
1125
  /**
1009
- * Get MCP configuration from .claude.json
1126
+ * Safely read and parse JSON file
1127
+ * @param {string} filePath
1128
+ * @returns {Object|null}
1129
+ */
1130
+ function safeReadJson(filePath) {
1131
+ try {
1132
+ if (!existsSync(filePath)) return null;
1133
+ const content = readFileSync(filePath, 'utf8');
1134
+ return JSON.parse(content);
1135
+ } catch {
1136
+ return null;
1137
+ }
1138
+ }
1139
+
1140
+ /**
1141
+ * Get MCP servers from a JSON file (expects mcpServers key at top level)
1142
+ * @param {string} filePath
1143
+ * @returns {Object} mcpServers object or empty object
1144
+ */
1145
+ function getMcpServersFromFile(filePath) {
1146
+ const config = safeReadJson(filePath);
1147
+ if (!config) return {};
1148
+ return config.mcpServers || {};
1149
+ }
1150
+
1151
+ /**
1152
+ * Get MCP configuration from multiple sources (per official Claude Code docs):
1153
+ *
1154
+ * Priority (highest to lowest):
1155
+ * 1. Enterprise managed-mcp.json (cannot be overridden)
1156
+ * 2. Local scope (project-specific private in ~/.claude.json)
1157
+ * 3. Project scope (.mcp.json in project root)
1158
+ * 4. User scope (mcpServers in ~/.claude.json)
1159
+ *
1160
+ * Note: ~/.claude/settings.json is for MCP PERMISSIONS, NOT definitions!
1161
+ *
1010
1162
  * @returns {Object}
1011
1163
  */
1012
1164
  function getMcpConfig() {
1013
1165
  try {
1014
- if (!existsSync(CLAUDE_CONFIG_PATH)) {
1015
- return { projects: {} };
1166
+ const result = {
1167
+ projects: {},
1168
+ userServers: {}, // User-level servers from ~/.claude.json mcpServers
1169
+ enterpriseServers: {}, // Enterprise managed servers (highest priority)
1170
+ configSources: [] // Track where configs came from for debugging
1171
+ };
1172
+
1173
+ // 1. Read Enterprise managed MCP servers (highest priority)
1174
+ const enterprisePath = getEnterpriseMcpPath();
1175
+ if (existsSync(enterprisePath)) {
1176
+ const enterpriseConfig = safeReadJson(enterprisePath);
1177
+ if (enterpriseConfig?.mcpServers) {
1178
+ result.enterpriseServers = enterpriseConfig.mcpServers;
1179
+ result.configSources.push({ type: 'enterprise', path: enterprisePath, count: Object.keys(enterpriseConfig.mcpServers).length });
1180
+ }
1016
1181
  }
1017
- const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
1018
- const config = JSON.parse(content);
1019
- return {
1020
- projects: config.projects || {}
1182
+
1183
+ // 2. Read from ~/.claude.json
1184
+ if (existsSync(CLAUDE_CONFIG_PATH)) {
1185
+ const claudeConfig = safeReadJson(CLAUDE_CONFIG_PATH);
1186
+ if (claudeConfig) {
1187
+ // 2a. User-level mcpServers (top-level mcpServers key)
1188
+ if (claudeConfig.mcpServers) {
1189
+ result.userServers = claudeConfig.mcpServers;
1190
+ result.configSources.push({ type: 'user', path: CLAUDE_CONFIG_PATH, count: Object.keys(claudeConfig.mcpServers).length });
1191
+ }
1192
+
1193
+ // 2b. Project-specific configurations (projects[path].mcpServers)
1194
+ if (claudeConfig.projects) {
1195
+ result.projects = claudeConfig.projects;
1196
+ }
1197
+ }
1198
+ }
1199
+
1200
+ // 3. For each known project, check for .mcp.json (project-level config)
1201
+ const projectPaths = Object.keys(result.projects);
1202
+ for (const projectPath of projectPaths) {
1203
+ const mcpJsonPath = join(projectPath, '.mcp.json');
1204
+ if (existsSync(mcpJsonPath)) {
1205
+ const mcpJsonConfig = safeReadJson(mcpJsonPath);
1206
+ if (mcpJsonConfig?.mcpServers) {
1207
+ // Merge .mcp.json servers into project config
1208
+ // Project's .mcp.json has lower priority than ~/.claude.json projects[path].mcpServers
1209
+ const existingServers = result.projects[projectPath]?.mcpServers || {};
1210
+ result.projects[projectPath] = {
1211
+ ...result.projects[projectPath],
1212
+ mcpServers: {
1213
+ ...mcpJsonConfig.mcpServers, // .mcp.json (lower priority)
1214
+ ...existingServers // ~/.claude.json projects[path] (higher priority)
1215
+ },
1216
+ mcpJsonPath: mcpJsonPath // Track source for debugging
1217
+ };
1218
+ result.configSources.push({ type: 'project-mcp-json', path: mcpJsonPath, count: Object.keys(mcpJsonConfig.mcpServers).length });
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ // Build globalServers by merging user and enterprise servers
1224
+ // Enterprise servers override user servers
1225
+ result.globalServers = {
1226
+ ...result.userServers,
1227
+ ...result.enterpriseServers
1021
1228
  };
1229
+
1230
+ return result;
1022
1231
  } catch (error) {
1023
1232
  console.error('Error reading MCP config:', error);
1024
- return { projects: {}, error: error.message };
1233
+ return { projects: {}, globalServers: {}, userServers: {}, enterpriseServers: {}, configSources: [], error: error.message };
1025
1234
  }
1026
1235
  }
1027
1236
 
@@ -1380,3 +1589,360 @@ function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
1380
1589
  return { error: error.message };
1381
1590
  }
1382
1591
  }
1592
+
1593
+ // ========================================
1594
+ // Explorer View Functions
1595
+ // ========================================
1596
+
1597
+ // Directories to always exclude from file tree
1598
+ const EXPLORER_EXCLUDE_DIRS = [
1599
+ '.git', '__pycache__', 'node_modules', '.venv', 'venv', 'env',
1600
+ 'dist', 'build', '.cache', '.pytest_cache', '.mypy_cache',
1601
+ 'coverage', '.nyc_output', 'logs', 'tmp', 'temp', '.next',
1602
+ '.nuxt', '.output', '.turbo', '.parcel-cache'
1603
+ ];
1604
+
1605
+ // File extensions to language mapping for syntax highlighting
1606
+ const EXT_TO_LANGUAGE = {
1607
+ '.js': 'javascript',
1608
+ '.jsx': 'javascript',
1609
+ '.ts': 'typescript',
1610
+ '.tsx': 'typescript',
1611
+ '.py': 'python',
1612
+ '.rb': 'ruby',
1613
+ '.java': 'java',
1614
+ '.go': 'go',
1615
+ '.rs': 'rust',
1616
+ '.c': 'c',
1617
+ '.cpp': 'cpp',
1618
+ '.h': 'c',
1619
+ '.hpp': 'cpp',
1620
+ '.cs': 'csharp',
1621
+ '.php': 'php',
1622
+ '.swift': 'swift',
1623
+ '.kt': 'kotlin',
1624
+ '.scala': 'scala',
1625
+ '.sh': 'bash',
1626
+ '.bash': 'bash',
1627
+ '.zsh': 'bash',
1628
+ '.ps1': 'powershell',
1629
+ '.sql': 'sql',
1630
+ '.html': 'html',
1631
+ '.htm': 'html',
1632
+ '.css': 'css',
1633
+ '.scss': 'scss',
1634
+ '.sass': 'sass',
1635
+ '.less': 'less',
1636
+ '.json': 'json',
1637
+ '.xml': 'xml',
1638
+ '.yaml': 'yaml',
1639
+ '.yml': 'yaml',
1640
+ '.toml': 'toml',
1641
+ '.ini': 'ini',
1642
+ '.cfg': 'ini',
1643
+ '.conf': 'nginx',
1644
+ '.md': 'markdown',
1645
+ '.markdown': 'markdown',
1646
+ '.txt': 'plaintext',
1647
+ '.log': 'plaintext',
1648
+ '.env': 'bash',
1649
+ '.dockerfile': 'dockerfile',
1650
+ '.vue': 'html',
1651
+ '.svelte': 'html'
1652
+ };
1653
+
1654
+ /**
1655
+ * Parse .gitignore file and return patterns
1656
+ * @param {string} gitignorePath - Path to .gitignore file
1657
+ * @returns {string[]} Array of gitignore patterns
1658
+ */
1659
+ function parseGitignore(gitignorePath) {
1660
+ try {
1661
+ if (!existsSync(gitignorePath)) return [];
1662
+ const content = readFileSync(gitignorePath, 'utf8');
1663
+ return content
1664
+ .split('\n')
1665
+ .map(line => line.trim())
1666
+ .filter(line => line && !line.startsWith('#'));
1667
+ } catch {
1668
+ return [];
1669
+ }
1670
+ }
1671
+
1672
+ /**
1673
+ * Check if a file/directory should be ignored based on gitignore patterns
1674
+ * Simple pattern matching (supports basic glob patterns)
1675
+ * @param {string} name - File or directory name
1676
+ * @param {string[]} patterns - Gitignore patterns
1677
+ * @param {boolean} isDirectory - Whether the entry is a directory
1678
+ * @returns {boolean}
1679
+ */
1680
+ function shouldIgnore(name, patterns, isDirectory) {
1681
+ // Always exclude certain directories
1682
+ if (isDirectory && EXPLORER_EXCLUDE_DIRS.includes(name)) {
1683
+ return true;
1684
+ }
1685
+
1686
+ // Skip hidden files/directories (starting with .)
1687
+ if (name.startsWith('.') && name !== '.claude' && name !== '.workflow') {
1688
+ return true;
1689
+ }
1690
+
1691
+ for (const pattern of patterns) {
1692
+ let p = pattern;
1693
+
1694
+ // Handle negation patterns (we skip them for simplicity)
1695
+ if (p.startsWith('!')) continue;
1696
+
1697
+ // Handle directory-only patterns
1698
+ if (p.endsWith('/')) {
1699
+ if (!isDirectory) continue;
1700
+ p = p.slice(0, -1);
1701
+ }
1702
+
1703
+ // Simple pattern matching
1704
+ if (p === name) return true;
1705
+
1706
+ // Handle wildcard patterns
1707
+ if (p.includes('*')) {
1708
+ const regex = new RegExp('^' + p.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
1709
+ if (regex.test(name)) return true;
1710
+ }
1711
+
1712
+ // Handle extension patterns like *.log
1713
+ if (p.startsWith('*.')) {
1714
+ const ext = p.slice(1);
1715
+ if (name.endsWith(ext)) return true;
1716
+ }
1717
+ }
1718
+
1719
+ return false;
1720
+ }
1721
+
1722
+ /**
1723
+ * List directory files with .gitignore filtering
1724
+ * @param {string} dirPath - Directory path to list
1725
+ * @returns {Promise<Object>}
1726
+ */
1727
+ async function listDirectoryFiles(dirPath) {
1728
+ try {
1729
+ // Normalize path
1730
+ let normalizedPath = dirPath.replace(/\\/g, '/');
1731
+ if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
1732
+ normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
1733
+ }
1734
+
1735
+ if (!existsSync(normalizedPath)) {
1736
+ return { error: 'Directory not found', files: [] };
1737
+ }
1738
+
1739
+ if (!statSync(normalizedPath).isDirectory()) {
1740
+ return { error: 'Not a directory', files: [] };
1741
+ }
1742
+
1743
+ // Parse .gitignore patterns
1744
+ const gitignorePath = join(normalizedPath, '.gitignore');
1745
+ const gitignorePatterns = parseGitignore(gitignorePath);
1746
+
1747
+ // Read directory entries
1748
+ const entries = readdirSync(normalizedPath, { withFileTypes: true });
1749
+
1750
+ const files = [];
1751
+ for (const entry of entries) {
1752
+ const isDirectory = entry.isDirectory();
1753
+
1754
+ // Check if should be ignored
1755
+ if (shouldIgnore(entry.name, gitignorePatterns, isDirectory)) {
1756
+ continue;
1757
+ }
1758
+
1759
+ const entryPath = join(normalizedPath, entry.name);
1760
+ const fileInfo = {
1761
+ name: entry.name,
1762
+ type: isDirectory ? 'directory' : 'file',
1763
+ path: entryPath.replace(/\\/g, '/')
1764
+ };
1765
+
1766
+ // Check if directory has CLAUDE.md
1767
+ if (isDirectory) {
1768
+ const claudeMdPath = join(entryPath, 'CLAUDE.md');
1769
+ fileInfo.hasClaudeMd = existsSync(claudeMdPath);
1770
+ }
1771
+
1772
+ files.push(fileInfo);
1773
+ }
1774
+
1775
+ // Sort: directories first, then alphabetically
1776
+ files.sort((a, b) => {
1777
+ if (a.type === 'directory' && b.type !== 'directory') return -1;
1778
+ if (a.type !== 'directory' && b.type === 'directory') return 1;
1779
+ return a.name.localeCompare(b.name);
1780
+ });
1781
+
1782
+ return {
1783
+ path: normalizedPath.replace(/\\/g, '/'),
1784
+ files,
1785
+ gitignorePatterns
1786
+ };
1787
+ } catch (error) {
1788
+ console.error('Error listing directory:', error);
1789
+ return { error: error.message, files: [] };
1790
+ }
1791
+ }
1792
+
1793
+ /**
1794
+ * Get file content for preview
1795
+ * @param {string} filePath - Path to file
1796
+ * @returns {Promise<Object>}
1797
+ */
1798
+ async function getFileContent(filePath) {
1799
+ try {
1800
+ // Normalize path
1801
+ let normalizedPath = filePath.replace(/\\/g, '/');
1802
+ if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
1803
+ normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
1804
+ }
1805
+
1806
+ if (!existsSync(normalizedPath)) {
1807
+ return { error: 'File not found' };
1808
+ }
1809
+
1810
+ const stats = statSync(normalizedPath);
1811
+ if (stats.isDirectory()) {
1812
+ return { error: 'Cannot read directory' };
1813
+ }
1814
+
1815
+ // Check file size (limit to 1MB for preview)
1816
+ if (stats.size > 1024 * 1024) {
1817
+ return { error: 'File too large for preview (max 1MB)', size: stats.size };
1818
+ }
1819
+
1820
+ // Read file content
1821
+ const content = readFileSync(normalizedPath, 'utf8');
1822
+ const ext = normalizedPath.substring(normalizedPath.lastIndexOf('.')).toLowerCase();
1823
+ const language = EXT_TO_LANGUAGE[ext] || 'plaintext';
1824
+ const isMarkdown = ext === '.md' || ext === '.markdown';
1825
+ const fileName = normalizedPath.split('/').pop();
1826
+
1827
+ return {
1828
+ content,
1829
+ language,
1830
+ isMarkdown,
1831
+ fileName,
1832
+ path: normalizedPath,
1833
+ size: stats.size,
1834
+ lines: content.split('\n').length
1835
+ };
1836
+ } catch (error) {
1837
+ console.error('Error reading file:', error);
1838
+ return { error: error.message };
1839
+ }
1840
+ }
1841
+
1842
+ /**
1843
+ * Trigger update-module-claude tool (async execution)
1844
+ * @param {string} targetPath - Directory path to update
1845
+ * @param {string} tool - CLI tool to use (gemini, qwen, codex)
1846
+ * @param {string} strategy - Update strategy (single-layer, multi-layer)
1847
+ * @returns {Promise<Object>}
1848
+ */
1849
+ async function triggerUpdateClaudeMd(targetPath, tool, strategy) {
1850
+ const { spawn } = await import('child_process');
1851
+
1852
+ // Normalize path
1853
+ let normalizedPath = targetPath.replace(/\\/g, '/');
1854
+ if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
1855
+ normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
1856
+ }
1857
+
1858
+ if (!existsSync(normalizedPath)) {
1859
+ return { error: 'Directory not found' };
1860
+ }
1861
+
1862
+ if (!statSync(normalizedPath).isDirectory()) {
1863
+ return { error: 'Not a directory' };
1864
+ }
1865
+
1866
+ // Build ccw tool command with JSON parameters
1867
+ const params = JSON.stringify({
1868
+ strategy,
1869
+ path: normalizedPath,
1870
+ tool
1871
+ });
1872
+
1873
+ console.log(`[Explorer] Running async: ccw tool exec update_module_claude with ${tool} (${strategy})`);
1874
+
1875
+ return new Promise((resolve) => {
1876
+ const isWindows = process.platform === 'win32';
1877
+
1878
+ // Spawn the process
1879
+ const child = spawn('ccw', ['tool', 'exec', 'update_module_claude', params], {
1880
+ cwd: normalizedPath,
1881
+ shell: isWindows,
1882
+ stdio: ['ignore', 'pipe', 'pipe']
1883
+ });
1884
+
1885
+ let stdout = '';
1886
+ let stderr = '';
1887
+
1888
+ child.stdout.on('data', (data) => {
1889
+ stdout += data.toString();
1890
+ });
1891
+
1892
+ child.stderr.on('data', (data) => {
1893
+ stderr += data.toString();
1894
+ });
1895
+
1896
+ child.on('close', (code) => {
1897
+ if (code === 0) {
1898
+ // Parse the JSON output from the tool
1899
+ let result;
1900
+ try {
1901
+ result = JSON.parse(stdout);
1902
+ } catch {
1903
+ result = { output: stdout };
1904
+ }
1905
+
1906
+ if (result.success === false || result.error) {
1907
+ resolve({
1908
+ success: false,
1909
+ error: result.error || result.message || 'Update failed',
1910
+ output: stdout
1911
+ });
1912
+ } else {
1913
+ resolve({
1914
+ success: true,
1915
+ message: result.message || `CLAUDE.md updated successfully using ${tool} (${strategy})`,
1916
+ output: stdout,
1917
+ path: normalizedPath
1918
+ });
1919
+ }
1920
+ } else {
1921
+ resolve({
1922
+ success: false,
1923
+ error: stderr || `Process exited with code ${code}`,
1924
+ output: stdout + stderr
1925
+ });
1926
+ }
1927
+ });
1928
+
1929
+ child.on('error', (error) => {
1930
+ console.error('Error spawning process:', error);
1931
+ resolve({
1932
+ success: false,
1933
+ error: error.message,
1934
+ output: ''
1935
+ });
1936
+ });
1937
+
1938
+ // Timeout after 5 minutes
1939
+ setTimeout(() => {
1940
+ child.kill();
1941
+ resolve({
1942
+ success: false,
1943
+ error: 'Timeout: Process took longer than 5 minutes',
1944
+ output: stdout
1945
+ });
1946
+ }, 300000);
1947
+ });
1948
+ }