diff --git a/extension.js b/extension.js index f2e4b1f..950344b 100644 --- a/extension.js +++ b/extension.js @@ -218,6 +218,41 @@ function buildFullContext() { return ctx; } +// ═══════════════════════════════════════════════════════════════════════════ +// TOOL REGISTRY — Formal tool definitions for AI agent awareness +// Claude Code pattern: tools defined with schemas, discoverable by AI +// ═══════════════════════════════════════════════════════════════════════════ + +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)' } }, + { 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: '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' } }, +]; + +function buildToolAwareness() { + let section = '\nAVAILABLE IDE TOOLS (you can request these via the IDE):\n'; + for (const tool of TOOL_REGISTRY) { + section += `- ${tool.name}: ${tool.description}\n`; + } + section += '\nYou have direct access to the workspace file system, LSP (Language Server Protocol) for code intelligence,\n'; + section += 'git operations, diagnostics, and terminal execution through these tools.\n'; + return section; +} + // ═══════════════════════════════════════════════════════════════════════════ // COMMANDER SYSTEM PROMPT — The soul of Alfred. Knows who Danny is. // ═══════════════════════════════════════════════════════════════════════════ @@ -307,6 +342,7 @@ You are assisting ${name}. Be helpful, precise, and technically thorough. - Check the IDE diagnostics panel for current errors - Suggest targeted fixes, not wholesale rewrites - Explain WHY something broke, not just HOW to fix it + - Diagnose failures before switching tactics — don't abandon after one failure 5. ARCHITECTURE & DESIGN: - Choose simple solutions over clever ones @@ -328,8 +364,40 @@ You are assisting ${name}. Be helpful, precise, and technically thorough. - For commands: provide the exact terminal command to run - Be concise but complete — no fluff, no filler +IMPLEMENTATION DISCIPLINE: +- Only make changes that are directly requested or clearly necessary +- Don't add features, refactor code, or make "improvements" beyond what was asked +- A bug fix doesn't need surrounding code cleaned up; a simple feature doesn't need extra configurability +- Default to writing NO comments — only add one when the WHY is non-obvious (well-named code explains itself) +- Don't add error handling, fallbacks, or validation for scenarios that can't happen +- Don't create helpers, utilities, or abstractions for one-time operations — 3 similar lines are better than a premature abstraction +- Read files before proposing changes — NEVER suggest edits to code you haven't seen +- Don't create new files unless absolutely necessary — prefer editing existing files + +EXECUTING ACTIONS WITH CARE: +- Consider the reversibility and potential impact of every action +- Take local, reversible actions freely (edits, tests) but confirm destructive or shared-system operations first +- Destructive operations (deleting files/branches, dropping tables, rm -rf): ALWAYS confirm +- Hard to reverse operations (force push, reset --hard, amending published commits): ALWAYS confirm +- Operations visible to others (pushing code, commenting on PRs, sending messages): confirm first +- When encountering unexpected state, investigate before deleting — don't bypass safety checks as a shortcut + +VERIFICATION PROTOCOL: +- Before reporting a task complete, verify it actually works: run the test, execute the script, check the output +- Report outcomes faithfully — if tests fail, say so. If something wasn't verified, state that clearly +- For non-trivial changes (3+ file edits, backend/API changes, infrastructure): independently verify by reviewing the changes +- Never suppress failures or gloss over errors to appear successful + +OUTPUT EXCELLENCE: +- State what you're about to do before the first action — give the user orientation +- Provide brief updates at key moments: found a bug, changing direction, hit a blocker +- Assume the user may have stepped away — give complete context, not fragments +- 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 + `; + if (projectTypes.length > 0) { prompt += `PROJECT CONTEXT: This is a ${projectTypes.join(' + ')} project.\n`; } @@ -339,6 +407,8 @@ You are assisting ${name}. Be helpful, precise, and technically thorough. prompt += '\n'; } + prompt += buildToolAwareness(); + return prompt; } @@ -755,6 +825,92 @@ class AlfredCommanderProvider { // Return full workspace context to webview const ctx = buildFullContext(); webviewView.webview.postMessage({ type: 'workspace-context', context: ctx, id: msg.id }); + } else if (msg.type === 'goto-definition') { + // LSP: Go to definition of symbol at position + 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 pos = new vscode.Position((msg.line || 1) - 1, msg.character || 0); + const locations = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', uri, pos); + const results = (locations || []).map(loc => ({ + file: vscode.workspace.asRelativePath(loc.uri || loc.targetUri, false), + line: ((loc.range || loc.targetRange || {}).start || {}).line + 1, + character: ((loc.range || loc.targetRange || {}).start || {}).character + })); + webviewView.webview.postMessage({ type: 'definition-result', results, id: msg.id }); + } catch (e) { + webviewView.webview.postMessage({ type: 'definition-result', results: [], error: e.message, id: msg.id }); + } + } else if (msg.type === 'find-references') { + // LSP: Find all references to symbol + 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 pos = new vscode.Position((msg.line || 1) - 1, msg.character || 0); + const locations = await vscode.commands.executeCommand('vscode.executeReferenceProvider', uri, pos); + const results = (locations || []).slice(0, 50).map(loc => ({ + file: vscode.workspace.asRelativePath(loc.uri, false), + line: (loc.range.start.line || 0) + 1, + preview: '' + })); + webviewView.webview.postMessage({ type: 'references-result', results, id: msg.id }); + } catch (e) { + webviewView.webview.postMessage({ type: 'references-result', results: [], error: e.message, id: msg.id }); + } + } else if (msg.type === 'document-symbols') { + // LSP: Get all symbols in a document (functions, classes, variables) + 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 symbols = await vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', uri); + const flatten = (syms, depth) => { + const out = []; + for (const s of (syms || [])) { + out.push({ name: s.name, kind: vscode.SymbolKind[s.kind] || s.kind, line: (s.range || s.location?.range)?.start?.line + 1, detail: s.detail || '' }); + if (s.children) out.push(...flatten(s.children, depth + 1)); + } + return out; + }; + webviewView.webview.postMessage({ type: 'symbols-result', symbols: flatten(symbols, 0).slice(0, 200), id: msg.id }); + } catch (e) { + webviewView.webview.postMessage({ type: 'symbols-result', symbols: [], error: e.message, id: msg.id }); + } + } else if (msg.type === 'hover-info') { + // LSP: Get hover info (type info, docs) for symbol at position + 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 pos = new vscode.Position((msg.line || 1) - 1, msg.character || 0); + const hovers = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, pos); + const contents = (hovers || []).map(h => (h.contents || []).map(c => typeof c === 'string' ? c : c.value || '').join('\\n')).join('\\n'); + webviewView.webview.postMessage({ type: 'hover-result', info: contents, id: msg.id }); + } catch (e) { + webviewView.webview.postMessage({ type: 'hover-result', info: '', error: e.message, id: msg.id }); + } + } else if (msg.type === 'workspace-symbols') { + // LSP: Search symbols across workspace by query + try { + const symbols = await vscode.commands.executeCommand('vscode.executeWorkspaceSymbolProvider', msg.query || ''); + const results = (symbols || []).slice(0, 50).map(s => ({ + name: s.name, kind: vscode.SymbolKind[s.kind] || s.kind, + file: vscode.workspace.asRelativePath(s.location.uri, false), + line: (s.location.range?.start?.line || 0) + 1 + })); + webviewView.webview.postMessage({ type: 'workspace-symbols-result', symbols: results, id: msg.id }); + } catch (e) { + 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 }); } }); diff --git a/package.json b/package.json index 7ca12a1..844c860 100644 --- a/package.json +++ b/package.json @@ -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.0.1", + "version": "1.2.0", "publisher": "gositeme", "repository": { "type": "git",