v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools): - git-commit: Stage all + commit with message - git-diff: Working tree or staged diff (per-file or full) - git-log: Commit history with count and file filter - git-blame: Line-by-line authorship (with range support) - git-stash: push/pop/list/drop operations - git-branch: list/create/switch/delete branches CODE REFACTORING (4 tools): - rename-symbol: LSP-powered rename across all files - code-actions: Get available quick fixes and refactorings - apply-code-action: Execute a specific code action by title - format-document: Auto-format any document SYSTEM & PROCESS TOOLS (5 tools): - system-info: CPU, RAM, uptime, node version, load average - list-ports: All listening TCP ports with processes - pm2-list: Full PM2 service inventory with CPU/mem/restarts - disk-usage: Storage for / and /home - env-vars: Environment variables with filter DATA FORMAT UTILITIES (6 tools): - format-json: Pretty-print JSON - compute-hash: SHA-256/512, MD5, SHA-1 - base64: Encode/decode - url-encode: Encode/decode - generate-uuid: Random UUID v4 - count-lines: Lines/words/chars/blanks per file ADVANCED FILE OPERATIONS (4 tools): - diff-files: Unified diff of any two files - find-replace-all: Bulk find/replace with regex support - recent-files: Recently modified files - file-sizes: File sizes by glob pattern TEST RUNNER (2 tools): - detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go) - run-tests: Execute tests with framework selection CODE ANALYSIS ENGINE (4 tools): - import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust) - analyze-complexity: Functions, nesting depth, loops, conditions, try/catch - dependency-info: All project deps (Node/Python/PHP Composer) - project-stats: Language breakdown, file counts, sizes, largest files ENHANCED DIAGNOSTICS: - get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source SYSTEM PROMPT — Advanced Reasoning Protocol: - Planning Mode: numbered steps, dependencies, parallel opportunities - Chain-of-Thought: observe → hypothesize → identify → fix → verify - Context Synthesis: trace call chains via LSP before recommendations - Adversarial Review: self-review after implementation - Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests - System Awareness: AI knows it has 48 tools and how to use them proactively
This commit is contained in:
parent
553487a020
commit
8af9a1cdcf
611
extension.js
611
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('<!--');
|
||||
}).length;
|
||||
const codeLines = totalLines - blankLines - commentLines;
|
||||
|
||||
// Function count
|
||||
const funcMatches = content.match(/(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\(|(?:public|private|protected|static)\s+(?:async\s+)?(?:function\s+)?\w+\s*\(|def\s+\w+\s*\(|fn\s+\w+\s*\()/g);
|
||||
const functionCount = funcMatches ? funcMatches.length : 0;
|
||||
|
||||
// Nesting depth (rough)
|
||||
let maxDepth = 0, depth = 0;
|
||||
for (const line of lines) {
|
||||
depth += (line.match(/{/g) || []).length;
|
||||
depth -= (line.match(/}/g) || []).length;
|
||||
if (depth > maxDepth) maxDepth = depth;
|
||||
}
|
||||
|
||||
// Complexity indicators
|
||||
const loops = (content.match(/\b(for|while|do)\b/g) || []).length;
|
||||
const conditions = (content.match(/\b(if|else if|elif|switch|case|when|unless)\b/g) || []).length;
|
||||
const tryCatch = (content.match(/\b(try|catch|except|rescue)\b/g) || []).length;
|
||||
|
||||
return {
|
||||
file: filePath, totalLines, codeLines, blankLines, commentLines,
|
||||
functionCount, maxNestingDepth: maxDepth,
|
||||
loops, conditions, tryCatch,
|
||||
complexity: conditions + loops + tryCatch
|
||||
};
|
||||
} catch (e) { return { file: filePath, error: e.message }; }
|
||||
}
|
||||
|
||||
function getDependencyInfo() {
|
||||
const root = getWorkspaceRoot();
|
||||
const result = {};
|
||||
// Node.js
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||
const deps = Object.entries(pkg.dependencies || {}).map(([k, v]) => `${k}@${v}`);
|
||||
const devDeps = Object.entries(pkg.devDependencies || {}).map(([k, v]) => `${k}@${v}`);
|
||||
result.node = { dependencies: deps, devDependencies: devDeps, total: deps.length + devDeps.length };
|
||||
} catch (_) {}
|
||||
// Python
|
||||
try {
|
||||
const req = fs.readFileSync(path.join(root, 'requirements.txt'), 'utf8');
|
||||
result.python = { packages: req.split('\n').filter(l => l.trim() && !l.startsWith('#')), total: 0 };
|
||||
result.python.total = result.python.packages.length;
|
||||
} catch (_) {}
|
||||
// PHP Composer
|
||||
try {
|
||||
const composer = JSON.parse(fs.readFileSync(path.join(root, 'composer.json'), 'utf8'));
|
||||
result.php = { require: Object.keys(composer.require || {}), requireDev: Object.keys(composer['require-dev'] || {}) };
|
||||
} catch (_) {}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getProjectStats() {
|
||||
const root = getWorkspaceRoot();
|
||||
const langStats = safeExec(`find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.venv/*" -not -path "./vendor/*" | sed 's/.*\\.//' | sort | uniq -c | sort -rn | head -25`, root, 10000);
|
||||
const fileCount = safeExec('find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" | wc -l', root);
|
||||
const dirCount = safeExec('find . -maxdepth 5 -type d -not -path "./.git/*" -not -path "./node_modules/*" | wc -l', root);
|
||||
const totalSize = safeExec('du -sh . --exclude=.git --exclude=node_modules 2>/dev/null | cut -f1', root);
|
||||
const largestFiles = safeExec('find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" -exec ls -lhS {} + 2>/dev/null | head -10 | awk \'{print $5, $9}\'', root, 8000);
|
||||
return { langStats, fileCount: parseInt(fileCount) || 0, dirCount: parseInt(dirCount) || 0, totalSize, largestFiles };
|
||||
}
|
||||
|
||||
function buildFullContext() {
|
||||
const activeFile = getActiveFileDetails();
|
||||
const git = getGitInfo();
|
||||
@ -224,21 +553,62 @@ function buildFullContext() {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const TOOL_REGISTRY = [
|
||||
{ name: 'read-file', description: 'Read file content from workspace', params: { filePath: 'string (relative or absolute path)', maxChars: 'number (optional, default 50000)' } },
|
||||
{ name: 'search-files', description: 'Search for files by name pattern in workspace', params: { pattern: 'string (filename pattern)' } },
|
||||
{ name: 'grep-search', description: 'Search file contents with text/regex pattern', params: { text: 'string (search text)', filePattern: 'string (optional glob, e.g. "*.php")' } },
|
||||
{ name: 'open-file', description: 'Open a file in the editor at optional line number', params: { filePath: 'string', line: 'number (optional)' } },
|
||||
{ name: 'apply-edit', description: 'Apply a code edit: find-and-replace or insert-at-line', params: { filePath: 'string', oldText: 'string (text to find)', newText: 'string (replacement)', line: 'number (for insert mode)', text: 'string (for insert mode)' } },
|
||||
// FILE I/O
|
||||
{ name: 'read-file', description: 'Read file content from workspace', params: { filePath: 'string', maxChars: 'number (optional, default 50000)' } },
|
||||
{ name: 'create-file', description: 'Create a new file with content (creates directories as needed)', params: { filePath: 'string', content: 'string' } },
|
||||
{ name: 'get-context', description: 'Get full workspace context (project type, git, diagnostics, active file)', params: {} },
|
||||
{ name: 'get-git-info', description: 'Get git branch, status, last commit, remote URL', params: {} },
|
||||
{ name: 'get-diagnostics', description: 'Get error/warning counts and affected files', params: {} },
|
||||
{ name: 'get-project-structure', description: 'Get project file tree (3 levels deep, excluding .git/node_modules)', params: {} },
|
||||
{ name: 'goto-definition', description: 'LSP: Jump to the definition of a symbol at a given position', params: { filePath: 'string', line: 'number (1-based)', character: 'number (0-based column)' } },
|
||||
{ name: 'find-references', description: 'LSP: Find all references to a symbol across the workspace', params: { filePath: 'string', line: 'number (1-based)', character: 'number (0-based column)' } },
|
||||
{ name: 'document-symbols', description: 'LSP: List all symbols (functions, classes, variables) in a file', params: { filePath: 'string' } },
|
||||
{ name: 'hover-info', description: 'LSP: Get type info and documentation for symbol at position', params: { filePath: 'string', line: 'number (1-based)', character: 'number (0-based column)' } },
|
||||
{ name: 'workspace-symbols', description: 'LSP: Search for symbols by name across entire workspace', params: { query: 'string (symbol name or pattern)' } },
|
||||
{ name: 'apply-edit', description: 'Apply a code edit: find-and-replace or insert-at-line', params: { filePath: 'string', oldText: 'string', newText: 'string', line: 'number', text: 'string' } },
|
||||
{ name: 'search-files', description: 'Search for files by name pattern', params: { pattern: 'string' } },
|
||||
{ name: 'grep-search', description: 'Search file contents with text/regex', params: { text: 'string', filePattern: 'string (optional glob)' } },
|
||||
{ name: 'open-file', description: 'Open file in editor at optional line', params: { filePath: 'string', line: 'number' } },
|
||||
{ name: 'diff-files', description: 'Unified diff of two files', params: { fileA: 'string', fileB: 'string' } },
|
||||
{ name: 'find-replace-all', description: 'Find and replace all occurrences in a file (supports regex)', params: { filePath: 'string', search: 'string', replace: 'string', isRegex: 'boolean' } },
|
||||
{ name: 'recent-files', description: 'List recently modified files', params: { count: 'number (default 20)' } },
|
||||
{ name: 'file-sizes', description: 'List file sizes matching pattern', params: { pattern: 'string (glob)' } },
|
||||
{ name: 'count-lines', description: 'Count lines, words, characters, blanks in a file', params: { filePath: 'string' } },
|
||||
// LSP CODE INTELLIGENCE
|
||||
{ name: 'goto-definition', description: 'LSP: Jump to symbol definition', params: { filePath: 'string', line: 'number', character: 'number' } },
|
||||
{ name: 'find-references', description: 'LSP: Find all references to symbol', params: { filePath: 'string', line: 'number', character: 'number' } },
|
||||
{ name: 'document-symbols', description: 'LSP: List all symbols in a file (functions, classes, variables)', params: { filePath: 'string' } },
|
||||
{ name: 'hover-info', description: 'LSP: Get type info and docs for symbol', params: { filePath: 'string', line: 'number', character: 'number' } },
|
||||
{ name: 'workspace-symbols', description: 'LSP: Search symbols across workspace', params: { query: 'string' } },
|
||||
{ name: 'rename-symbol', description: 'LSP: Rename symbol across all files', params: { filePath: 'string', line: 'number', character: 'number', newName: 'string' } },
|
||||
{ name: 'code-actions', description: 'LSP: Get available quick fixes and refactorings for a range', params: { filePath: 'string', startLine: 'number', endLine: 'number' } },
|
||||
{ name: 'apply-code-action', description: 'LSP: Apply a specific quick fix/refactoring by title', params: { filePath: 'string', startLine: 'number', endLine: 'number', actionTitle: 'string' } },
|
||||
{ name: 'format-document', description: 'LSP: Auto-format entire document', params: { filePath: 'string' } },
|
||||
// GIT OPERATIONS
|
||||
{ name: 'get-git-info', description: 'Git branch, status, last commit, remote, ahead/behind', params: {} },
|
||||
{ name: 'git-commit', description: 'Stage all changes and commit', params: { message: 'string' } },
|
||||
{ name: 'git-diff', description: 'Show diff (working tree or staged)', params: { filePath: 'string (optional)', staged: 'boolean' } },
|
||||
{ name: 'git-log', description: 'Show commit history', params: { count: 'number (default 10)', filePath: 'string (optional)' } },
|
||||
{ name: 'git-blame', description: 'Show line-by-line authorship', params: { filePath: 'string', startLine: 'number', endLine: 'number' } },
|
||||
{ name: 'git-stash', description: 'Stash operations: push/pop/list/drop', params: { action: 'string', message: 'string (optional)' } },
|
||||
{ name: 'git-branch', description: 'Branch operations: list/create/switch/delete', params: { action: 'string', branchName: 'string' } },
|
||||
// WORKSPACE CONTEXT
|
||||
{ name: 'get-context', description: 'Full workspace context (project, git, diagnostics, active file)', params: {} },
|
||||
{ name: 'get-diagnostics', description: 'Error/warning counts and affected files', params: {} },
|
||||
{ name: 'get-diagnostics-detail', description: 'Detailed diagnostics: file, line, severity, message, code for every issue', params: {} },
|
||||
{ name: 'get-project-structure', description: 'File tree (3 levels deep)', params: {} },
|
||||
{ name: 'project-stats', description: 'Language breakdown, file count, directory count, total size, largest files', params: {} },
|
||||
// ANALYSIS
|
||||
{ name: 'import-graph', description: 'Extract all imports/requires/includes from a file (JS, Python, PHP, Go, Rust)', params: { filePath: 'string' } },
|
||||
{ name: 'analyze-complexity', description: 'Code complexity: lines, functions, nesting depth, loops, conditions', params: { filePath: 'string' } },
|
||||
{ name: 'dependency-info', description: 'List all project dependencies (Node, Python, PHP, etc.)', params: {} },
|
||||
// TESTING
|
||||
{ name: 'detect-tests', description: 'Detect test frameworks in the project', params: {} },
|
||||
{ name: 'run-tests', description: 'Run tests (auto-detects framework or specify)', params: { framework: 'string (optional)', testFile: 'string (optional)' } },
|
||||
// SYSTEM & PROCESS
|
||||
{ name: 'system-info', description: 'Server info: CPU, RAM, disk, uptime, node version', params: {} },
|
||||
{ name: 'list-ports', description: 'List all listening TCP ports and their processes', params: {} },
|
||||
{ name: 'pm2-list', description: 'List all PM2 services with status, CPU, memory, restarts', params: {} },
|
||||
{ name: 'disk-usage', description: 'Disk usage for / and /home', params: {} },
|
||||
{ name: 'env-vars', description: 'List environment variables (with optional filter)', params: { filter: 'string (optional grep pattern)' } },
|
||||
// DATA UTILITIES
|
||||
{ name: 'format-json', description: 'Pretty-print JSON', params: { text: 'string (raw JSON)' } },
|
||||
{ name: 'compute-hash', description: 'Compute hash (sha256, sha512, md5, sha1)', params: { text: 'string', algorithm: 'string' } },
|
||||
{ name: 'base64', description: 'Base64 encode or decode', params: { text: 'string', action: '"encode" or "decode"' } },
|
||||
{ name: 'url-encode', description: 'URL encode or decode', params: { text: 'string', action: '"encode" or "decode"' } },
|
||||
{ name: 'generate-uuid', description: 'Generate a random UUID v4', params: {} },
|
||||
// EXECUTION
|
||||
{ name: 'run-terminal', description: 'Execute a command in a new terminal', params: { command: 'string' } },
|
||||
{ name: 'insert-code', description: 'Insert code at cursor position in active editor', params: { code: 'string' } },
|
||||
];
|
||||
@ -395,6 +765,51 @@ OUTPUT EXCELLENCE:
|
||||
- Avoid time estimates or predictions for how long tasks will take — focus on what needs to be done
|
||||
- Only use tables for factual data (filenames, line numbers, pass/fail) or quantitative comparisons
|
||||
|
||||
ADVANCED REASONING PROTOCOL:
|
||||
1. PLANNING MODE — For complex multi-step tasks:
|
||||
- Break the task into numbered steps before starting
|
||||
- Identify dependencies between steps
|
||||
- Note which steps can be done in parallel
|
||||
- Execute steps in order, reporting progress
|
||||
|
||||
2. CHAIN-OF-THOUGHT — For debugging and architecture decisions:
|
||||
- State what you observe (the symptom)
|
||||
- List possible causes (hypotheses)
|
||||
- Identify the most likely cause and WHY
|
||||
- Propose the fix and explain the reasoning
|
||||
- Verify the fix addresses the root cause, not just the symptom
|
||||
|
||||
3. CONTEXT SYNTHESIS — For large codebases:
|
||||
- Start from the entry point or the file the user is working in
|
||||
- Trace the call chain / data flow before making recommendations
|
||||
- Use LSP tools (goto-definition, find-references) to follow the code
|
||||
- Build a mental model of the architecture before suggesting changes
|
||||
|
||||
4. ADVERSARIAL REVIEW — For non-trivial changes:
|
||||
- After implementing, review your own changes as if reviewing someone else's code
|
||||
- Check: edge cases, error paths, performance implications, security surface
|
||||
- If you find issues in your own review, fix them before reporting done
|
||||
- State what you checked and what passed
|
||||
|
||||
5. FULL-STACK DEBUGGING PROTOCOL:
|
||||
- Check diagnostics panel first (get-diagnostics-detail) for existing errors
|
||||
- Read the relevant source files to understand the current state
|
||||
- Check git diff to see recent changes that might have caused the issue
|
||||
- Check import graph to understand file dependencies
|
||||
- Run tests if available to verify the fix
|
||||
- Check for cascading effects in referenced files
|
||||
|
||||
SYSTEM AWARENESS — Your full power:
|
||||
- You have 48 IDE tools giving you deep access to the workspace
|
||||
- You can read, write, search, grep, diff, and refactor any file
|
||||
- You can navigate code via LSP: definitions, references, symbols, type info, rename, code actions
|
||||
- You can run git operations: commit, diff, log, blame, stash, branch management
|
||||
- You can analyze code: complexity, import graphs, dependency audits, project stats
|
||||
- You can detect and run tests across any framework
|
||||
- You can inspect the server: CPU, RAM, ports, PM2 services, env vars, disk
|
||||
- You can format, hash, encode/decode data
|
||||
- Use these tools proactively — don't just suggest commands, execute them when appropriate
|
||||
|
||||
`;
|
||||
|
||||
|
||||
@ -909,8 +1324,174 @@ class AlfredCommanderProvider {
|
||||
webviewView.webview.postMessage({ type: 'workspace-symbols-result', symbols: [], error: e.message, id: msg.id });
|
||||
}
|
||||
} else if (msg.type === 'get-tool-registry') {
|
||||
// Return formal tool definitions for AI tool-use awareness
|
||||
webviewView.webview.postMessage({ type: 'tool-registry', tools: TOOL_REGISTRY, id: msg.id });
|
||||
// ────── GIT OPERATIONS ──────
|
||||
} else if (msg.type === 'git-commit') {
|
||||
const result = gitCommit(msg.message || 'Auto-commit from Alfred IDE');
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'commit', result, id: msg.id });
|
||||
} else if (msg.type === 'git-diff') {
|
||||
const result = gitDiff(msg.filePath, msg.staged);
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'diff', result, id: msg.id });
|
||||
} else if (msg.type === 'git-log') {
|
||||
const result = gitLog(msg.count, msg.filePath);
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'log', result, id: msg.id });
|
||||
} else if (msg.type === 'git-blame') {
|
||||
const result = gitBlame(msg.filePath, msg.startLine, msg.endLine);
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'blame', result, id: msg.id });
|
||||
} else if (msg.type === 'git-stash') {
|
||||
const result = gitStash(msg.action || 'push', msg.message);
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'stash', result, id: msg.id });
|
||||
} else if (msg.type === 'git-branch') {
|
||||
const result = gitBranch(msg.action || 'list', msg.branchName);
|
||||
webviewView.webview.postMessage({ type: 'git-result', action: 'branch', result, id: msg.id });
|
||||
// ────── CODE REFACTORING ──────
|
||||
} else if (msg.type === 'rename-symbol') {
|
||||
try {
|
||||
const root = getWorkspaceRoot();
|
||||
const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath);
|
||||
const uri = vscode.Uri.file(fullPath);
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
const pos = new vscode.Position((msg.line || 1) - 1, msg.character || 0);
|
||||
const edit = await vscode.commands.executeCommand('vscode.executeDocumentRenameProvider', uri, pos, msg.newName || 'renamed');
|
||||
if (edit) {
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'rename', success: true, id: msg.id });
|
||||
} else {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'rename', success: false, error: 'No rename available at position', id: msg.id });
|
||||
}
|
||||
} catch (e) {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'rename', success: false, error: e.message, id: msg.id });
|
||||
}
|
||||
} else if (msg.type === 'code-actions') {
|
||||
try {
|
||||
const root = getWorkspaceRoot();
|
||||
const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath);
|
||||
const uri = vscode.Uri.file(fullPath);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
const startPos = new vscode.Position((msg.startLine || 1) - 1, 0);
|
||||
const endPos = new vscode.Position((msg.endLine || msg.startLine || 1) - 1, 999);
|
||||
const range = new vscode.Range(startPos, endPos);
|
||||
const actions = await vscode.commands.executeCommand('vscode.executeCodeActionProvider', uri, range);
|
||||
const results = (actions || []).slice(0, 20).map(a => ({
|
||||
title: a.title, kind: a.kind ? a.kind.value : '', isPreferred: a.isPreferred || false
|
||||
}));
|
||||
webviewView.webview.postMessage({ type: 'code-actions-result', actions: results, id: msg.id });
|
||||
} catch (e) {
|
||||
webviewView.webview.postMessage({ type: 'code-actions-result', actions: [], error: e.message, id: msg.id });
|
||||
}
|
||||
} else if (msg.type === 'apply-code-action') {
|
||||
try {
|
||||
const root = getWorkspaceRoot();
|
||||
const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath);
|
||||
const uri = vscode.Uri.file(fullPath);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
const startPos = new vscode.Position((msg.startLine || 1) - 1, 0);
|
||||
const endPos = new vscode.Position((msg.endLine || msg.startLine || 1) - 1, 999);
|
||||
const range = new vscode.Range(startPos, endPos);
|
||||
const actions = await vscode.commands.executeCommand('vscode.executeCodeActionProvider', uri, range);
|
||||
const target = (actions || []).find(a => a.title === msg.actionTitle);
|
||||
if (target) {
|
||||
if (target.edit) await vscode.workspace.applyEdit(target.edit);
|
||||
if (target.command) await vscode.commands.executeCommand(target.command.command, ...(target.command.arguments || []));
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'code-action', success: true, title: msg.actionTitle, id: msg.id });
|
||||
} else {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'code-action', success: false, error: 'Action not found', id: msg.id });
|
||||
}
|
||||
} catch (e) {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'code-action', success: false, error: e.message, id: msg.id });
|
||||
}
|
||||
} else if (msg.type === 'format-document') {
|
||||
try {
|
||||
const root = getWorkspaceRoot();
|
||||
const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath);
|
||||
const uri = vscode.Uri.file(fullPath);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
const edits = await vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', uri, { tabSize: 2, insertSpaces: true });
|
||||
if (edits && edits.length > 0) {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const e of edits) edit.replace(uri, e.range, e.newText);
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'format', success: true, edits: edits.length, id: msg.id });
|
||||
} else {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'format', success: true, edits: 0, id: msg.id });
|
||||
}
|
||||
} catch (e) {
|
||||
webviewView.webview.postMessage({ type: 'refactor-result', action: 'format', success: false, error: e.message, id: msg.id });
|
||||
}
|
||||
// ────── SYSTEM & PROCESS TOOLS ──────
|
||||
} else if (msg.type === 'system-info') {
|
||||
webviewView.webview.postMessage({ type: 'system-info-result', info: getSystemInfo(), id: msg.id });
|
||||
} else if (msg.type === 'list-ports') {
|
||||
webviewView.webview.postMessage({ type: 'ports-result', ports: getRunningPorts(), id: msg.id });
|
||||
} else if (msg.type === 'pm2-list') {
|
||||
try {
|
||||
const raw = getPm2Services();
|
||||
const services = JSON.parse(raw || '[]').map(s => ({
|
||||
name: s.name, id: s.pm_id, status: s.pm2_env?.status, cpu: s.monit?.cpu, mem: ((s.monit?.memory || 0) / 1048576).toFixed(1) + 'MB',
|
||||
restarts: s.pm2_env?.restart_time, uptime: s.pm2_env?.pm_uptime ? new Date(s.pm2_env.pm_uptime).toISOString() : ''
|
||||
}));
|
||||
webviewView.webview.postMessage({ type: 'pm2-result', services, id: msg.id });
|
||||
} catch (e) {
|
||||
webviewView.webview.postMessage({ type: 'pm2-result', services: [], error: e.message, id: msg.id });
|
||||
}
|
||||
} else if (msg.type === 'disk-usage') {
|
||||
webviewView.webview.postMessage({ type: 'disk-result', usage: getDiskUsage(), id: msg.id });
|
||||
} else if (msg.type === 'env-vars') {
|
||||
webviewView.webview.postMessage({ type: 'env-result', vars: getEnvironmentVars(msg.filter), id: msg.id });
|
||||
// ────── DATA FORMAT UTILITIES ──────
|
||||
} else if (msg.type === 'format-json') {
|
||||
webviewView.webview.postMessage({ type: 'format-result', result: formatJson(msg.text || ''), id: msg.id });
|
||||
} else if (msg.type === 'compute-hash') {
|
||||
webviewView.webview.postMessage({ type: 'hash-result', hash: computeHash(msg.text || '', msg.algorithm), algorithm: msg.algorithm || 'sha256', id: msg.id });
|
||||
} else if (msg.type === 'base64') {
|
||||
const result = msg.action === 'decode' ? base64Decode(msg.text || '') : base64Encode(msg.text || '');
|
||||
webviewView.webview.postMessage({ type: 'base64-result', result, action: msg.action, id: msg.id });
|
||||
} else if (msg.type === 'url-encode') {
|
||||
const result = msg.action === 'decode' ? urlDecode(msg.text || '') : urlEncode(msg.text || '');
|
||||
webviewView.webview.postMessage({ type: 'url-encode-result', result, action: msg.action, id: msg.id });
|
||||
} else if (msg.type === 'generate-uuid') {
|
||||
webviewView.webview.postMessage({ type: 'uuid-result', uuid: generateUuid(), id: msg.id });
|
||||
} else if (msg.type === 'count-lines') {
|
||||
webviewView.webview.postMessage({ type: 'count-result', stats: countLines(msg.filePath || ''), id: msg.id });
|
||||
// ────── ADVANCED FILE OPS ──────
|
||||
} else if (msg.type === 'diff-files') {
|
||||
webviewView.webview.postMessage({ type: 'diff-result', diff: diffFiles(msg.fileA, msg.fileB), id: msg.id });
|
||||
} else if (msg.type === 'find-replace-all') {
|
||||
const result = findReplace(msg.filePath, msg.search, msg.replace, msg.isRegex);
|
||||
webviewView.webview.postMessage({ type: 'find-replace-result', result, id: msg.id });
|
||||
} else if (msg.type === 'recent-files') {
|
||||
webviewView.webview.postMessage({ type: 'recent-files-result', files: getRecentFiles(msg.count), id: msg.id });
|
||||
} else if (msg.type === 'file-sizes') {
|
||||
webviewView.webview.postMessage({ type: 'file-sizes-result', sizes: getFileSizes(msg.pattern), id: msg.id });
|
||||
// ────── TEST RUNNER ──────
|
||||
} else if (msg.type === 'detect-tests') {
|
||||
webviewView.webview.postMessage({ type: 'test-frameworks', frameworks: detectTestFramework(), id: msg.id });
|
||||
} else if (msg.type === 'run-tests') {
|
||||
const result = runTests(msg.framework, msg.testFile);
|
||||
webviewView.webview.postMessage({ type: 'test-result', result, id: msg.id });
|
||||
// ────── ANALYSIS TOOLS ──────
|
||||
} else if (msg.type === 'import-graph') {
|
||||
webviewView.webview.postMessage({ type: 'import-graph-result', graph: getImportGraph(msg.filePath), id: msg.id });
|
||||
} else if (msg.type === 'analyze-complexity') {
|
||||
webviewView.webview.postMessage({ type: 'complexity-result', analysis: analyzeComplexity(msg.filePath), id: msg.id });
|
||||
} else if (msg.type === 'dependency-info') {
|
||||
webviewView.webview.postMessage({ type: 'dependency-result', info: getDependencyInfo(), id: msg.id });
|
||||
} else if (msg.type === 'project-stats') {
|
||||
webviewView.webview.postMessage({ type: 'project-stats-result', stats: getProjectStats(), id: msg.id });
|
||||
} else if (msg.type === 'get-diagnostics-detail') {
|
||||
const diags = vscode.languages.getDiagnostics();
|
||||
const detailed = [];
|
||||
for (const [uri, items] of diags) {
|
||||
for (const d of items) {
|
||||
detailed.push({
|
||||
file: vscode.workspace.asRelativePath(uri, false),
|
||||
line: d.range.start.line + 1, col: d.range.start.character,
|
||||
severity: d.severity === 0 ? 'error' : d.severity === 1 ? 'warning' : d.severity === 2 ? 'info' : 'hint',
|
||||
message: d.message, source: d.source || '', code: d.code ? (typeof d.code === 'object' ? d.code.value : d.code) : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
webviewView.webview.postMessage({ type: 'diagnostics-detail-result', diagnostics: detailed.slice(0, 100), id: msg.id });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "alfred-commander",
|
||||
"displayName": "Alfred IDE Assistant — Full IDE Chat & Voice",
|
||||
"description": "Fresh Alfred panel: all agents & models, voice STT/TTS, attachments, hands-free. IDE shortcuts are explicit buttons — chat text always goes to AI.",
|
||||
"version": "1.2.0",
|
||||
"version": "2.0.0",
|
||||
"publisher": "gositeme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user