// @ts-nocheck /** * CLAUDE.md Routes Module * Handles all CLAUDE.md memory rules management endpoints */ import type { IncomingMessage, ServerResponse } from 'http'; import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, unlinkSync, mkdirSync } from 'fs'; import { join, relative } from 'path'; import { homedir } from 'os'; export interface RouteContext { pathname: string; url: URL; req: IncomingMessage; res: ServerResponse; initialPath: string; handlePostRequest: (req: IncomingMessage, res: ServerResponse, handler: (body: unknown) => Promise) => void; broadcastToClients: (data: unknown) => void; } interface ClaudeFile { id: string; level: 'user' | 'project' | 'module'; path: string; relativePath: string; name: string; content?: string; size: number; lastModified: string; frontmatter?: { paths?: string[] }; stats?: { lines: number; words: number; characters: number }; isMainFile: boolean; parentDirectory?: string; depth?: number; } interface ClaudeFilesHierarchy { user: { main: ClaudeFile | null }; project: { main: ClaudeFile | null }; modules: ClaudeFile[]; summary: { totalFiles: number; totalSize: number; lastSync?: string }; } /** * Parse frontmatter from markdown file * Reuses logic from rules-routes.ts */ function parseClaudeFrontmatter(content: string) { const result = { paths: [] as string[], content: content }; if (content.startsWith('---')) { const endIndex = content.indexOf('---', 3); if (endIndex > 0) { const frontmatter = content.substring(3, endIndex).trim(); result.content = content.substring(endIndex + 3).trim(); const lines = frontmatter.split('\n'); for (const line of lines) { const colonIndex = line.indexOf(':'); if (colonIndex > 0) { const key = line.substring(0, colonIndex).trim().toLowerCase(); const value = line.substring(colonIndex + 1).trim(); if (key === 'paths') { result.paths = value.replace(/^\[|\]$/g, '').split(',').map(t => t.trim()).filter(Boolean); } } } } } return result; } /** * Calculate file statistics */ function calculateFileStats(content: string) { const lines = content.split('\n').length; const words = content.split(/\s+/).filter(w => w.length > 0).length; const characters = content.length; return { lines, words, characters }; } /** * Create ClaudeFile object from file path */ function createClaudeFile( filePath: string, level: 'user' | 'project' | 'module', basePath: string, isMainFile: boolean, depth?: number ): ClaudeFile | null { try { if (!existsSync(filePath)) return null; const stat = statSync(filePath); const content = readFileSync(filePath, 'utf8'); const parsed = parseClaudeFrontmatter(content); const relativePath = relative(basePath, filePath).replace(/\\/g, '/'); const fileName = filePath.split(/[\\/]/).pop() || 'CLAUDE.md'; // Parent directory for module-level files const parentDir = level === 'module' ? filePath.split(/[\\/]/).slice(-2, -1)[0] : undefined; return { id: `${level}-${relativePath}`, level, path: filePath, relativePath, name: fileName, content: parsed.content, size: stat.size, lastModified: stat.mtime.toISOString(), frontmatter: { paths: parsed.paths }, stats: calculateFileStats(content), isMainFile, parentDirectory: parentDir, depth }; } catch (e) { console.error(`Error creating ClaudeFile for ${filePath}:`, e); return null; } } /** * Scan rules directory (recursive) * Adapted from rules-routes.ts::scanRulesDirectory */ function scanClaudeRulesDirectory(dirPath: string, level: 'user' | 'project', basePath: string): ClaudeFile[] { const files: ClaudeFile[] = []; try { const entries = readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dirPath, entry.name); if (entry.isFile() && entry.name.endsWith('.md')) { const file = createClaudeFile(fullPath, level, basePath, false); if (file) files.push(file); } else if (entry.isDirectory()) { const subFiles = scanClaudeRulesDirectory(fullPath, level, basePath); files.push(...subFiles); } } } catch (e) { // Ignore errors } return files; } /** * Scan modules for CLAUDE.md files * Uses get-modules-by-depth logic */ function scanModules(projectPath: string): ClaudeFile[] { const modules: ClaudeFile[] = []; const visited = new Set(); // Directories to exclude (from get-modules-by-depth.ts) const SYSTEM_EXCLUDES = [ '.git', '.svn', '.hg', '__pycache__', 'node_modules', '.npm', '.yarn', 'dist', 'build', 'out', '.cache', '.venv', 'venv', 'env', 'coverage' ]; function scanDirectory(dirPath: string, depth: number) { try { const entries = readdirSync(dirPath, { withFileTypes: true }); // Check for CLAUDE.md in current directory const claudePath = join(dirPath, 'CLAUDE.md'); if (existsSync(claudePath) && !visited.has(claudePath)) { visited.add(claudePath); const file = createClaudeFile(claudePath, 'module', projectPath, true, depth); if (file) modules.push(file); } // Recurse into subdirectories for (const entry of entries) { if (!entry.isDirectory()) continue; if (SYSTEM_EXCLUDES.includes(entry.name)) continue; const fullPath = join(dirPath, entry.name); scanDirectory(fullPath, depth + 1); } } catch (e) { // Ignore permission errors } } scanDirectory(projectPath, 0); return modules.sort((a, b) => (b.depth || 0) - (a.depth || 0)); // Deepest first } /** * Scan all CLAUDE.md files */ function scanAllClaudeFiles(projectPath: string): ClaudeFilesHierarchy { const result: ClaudeFilesHierarchy = { user: { main: null }, project: { main: null }, modules: [], summary: { totalFiles: 0, totalSize: 0 } }; // User-level files (only main CLAUDE.md, no rules) const userHome = homedir(); const userClaudeDir = join(userHome, '.claude'); const userClaudePath = join(userClaudeDir, 'CLAUDE.md'); if (existsSync(userClaudePath)) { result.user.main = createClaudeFile(userClaudePath, 'user', userHome, true); } // Project-level files (only main CLAUDE.md, no rules) const projectClaudeDir = join(projectPath, '.claude'); const projectClaudePath = join(projectClaudeDir, 'CLAUDE.md'); if (existsSync(projectClaudePath)) { result.project.main = createClaudeFile(projectClaudePath, 'project', projectPath, true); } // Module-level files result.modules = scanModules(projectPath); // Calculate summary (only main CLAUDE.md files, no rules) const allFiles = [ result.user.main, result.project.main, ...result.modules ].filter(f => f !== null) as ClaudeFile[]; result.summary = { totalFiles: allFiles.length, totalSize: allFiles.reduce((sum, f) => sum + f.size, 0), lastSync: new Date().toISOString() }; return result; } /** * Get single file content */ function getClaudeFile(filePath: string): ClaudeFile | null { try { if (!existsSync(filePath)) { return null; } const stat = statSync(filePath); const content = readFileSync(filePath, 'utf8'); const parsed = parseClaudeFrontmatter(content); // Determine level based on path let level: 'user' | 'project' | 'module' = 'module'; if (filePath.includes(join(homedir(), '.claude'))) { level = 'user'; } else if (filePath.includes('.claude')) { level = 'project'; } const isMainFile = filePath.endsWith('CLAUDE.md') && !filePath.includes('rules'); return { id: `${level}-${filePath}`, level, path: filePath, relativePath: filePath, name: filePath.split(/[\\/]/).pop() || 'CLAUDE.md', content: parsed.content, size: stat.size, lastModified: stat.mtime.toISOString(), frontmatter: { paths: parsed.paths }, stats: calculateFileStats(content), isMainFile }; } catch (error) { console.error('Error reading CLAUDE.md file:', error); return null; } } /** * Save file content */ function saveClaudeFile(filePath: string, content: string, createBackup: boolean = false): { success: boolean; error?: string } { try { if (!existsSync(filePath)) { return { success: false, error: 'File not found' }; } // Create backup if requested if (createBackup) { const backupPath = `${filePath}.backup-${Date.now()}`; const originalContent = readFileSync(filePath, 'utf8'); writeFileSync(backupPath, originalContent, 'utf8'); } // Write new content writeFileSync(filePath, content, 'utf8'); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } } /** * Generate CLI prompt for syncing CLAUDE.md files */ function generateSyncPrompt(level: 'user' | 'project' | 'module', modulePath?: string): string { if (level === 'module' && modulePath) { // Module-level prompt return `PURPOSE: Generate module-level CLAUDE.md for ${modulePath} TASK: • Analyze module's purpose and responsibilities • Document public APIs and interfaces • Identify dependencies and integration points • Note testing patterns and conventions MODE: analysis CONTEXT: @${modulePath}/**/* | Memory: Project conventions from .claude/CLAUDE.md EXPECTED: Module-level CLAUDE.md with: - Module purpose (1-2 sentences) - Key files and their roles - Public API documentation - Integration points - Testing approach RULES: $(cat ~/.claude/workflows/cli-templates/prompts/planning/02-design-component-spec.txt) | Module-level perspective only | Concrete examples | analysis=READ-ONLY`; } else { // User/Project level prompt const contextPath = level === 'user' ? '~/.claude' : '.claude'; return `PURPOSE: Update CLAUDE.md with current ${level} understanding TASK: • Analyze ${level} configuration and conventions • Identify common patterns and anti-patterns • Generate concise, actionable rules • Maintain existing structure and formatting MODE: analysis CONTEXT: @${contextPath}/**/* EXPECTED: Updated CLAUDE.md content with: - Preserved existing sections - New insights appended to relevant sections - Timestamp header - Focus on ${level}-level concerns RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Maintain existing CLAUDE.md structure | Focus on actionable rules | analysis=READ-ONLY`; } } /** * Smart merge CLAUDE.md content (update mode) */ function smartMergeContent(existingContent: string, cliOutput: string): string { // For now, use simple append strategy // TODO: Implement intelligent section-based merging const timestamp = new Date().toISOString(); const separator = '\n\n---\n\n'; const header = `## Updated: ${timestamp}\n\n`; return existingContent + separator + header + cliOutput; } /** * Scan all files in project directory */ function scanAllProjectFiles(projectPath: string): any { const SYSTEM_EXCLUDES = [ '.git', '.svn', '.hg', '__pycache__', 'node_modules', '.npm', '.yarn', 'dist', 'build', 'out', '.cache', '.venv', 'venv', 'env', 'coverage', '.next', '.nuxt', '.output', '.turbo', '.parcel-cache', 'logs', 'tmp', 'temp' ]; const results: any = { files: [], summary: { totalFiles: 0, totalDirectories: 0, totalSize: 0 } }; function scanDir(dirPath: string, depth: number = 0): any[] { if (depth > 10) return []; // Max depth limit const files: any[] = []; try { const entries = readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { // Skip system excludes and hidden files (except .claude and .workflow) if (SYSTEM_EXCLUDES.includes(entry.name)) continue; if (entry.name.startsWith('.') && entry.name !== '.claude' && entry.name !== '.workflow') continue; const fullPath = join(dirPath, entry.name); const relativePath = relative(projectPath, fullPath).replace(/\\/g, '/'); if (entry.isDirectory()) { results.summary.totalDirectories++; const dirNode: any = { path: fullPath, name: entry.name, type: 'directory', depth, children: scanDir(fullPath, depth + 1) }; files.push(dirNode); } else { const stat = statSync(fullPath); results.summary.totalFiles++; results.summary.totalSize += stat.size; files.push({ path: fullPath, name: entry.name, type: 'file', size: stat.size, lastModified: stat.mtime.toISOString(), depth }); } } } catch (e) { // Ignore permission errors } return files; } results.files = scanDir(projectPath); return results; } /** * Read single file content */ function readSingleFile(filePath: string): { content: string; size: number; lastModified: string } | null { try { if (!existsSync(filePath)) return null; const stat = statSync(filePath); const content = readFileSync(filePath, 'utf8'); return { content, size: stat.size, lastModified: stat.mtime.toISOString() }; } catch (e) { return null; } } /** * Delete CLAUDE.md file */ function deleteClaudeFile(filePath: string): { success: boolean; error?: string } { try { if (!existsSync(filePath)) { return { success: false, error: 'File not found' }; } // Create backup before deletion const backupPath = `${filePath}.deleted-${Date.now()}`; const content = readFileSync(filePath, 'utf8'); writeFileSync(backupPath, content, 'utf8'); // Delete original file unlinkSync(filePath); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } } /** * Create new CLAUDE.md file with template */ function createNewClaudeFile(level: 'user' | 'project' | 'module', template: string, pathParam?: string): { success: boolean; path?: string; error?: string } { try { let filePath: string; let content: string; // Determine file path if (level === 'user') { filePath = join(homedir(), '.claude', 'CLAUDE.md'); } else if (level === 'project' && pathParam) { filePath = join(pathParam, '.claude', 'CLAUDE.md'); } else if (level === 'module' && pathParam) { filePath = join(pathParam, 'CLAUDE.md'); } else { return { success: false, error: 'Invalid parameters' }; } // Check if file already exists if (existsSync(filePath)) { return { success: false, error: 'File already exists' }; } // Generate content based on template const timestamp = new Date().toISOString(); if (template === 'minimal') { content = `# CLAUDE.md (${level.toUpperCase()} Level)\n\n> Created: ${timestamp}\n\n## Purpose\n\n[Describe the purpose of this ${level}-level context]\n\n## Guidelines\n\n- [Add guideline 1]\n- [Add guideline 2]\n`; } else if (template === 'comprehensive') { content = `# CLAUDE.md (${level.toUpperCase()} Level)\n\n> Created: ${timestamp}\n\n## Purpose\n\n[Describe the purpose and scope]\n\n## Architecture\n\n[Describe key architectural decisions]\n\n## Coding Conventions\n\n### Naming\n\n- [Convention 1]\n- [Convention 2]\n\n### Patterns\n\n- [Pattern 1]\n- [Pattern 2]\n\n## Testing Guidelines\n\n[Testing approach and conventions]\n\n## Dependencies\n\n[Key dependencies and integration points]\n\n## Common Tasks\n\n### Task 1\n\n[Steps for task 1]\n\n### Task 2\n\n[Steps for task 2]\n`; } else { // default template content = `# CLAUDE.md (${level.toUpperCase()} Level)\n\n> Created: ${timestamp}\n\n## Overview\n\n[Brief description of this ${level}-level context]\n\n## Key Conventions\n\n- [Convention 1]\n- [Convention 2]\n- [Convention 3]\n\n## Guidelines\n\n### Code Style\n\n[Style guidelines]\n\n### Best Practices\n\n[Best practices]\n`; } // Ensure directory exists const dir = filePath.substring(0, filePath.lastIndexOf('/') || filePath.lastIndexOf('\\')); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } // Write file writeFileSync(filePath, content, 'utf8'); return { success: true, path: filePath }; } catch (error) { return { success: false, error: (error as Error).message }; } } /** * Handle CLAUDE.md routes */ export async function handleClaudeRoutes(ctx: RouteContext): Promise { const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx; // API: Scan all CLAUDE.md files if (pathname === '/api/memory/claude/scan') { const projectPathParam = url.searchParams.get('path') || initialPath; const filesData = scanAllClaudeFiles(projectPathParam); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(filesData)); return true; } // API: Scan all project files (not just CLAUDE.md) if (pathname === '/api/memory/claude/scan-all') { const projectPathParam = url.searchParams.get('path') || initialPath; const filesData = scanAllProjectFiles(projectPathParam); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(filesData)); return true; } // API: Read single file if (pathname === '/api/memory/claude/read-file' && req.method === 'GET') { const filePath = url.searchParams.get('path'); if (!filePath) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing path parameter' })); return true; } const fileData = readSingleFile(filePath); if (!fileData) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'File not found' })); return true; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(fileData)); return true; } // API: CLI Sync (analyze and update CLAUDE.md using CLI tools) if (pathname === '/api/memory/claude/sync' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { level, path: modulePath, tool = 'gemini', mode = 'update', targets } = body; if (!level) { return { error: 'Missing level parameter', status: 400 }; } try { // Import CLI executor const { executeCliTool } = await import('../../tools/cli-executor.js'); // Determine file path based on level let filePath: string; let workingDir: string; if (level === 'user') { filePath = join(homedir(), '.claude', 'CLAUDE.md'); workingDir = join(homedir(), '.claude'); } else if (level === 'project') { filePath = join(initialPath, '.claude', 'CLAUDE.md'); workingDir = join(initialPath, '.claude'); } else if (level === 'module' && modulePath) { filePath = join(modulePath, 'CLAUDE.md'); workingDir = modulePath; } else { return { error: 'Invalid level or missing path for module level', status: 400 }; } // Check if file exists (for update/append modes) const fileExists = existsSync(filePath); if (!fileExists && mode !== 'generate') { return { error: 'File does not exist. Use generate mode to create it.', status: 404 }; } // Read existing content const existingContent = fileExists ? readFileSync(filePath, 'utf8') : ''; // Generate CLI prompt const cliPrompt = generateSyncPrompt(level, modulePath); // Execute CLI tool const syncId = `claude-sync-${level}-${Date.now()}`; const result = await executeCliTool({ tool: tool === 'qwen' ? 'qwen' : 'gemini', prompt: cliPrompt, mode: 'analysis', format: 'plain', cd: workingDir, timeout: 600000, // 10 minutes stream: false, category: 'internal', id: syncId }); if (!result.success || !result.execution?.output) { return { error: 'CLI execution failed', details: result.execution?.error || 'No output received', status: 500 }; } // Extract CLI output const cliOutput = typeof result.execution.output === 'string' ? result.execution.output : result.execution.output.stdout || ''; if (!cliOutput || cliOutput.trim().length === 0) { return { error: 'CLI returned empty output', status: 500 }; } // Process content based on mode let finalContent: string; if (mode === 'generate') { // Full replace const timestamp = new Date().toISOString(); finalContent = `# CLAUDE.md (${level.toUpperCase()} Level)\n\n> Auto-generated using ${tool.toUpperCase()}\n> Last updated: ${timestamp}\n\n---\n\n${cliOutput}`; } else if (mode === 'append') { // Simple append const timestamp = new Date().toISOString(); finalContent = existingContent + `\n\n---\n\n## Updated: ${timestamp}\n\n${cliOutput}`; } else { // Smart merge (update mode) finalContent = smartMergeContent(existingContent, cliOutput); } // Write updated content writeFileSync(filePath, finalContent, 'utf8'); // Mark file as updated for freshness tracking try { const { markFileAsUpdated } = await import('../claude-freshness.js'); markFileAsUpdated(filePath, level, 'cli_sync', initialPath, { tool, mode }); } catch (e) { console.error('Failed to mark file as updated:', e); } // Broadcast WebSocket event broadcastToClients({ type: 'CLAUDE_FILE_SYNCED', payload: { path: filePath, level, tool, mode, executionId: syncId, timestamp: new Date().toISOString() } }); return { success: true, path: filePath, executionId: syncId, mode, tool }; } catch (error) { console.error('Error syncing CLAUDE.md file:', error); return { error: 'Sync failed', details: (error as Error).message, status: 500 }; } }); return true; } // API: Get single file if (pathname === '/api/memory/claude/file' && req.method === 'GET') { const filePath = url.searchParams.get('path'); if (!filePath) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing path parameter' })); return true; } const file = getClaudeFile(filePath); if (!file) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'File not found' })); return true; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(file)); return true; } // API: Save file if (pathname === '/api/memory/claude/file' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { path: filePath, content, createBackup } = body; if (!filePath || content === undefined) { return { error: 'Missing path or content parameter', status: 400 }; } const result = saveClaudeFile(filePath, content, createBackup); if (result.success) { // Broadcast update to all clients ctx.broadcastToClients({ type: 'CLAUDE_FILE_UPDATED', data: { path: filePath } }); return { success: true, path: filePath }; } else { return { error: result.error, status: 500 }; } }); return true; } // API: Delete file if (pathname === '/api/memory/claude/file' && req.method === 'DELETE') { const filePath = url.searchParams.get('path'); const confirm = url.searchParams.get('confirm'); if (!filePath) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing path parameter' })); return true; } if (confirm !== 'true') { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Confirmation required' })); return true; } const result = deleteClaudeFile(filePath); if (result.success) { broadcastToClients({ type: 'CLAUDE_FILE_DELETED', data: { path: filePath } }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true })); } else { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: result.error })); } return true; } // API: Create file if (pathname === '/api/memory/claude/create' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { level, path, template = 'default' } = body; if (!level) { return { error: 'Missing level parameter', status: 400 }; } let result: any; if (level === 'project') { // For project level, use initialPath const filePath = join(initialPath, '.claude', 'CLAUDE.md'); result = createNewClaudeFile(level, template, initialPath); } else if (level === 'module') { if (!path) { return { error: 'Module path required', status: 400 }; } result = createNewClaudeFile(level, template, path); } else { result = createNewClaudeFile(level, template); } if (result.success) { broadcastToClients({ type: 'CLAUDE_FILE_CREATED', data: { path: result.path, level } }); return { success: true, path: result.path }; } else { return { error: result.error, status: 500 }; } }); return true; } // API: Get Chinese response setting status if (pathname === '/api/language/chinese-response' && req.method === 'GET') { try { const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); const chineseRefPattern = /@.*chinese-response\.md/i; let enabled = false; let guidelinesPath = ''; // Check if user CLAUDE.md exists and contains Chinese response reference if (existsSync(userClaudePath)) { const content = readFileSync(userClaudePath, 'utf8'); enabled = chineseRefPattern.test(content); } // Find guidelines file path - always use user-level path const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ enabled, guidelinesPath, guidelinesExists: !!guidelinesPath, userClaudeMdExists: existsSync(userClaudePath) })); return true; } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); return true; } } // API: Toggle Chinese response setting if (pathname === '/api/language/chinese-response' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { enabled } = body; if (typeof enabled !== 'boolean') { return { error: 'Missing or invalid enabled parameter', status: 400 }; } try { const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); const userClaudeDir = join(homedir(), '.claude'); // Find guidelines file path - always use user-level path with ~ shorthand const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); if (!existsSync(userGuidelinesPath)) { return { error: 'Chinese response guidelines file not found at ~/.claude/workflows/chinese-response.md', status: 404 }; } const guidelinesRef = '~/.claude/workflows/chinese-response.md'; const chineseRefLine = `- **中文回复准则**: @${guidelinesRef}`; const chineseRefPattern = /^- \*\*中文回复准则\*\*:.*chinese-response\.md.*$/gm; // Ensure user .claude directory exists if (!existsSync(userClaudeDir)) { const fs = require('fs'); fs.mkdirSync(userClaudeDir, { recursive: true }); } let content = ''; if (existsSync(userClaudePath)) { content = readFileSync(userClaudePath, 'utf8'); } else { // Create new CLAUDE.md with header content = '# Claude Instructions\n\n'; } if (enabled) { // Check if reference already exists if (chineseRefPattern.test(content)) { return { success: true, message: 'Already enabled' }; } // Add reference after the header line or at the beginning const headerMatch = content.match(/^# Claude Instructions\n\n?/); if (headerMatch) { const insertPosition = headerMatch[0].length; content = content.slice(0, insertPosition) + chineseRefLine + '\n' + content.slice(insertPosition); } else { // Add header and reference content = '# Claude Instructions\n\n' + chineseRefLine + '\n' + content; } } else { // Remove reference content = content.replace(chineseRefPattern, '').replace(/\n{3,}/g, '\n\n').trim(); if (content) content += '\n'; } writeFileSync(userClaudePath, content, 'utf8'); // Broadcast update broadcastToClients({ type: 'LANGUAGE_SETTING_CHANGED', data: { chineseResponse: enabled } }); return { success: true, enabled }; } catch (error) { return { error: (error as Error).message, status: 500 }; } }); return true; } // API: Get Windows platform setting status if (pathname === '/api/language/windows-platform' && req.method === 'GET') { try { const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); const windowsRefPattern = /@.*windows-platform\.md/i; let enabled = false; let guidelinesPath = ''; // Check if user CLAUDE.md exists and contains Windows platform reference if (existsSync(userClaudePath)) { const content = readFileSync(userClaudePath, 'utf8'); enabled = windowsRefPattern.test(content); } // Find guidelines file path - always use user-level path const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ enabled, guidelinesPath, guidelinesExists: !!guidelinesPath, userClaudeMdExists: existsSync(userClaudePath) })); return true; } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); return true; } } // API: Toggle Windows platform setting if (pathname === '/api/language/windows-platform' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { enabled } = body; if (typeof enabled !== 'boolean') { return { error: 'Missing or invalid enabled parameter', status: 400 }; } try { const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); const userClaudeDir = join(homedir(), '.claude'); // Find guidelines file path - always use user-level path with ~ shorthand const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); if (!existsSync(userGuidelinesPath)) { return { error: 'Windows platform guidelines file not found at ~/.claude/workflows/windows-platform.md', status: 404 }; } const guidelinesRef = '~/.claude/workflows/windows-platform.md'; const windowsRefLine = `- **Windows Platform**: @${guidelinesRef}`; const windowsRefPattern = /^- \*\*Windows Platform\*\*:.*windows-platform\.md.*$/gm; // Ensure user .claude directory exists if (!existsSync(userClaudeDir)) { const fs = require('fs'); fs.mkdirSync(userClaudeDir, { recursive: true }); } let content = ''; if (existsSync(userClaudePath)) { content = readFileSync(userClaudePath, 'utf8'); } else { // Create new CLAUDE.md with header content = '# Claude Instructions\n\n'; } if (enabled) { // Check if reference already exists if (windowsRefPattern.test(content)) { return { success: true, message: 'Already enabled' }; } // Add reference after the header line or at the beginning const headerMatch = content.match(/^# Claude Instructions\n\n?/); if (headerMatch) { const insertPosition = headerMatch[0].length; content = content.slice(0, insertPosition) + windowsRefLine + '\n' + content.slice(insertPosition); } else { // Add header and reference content = '# Claude Instructions\n\n' + windowsRefLine + '\n' + content; } } else { // Remove reference content = content.replace(windowsRefPattern, '').replace(/\n{3,}/g, '\n\n').trim(); if (content) content += '\n'; } writeFileSync(userClaudePath, content, 'utf8'); // Broadcast update broadcastToClients({ type: 'LANGUAGE_SETTING_CHANGED', data: { windowsPlatform: enabled } }); return { success: true, enabled }; } catch (error) { return { error: (error as Error).message, status: 500 }; } }); return true; } // API: Get freshness scores for all CLAUDE.md files if (pathname === '/api/memory/claude/freshness' && req.method === 'GET') { try { const { calculateAllFreshness } = await import('../claude-freshness.js'); const projectPathParam = url.searchParams.get('path') || initialPath; const threshold = parseInt(url.searchParams.get('threshold') || '20', 10); // Get all CLAUDE.md files const filesData = scanAllClaudeFiles(projectPathParam); // Prepare file list for freshness calculation const claudeFiles: Array<{ path: string; level: 'user' | 'project' | 'module'; lastModified: string; }> = []; if (filesData.user.main) { claudeFiles.push({ path: filesData.user.main.path, level: 'user', lastModified: filesData.user.main.lastModified }); } if (filesData.project.main) { claudeFiles.push({ path: filesData.project.main.path, level: 'project', lastModified: filesData.project.main.lastModified }); } for (const module of filesData.modules) { claudeFiles.push({ path: module.path, level: 'module', lastModified: module.lastModified }); } // Calculate freshness const freshnessData = calculateAllFreshness(claudeFiles, projectPathParam, threshold); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(freshnessData)); return true; } catch (error) { console.error('Error calculating freshness:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); return true; } } // API: Mark a CLAUDE.md file as updated if (pathname === '/api/memory/claude/mark-updated' && req.method === 'POST') { handlePostRequest(req, res, async (body: any) => { const { path: filePath, source, metadata } = body; if (!filePath) { return { error: 'Missing path parameter', status: 400 }; } if (!source || !['manual', 'cli_sync', 'dashboard', 'api'].includes(source)) { return { error: 'Invalid or missing source parameter', status: 400 }; } try { const { markFileAsUpdated } = await import('../claude-freshness.js'); // Determine file level let level: 'user' | 'project' | 'module' = 'module'; if (filePath.includes(join(homedir(), '.claude'))) { level = 'user'; } else if (filePath.includes('.claude')) { level = 'project'; } const record = markFileAsUpdated(filePath, level, source, initialPath, metadata); // Broadcast update broadcastToClients({ type: 'CLAUDE_FRESHNESS_UPDATED', data: { path: filePath, level, updatedAt: record.updated_at, source } }); return { success: true, record: { id: record.id, updated_at: record.updated_at, filesChangedBeforeUpdate: record.files_changed_before_update } }; } catch (error) { console.error('Error marking file as updated:', error); return { error: (error as Error).message, status: 500 }; } }); return true; } // API: Get update history for a CLAUDE.md file if (pathname === '/api/memory/claude/history' && req.method === 'GET') { const filePath = url.searchParams.get('path'); const limit = parseInt(url.searchParams.get('limit') || '50', 10); if (!filePath) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing path parameter' })); return true; } try { const { getUpdateHistory } = await import('../claude-freshness.js'); const records = getUpdateHistory(filePath, initialPath, limit); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ records: records.map(r => ({ id: r.id, updated_at: r.updated_at, update_source: r.update_source, git_commit_hash: r.git_commit_hash, files_changed_before_update: r.files_changed_before_update, metadata: r.metadata ? JSON.parse(r.metadata) : undefined })) })); return true; } catch (error) { console.error('Error getting update history:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message })); return true; } } return false; }