diff --git a/extension.js b/extension.js index 950344b..75b9efa 100644 --- a/extension.js +++ b/extension.js @@ -179,6 +179,335 @@ function grepInWorkspace(text, filePattern) { return results || ''; } +// ═══════════════════════════════════════════════════════════════════════════ +// GIT OPERATIONS TOOLKIT — Full git power from chat +// ═══════════════════════════════════════════════════════════════════════════ + +function gitCommit(message) { + const root = getWorkspaceRoot(); + const safeMsg = message.replace(/"/g, '\\"').replace(/\$/g, '\\$').substring(0, 500); + const addResult = safeExec('git add -A', root); + const commitResult = safeExec(`git commit -m "${safeMsg}" 2>&1`, root, 15000); + return commitResult || 'Nothing to commit'; +} + +function gitDiff(filePath, staged) { + const root = getWorkspaceRoot(); + const stageFlag = staged ? '--staged ' : ''; + const fileArg = filePath ? ` -- "${filePath.replace(/"/g, '')}"` : ''; + return safeExec(`git diff ${stageFlag}--stat${fileArg}`, root, 10000) + '\n' + + safeExec(`git diff ${stageFlag}${fileArg}`, root, 10000).substring(0, 30000); +} + +function gitLog(count, filePath) { + const root = getWorkspaceRoot(); + const n = Math.min(Math.max(parseInt(count) || 10, 1), 100); + const fileArg = filePath ? ` -- "${filePath.replace(/"/g, '')}"` : ''; + return safeExec(`git log --oneline --decorate -n ${n}${fileArg}`, root, 10000); +} + +function gitBlame(filePath, startLine, endLine) { + const root = getWorkspaceRoot(); + const safePath = filePath.replace(/"/g, ''); + const lineRange = (startLine && endLine) ? `-L ${parseInt(startLine)},${parseInt(endLine)} ` : ''; + return safeExec(`git blame ${lineRange}"${safePath}" 2>&1`, root, 10000).substring(0, 20000); +} + +function gitStash(action, message) { + const root = getWorkspaceRoot(); + if (action === 'list') return safeExec('git stash list', root); + if (action === 'pop') return safeExec('git stash pop 2>&1', root, 10000); + if (action === 'drop') return safeExec('git stash drop 2>&1', root); + const safeMsg = message ? ` -m "${message.replace(/"/g, '\\"').substring(0, 200)}"` : ''; + return safeExec(`git stash push${safeMsg} 2>&1`, root, 10000); +} + +function gitBranch(action, branchName) { + const root = getWorkspaceRoot(); + const safeName = (branchName || '').replace(/[^a-zA-Z0-9/_.\-]/g, '').substring(0, 100); + if (action === 'list') return safeExec('git branch -a', root); + if (action === 'create' && safeName) return safeExec(`git checkout -b "${safeName}" 2>&1`, root); + if (action === 'switch' && safeName) return safeExec(`git checkout "${safeName}" 2>&1`, root); + if (action === 'delete' && safeName) return safeExec(`git branch -d "${safeName}" 2>&1`, root); + return 'Invalid branch operation'; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// SYSTEM & PROCESS TOOLS — Server awareness, ports, processes, env +// ═══════════════════════════════════════════════════════════════════════════ + +function getSystemInfo() { + return { + hostname: os.hostname(), + platform: os.platform(), + arch: os.arch(), + cpus: os.cpus().length, + totalMem: (os.totalmem() / 1073741824).toFixed(1) + ' GB', + freeMem: (os.freemem() / 1073741824).toFixed(1) + ' GB', + uptime: (os.uptime() / 3600).toFixed(1) + ' hours', + nodeVersion: process.version, + user: os.userInfo().username, + homeDir: os.homedir(), + loadAvg: os.loadavg().map(l => l.toFixed(2)).join(', '), + }; +} + +function getRunningPorts() { + return safeExec("ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4, $6}' | head -40", os.homedir(), 8000); +} + +function getPm2Services() { + return safeExec('pm2 jlist 2>/dev/null', os.homedir(), 10000); +} + +function getDiskUsage() { + return safeExec("df -h / /home 2>/dev/null | tail -n +2", os.homedir()); +} + +function getEnvironmentVars(filter) { + const safeFilter = (filter || '').replace(/[^a-zA-Z0-9_*]/g, ''); + if (safeFilter) { + return safeExec(`env | grep -i "${safeFilter}" | sort | head -50`, os.homedir()); + } + return safeExec('env | grep -v "^LS_COLORS\\|^SSH_\\|^LESSOPEN\\|^XDG_" | sort | head -80', os.homedir()); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// DATA FORMAT UTILITIES — JSON, base64, hashing, encoding +// ═══════════════════════════════════════════════════════════════════════════ + +function formatJson(text) { + try { return JSON.stringify(JSON.parse(text), null, 2); } + catch (e) { return 'Invalid JSON: ' + e.message; } +} + +function computeHash(text, algorithm) { + const algo = ['sha256', 'sha512', 'md5', 'sha1'].includes(algorithm) ? algorithm : 'sha256'; + return crypto.createHash(algo).update(text).digest('hex'); +} + +function base64Encode(text) { return Buffer.from(text).toString('base64'); } +function base64Decode(text) { + try { return Buffer.from(text, 'base64').toString('utf8'); } + catch (e) { return 'Invalid base64: ' + e.message; } +} + +function urlEncode(text) { return encodeURIComponent(text); } +function urlDecode(text) { + try { return decodeURIComponent(text); } + catch (e) { return 'Invalid URL encoding: ' + e.message; } +} + +function generateUuid() { return crypto.randomUUID(); } + +function countLines(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const lines = content.split('\n').length; + const chars = content.length; + const words = content.split(/\s+/).filter(Boolean).length; + const blanks = content.split('\n').filter(l => !l.trim()).length; + return { lines, chars, words, blanks, codeLines: lines - blanks }; + } catch (e) { return { error: e.message }; } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// ADVANCED FILE OPERATIONS — Diff, multi-edit, bulk search-replace +// ═══════════════════════════════════════════════════════════════════════════ + +function diffFiles(fileA, fileB) { + const root = getWorkspaceRoot(); + const pathA = path.isAbsolute(fileA) ? fileA : path.join(root, fileA); + const pathB = path.isAbsolute(fileB) ? fileB : path.join(root, fileB); + return safeExec(`diff -u "${pathA}" "${pathB}" 2>&1`, root, 10000).substring(0, 30000); +} + +function findReplace(filePath, search, replace, isRegex) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + let content = fs.readFileSync(fullPath, 'utf8'); + let count = 0; + if (isRegex) { + const re = new RegExp(search, 'g'); + content = content.replace(re, (...args) => { count++; return replace; }); + } else { + while (content.includes(search)) { + content = content.replace(search, replace); + count++; + if (count > 10000) break; + } + } + if (count > 0) fs.writeFileSync(fullPath, content, 'utf8'); + return { count, filePath }; + } catch (e) { return { error: e.message }; } +} + +function getRecentFiles(count) { + const root = getWorkspaceRoot(); + const n = Math.min(parseInt(count) || 20, 50); + return safeExec(`find . -maxdepth 4 -type f -not -path "./.git/*" -not -path "./node_modules/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -${n} | awk '{print $2}'`, root, 8000); +} + +function getFileSizes(pattern) { + const root = getWorkspaceRoot(); + const safePattern = (pattern || '*').replace(/[^a-zA-Z0-9.*_\-/]/g, ''); + return safeExec(`find . -maxdepth 4 -type f -name "${safePattern}" -not -path "./.git/*" -not -path "./node_modules/*" -exec ls -lhS {} + 2>/dev/null | head -30`, root, 8000); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// TEST RUNNER — Detect and run project tests +// ═══════════════════════════════════════════════════════════════════════════ + +function detectTestFramework() { + const root = getWorkspaceRoot(); + const frameworks = []; + try { + const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + const deps = { ...(pkg.devDependencies || {}), ...(pkg.dependencies || {}) }; + if (deps.jest || deps['@jest/core']) frameworks.push({ name: 'jest', cmd: 'npx jest --verbose' }); + if (deps.mocha) frameworks.push({ name: 'mocha', cmd: 'npx mocha' }); + if (deps.vitest) frameworks.push({ name: 'vitest', cmd: 'npx vitest run' }); + if (deps.ava) frameworks.push({ name: 'ava', cmd: 'npx ava' }); + if (pkg.scripts && pkg.scripts.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') { + frameworks.push({ name: 'npm-test', cmd: 'npm test' }); + } + } catch (_) {} + if (fs.existsSync(path.join(root, 'pytest.ini')) || fs.existsSync(path.join(root, 'pyproject.toml'))) { + frameworks.push({ name: 'pytest', cmd: 'python -m pytest -v' }); + } + if (fs.existsSync(path.join(root, 'phpunit.xml')) || fs.existsSync(path.join(root, 'phpunit.xml.dist'))) { + frameworks.push({ name: 'phpunit', cmd: 'vendor/bin/phpunit' }); + } + if (fs.existsSync(path.join(root, 'Cargo.toml'))) { + frameworks.push({ name: 'cargo-test', cmd: 'cargo test' }); + } + if (fs.existsSync(path.join(root, 'go.mod'))) { + frameworks.push({ name: 'go-test', cmd: 'go test ./...' }); + } + return frameworks; +} + +function runTests(framework, testFile) { + const root = getWorkspaceRoot(); + const fw = detectTestFramework().find(f => !framework || f.name === framework) || detectTestFramework()[0]; + if (!fw) return 'No test framework detected'; + const fileArg = testFile ? ` "${testFile.replace(/"/g, '')}"` : ''; + return safeExec(`${fw.cmd}${fileArg} 2>&1`, root, 60000).substring(0, 30000); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// ENHANCED CONTEXT ENGINE — Import graph, complexity, dependency audit +// ═══════════════════════════════════════════════════════════════════════════ + +function getImportGraph(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const imports = []; + // JS/TS imports + const jsImports = content.matchAll(/(?:import\s+.*?from\s+['"](.+?)['"]|require\s*\(\s*['"](.+?)['"]\s*\))/g); + for (const m of jsImports) imports.push(m[1] || m[2]); + // Python imports + const pyImports = content.matchAll(/(?:from\s+(\S+)\s+import|import\s+(\S+))/g); + for (const m of pyImports) imports.push(m[1] || m[2]); + // PHP includes + const phpIncludes = content.matchAll(/(?:require|include)(?:_once)?\s*[\(]?\s*['"](.+?)['"]/g); + for (const m of phpIncludes) imports.push(m[1]); + // Go imports + const goImports = content.matchAll(/import\s+(?:\(\s*([\s\S]*?)\)|"(.+?)")/g); + for (const m of goImports) { + if (m[1]) { + for (const line of m[1].split('\n')) { + const gm = line.match(/["'](.+?)["']/); + if (gm) imports.push(gm[1]); + } + } else if (m[2]) imports.push(m[2]); + } + // Rust use + const rustUse = content.matchAll(/use\s+(\S+)/g); + for (const m of rustUse) imports.push(m[1].replace(/;$/, '')); + return { file: filePath, imports: [...new Set(imports)] }; + } catch (e) { return { file: filePath, imports: [], error: e.message }; } +} + +function analyzeComplexity(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const lines = content.split('\n'); + const totalLines = lines.length; + const blankLines = lines.filter(l => !l.trim()).length; + const commentLines = lines.filter(l => { + const t = l.trim(); + return t.startsWith('//') || t.startsWith('#') || t.startsWith('*') || t.startsWith('/*') || t.startsWith('