claude-code-workflow 6.0.5 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/action-planning-agent.md +1 -1
- package/.claude/agents/cli-execution-agent.md +269 -269
- package/.claude/agents/cli-explore-agent.md +182 -182
- package/.claude/agents/context-search-agent.md +582 -582
- package/.claude/agents/memory-bridge.md +93 -93
- package/.claude/commands/cli/cli-init.md +1 -1
- package/.claude/commands/memory/docs-full-cli.md +471 -471
- package/.claude/commands/memory/docs-related-cli.md +386 -386
- package/.claude/commands/memory/docs.md +615 -615
- package/.claude/commands/memory/load.md +1 -1
- package/.claude/commands/memory/update-full.md +332 -332
- package/.claude/commands/memory/update-related.md +5 -5
- package/.claude/commands/workflow/init.md +1 -1
- package/.claude/commands/workflow/lite-fix.md +621 -621
- package/.claude/commands/workflow/lite-plan.md +592 -592
- package/.claude/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/scripts/classify-folders.sh +4 -0
- package/.claude/scripts/convert_tokens_to_css.sh +4 -0
- package/.claude/scripts/detect_changed_modules.sh +5 -1
- package/.claude/scripts/discover-design-files.sh +87 -83
- package/.claude/scripts/generate_module_docs.sh +717 -713
- package/.claude/scripts/get_modules_by_depth.sh +5 -1
- package/.claude/scripts/ui-generate-preview.sh +4 -0
- package/.claude/scripts/ui-instantiate-prototypes.sh +4 -0
- package/.claude/scripts/update_module_claude.sh +4 -0
- package/.claude/skills/command-guide/index/all-commands.json +1 -12
- package/.claude/skills/command-guide/index/by-category.json +1 -12
- package/.claude/skills/command-guide/index/by-use-case.json +1 -12
- package/.claude/skills/command-guide/index/essential-commands.json +1 -12
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +127 -71
- package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +269 -269
- package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -182
- package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +18 -38
- package/.claude/skills/command-guide/reference/agents/context-search-agent.md +582 -577
- package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
- package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
- package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
- package/.claude/skills/command-guide/reference/commands/memory/docs.md +615 -610
- package/.claude/skills/command-guide/reference/commands/memory/load.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
- package/.claude/skills/command-guide/reference/commands/memory/update-related.md +5 -5
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +299 -451
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +14 -37
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +252 -350
- package/.claude/skills/command-guide/reference/commands/workflow/init.md +2 -2
- package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +52 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +621 -602
- package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +46 -36
- package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +18 -58
- package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +22 -52
- package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +19 -48
- package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +25 -5
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +7 -7
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -11
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +4 -4
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/workflows/context-search-strategy.md +77 -77
- package/.claude/workflows/tool-strategy.md +90 -71
- package/.claude/workflows/workflow-architecture.md +1 -1
- package/README.md +285 -285
- package/ccw/src/cli.js +7 -0
- package/ccw/src/commands/tool.js +217 -0
- package/ccw/src/core/dashboard-generator.js +18 -3
- package/ccw/src/core/lite-scanner.js +35 -11
- package/ccw/src/core/server.js +531 -46
- package/ccw/src/templates/dashboard-css/01-base.css +161 -0
- package/ccw/src/templates/dashboard-css/02-session.css +726 -0
- package/ccw/src/templates/dashboard-css/03-tasks.css +512 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +843 -0
- package/ccw/src/templates/dashboard-css/05-context.css +2206 -0
- package/ccw/src/templates/dashboard-css/06-cards.css +1570 -0
- package/ccw/src/templates/dashboard-css/07-managers.css +936 -0
- package/ccw/src/templates/dashboard-css/08-review.css +1266 -0
- package/ccw/src/templates/dashboard-css/09-explorer.css +1397 -0
- package/ccw/src/templates/dashboard-js/components/global-notifications.js +219 -0
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +10 -0
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +11 -1
- package/ccw/src/templates/dashboard-js/components/navigation.js +11 -5
- package/ccw/src/templates/dashboard-js/components/tabs-context.js +20 -20
- package/ccw/src/templates/dashboard-js/components/tabs-other.js +11 -11
- package/ccw/src/templates/dashboard-js/components/theme.js +29 -1
- package/ccw/src/templates/dashboard-js/main.js +4 -0
- package/ccw/src/templates/dashboard-js/state.js +5 -0
- package/ccw/src/templates/dashboard-js/views/explorer.js +852 -0
- package/ccw/src/templates/dashboard-js/views/home.js +13 -9
- package/ccw/src/templates/dashboard-js/views/hook-manager.js +8 -5
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +21 -16
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +90 -19
- package/ccw/src/templates/dashboard-js/views/project-overview.js +15 -11
- package/ccw/src/templates/dashboard-js/views/review-session.js +3 -3
- package/ccw/src/templates/dashboard-js/views/session-detail.js +38 -28
- package/ccw/src/templates/dashboard.html +129 -28
- package/ccw/src/tools/classify-folders.js +204 -0
- package/ccw/src/tools/convert-tokens-to-css.js +250 -0
- package/ccw/src/tools/detect-changed-modules.js +288 -0
- package/ccw/src/tools/discover-design-files.js +134 -0
- package/ccw/src/tools/edit-file.js +266 -0
- package/ccw/src/tools/generate-module-docs.js +416 -0
- package/ccw/src/tools/get-modules-by-depth.js +308 -0
- package/ccw/src/tools/index.js +176 -0
- package/ccw/src/tools/ui-generate-preview.js +327 -0
- package/ccw/src/tools/ui-instantiate-prototypes.js +301 -0
- package/ccw/src/tools/update-module-claude.js +380 -0
- package/package.json +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/status.md +0 -352
- package/ccw/src/core/server.js.bak +0 -385
- package/ccw/src/core/server_original.bak +0 -385
- package/ccw/src/templates/dashboard.css +0 -8187
- package/ccw/src/templates/dashboard_tailwind.html +0 -42
- package/ccw/src/templates/dashboard_test.html +0 -37
- package/ccw/src/templates/tailwind-base.css +0 -212
package/ccw/src/core/server.js
CHANGED
|
@@ -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';
|
|
@@ -14,14 +14,40 @@ const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
|
|
|
14
14
|
const CLAUDE_GLOBAL_SETTINGS = join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
15
15
|
const CLAUDE_GLOBAL_SETTINGS_LOCAL = join(CLAUDE_SETTINGS_DIR, 'settings.local.json');
|
|
16
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
|
+
}
|
|
29
|
+
|
|
17
30
|
// WebSocket clients for real-time notifications
|
|
18
31
|
const wsClients = new Set();
|
|
19
32
|
|
|
20
33
|
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
|
21
|
-
const
|
|
34
|
+
const MODULE_CSS_DIR = join(import.meta.dirname, '../templates/dashboard-css');
|
|
22
35
|
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
|
23
36
|
const MODULE_JS_DIR = join(import.meta.dirname, '../templates/dashboard-js');
|
|
24
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
|
+
|
|
25
51
|
/**
|
|
26
52
|
* Handle POST request with JSON body
|
|
27
53
|
*/
|
|
@@ -59,6 +85,7 @@ const MODULE_FILES = [
|
|
|
59
85
|
'components/sidebar.js',
|
|
60
86
|
'components/carousel.js',
|
|
61
87
|
'components/notifications.js',
|
|
88
|
+
'components/global-notifications.js',
|
|
62
89
|
'components/mcp-manager.js',
|
|
63
90
|
'components/hook-manager.js',
|
|
64
91
|
'components/_exp_helpers.js',
|
|
@@ -77,6 +104,7 @@ const MODULE_FILES = [
|
|
|
77
104
|
'views/fix-session.js',
|
|
78
105
|
'views/mcp-manager.js',
|
|
79
106
|
'views/hook-manager.js',
|
|
107
|
+
'views/explorer.js',
|
|
80
108
|
'main.js'
|
|
81
109
|
];
|
|
82
110
|
/**
|
|
@@ -374,6 +402,41 @@ export async function startServer(options = {}) {
|
|
|
374
402
|
return;
|
|
375
403
|
}
|
|
376
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
|
+
|
|
377
440
|
// Serve dashboard HTML
|
|
378
441
|
if (pathname === '/' || pathname === '/index.html') {
|
|
379
442
|
const html = generateServerDashboard(initialPath);
|
|
@@ -444,9 +507,31 @@ function handleWebSocketUpgrade(req, socket, head) {
|
|
|
444
507
|
// Handle incoming messages
|
|
445
508
|
socket.on('data', (buffer) => {
|
|
446
509
|
try {
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
449
|
-
|
|
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;
|
|
450
535
|
}
|
|
451
536
|
} catch (e) {
|
|
452
537
|
// Ignore parse errors
|
|
@@ -466,10 +551,18 @@ function handleWebSocketUpgrade(req, socket, head) {
|
|
|
466
551
|
|
|
467
552
|
/**
|
|
468
553
|
* Parse WebSocket frame (simplified)
|
|
554
|
+
* Returns { opcode, payload } or null
|
|
469
555
|
*/
|
|
470
556
|
function parseWebSocketFrame(buffer) {
|
|
471
557
|
if (buffer.length < 2) return null;
|
|
472
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
|
+
|
|
473
566
|
const secondByte = buffer[1];
|
|
474
567
|
const isMasked = (secondByte & 0x80) !== 0;
|
|
475
568
|
let payloadLength = secondByte & 0x7f;
|
|
@@ -497,7 +590,7 @@ function parseWebSocketFrame(buffer) {
|
|
|
497
590
|
}
|
|
498
591
|
}
|
|
499
592
|
|
|
500
|
-
return payload.toString('utf8');
|
|
593
|
+
return { opcode, payload: payload.toString('utf8') };
|
|
501
594
|
}
|
|
502
595
|
|
|
503
596
|
/**
|
|
@@ -952,8 +1045,11 @@ async function updateTaskStatus(sessionPath, taskId, newStatus) {
|
|
|
952
1045
|
function generateServerDashboard(initialPath) {
|
|
953
1046
|
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
|
954
1047
|
|
|
955
|
-
// Read CSS
|
|
956
|
-
const cssContent =
|
|
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');
|
|
957
1053
|
|
|
958
1054
|
// Read and concatenate modular JS files in dependency order
|
|
959
1055
|
let jsContent = MODULE_FILES.map(file => {
|
|
@@ -1042,67 +1138,99 @@ function safeReadJson(filePath) {
|
|
|
1042
1138
|
}
|
|
1043
1139
|
|
|
1044
1140
|
/**
|
|
1045
|
-
* Get MCP servers from a
|
|
1141
|
+
* Get MCP servers from a JSON file (expects mcpServers key at top level)
|
|
1046
1142
|
* @param {string} filePath
|
|
1047
1143
|
* @returns {Object} mcpServers object or empty object
|
|
1048
1144
|
*/
|
|
1049
|
-
function
|
|
1145
|
+
function getMcpServersFromFile(filePath) {
|
|
1050
1146
|
const config = safeReadJson(filePath);
|
|
1051
1147
|
if (!config) return {};
|
|
1052
1148
|
return config.mcpServers || {};
|
|
1053
1149
|
}
|
|
1054
1150
|
|
|
1055
1151
|
/**
|
|
1056
|
-
* Get MCP configuration from multiple sources:
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
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
|
+
*
|
|
1060
1162
|
* @returns {Object}
|
|
1061
1163
|
*/
|
|
1062
1164
|
function getMcpConfig() {
|
|
1063
1165
|
try {
|
|
1064
|
-
const result = {
|
|
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
|
+
};
|
|
1065
1172
|
|
|
1066
|
-
// 1. Read
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
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
|
+
}
|
|
1071
1181
|
}
|
|
1072
1182
|
|
|
1073
|
-
// 2. Read
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const projectSettingsLocal = join(projectClaudeDir, 'settings.local.json');
|
|
1083
|
-
|
|
1084
|
-
// Merge MCP servers from workspace settings into project config
|
|
1085
|
-
const workspaceServers = {
|
|
1086
|
-
...getMcpServersFromSettings(projectSettings),
|
|
1087
|
-
...getMcpServersFromSettings(projectSettingsLocal)
|
|
1088
|
-
};
|
|
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
|
+
}
|
|
1089
1192
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
+
}
|
|
1099
1220
|
}
|
|
1100
1221
|
}
|
|
1101
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
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1102
1230
|
return result;
|
|
1103
1231
|
} catch (error) {
|
|
1104
1232
|
console.error('Error reading MCP config:', error);
|
|
1105
|
-
return { projects: {}, globalServers: {}, error: error.message };
|
|
1233
|
+
return { projects: {}, globalServers: {}, userServers: {}, enterpriseServers: {}, configSources: [], error: error.message };
|
|
1106
1234
|
}
|
|
1107
1235
|
}
|
|
1108
1236
|
|
|
@@ -1461,3 +1589,360 @@ function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
|
|
|
1461
1589
|
return { error: error.message };
|
|
1462
1590
|
}
|
|
1463
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
|
+
}
|