diff --git a/TOOLS.md b/TOOLS.md deleted file mode 100644 index 917e2fa86ccb01bab7227e223555daa1f5a76ebc..0000000000000000000000000000000000000000 --- a/TOOLS.md +++ /dev/null @@ -1,40 +0,0 @@ -# TOOLS.md - Local Notes - -Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup. - -## What Goes Here - -Things like: - -- Camera names and locations -- SSH hosts and aliases -- Preferred voices for TTS -- Speaker/room names -- Device nicknames -- Anything environment-specific - -## Examples - -```markdown -### Cameras - -- living-room → Main area, 180° wide angle -- front-door → Entrance, motion-triggered - -### SSH - -- home-server → 192.168.1.100, user: admin - -### TTS - -- Preferred voice: "Nova" (warm, slightly British) -- Default speaker: Kitchen HomePod -``` - -## Why Separate? - -Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure. - ---- - -Add whatever helps you do your job. This is your cheat sheet. diff --git a/archived-selective/BENCHMARK_BADGES.md b/archived-selective/BENCHMARK_BADGES.md new file mode 100644 index 0000000000000000000000000000000000000000..83ef884a873ebd1c69a6e628e766f4b3f1067a66 --- /dev/null +++ b/archived-selective/BENCHMARK_BADGES.md @@ -0,0 +1,57 @@ +# Benchmark Badges for README.md + +Copy these badge markdown lines into your main README to display evaluation scores. + +## HumanEval Pass@k Badges + +Replace the static badges with dynamic ones after evaluation completes. + +### Template (placeholders): + +```markdown +[![HumanEval Pass@1](https://img.shields.io/static/v1?label=HumanEval&message=Pass%401&color=yellow)](https://github.com/your-repo) +[![HumanEval Pass@10](https://img.shields.io/static/v1?label=HumanEval&message=Pass%4010&color=yellow)](https://github.com/your-repo) +[![HumanEval Pass@100](https://img.shields.io/static/v1?label=HumanEval&message=Pass%40100&color=orange)](https://github.com/your-repo) +``` + +### Example with actual scores (update after running evaluation): + +If you get 82% Pass@1: +```markdown +[![HumanEval Pass@1: 82%](https://img.shields.io/badge/HumanEval-Pass%401-82%25-yellow?logo=python)](https://github.com/your-repo) +[![HumanEval Pass@10: 89%](https://img.shields.io/badge/HumanEval-Pass%4010-89%25-yellow?logo=python)](https://github.com/your-repo) +[![HumanEval Pass@100: 92%](https://img.shields.io/badge/HumanEval-Pass%40100-92%25-orange?logo=python)](https://github.com/your-repo) +``` + +## MBPP Badges + +### Template: +```markdown +[![MBPP Pass@1](https://img.shields.io/static/v1?label=MBPP&message=Pass%401&color=blue)](https://github.com/your-repo) +[![MBPP Pass@10](https://img.shields.io/static/v1?label=MBPP&message=Pass%4010&color=blue)](https://github.com/your-repo) +[![MBPP Pass@100](https://img.shields.io/static/v1?label=MBPP&message=Pass%40100&color=blue)](https://github.com/your-repo) +``` + +### Example with actual scores: +If you get 80% Pass@1: +```markdown +[![MBPP Pass@1: 80%](https://img.shields.io/badge/MBPP-Pass%401-80%25-blue?logo=python)](https://github.com/your-repo) +[![MBPP Pass@10: 85%](https://img.shields.io/badge/MBPP-Pass%4010-85%25-blue?logo=python)](https://github.com/your-repo) +[![MBPP Pass@100: 88%](https://img.shields.io/badge/MBPP-Pass%40100-88%25-blue?logo=python)](https://github.com/your-repo) +``` + +## Auto-generating Badges + +After running evaluation, use the scores from the generated summary files: + +- `results/humaneval_summary.json` → contains `pass@k` value for HumanEval +- `results/mbpp_summary.json` → contains `pass@k` value for MBPP + +You can create a script to auto-update README.md with the latest scores. + +## Combined Badges + +```markdown +[![HumanEval](https://img.shields.io/badge/HumanEval-164%20problems-green)](https://github.com/your-repo) +[![MBPP](https://img.shields.io/badge/MBPP-500%20problems-green)](https://github.com/your-repo) +``` diff --git a/archived-selective/cli-backup/__init__.py b/archived-selective/cli-backup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c3e971a02425149e058150a3e3e1a70a09fb449 --- /dev/null +++ b/archived-selective/cli-backup/__init__.py @@ -0,0 +1,11 @@ +""" +Stack 2.9 CLI Package +Terminal user interface for Stack 2.9 +""" + +__version__ = "2.9.0" +__author__ = "my-ai-stack" + +from .main import Stack29CLI, main + +__all__ = ["Stack29CLI", "main"] \ No newline at end of file diff --git a/archived-selective/cli-backup/agent.py b/archived-selective/cli-backup/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..2231941a583cf8ca0372f5301355b367816661db --- /dev/null +++ b/archived-selective/cli-backup/agent.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +""" +Stack 2.9 - Core Agent Logic Module +Query understanding, tool selection, response generation, and self-reflection loop. +""" + +import os +import json +import re +import asyncio +from pathlib import Path +from typing import Any, Dict, List, Optional, Union, Callable +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum + +from .tools import TOOLS, get_tool, list_tools, get_tool_schemas +from .context import ContextManager, create_context_manager + + +class QueryIntent(Enum): + """Intents recognized by the agent.""" + FILE_READ = "file_read" + FILE_WRITE = "file_write" + FILE_EDIT = "file_edit" + FILE_SEARCH = "file_search" + GIT_OPERATION = "git_operation" + CODE_EXECUTION = "code_execution" + WEB_SEARCH = "web_search" + MEMORY = "memory" + TASK = "task" + QUESTION = "question" + GENERAL = "general" + GENERAL_HELP = "general_help" + + +@dataclass +class ToolCall: + """Represents a tool call.""" + tool_name: str + arguments: Dict[str, Any] + result: Optional[Dict[str, Any]] = None + success: bool = False + error: Optional[str] = None + + +@dataclass +class AgentResponse: + """Represents the agent's response.""" + content: str + tool_calls: List[ToolCall] = field(default_factory=list) + context_used: List[str] = field(default_factory=list) + confidence: float = 1.0 + needs_clarification: bool = False + clarification_needed: Optional[str] = None + + +class QueryUnderstanding: + """Understands user queries and maps them to intents and tools.""" + + # Intent patterns + PATTERNS = { + QueryIntent.FILE_READ: [ + r"read\s+(?:the\s+)?(?:file\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"show\s+(?:me\s+)?(?:the\s+)?(?:content\s+of\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"what('s| is)\s+in\s+(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"cat\s+(.+)", + r"view\s+(.+)", + ], + QueryIntent.FILE_WRITE: [ + r"write\s+(?:to\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"create\s+(?:file\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"save\s+(?:to\s+)?(.+)", + ], + QueryIntent.FILE_EDIT: [ + r"edit\s+(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)", + r"modify\s+(.+)", + r"change\s+(.+)", + r"replace\s+(.+)", + ], + QueryIntent.FILE_SEARCH: [ + r"find\s+(?:files?\s+)?(?:named\s+)?(.+)", + r"search\s+for\s+(?:files?\s+)?(.+)", + r"grep\s+for\s+(.+)", + r"where\s+is\s+(.+)", + r"locate\s+(.+)", + ], + QueryIntent.GIT_OPERATION: [ + r"git\s+(commit|push|pull|branch|status|log|diff)", + r"(commit|push|pull|branch)\s+(?:to\s+)?(?:the\s+)?(?:repo|repository)?", + ], + QueryIntent.CODE_EXECUTION: [ + r"^run\s+(?:the\s+)?(?:command\s+)?(.+)", + r"^execute\s+(.+)", + r"^start\s+(?:the\s+)?(?:server\s+)?(.+)", + r"^test\s+(?:the\s+)?(.+)", + r"^lint\s+(.+)", + r"^format\s+(.+)", + ], + QueryIntent.WEB_SEARCH: [ + r"^search\s+(?:the\s+)?web\s+for\s+(.+)", + r"^google\s+(.+)", + r"^look\s+up\s+(.+)", + r"^find\s+information\s+about\s+(.+)", + r"latest\s+ai\s+news", + r"what('s|\s+is)\s+new\s+in\s+ai", + ], + QueryIntent.MEMORY: [ + r"(remember|recall|what do you remember)\s+(.+)", + r"(save|store)\s+(?:to\s+)?memory\s+(.+)", + r"what('s| is)\s+in\s+(?:the\s+)?memory", + ], + QueryIntent.GENERAL_HELP: [ + r"list\s+(?:all\s+)?tools?", + r"what\s+tools\s+(?:do\s+you\s+have|can\s+you\s+do)", + r"help\s+me", + r"what\s+can\s+you\s+do", + r"how\s+to\s+use\s+you", + ], + QueryIntent.TASK: [ + r"(create|add|new)\s+task\s+(.+)", + r"list\s+(?:my\s+)?tasks?", + r"(complete|finish|done)\s+task\s+(.+)", + ], + QueryIntent.QUESTION: [ + r"what\s+is\s+(.+)", + r"how\s+(?:do|does)\s+(.+)", + r"why\s+(.+)", + r"can\s+(.+)", + r"(?:help|explain)\s+(.+)", + ], + } + + def __init__(self): + self.tools = list_tools() + + def parse(self, query: str) -> Dict[str, Any]: + """Parse query and determine intent.""" + query = query.strip().lower() + + # Check each intent pattern + for intent, patterns in self.PATTERNS.items(): + for pattern in patterns: + match = re.search(pattern, query, re.IGNORECASE) + if match: + return { + "intent": intent.value, + "matched": match.group(0), + "extracted": match.groups() if match.groups() else None, + "confidence": 0.8 + } + + return { + "intent": QueryIntent.GENERAL.value, + "matched": None, + "extracted": None, + "confidence": 0.5 + } + + def extract_file_path(self, text: str) -> Optional[str]: + """Extract file path from text.""" + # Common patterns for file paths + patterns = [ + r"([a-zA-Z0-9_/\-\.]+\.py)", + r"([a-zA-Z0-9_/\-\.]+\.js)", + r"([a-zA-Z0-9_/\-\.]+\.ts)", + r"([a-zA-Z0-9_/\-\.]+\.md)", + r"([a-zA-Z0-9_/\-\.]+\.json)", + r"([a-zA-Z0-9_/\-\.]+\.txt)", + r"([a-zA-Z0-9_/\-\.]+\.yaml|\.yml)", + r"([a-zA-Z0-9_/\-\.]+)", + ] + + for pattern in patterns: + match = re.search(pattern, text) + if match: + return match.group(1) + + return None + + +class ToolSelector: + """Selects appropriate tools based on query intent.""" + + # Intent to tool mapping + INTENT_TOOLS = { + QueryIntent.FILE_READ: ["read"], + QueryIntent.FILE_WRITE: ["write"], + QueryIntent.FILE_EDIT: ["edit"], + QueryIntent.FILE_SEARCH: ["search", "grep"], + QueryIntent.GIT_OPERATION: ["git_status", "git_commit", "git_push", "git_pull", "git_branch", "git_log"], + QueryIntent.CODE_EXECUTION: ["run", "test", "lint", "format"], + QueryIntent.WEB_SEARCH: ["web_search", "fetch"], + QueryIntent.MEMORY: ["memory_recall", "memory_save", "memory_list"], + QueryIntent.TASK: ["create_task", "list_tasks", "update_task"], + QueryIntent.GENERAL_HELP: [], + } + + def select(self, intent: str, context: Dict[str, Any]) -> List[str]: + """Select tools for given intent.""" + # Map string to QueryIntent enum + INTENT_MAP = { + "file_read": QueryIntent.FILE_READ, + "file_write": QueryIntent.FILE_WRITE, + "file_edit": QueryIntent.FILE_EDIT, + "file_search": QueryIntent.FILE_SEARCH, + "git_operation": QueryIntent.GIT_OPERATION, + "code_execution": QueryIntent.CODE_EXECUTION, + "web_search": QueryIntent.WEB_SEARCH, + "memory": QueryIntent.MEMORY, + "task": QueryIntent.TASK, + "general": QueryIntent.GENERAL, + "general_help": QueryIntent.GENERAL_HELP, + } + + tools = [] + intent_enum = INTENT_MAP.get(intent) + if intent_enum: + tools = list(self.INTENT_TOOLS.get(intent_enum, [])) + + # For git operations, filter based on query keywords + if intent == "git_operation" and context.get("query"): + query = context["query"].lower() + git_keyword_tools = { + "status": ["git_status"], + "commit": ["git_commit"], + "push": ["git_push"], + "pull": ["git_pull"], + "branch": ["git_branch"], + "log": ["git_log"], + "diff": ["git_diff"], + } + filtered = [] + for kw, tool_list in git_keyword_tools.items(): + if kw in query: + filtered.extend(tool_list) + # Default to git_status if no specific keyword found but query mentions git + if not filtered and "git" in query: + filtered = ["git_status"] + if filtered: + tools = filtered + + return tools + + def get_tool_parameters(self, tool_name: str, query: str, context: Dict[str, Any]) -> Dict[str, Any]: + """Extract parameters for a tool from query and context.""" + params = {} + + query_lower = query.lower() + + if tool_name == "read": + path = re.search(r"(?:read|show|cat|view)\s+(?:the\s+)?(?:file\s+)?(.+)", query, re.IGNORECASE) + if path: + params["path"] = path.group(1).strip() + + elif tool_name == "write": + path = re.search(r"write\s+(?:to\s+)?(.+?)(?:\s+with|\s+content|$)", query, re.IGNORECASE) + if path: + params["path"] = path.group(1).strip() + # Try to extract content + content_match = re.search(r"(?:content|with):\s*(.+)$", query, re.IGNORECASE) + if content_match: + params["content"] = content_match.group(1) + + elif tool_name == "git_commit": + msg = re.search(r"commit(?:\s+with)?\s+(?:message\s+)?[\"']?(.+)[\"']?", query, re.IGNORECASE) + if msg: + params["message"] = msg.group(1).strip() + + elif tool_name == "web_search": + # Extract search query + patterns = [ + r"search\s+(?:the\s+)?web\s+for\s+(.+)", + r"google\s+(.+)", + r"look\s+up\s+(.+)", + r"latest\s+ai\s+news", + r"what('s|\s+is)\s+new\s+in\s+ai", + ] + for pattern in patterns: + match = re.search(pattern, query, re.IGNORECASE) + if match: + # Only extract group(1) if it exists + if match.groups(): + params["query"] = match.group(1).strip() + else: + # For patterns without capture groups, use full match + params["query"] = match.group(0).strip() + break + + elif tool_name in ("grep", "search"): + # Extract pattern to search for - capture full phrase + # Strategy: split by " in " and take second part as path + parts = query.split(' in ') + if len(parts) >= 2: + # Last part is the path + path_part = ' in '.join(parts[1:]) + # Clean up path + if path_part.strip() in ['project', 'this project']: + path_part = '/Users/walidsobhi/stack-2.9/src' + elif path_part.strip() == 'src': + path_part = '/Users/walidsobhi/stack-2.9/src' + elif path_part.startswith('~') or path_part.startswith('/') or path_part.startswith('./'): + pass # Keep as-is + else: + path_part = '/Users/walidsobhi/stack-2.9/' + path_part.strip() + params["path"] = path_part + # First part is the pattern (remove grep/for/search) + pattern_part = parts[0] + for prefix in ['grep for', 'search for', 'find for', 'grep', 'search', 'find']: + if pattern_part.strip().lower().startswith(prefix): + pattern_part = pattern_part.strip()[len(prefix):].strip() + break + params["pattern"] = pattern_part + else: + # Default path is workspace root + params["path"] = "/Users/walidsobhi/stack-2.9/src" + + return params + + +class ResponseGenerator: + """Generates natural language responses.""" + + GREETING_VARIATIONS = [ + "Sure! I can help with that.", + "Got it! Let me assist with that.", + "No problem! Here's what I found:", + "Alright! Here you go:", + "Sure thing! Let me show you:", + ] + + HELP_RESPONSES = [ + "I support these operations:", + "Here are some things I can do:", + "Here's my toolkit:", + "I can help with the following:", + ] + + def __init__(self): + self.context_manager = create_context_manager() + self.last_intent = None + self.last_query = None + + def generate( + self, + tool_results: List[ToolCall], + intent: str, + context: Dict[str, Any] + ) -> str: + """Generate response from tool results.""" + import random + + # Track intent for conversation flow + previous_intent = self.last_intent + self.last_intent = intent + + if not tool_results: + # Handle queries that don't need tools + if intent == "question": + return ("I can help with reading/writing files, running commands, " + "git operations, web search, and more. " + "Try asking me something like 'read the file README.md' " + "or 'check git status'.") + elif intent == "general_help": + greeting = random.choice(self.HELP_RESPONSES) + return (f"{greeting}\n" + "- Read/write/edit files\n" + "- Run commands and code\n" + "- Git operations (status, commit, push, pull)\n" + "- Code search with grep\n" + "- Web search\n" + "- Manage tasks\n\n" + "Examples:\n" + "- 'read the file /path/to/file'\n" + "- 'check git status'\n" + "- 'grep for def main in ~/project/src'\n" + "- 'run the command ls'\n" + "- 'what is 2 + 2'") + elif intent == "general": + # Don't repeat the same greeting + if previous_intent == "general": + return "What would you like me to help you with?" + return "What can I help you with?" + return None + + responses = [] + greeting = random.choice(self.GREETING_VARIATIONS) if tool_results else None + + for call in tool_results: + if call.result is None: + responses.append(f"Hmm, {call.tool_name} didn't return anything.") + continue + + if call.result.get("success"): + result = call.result + + # Format based on tool + if call.tool_name == "read": + if "content" in result: + content = result["content"] + if len(content) > 500: + content = content[:500] + "..." + responses.append(f"Here's the content:\n```\n{content}\n```") + + elif call.tool_name == "search": + # Skip search tool if it has no matches - grep will show results + if "matches" in result and result["matches"]: + matches = result["matches"] + resp = f"Found {len(matches)} matches:\n" + for m in matches[:10]: + resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n" + responses.append(resp) + # else: skip - grep will show results + + elif call.tool_name == "grep": + if "matches" in result: + matches = result["matches"] + if matches: + resp = f"Found {len(matches)} matches:\n" + for m in matches[:10]: + resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n" + responses.append(resp) + else: + responses.append("Didn't find any matches for that.") + + elif call.tool_name in ["git_status", "git_log"]: + if "files" in result: + files = result["files"] + if files: + responses.append(f"Changed files ({len(files)}):\n" + "\n".join(f" - {f}" for f in files)) + else: + responses.append("No changes detected.") + elif "commits" in result: + commits = result["commits"] + if commits: + responses.append("Recent commits:\n" + "\n".join(f" - {c}" for c in commits[:5])) + + elif call.tool_name == "web_search": + if "results" in result: + results = result["results"] + resp = "Search results:\n" + for r in results[:5]: + resp += f"- {r.get('title', 'Untitled')}\n" + responses.append(resp) + + elif call.tool_name == "run": + stdout = result.get("stdout", "") + stderr = result.get("stderr", "") + if stdout: + responses.append(f"Output:\n```\n{stdout[:500]}\n```") + if stderr: + responses.append(f"Errors:\n```\n{stderr[:500]}\n```") + if not stdout and not stderr: + responses.append("Command executed successfully.") + + elif call.tool_name == "memory_recall": + if "matches" in result: + matches = result["matches"] + if matches: + responses.append(f"Found {len(matches)} memory entries.") + else: + responses.append("No matching memories found.") + + else: + # Generic success response + responses.append(f"{call.tool_name}: {json.dumps(result)[:200]}") + else: + error = call.result.get("error", "Unknown error") + responses.append(f"Error in {call.tool_name}: {error}") + + return "\n\n".join(responses) or "I processed your request but have no results to show." + + def generate_clarification(self, question: str) -> str: + """Generate clarification question.""" + return f"I need some clarification: {question}" + + +class SelfReflection: + """Self-reflection loop for improving responses.""" + + def __init__(self): + self.max_iterations = 3 + self.min_confidence = 0.7 + + def reflect( + self, + query: str, + tool_calls: List[ToolCall], + response: str + ) -> Dict[str, Any]: + """Reflect on the response and determine if improvement is needed.""" + # Check if any tool call failed + failed_calls = [c for c in tool_calls if not c.success] + + # Calculate confidence + success_rate = len(tool_calls) / max(len(tool_calls), 1) + confidence = success_rate + + needs_reflection = ( + len(failed_calls) > 0 or + confidence < self.min_confidence or + len(response) < 20 + ) + + return { + "needs_reflection": needs_reflection, + "confidence": confidence, + "failed_calls": len(failed_calls), + "response_length": len(response), + "suggestion": self._get_suggestion(failed_calls, confidence) if needs_reflection else None + } + + def _get_suggestion(self, failed_calls: List[ToolCall], confidence: float) -> str: + """Get improvement suggestion.""" + if not failed_calls: + return "Try providing more context in your query." + + return f"Failed tool calls: {', '.join(c.tool_name for c in failed_calls)}" + + +class StackAgent: + """ + Core agent that combines all components for intelligent assistance. + """ + + def __init__(self, workspace: Optional[str] = None): + self.query_understanding = QueryUnderstanding() + self.tool_selector = ToolSelector() + self.response_generator = ResponseGenerator() + self.self_reflection = SelfReflection() + self.context_manager = create_context_manager(workspace) + self.conversation_history: List[Dict[str, Any]] = [] + + def process(self, query: str, context: Optional[Dict] = None) -> AgentResponse: + """Process a user query.""" + context = context or {} + + # Step 1: Understand query + parsed = self.query_understanding.parse(query) + intent = parsed["intent"] + confidence = parsed["confidence"] + + # Step 2: Select tools (pass query in context for smart filtering) + selected_tools = self.tool_selector.select(intent, {"query": query, **context}) + tool_params = {} + + for tool_name in selected_tools: + tool_params[tool_name] = self.tool_selector.get_tool_parameters(tool_name, query, context) + + # Step 3: Execute tools + tool_calls = [] + for tool_name in selected_tools: + tool = get_tool(tool_name) + if tool is None: + continue + + params = tool_params.get(tool_name, {}) + try: + result = tool(**params) + call = ToolCall( + tool_name=tool_name, + arguments=params, + result=result, + success=result.get("success", False) if isinstance(result, dict) else True + ) + except Exception as e: + call = ToolCall( + tool_name=tool_name, + arguments=params, + error=str(e), + success=False + ) + + tool_calls.append(call) + + # Record in session + self.context_manager.session.add_tool_usage(tool_name, call.result) + + # Step 4: Generate response + response_content = self.response_generator.generate(tool_calls, intent, context) + + # Step 5: Self-reflect + reflection = self.self_reflection.reflect(query, tool_calls, response_content) + + # Step 6: Add to conversation history + self.conversation_history.append({ + "query": query, + "intent": intent, + "tool_calls": [c.tool_name for c in tool_calls], + "response": response_content, + "reflection": reflection, + "timestamp": datetime.now().isoformat() + }) + + return AgentResponse( + content=response_content, + tool_calls=tool_calls, + confidence=reflection.get("confidence", confidence), + needs_clarification=reflection.get("needs_reflection", False), + clarification_needed=reflection.get("suggestion") + ) + + def process_with_tools(self, query: str, forced_tools: List[str]) -> AgentResponse: + """Process query with explicitly specified tools.""" + tool_calls = [] + + for tool_name in forced_tools: + tool = get_tool(tool_name) + if tool is None: + continue + + try: + result = tool() + call = ToolCall( + tool_name=tool_name, + arguments={}, + result=result, + success=result.get("success", False) if isinstance(result, dict) else True + ) + except Exception as e: + call = ToolCall( + tool_name=tool_name, + arguments={}, + error=str(e), + success=False + ) + + tool_calls.append(call) + + response_content = self.response_generator.generate(tool_calls, "general", {}) + + return AgentResponse( + content=response_content, + tool_calls=tool_calls, + confidence=1.0 + ) + + def get_context(self) -> str: + """Get current context as string.""" + return self.context_manager.get_workspace_context() + + def get_schemas(self) -> List[Dict[str, Any]]: + """Get tool schemas for tool calling.""" + return get_tool_schemas() + + +def create_agent(workspace: Optional[str] = None) -> StackAgent: + """Factory function to create agent.""" + return StackAgent(workspace) + + +if __name__ == "__main__": + print("Stack 2.9 Agent Module") + agent = create_agent() + print(f"Agent initialized with {len(list_tools())} tools") + + # Test query + response = agent.process("list my tasks") + print(f"\nQuery: 'list my tasks'") + print(f"Response: {response.content[:200]}") diff --git a/archived-selective/cli-backup/cli.py b/archived-selective/cli-backup/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..9fd029e0d8176f1ecec1bbadc22cebb1a9a8d2c7 --- /dev/null +++ b/archived-selective/cli-backup/cli.py @@ -0,0 +1,562 @@ +#!/usr/bin/env python3 +""" +Stack 2.9 - CLI Entry Point +A complete CLI and agent interface showcasing Stack 2.9 capabilities. +""" + +import os +import sys +import cmd +import json +import argparse +import asyncio +import subprocess +from pathlib import Path +from typing import Any, Dict, List, Optional + +from .agent import create_agent, StackAgent +from .context import create_context_manager + + +# ============================================================================ +# UTILITIES +# ============================================================================ + +def print_banner(): + """Print the Stack 2.9 banner.""" + banner = r""" + ____ _ _ _ + | _ \ ___ _ __ __| |_ __ ___ (_)_ __ | | __ + | |_) / _ \ '_ \ / _` | '__/ _ \| | '_ \ | |/ / + | _ < __/ | | | (_| | | | (_) | | | | | | < + |_| \_\___|_| |_|\__,_|_| \___/|_|_| |_| |_|\_\ + + Stack 2.9 CLI & Agent Interface + """ + print(banner) + + +def print_colored(text: str, color: str = "white", bold: bool = False): + """Print colored text.""" + colors = { + "red": "\033[91m", + "green": "\033[92m", + "yellow": "\033[93m", + "blue": "\033[94m", + "magenta": "\033[95m", + "cyan": "\033[96m", + "white": "\033[97m", + "reset": "\033[0m" + } + + if bold: + text = f"\033[1m{text}\033[0m" + + sys.stdout.write(colors.get(color, "white")) + print(text) + sys.stdout.write(colors["reset"]) + + +def format_output(data: Any, format: str = "text") -> str: + """Format output for display.""" + if format == "json": + return json.dumps(data, indent=2) + elif isinstance(data, dict): + return "\n".join(f" {k}: {v}" for k, v in data.items()) + elif isinstance(data, list): + return "\n".join(f" - {item}" for item in data) + else: + return str(data) + + +# ============================================================================ +# MODE 1: INTERACTIVE CHAT +# ============================================================================ + +class ChatMode(cmd.Cmd): + """Interactive chat mode with the agent.""" + + intro = """ + Welcome to Stack 2.9 Interactive Chat! + Type your queries or use commands: + /tools - List available tools + /schema - Show tool schemas + /context - Show current context + /history - Show conversation history + /clear - Clear chat history + /exit - Exit chat mode + + Just type your question or task to get started! + """ + prompt = "\n[Stack]> " + + def __init__(self, agent: StackAgent): + super().__init__() + self.agent = agent + self.history: List[Dict[str, Any]] = [] + self.show_tool_calls = False + self.output_format = "text" + self.voice_input = False + + def default(self, line: str): + """Handle user input as a query to the agent.""" + if line.startswith('/'): + return + + print_colored("\nThinking...", "cyan") + + try: + response = self.agent.process(line) + self.history.append({ + "query": line, + "response": response.content, + "tool_calls": [tc.tool_name for tc in response.tool_calls], + "timestamp": "now" + }) + + # Display response + print("\n" + "="*50) + print_colored("Response:", "green", bold=True) + print(response.content) + + # Show tool usage if enabled + if self.show_tool_calls and response.tool_calls: + print("\n" + "-"*50) + print_colored("Tools Used:", "yellow") + for tc in response.tool_calls: + status = "✓" if tc.success else "✗" + print(f" {status} {tc.tool_name}") + if not tc.success and tc.error: + print(f" Error: {tc.error}") + + if response.needs_clarification: + print_colored(f"\nNote: {response.clarification_needed}", "yellow") + + except KeyboardInterrupt: + print_colored("\nInterrupted.", "red") + except Exception as e: + print_colored(f"\nError: {e}", "red") + + def do_tools(self, arg): + """List all available tools.""" + tools = self.agent.context_manager.session.tools_used + if tools: + print(f"\nUsed {len(tools)} tools this session:") + for tool in set(tools): + print(f" - {tool}") + else: + print("\nNo tools used yet this session.") + + def do_schema(self, arg): + """Show tool schemas for tool calling.""" + schemas = self.agent.get_schemas() + print(f"\nTool Schemas ({len(schemas)} tools):") + for schema in schemas[:10]: + print(f"\n {schema['name']}: {schema['description']}") + print(f"\n ... and {len(schemas) - 10} more") + + def do_context(self, arg): + """Show current context.""" + context = self.agent.get_context() + print("\n" + context) + + def do_history(self, arg): + """Show conversation history.""" + print(f"\nChat History ({len(self.history)} messages):") + for i, entry in enumerate(self.history[-10:], 1): + print(f"\n{i}. Query: {entry['query'][:50]}...") + print(f" Tools: {', '.join(entry['tool_calls'])}") + + def do_clear(self, arg): + """Clear chat history.""" + self.history.clear() + self.agent.context_manager.session.messages.clear() + print_colored("Chat history cleared.", "green") + + def do_voice(self, arg): + """Toggle voice input.""" + self.voice_input = not self.voice_input + status = "enabled" if self.voice_input else "disabled" + print_colored(f"Voice input {status} (note: requires additional setup)", "cyan") + + def do_exit(self, arg): + """Exit chat mode.""" + print_colored("Goodbye!", "green") + return True + + def do_EOF(self, arg): + """Handle Ctrl+D.""" + print() + return self.do_exit(arg) + + def postcmd(self, stop: bool, line: str) -> bool: + """Save session after each command.""" + return stop + + def run(self): + """Run the chat loop.""" + print_banner() + print(self.intro) + + try: + self.cmdloop() + except KeyboardInterrupt: + print("\nExiting...") + + +# ============================================================================ +# MODE 2: COMMAND EXECUTION +# ============================================================================ + +class CommandMode: + """Execute single commands.""" + + def __init__(self, agent: StackAgent): + self.agent = agent + self.context_manager = create_context_manager() + + def execute( + self, + query: str, + output_file: Optional[str] = None, + output_format: str = "text" + ) -> str: + """Execute a query and return result.""" + print(f"Executing: {query}") + + response = self.agent.process(query) + + result = format_output(response.content, output_format) + + # Save to file if requested + if output_file: + with open(output_file, 'w') as f: + f.write(result) + print(f"Output saved to: {output_file}") + + return result + + def execute_tools( + self, + tools: List[str], + output_file: Optional[str] = None + ) -> str: + """Execute specific tools directly.""" + print(f"Executing tools: {', '.join(tools)}") + + response = self.agent.process_with_tools("", tools) + + if output_file: + with open(output_file, 'w') as f: + f.write(response.content) + print(f"Output saved to: {output_file}") + + return response.content + + +# ============================================================================ +# MODE 3: VOICE INTERFACE (PLACEHOLDER) +# ============================================================================ + +class VoiceInterface: + """Voice input/output interface (requires additional setup).""" + + def __init__(self): + self.available = self._check_availability() + + def _check_availability(self) -> bool: + """Check if voice dependencies are available.""" + try: + import speech_recognition as sr + import pyttsx3 + return True + except ImportError: + return False + + def listen(self) -> Optional[str]: + """Listen for voice input.""" + if not self.available: + print("Voice recognition not available. Install with: pip install SpeechRecognition pyttsx3 pyaudio") + return None + + try: + import speech_recognition as sr + + r = sr.Recognizer() + with sr.Microphone() as source: + print("Listening... (speak now)") + audio = r.listen(source, timeout=5) + + text = r.recognize_google(audio) + print(f"Heard: {text}") + return text + except Exception as e: + print(f"Voice error: {e}") + return None + + def speak(self, text: str): + """Speak text output.""" + if not self.available: + print(f"(TTS not available): {text}") + return + + try: + import pyttsx3 + + engine = pyttsx3.init() + engine.say(text) + engine.runAndWait() + except Exception as e: + print(f"TTS error: {e}") + + +# ============================================================================ +# MAIN CLI +# ============================================================================ + +class StackCLI: + """Main CLI entry point.""" + + def __init__(self): + self.agent = create_agent() + self.chat_mode = ChatMode(self.agent) + self.command_mode = CommandMode(self.agent) + self.voice = VoiceInterface() + + def run_interactive(self): + """Run interactive chat mode.""" + self.chat_mode.run() + + def run_command(self, query: str, out_file: Optional[str] = None, fmt: str = "text"): + """Run a single command.""" + result = self.command_mode.execute(query, out_file, fmt) + print(result) + return result + + def run_tools(self, tools: List[str], out_file: Optional[str] = None): + """Run specific tools.""" + result = self.command_mode.execute_tools(tools, out_file) + print(result) + return result + + def run_eval(self, benchmark: str, provider: str = 'ollama', model: str = None): + """Run evaluation benchmarks.""" + print_colored(f"\n=== Running {benchmark} benchmark ===", "blue") + + import sys + from pathlib import Path + eval_dir = Path(__file__).parent.parent / "stack-2.9-eval" + if eval_dir.exists(): + sys.path.insert(0, str(eval_dir)) + + try: + if benchmark == 'mbpp': + from benchmarks.mbpp import MBPP + b = MBPP(model_provider=provider, model_name=model) + elif benchmark == 'human_eval': + from benchmarks.human_eval import HumanEval + b = HumanEval(model_provider=provider, model_name=model) + elif benchmark == 'gsm8k': + from benchmarks.gsm8k import GSM8K + b = GSM8K(model_provider=provider, model_name=model) + elif benchmark == 'all': + from benchmarks.mbpp import MBPP + from benchmarks.human_eval import HumanEval + from benchmarks.gsm8k import GSM8K + for name, Benchmark in [('MBPP', MBPP), ('HumanEval', HumanEval), ('GSM8K', GSM8K)]: + print_colored(f"\n--- {name} ---", "yellow") + b = Benchmark(model_provider=provider, model_name=model) + results = b.evaluate() + print(f" Accuracy: {results['accuracy']*100:.1f}%") + return + + results = b.evaluate() + print_colored(f"\nResults:", "green") + print(f" Accuracy: {results['accuracy']*100:.1f}%") + print(f" Passed: {results['pass_at_1']}/{results['total_cases']}") + print(f" Model: {results['model']}") + except Exception as e: + print_colored(f"Error: {e}", "red") + + def run_patterns(self, action: str): + """Manage learned patterns.""" + print_colored(f"\n=== Pattern Management ===", "blue") + + import sys + from pathlib import Path + train_dir = Path(__file__).parent.parent / "stack-2.9-training" + if train_dir.exists(): + sys.path.insert(0, str(train_dir)) + + try: + from pattern_miner import PatternMiner + miner = PatternMiner() + + if action == 'list': + patterns = miner.get_relevant_patterns(limit=20) + print_colored(f"\nStored Patterns:", "yellow") + for p in patterns: + print(f" [{p.pattern_type}] {p.code_snippet[:50]}...") + elif action == 'stats': + stats = miner.get_statistics() + print_colored(f"\nStatistics:", "yellow") + print(f" Total Feedback: {stats['total_feedback']}") + print(f" Success Rate: {stats['success_rate']:.1%}") + print(f" Total Patterns: {stats['total_patterns']}") + except Exception as e: + print_colored(f"Error: {e}", "red") + + def run_voice(self): + """Run voice mode loop.""" + if not self.voice.available: + print_colored("Voice interface not available.", "red") + return + + print_banner() + print("Voice Mode - Say 'exit' to quit") + + try: + while True: + text = self.voice.listen() + if not text: + continue + + if "exit" in text.lower(): + print("Exiting...") + break + + # Process with agent + response = self.agent.process(text) + print(f"\nResponse: {response.content[:200]}") + + # Speak response + self.voice.speak(response.content[:200]) + + except KeyboardInterrupt: + print("\nExiting...") + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser( + description="Stack 2.9 CLI and Agent Interface", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s # Interactive chat mode + %(prog)s -c "read README.md" # Execute single command + %(prog)s -t read write # Execute specific tools + %(prog)s -v # Voice mode + """ + ) + + parser.add_argument( + '-c', '--command', + help="Execute a single query/command" + ) + + parser.add_argument( + '-t', '--tools', + nargs='+', + help="Execute specific tools" + ) + + parser.add_argument( + '-o', '--output', + help="Output file for results" + ) + + parser.add_argument( + '-f', '--format', + choices=['text', 'json'], + default='text', + help="Output format" + ) + + parser.add_argument( + '-v', '--voice', + action='store_true', + help="Enable voice mode" + ) + + parser.add_argument( + '-w', '--workspace', + default="/Users/walidsobhi/.openclaw/workspace", + help="Workspace path" + ) + + # Evaluation options + parser.add_argument( + '-e', '--eval', + choices=['mbpp', 'human_eval', 'gsm8k', 'all'], + help="Run evaluation benchmark" + ) + + parser.add_argument( + '--eval-provider', + default='ollama', + choices=['ollama', 'openai', 'anthropic', 'together'], + help="Model provider for evaluation" + ) + + parser.add_argument( + '--eval-model', + type=str, + help="Model name for evaluation" + ) + + # Pattern management + parser.add_argument( + '--patterns', + choices=['list', 'stats', 'clear'], + help="Manage learned patterns" + ) + + # Training + parser.add_argument( + '--train', + action='store_true', + help="Run LoRA training" + ) + + args = parser.parse_args() + + try: + # Create CLI with custom workspace if provided + cli = StackCLI() + + # Handle evaluation + if args.eval: + cli.run_eval(args.eval, args.eval_provider, args.eval_model) + return + + # Handle pattern management + if args.patterns: + cli.run_patterns(args.patterns) + return + + # Handle training + if args.train: + cli.run_train() + return + + if args.voice: + cli.run_voice() + elif args.tools: + cli.run_tools(args.tools, args.output) + elif args.command: + exit(0 if cli.run_command(args.command, args.output, args.format) else 1) + else: + # Interactive mode + cli.run_interactive() + + except KeyboardInterrupt: + print("\nGoodbye!") + sys.exit(0) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/archived-selective/cli-backup/context.py b/archived-selective/cli-backup/context.py new file mode 100644 index 0000000000000000000000000000000000000000..b8f50dfc5a3b7dcb3be7e86f1de5ac08ce2bd47a --- /dev/null +++ b/archived-selective/cli-backup/context.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +""" +Stack 2.9 - Context Management Module +Handles project awareness, session memory, and long-term memory integration. +""" + +import os +import json +import re +from pathlib import Path +from typing import Any, Dict, List, Optional, Set +from datetime import datetime, timedelta +from dataclasses import dataclass, field +from collections import defaultdict + + +@dataclass +class ProjectContext: + """Represents a project's context.""" + name: str + path: str + language: Optional[str] = None + framework: Optional[str] = None + files: List[str] = field(default_factory=list) + dirs: List[str] = field(default_factory=list) + has_git: bool = False + dependencies: List[str] = field(default_factory=list) + entry_points: List[str] = field(default_factory=list) + metadata: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class SessionMemory: + """Represents the current session's memory.""" + messages: List[Dict[str, Any]] = field(default_factory=list) + tools_used: List[str] = field(default_factory=list) + files_touched: List[str] = field(default_factory=list) + commands_run: List[str] = field(default_factory=list) + created_at: datetime = field(default_factory=datetime.now) + last_updated: datetime = field(default_factory=datetime.now) + + def add_message(self, role: str, content: str, metadata: Optional[Dict] = None): + """Add a message to session memory.""" + self.messages.append({ + "role": role, + "content": content, + "timestamp": datetime.now().isoformat(), + "metadata": metadata or {} + }) + self.last_updated = datetime.now() + + def add_tool_usage(self, tool_name: str, result: Any): + """Record tool usage.""" + self.tools_used.append({ + "tool": tool_name, + "timestamp": datetime.now().isoformat(), + "success": result.get("success", False) if isinstance(result, dict) else True + }) + self.last_updated = datetime.now() + + def add_file_touched(self, file_path: str, action: str): + """Record file access.""" + self.files_touched.append({ + "path": file_path, + "action": action, + "timestamp": datetime.now().isoformat() + }) + self.last_updated = datetime.now() + + def add_command(self, command: str, result: Optional[Dict] = None): + """Record command execution.""" + self.commands_run.append({ + "command": command, + "result": result, + "timestamp": datetime.now().isoformat() + }) + self.last_updated = datetime.now() + + def get_summary(self) -> Dict[str, Any]: + """Get session summary.""" + return { + "messages_count": len(self.messages), + "tools_used_count": len(self.tools_used), + "files_touched_count": len(self.files_touched), + "commands_run_count": len(self.commands_run), + "duration_minutes": (datetime.now() - self.created_at).total_seconds() / 60, + "created_at": self.created_at.isoformat(), + "last_updated": self.last_updated.isoformat() + } + + +class ContextManager: + """Manages context across projects, sessions, and long-term memory.""" + + def __init__(self, workspace_path: str = "/Users/walidsobhi/.openclaw/workspace"): + self.workspace = Path(workspace_path) + self.session = SessionMemory() + self.projects: Dict[str, ProjectContext] = {} + self.current_project: Optional[ProjectContext] = None + self._load_context() + + def _load_context(self): + """Load existing context files.""" + # Load workspace context files + context_files = { + "AGENTS.md": "agents", + "SOUL.md": "soul", + "TOOLS.md": "tools", + "USER.md": "user", + "MEMORY.md": "memory" + } + + self.context = {} + for filename, key in context_files.items(): + file_path = self.workspace / filename + if file_path.exists(): + self.context[key] = file_path.read_text(encoding='utf-8') + + # Scan for projects + self._scan_projects() + + def _scan_projects(self): + """Scan workspace for projects.""" + for item in self.workspace.iterdir(): + if item.is_dir() and not item.name.startswith('.'): + # Check if it's a project + if (item / "pyproject.toml").exists() or (item / "package.json").exists(): + self.projects[item.name] = ProjectContext( + name=item.name, + path=str(item) + ) + + def load_project(self, project_name: str) -> Optional[ProjectContext]: + """Load a specific project.""" + project_path = self.workspace / project_name + + if not project_path.exists(): + return None + + # Create project context + ctx = ProjectContext( + name=project_name, + path=str(project_path) + ) + + # Detect language/framework + if (project_path / "pyproject.toml").exists(): + ctx.language = "python" + try: + content = (project_path / "pyproject.toml").read_text() + if "fastapi" in content: + ctx.framework = "fastapi" + elif "django" in content: + ctx.framework = "django" + elif "flask" in content: + ctx.framework = "flask" + except: + pass + + if (project_path / "package.json").exists(): + ctx.language = "javascript" + try: + content = json.loads((project_path / "package.json").read_text()) + ctx.dependencies = list(content.get("dependencies", {}).keys()) + if "next" in content.get("dependencies", {}): + ctx.framework = "next" + elif "react" in content.get("dependencies", {}): + ctx.framework = "react" + except: + pass + + # Check for git + ctx.has_git = (project_path / ".git").exists() + + # Scan files + try: + for item in project_path.rglob("*"): + if len(ctx.files) > 100: + break + rel = item.relative_to(project_path) + if item.is_file(): + ctx.files.append(str(rel)) + elif item.is_dir() and not item.name.startswith('.'): + ctx.dirs.append(str(rel)) + except: + pass + + # Find entry points + entry_patterns = ["main.py", "app.py", "index.js", "main.js", "server.py"] + for pattern in entry_patterns: + for f in ctx.files: + if f.endswith(pattern): + ctx.entry_points.append(f) + + self.projects[project_name] = ctx + self.current_project = ctx + return ctx + + def get_context_summary(self) -> Dict[str, Any]: + """Get context summary.""" + return { + "workspace": str(self.workspace), + "projects": list(self.projects.keys()), + "current_project": self.current_project.name if self.current_project else None, + "session": self.session.get_summary(), + "has_agents": "agents" in self.context, + "has_soul": "soul" in self.context, + "has_tools": "tools" in self.context, + "has_memory": "memory" in self.context + } + + def get_workspace_context(self) -> str: + """Get formatted workspace context.""" + lines = ["# Workspace Context"] + lines.append(f"\n## Projects ({len(self.projects)})") + + for name, proj in self.projects.items(): + lines.append(f"- **{name}** ({proj.language or 'unknown'})") + if proj.framework: + lines.append(f" - Framework: {proj.framework}") + lines.append(f" - Path: {proj.path}") + if proj.has_git: + lines.append(" - Git: ✓") + + if self.current_project: + lines.append(f"\n## Current Project: {self.current_project.name}") + lines.append(f"- Files: {len(self.current_project.files)}") + lines.append(f"- Dirs: {len(self.current_project.dirs)}") + if self.current_project.entry_points: + lines.append(f"- Entry: {self.current_project.entry_points[0]}") + + lines.append(f"\n## Session") + summary = self.session.get_summary() + lines.append(f"- Messages: {summary['messages_count']}") + lines.append(f"- Tools used: {summary['tools_used_count']}") + lines.append(f"- Files touched: {summary['files_touched_count']}") + + return "\n".join(lines) + + def search_memory(self, query: str, max_results: int = 5) -> List[Dict[str, Any]]: + """Search long-term memory.""" + results = [] + + # Search MEMORY.md + memory_file = self.workspace / "MEMORY.md" + if memory_file.exists(): + content = memory_file.read_text() + if query.lower() in content.lower(): + results.append({ + "file": str(memory_file), + "type": "memory", + "content": content[:500] + }) + + # Search memory folder + memory_dir = self.workspace / "memory" + if memory_dir.exists(): + for f in memory_dir.rglob("*.md"): + try: + content = f.read_text() + if query.lower() in content.lower(): + results.append({ + "file": str(f), + "type": "daily", + "content": content[:500] + }) + except: + continue + + return results[:max_results] + + def save_to_memory(self, key: str, value: str): + """Save to long-term memory.""" + memory_file = self.workspace / "MEMORY.md" + + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") + entry = f"\n### {key}\n_{timestamp}_\n{value}\n" + + with open(memory_file, "a") as f: + f.write(entry) + + def get_recent_context(self, days: int = 7) -> List[Dict[str, Any]]: + """Get recent context from memory.""" + results = [] + + memory_dir = self.workspace / "memory" + if memory_dir.exists(): + # Get files from last N days + cutoff = datetime.now() - timedelta(days=days) + + for f in sorted(memory_dir.glob("*.md"), key=lambda x: x.stat().st_mtime, reverse=True): + try: + mtime = datetime.fromtimestamp(f.stat().st_mtime) + if mtime > cutoff: + content = f.read_text() + results.append({ + "file": str(f), + "date": mtime.isoformat(), + "content": content[:1000] + }) + except: + continue + + return results + + +class ProjectAware: + """Mixin for project-aware functionality.""" + + def __init__(self): + self.context_manager = ContextManager() + + def detect_project(self, path: str) -> Optional[str]: + """Detect project from path.""" + p = Path(path).resolve() + + # Walk up to find project root + while p != p.parent: + for name in ["pyproject.toml", "package.json", "Cargo.toml", "go.mod"]: + if (p / name).exists(): + return p.name + p = p.parent + + return None + + def get_project_context(self, project_name: str) -> Optional[ProjectContext]: + """Get project context.""" + return self.context_manager.load_project(project_name) + + def format_context_for_prompt(self) -> str: + """Format context for LLM prompt.""" + return self.context_manager.get_workspace_context() + + +def create_context_manager(workspace: Optional[str] = None) -> ContextManager: + """Factory function to create context manager.""" + return ContextManager(workspace or "/Users/walidsobhi/.openclaw/workspace") + + +if __name__ == "__main__": + print("Stack 2.9 Context Module") + cm = ContextManager() + print(cm.get_context_summary()) diff --git a/archived-selective/cli-backup/main.py b/archived-selective/cli-backup/main.py new file mode 100644 index 0000000000000000000000000000000000000000..c1a1df932b8902f888d096cda060933fb630d7d1 --- /dev/null +++ b/archived-selective/cli-backup/main.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +""" +Stack 2.9 CLI - Terminal User Interface +Main entry point for interacting with Stack 2.9 +""" + +import os +import sys +import argparse +from pathlib import Path +from typing import Optional + +# Add parent directories to path +sys.path.insert(0, str(Path(__file__).parent)) +sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "eval")) +sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "training")) + +from model_client import create_model_client, ChatMessage +from pattern_miner import PatternMiner +from data_quality import DataQualityAnalyzer + + +class Stack29CLI: + """Stack 2.9 Terminal User Interface""" + + def __init__(self, provider: str = None, model: str = None): + self.provider = provider or os.environ.get("MODEL_PROVIDER", "ollama") + self.model = model or os.environ.get("MODEL_NAME", "") + self.client = None + self.agent = None + self.miner = PatternMiner() + self.chat_history = [] + + # Colors + self.BLUE = '\033[94m' + self.GREEN = '\033[92m' + self.YELLOW = '\033[93m' + self.RED = '\033[91m' + self.END = '\033[0m' + self.BOLD = '\033[1m' + + def print_header(self): + """Print CLI header""" + print(f""" +{self.BLUE}╔═══════════════════════════════════════════════════════╗ +║ {self.BOLD}Stack 2.9 - Self-Evolving AI{self.END}{self.BLUE} ║ +║ {self.YELLOW}Your AI coding companion{self.END}{self.BLUE} ║ +╚═══════════════════════════════════════════════════════╝{self.END} +""") + + def print_menu(self): + """Print main menu""" + print(f""" +{self.BOLD}Main Menu:{self.END} + {self.GREEN}[1]{self.END} Chat with Stack 2.9 + {self.GREEN}[2]{self.END} Run Evaluation (Benchmarks) + {self.GREEN}[3]{self.END} Manage Patterns (Self-Evolution) + {self.GREEN}[4]{self.END} Train Model (LoRA Fine-tuning) + {self.GREEN}[5]{self.END} Settings + {self.GREEN}[0]{self.END} Exit + +""") + + def init_client(self): + """Initialize model client""" + try: + self.client = create_model_client(self.provider, self.model) + print(f"{self.GREEN}✓{self.END} Connected to {self.provider}: {self.client.get_model_name()}") + except Exception as e: + print(f"{self.RED}✗{self.END} Failed to connect: {e}") + print(f"{self.YELLOW}!{self.END} Make sure {self.provider} is running") + self.client = None + + def chat_mode(self): + """Interactive chat mode using agent with tool calling""" + if not self.client: + print(f"{self.RED}No model connected!{self.END}") + return + + print(f"\n{self.BLUE}=== Chat Mode ==={self.END}") + print("Type 'exit' to return to menu, 'clear' to clear history\n") + + # Initialize agent if not done + if not hasattr(self, 'agent') or self.agent is None: + from cli.agent import StackAgent + self.agent = StackAgent(workspace='/Users/walidsobhi/stack-2.9') + print(f"{self.GREEN}✓{self.END} Agent initialized") + + while True: + try: + user_input = input(f"{self.GREEN}You:{self.END} ").strip() + + if not user_input: + continue + + if user_input.lower() in ['exit', 'quit', 'q']: + break + + if user_input.lower() == 'clear': + print("Chat cleared.\n") + continue + + # Process through agent (handles tool calling) + print(f"{self.BLUE}Stack 2.9:{self.END} ", end="", flush=True) + + try: + response = self.agent.process(user_input) + print(response.content) + + except Exception as e: + print(f"{self.RED}Error: {e}{self.END}") + + except Exception as e: + print(f"{self.RED}Error: {e}{self.END}") + + print() + + except KeyboardInterrupt: + print("\n") + break + + def eval_mode(self): + """Run evaluation benchmarks""" + print(f"\n{self.BLUE}=== Evaluation ==={self.END}") + print(f"{self.GREEN}[1]{self.END} MBPP (Code Generation)") + print(f"{self.GREEN}[2]{self.END} HumanEval (Python)") + print(f"{self.GREEN}[3]{self.END} GSM8K (Math)") + print(f"{self.GREEN}[4]{self.END} Run All") + print(f"{self.GREEN}[0]{self.END} Back") + + choice = input("\nSelect: ").strip() + + if choice == '0': + return + + benchmarks = { + '1': ('mbpp', 'MBPP'), + '2': ('human_eval', 'HumanEval'), + '3': ('gsm8k', 'GSM8K'), + '4': ('all', 'All') + } + + if choice not in benchmarks: + print(f"{self.RED}Invalid choice{self.END}") + return + + bench_name, bench_label = benchmarks[choice] + + print(f"\n{self.YELLOW}Running {bench_label} benchmark...{self.END}") + + try: + if bench_name == 'all': + from benchmarks.mbpp import MBPP + from benchmarks.human_eval import HumanEval + from benchmarks.gsm8k import GSM8K + + for name, Benchmark in [('MBPP', MBPP), ('HumanEval', HumanEval), ('GSM8K', GSM8K)]: + print(f"\n--- {name} ---") + b = Benchmark(model_provider=self.provider, model_name=self.model) + results = b.evaluate() + print(f" Accuracy: {results['accuracy']*100:.1f}%") + else: + module = __import__(f'benchmarks.{bench_name}', fromlist=['MBPP', 'HumanEval', 'GSM8K']) + Benchmark = getattr(module, bench_name.upper() if bench_name != 'mbpp' else 'MBPP') + b = Benchmark(model_provider=self.provider, model_name=self.model) + results = b.evaluate() + print(f"\n{self.GREEN}Results:{self.END}") + print(f" Accuracy: {results['accuracy']*100:.1f}%") + print(f" Passed: {results['pass_at_1']}/{results['total_cases']}") + + except Exception as e: + print(f"{self.RED}Error: {e}{self.END}") + + input("\nPress Enter to continue...") + + def pattern_mode(self): + """Manage patterns for self-evolution""" + print(f"\n{self.BLUE}=== Pattern Manager ==={self.END}") + print(f"{self.GREEN}[1]{self.END} View Patterns") + print(f"{self.GREEN}[2]{self.END} View Statistics") + print(f"{self.GREEN}[3]{self.END} Generate Synthetic Data") + print(f"{self.GREEN}[4]{self.END} Clear Patterns") + print(f"{self.GREEN}[0]{self.END} Back") + + choice = input("\nSelect: ").strip() + + if choice == '0': + return + + if choice == '1': + patterns = self.miner.get_relevant_patterns(limit=20) + print(f"\n{self.YELLOW}Stored Patterns ({len(patterns)}):{self.END}") + for p in patterns: + print(f" [{p.pattern_type}] {p.code_snippet[:50]}... (rate: {p.success_rate:.0%})") + + elif choice == '2': + stats = self.miner.get_statistics() + print(f"\n{self.YELLOW}Statistics:{self.END}") + print(f" Total Feedback: {stats['total_feedback']}") + print(f" Success Rate: {stats['success_rate']:.1%}") + print(f" Total Patterns: {stats['total_patterns']}") + print(f" By Type: {stats['patterns_by_type']}") + + elif choice == '3': + try: + count = int(input("Number of examples: ").strip()) + self.miner.store_feedback( + problem_type="synthetic", + solution="# Synthetic pattern", + success=True + ) + print(f"{self.GREEN}✓{self.END} Generated {count} synthetic patterns") + except ValueError: + print(f"{self.RED}Invalid number{self.END}") + + elif choice == '4': + confirm = input("Clear all patterns? (y/n): ").strip().lower() + if confirm == 'y': + # Note: This would need a clear method in PatternMiner + print(f"{self.YELLOW}Feature not implemented{self.END}") + + input("\nPress Enter to continue...") + + def train_mode(self): + """Train model with LoRA""" + print(f"\n{self.BLUE}=== Training ==={self.END}") + print(f"{self.YELLOW}Note: Requires GPU and training data{self.END}") + print(f"\n{self.GREEN}[1]{self.END} Prepare Data") + print(f"{self.GREEN}[2]{self.END} Train LoRA") + print(f"{self.GREEN}[3]{self.END} Merge Adapter") + print(f"{self.GREEN}[0]{self.END} Back") + + choice = input("\nSelect: ").strip() + + if choice == '0': + return + + if choice == '1': + print(f"\n{self.YELLOW}Preparing training data...{self.END}") + try: + from prepare_data import prepare_data + result = prepare_data() + print(f"{self.GREEN}✓{self.END} Prepared {result['train_samples']} training samples") + except Exception as e: + print(f"{self.RED}Error: {e}{self.END}") + + elif choice == '2': + print(f"\n{YELLOW}Training LoRA...{self.END}") + print(f"{self.YELLOW}Note: This requires significant GPU resources{self.END}") + confirm = input("Continue? (y/n): ").strip().lower() + if confirm == 'y': + try: + from train_lora import train_lora + trainer = train_lora() + print(f"{self.GREEN}✓{self.END} Training complete") + except Exception as e: + print(f"{self.RED}Error: {e}{self.END}") + + input("\nPress Enter to continue...") + + def settings_mode(self): + """Configure settings""" + print(f"\n{self.BLUE}=== Settings ==={self.END}") + print(f"Provider: {self.provider}") + print(f"Model: {self.model}") + print(f"\n{self.GREEN}[1]{self.END} Change Provider") + print(f"{self.GREEN}[2]{self.END} Change Model") + print(f"{self.GREEN}[0]{self.END} Back") + + choice = input("\nSelect: ").strip() + + if choice == '1': + print("Providers: ollama, openai, anthropic") + new_provider = input("Provider: ").strip() + if new_provider in ['ollama', 'openai', 'anthropic']: + self.provider = new_provider + self.init_client() + + elif choice == '2': + new_model = input("Model name: ").strip() + if new_model: + self.model = new_model + self.init_client() + + def run(self): + """Run the CLI""" + self.print_header() + self.init_client() + + while True: + self.print_menu() + choice = input(f"{self.GREEN}Select>{self.END} ").strip() + + if choice == '0': + print(f"\n{self.BLUE}Thanks for using Stack 2.9!{self.END}\n") + break + + if choice == '1': + self.chat_mode() + elif choice == '2': + self.eval_mode() + elif choice == '3': + self.pattern_mode() + elif choice == '4': + self.train_mode() + elif choice == '5': + self.settings_mode() + else: + print(f"{self.RED}Invalid option{self.END}") + + +def main(): + parser = argparse.ArgumentParser(description="Stack 2.9 CLI") + parser.add_argument("--provider", "-p", choices=["ollama", "openai", "anthropic"], + help="Model provider") + parser.add_argument("--model", "-m", type=str, help="Model name") + parser.add_argument("--chat", "-c", action="store_true", help="Start in chat mode") + parser.add_argument("--eval", "-e", choices=["mbpp", "human_eval", "gsm8k", "all"], + help="Run evaluation") + + args = parser.parse_args() + + cli = Stack29CLI(provider=args.provider, model=args.model) + + if args.chat: + cli.init_client() + cli.chat_mode() + elif args.eval: + cli.init_client() + cli.eval_mode() + else: + cli.run() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/archived-selective/cli-backup/pyproject.toml b/archived-selective/cli-backup/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..743afe0572e1fd7b3ed90a31ebe248877687e61b --- /dev/null +++ b/archived-selective/cli-backup/pyproject.toml @@ -0,0 +1,70 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "stack-cli" +version = "2.9.0" +description = "Stack 2.9 CLI and Agent Interface" +readme = "../STACK_CLI_README.md" +license = {text = "MIT"} +authors = [ + {name = "Walid Sobhi", email = "walid@example.com"} +] +keywords = ["cli", "agent", "ai", "automation", "development"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Tools", +] +requires-python = ">=3.8" +dependencies = [ + "openai>=1.0.0", + "openrouter>=1.0.0", + "anthropic>=0.8.0", + "aiohttp>=3.9.0", + "python-dotenv>=1.0.0", + "prompt-toolkit>=3.0.0", + "rich>=13.0.0", + "click>=8.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "ruff>=0.1.0", + "mypy>=1.0.0", +] +voice = [ + "SpeechRecognition>=3.10.0", + "pyttsx3>=2.90", +] + +[project.scripts] +stack = "stack_cli.cli:main" + +[tool.setuptools.packages.find] +where = ["."] +include = ["stack_cli*"] + +[tool.black] +line-length = 100 +target-version = ['py38', 'py39', 'py310', 'py311'] +include = '\.pyi?$' + +[tool.ruff] +line-length = 100 +target-version = "py38" + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false diff --git a/archived-selective/cli-backup/tools.py b/archived-selective/cli-backup/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..5c7f507e60573af755d2c89c3a1e2e560de717d4 --- /dev/null +++ b/archived-selective/cli-backup/tools.py @@ -0,0 +1,1308 @@ +#!/usr/bin/env python3 +""" +Stack 2.9 - Built-in Tools Module +38 powerful tools for file operations, git, code execution, web, memory, and planning. +""" + +import os +import re +import json +import subprocess +import shutil +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional, Union +from datetime import datetime, timedelta +import hashlib + + +# ============================================================================ +# FILE OPERATIONS TOOLS (Tools 1-8) +# ============================================================================ + +def tool_read_file(path: str, offset: int = 0, limit: int = -1) -> Dict[str, Any]: + """Read file contents with optional offset and limit.""" + try: + p = Path(path) + if not p.exists(): + return {"success": False, "error": f"File not found: {path}"} + + content = p.read_text(encoding='utf-8') + lines = content.split('\n') + + if limit > 0: + lines = lines[offset:offset + limit] + + return { + "success": True, + "content": '\n'.join(lines), + "total_lines": len(content.split('\n')), + "path": path + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_write_file(path: str, content: str, append: bool = False) -> Dict[str, Any]: + """Write content to file (create or overwrite).""" + try: + p = Path(path) + p.parent.mkdir(parents=True, exist_ok=True) + + if append: + p.write_text(content, encoding='utf-8') + else: + p.write_text(content, encoding='utf-8') + + return { + "success": True, + "path": path, + "lines_written": len(content.split('\n')) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_edit_file(path: str, old_text: str, new_text: str) -> Dict[str, Any]: + """Edit file using exact text replacement.""" + try: + p = Path(path) + if not p.exists(): + return {"success": False, "error": f"File not found: {path}"} + + content = p.read_text(encoding='utf-8') + if old_text not in content: + return {"success": False, "error": "Text to replace not found"} + + new_content = content.replace(old_text, new_text, 1) + p.write_text(new_content, encoding='utf-8') + + return { + "success": True, + "path": path, + "edits_made": 1 + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_search_files( + path: str, + pattern: str, + exclude: Optional[List[str]] = None +) -> Dict[str, Any]: + """Recursively search for files matching a pattern.""" + try: + # Expand ~ to home directory + base_path = Path(os.path.expanduser(path)) + if not base_path.exists(): + return {"success": False, "error": f"Path not found: {path}"} + + results = [] + exclude = exclude or [] + + for p in base_path.rglob(pattern): + # Check if any exclusion pattern matches + skip = False + for exc in exclude: + if exc in str(p): + skip = True + break + if not skip: + results.append(str(p)) + + return { + "success": True, + "matches": results, + "count": len(results) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_grep(path: str, pattern: str, context: int = 0) -> Dict[str, Any]: + """Search for pattern in file(s).""" + try: + # Expand ~ to home directory + base_path = Path(os.path.expanduser(path)) + results = [] + + if base_path.is_file(): + files = [base_path] + elif base_path.is_dir(): + files = list(base_path.rglob('*')) + files = [f for f in files if f.is_file()] + else: + return {"success": False, "error": f"Invalid path: {path}"} + + for f in files: + try: + content = f.read_text(encoding='utf-8', errors='ignore') + lines = content.split('\n') + + for i, line in enumerate(lines): + if re.search(pattern, line): + result = { + "file": str(f), + "line": i + 1, + "content": line.strip() + } + if context > 0: + start = max(0, i - context) + end = min(len(lines), i + context + 1) + result["context"] = lines[start:end] + results.append(result) + except: + continue + + return { + "success": True, + "matches": results, + "count": len(results) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_copy_file(source: str, destination: str) -> Dict[str, Any]: + """Copy file or directory.""" + try: + src = Path(source) + dst = Path(destination) + + if not src.exists(): + return {"success": False, "error": f"Source not found: {source}"} + + if src.is_dir(): + shutil.copytree(src, dst) + else: + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dst) + + return { + "success": True, + "source": source, + "destination": destination + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_move_file(source: str, destination: str) -> Dict[str, Any]: + """Move or rename file or directory.""" + try: + src = Path(source) + dst = Path(destination) + + if not src.exists(): + return {"success": False, "error": f"Source not found: {source}"} + + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.move(str(src), str(dst)) + + return { + "success": True, + "source": source, + "destination": destination + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_delete_file(path: str, force: bool = False) -> Dict[str, Any]: + """Delete file or directory (use trash for safe delete).""" + try: + p = Path(path) + + if not p.exists(): + return {"success": False, "error": f"Path not found: {path}"} + + # For safety, require force=True for destructive delete + if not force: + # Just report what would be deleted + return { + "success": True, + "would_delete": str(p), + "warning": "Set force=True to actually delete" + } + + if p.is_dir(): + shutil.rmtree(p) + else: + p.unlink() + + return { + "success": True, + "deleted": str(p) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# GIT OPERATIONS TOOLS (Tools 9-15) +# ============================================================================ + +def tool_git_status(repo_path: str = ".") -> Dict[str, Any]: + """Get git status.""" + try: + result = subprocess.run( + ["git", "-C", repo_path, "status", "--porcelain"], + capture_output=True, + text=True, + timeout=30 + ) + + files = [line[3:] for line in result.stdout.strip().split('\n') if line] + + return { + "success": True, + "files": files, + "count": len(files), + "repo": repo_path + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_commit(repo_path: str, message: str, files: Optional[List[str]] = None) -> Dict[str, Any]: + """Create a git commit.""" + try: + # Stage files if provided + if files: + for f in files: + subprocess.run( + ["git", "-C", repo_path, "add", f], + capture_output=True, + timeout=30 + ) + else: + subprocess.run( + ["git", "-C", repo_path, "add", "-A"], + capture_output=True, + timeout=30 + ) + + # Check if there are changes to commit + result = subprocess.run( + ["git", "-C", repo_path, "status", "--porcelain"], + capture_output=True, + text=True, + timeout=30 + ) + + if not result.stdout.strip(): + return {"success": True, "message": "No changes to commit"} + + # Commit + result = subprocess.run( + ["git", "-C", repo_path, "commit", "-m", message], + capture_output=True, + text=True, + timeout=30 + ) + + return { + "success": True, + "message": message, + "output": result.stdout + result.stderr + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_push(repo_path: str = ".", remote: str = "origin", branch: Optional[str] = None) -> Dict[str, Any]: + """Push to remote.""" + try: + cmd = ["git", "-C", repo_path, "push", remote] + if branch: + cmd.append(branch) + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=60 + ) + + return { + "success": True, + "remote": remote, + "branch": branch, + "output": result.stdout + result.stderr + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_pull(repo_path: str = ".", remote: str = "origin", branch: Optional[str] = None) -> Dict[str, Any]: + """Pull from remote.""" + try: + cmd = ["git", "-C", repo_path, "pull", remote] + if branch: + cmd.append(branch) + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=60 + ) + + return { + "success": True, + "remote": remote, + "branch": branch, + "output": result.stdout + result.stderr + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_branch(repo_path: str = ".", create: Optional[str] = None, delete: Optional[str] = None) -> Dict[str, Any]: + """List, create, or delete branches.""" + try: + if create: + result = subprocess.run( + ["git", "-C", repo_path, "checkout", "-b", create], + capture_output=True, + text=True, + timeout=30 + ) + return {"success": True, "created": create} + + if delete: + result = subprocess.run( + ["git", "-C", repo_path, "branch", "-D", delete], + capture_output=True, + text=True, + timeout=30 + ) + return {"success": True, "deleted": delete} + + # List branches + result = subprocess.run( + ["git", "-C", repo_path, "branch", "-a"], + capture_output=True, + text=True, + timeout=30 + ) + + branches = [b.strip().replace('* ', '') for b in result.stdout.strip().split('\n') if b] + + return { + "success": True, + "branches": branches, + "count": len(branches) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_log(repo_path: str = ".", limit: int = 10) -> Dict[str, Any]: + """Get git log.""" + try: + result = subprocess.run( + ["git", "-C", repo_path, "log", f"--max-count={limit}", "--oneline"], + capture_output=True, + text=True, + timeout=30 + ) + + commits = result.stdout.strip().split('\n') + + return { + "success": True, + "commits": commits, + "count": len([c for c in commits if c]) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_git_diff(repo_path: str = ".", file: Optional[str] = None, staged: bool = False) -> Dict[str, Any]: + """Get git diff.""" + try: + cmd = ["git", "-C", repo_path, "diff"] + if staged: + cmd.append("--staged") + if file: + cmd.append(file) + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=30 + ) + + return { + "success": True, + "diff": result.stdout, + "has_changes": bool(result.stdout.strip()) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# CODE EXECUTION TOOLS (Tools 16-22) +# ============================================================================ + +def tool_run_command( + command: str, + timeout: int = 60, + cwd: Optional[str] = None, + env: Optional[Dict[str, str]] = None +) -> Dict[str, Any]: + """Run shell command.""" + try: + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + timeout=timeout, + cwd=cwd, + env={**os.environ, **(env or {})} + ) + + return { + "success": result.returncode == 0, + "returncode": result.returncode, + "stdout": result.stdout, + "stderr": result.stderr, + "command": command + } + except subprocess.TimeoutExpired: + return {"success": False, "error": "Command timed out"} + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_run_tests(path: str = ".", pattern: str = "test*.py", verbose: bool = True) -> Dict[str, Any]: + """Run tests using pytest.""" + try: + cmd = ["pytest", path, "-k", pattern] + if verbose: + cmd.append("-v") + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=300, + cwd=path + ) + + return { + "success": result.returncode == 0, + "output": result.stdout, + "errors": result.stderr, + "returncode": result.returncode + } + except FileNotFoundError: + return {"success": False, "error": "pytest not found"} + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_lint_code(path: str = ".", linter: str = "ruff") -> Dict[str, Any]: + """Lint code.""" + try: + if linter == "ruff": + result = subprocess.run( + ["ruff", "check", path], + capture_output=True, + text=True, + timeout=120 + ) + elif linter == "pylint": + result = subprocess.run( + ["pylint", path], + capture_output=True, + text=True, + timeout=120 + ) + elif linter == "mypy": + result = subprocess.run( + ["mypy", path], + capture_output=True, + text=True, + timeout=120 + ) + else: + return {"success": False, "error": f"Unknown linter: {linter}"} + + return { + "success": result.returncode == 0, + "output": result.stdout, + "errors": result.stderr + } + except FileNotFoundError: + return {"success": False, "error": f"{linter} not found"} + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_format_code(path: str = ".", formatter: str = "ruff") -> Dict[str, Any]: + """Format code.""" + try: + if formatter == "ruff": + result = subprocess.run( + ["ruff", "format", path], + capture_output=True, + text=True, + timeout=120 + ) + elif formatter == "black": + result = subprocess.run( + ["black", path], + capture_output=True, + text=True, + timeout=120 + ) + else: + return {"success": False, "error": f"Unknown formatter: {formatter}"} + + return { + "success": result.returncode == 0, + "output": result.stdout, + "errors": result.stderr + } + except FileNotFoundError: + return {"success": False, "error": f"{formatter} not found"} + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_check_type(path: str = ".") -> Dict[str, Any]: + """Type check with mypy.""" + try: + result = subprocess.run( + ["mypy", path, "--ignore-missing-imports"], + capture_output=True, + text=True, + timeout=180 + ) + + return { + "success": result.returncode == 0, + "output": result.stdout, + "errors": result.stderr + } + except FileNotFoundError: + return {"success": False, "error": "mypy not found"} + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_start_server( + command: str, + port: int, + cwd: Optional[str] = None, + background: bool = False +) -> Dict[str, Any]: + """Start a development server.""" + try: + if background: + proc = subprocess.Popen( + command, + shell=True, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + return { + "success": True, + "pid": proc.pid, + "port": port, + "message": f"Server started on port {port}" + } + else: + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + cwd=cwd + ) + return { + "success": result.returncode == 0, + "output": result.stdout + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_install_dependencies(path: str = ".", package_manager: str = "pip") -> Dict[str, Any]: + """Install dependencies.""" + try: + if package_manager == "pip": + result = subprocess.run( + ["pip", "install", "-r", "requirements.txt"], + capture_output=True, + text=True, + timeout=300, + cwd=path + ) + elif package_manager == "poetry": + result = subprocess.run( + ["poetry", "install"], + capture_output=True, + text=True, + timeout=300, + cwd=path + ) + elif package_manager == "npm": + result = subprocess.run( + ["npm", "install"], + capture_output=True, + text=True, + timeout=300, + cwd=path + ) + else: + return {"success": False, "error": f"Unknown package manager: {package_manager}"} + + return { + "success": result.returncode == 0, + "output": result.stdout, + "errors": result.stderr + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# WEB TOOLS (Tools 23-27) +# ============================================================================ + +def tool_web_search( + query: str, + count: int = 5, + freshness: Optional[str] = None, + language: Optional[str] = None +) -> Dict[str, Any]: + """Search the web using DuckDuckGo.""" + try: + import urllib.request + import urllib.parse + import re + from html import unescape + + # DuckDuckGo Lite + encoded_query = urllib.parse.quote(query) + url = f"https://lite.duckduckgo.com/lite/?q={encoded_query}" + + req = urllib.request.Request(url, headers={ + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + }) + with urllib.request.urlopen(req, timeout=30) as response: + html = response.read().decode('utf-8') + + results = [] + # Find links - look for anchor tags with titles + all_links = re.findall(r']*href="(https?://[^"]+)"[^>]*>([^<]+)', html) + + for url, title in all_links[:count]: + title = unescape(title).strip() + if title and len(title) > 3: + results.append({"title": title, "url": url}) + + return { + "success": True, + "query": query, + "results": results[:count], + "count": len(results) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_web_fetch(url: str, max_chars: int = 10000) -> Dict[str, Any]: + """Fetch and extract content from URL.""" + try: + result = subprocess.run( + ["curl", "-s", url], + capture_output=True, + text=True, + timeout=30 + ) + + content = result.stdout[:max_chars] + + return { + "success": True, + "url": url, + "content": content, + "length": len(content) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_download_file(url: str, destination: str) -> Dict[str, Any]: + """Download file from URL.""" + try: + result = subprocess.run( + ["curl", "-L", "-o", destination, url], + capture_output=True, + text=True, + timeout=120 + ) + + size = Path(destination).stat().st_size if Path(destination).exists() else 0 + + return { + "success": result.returncode == 0, + "url": url, + "destination": destination, + "size": size + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_check_url(url: str) -> Dict[str, Any]: + """Check if URL is accessible.""" + try: + result = subprocess.run( + ["curl", "-I", "-s", "-o", "/dev/null", "-w", "%{http_code}", url], + capture_output=True, + text=True, + timeout=15 + ) + + code = result.stdout.strip() + + return { + "success": code in ["200", "301", "302"], + "url": url, + "status_code": code + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_screenshot(url: str, destination: str = "screenshot.png") -> Dict[str, Any]: + """Take screenshot of webpage.""" + try: + # Try playwright or puppeteer + result = subprocess.run( + ["npx", "puppeteer", url, "--output", destination], + capture_output=True, + text=True, + timeout=60 + ) + + if result.returncode != 0: + return {"success": False, "error": "Failed to take screenshot"} + + return { + "success": True, + "url": url, + "destination": destination + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# MEMORY TOOLS (Tools 28-32) +# ============================================================================ + +def tool_memory_recall(query: str, max_results: int = 5) -> Dict[str, Any]: + """Recall from memory (searches memory files).""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + + # Search in memory files + results = [] + + # Search MEMORY.md + memory_file = workspace / "MEMORY.md" + if memory_file.exists(): + content = memory_file.read_text() + if query.lower() in content.lower(): + results.append(str(memory_file)) + + # Search memory folder + memory_dir = workspace / "memory" + if memory_dir.exists(): + for f in memory_dir.rglob("*.md"): + try: + content = f.read_text() + if query.lower() in content.lower(): + results.append(str(f)) + except: + continue + + return { + "success": True, + "query": query, + "matches": results[:max_results], + "count": len(results) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_memory_save(key: str, value: str) -> Dict[str, Any]: + """Save to memory.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + memory_file = workspace / "MEMORY.md" + + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") + entry = f"\n### {key}\n_{timestamp}_\n{value}\n" + + with open(memory_file, "a") as f: + f.write(entry) + + return { + "success": True, + "key": key, + "saved": True + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_memory_list() -> Dict[str, Any]: + """List memory entries.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + memory_file = workspace / "MEMORY.md" + + if not memory_file.exists(): + return {"success": True, "entries": []} + + content = memory_file.read_text() + + # Extract sections + pattern = r"### (.+?)\n.*?\n(.*?)(?=### |$)" + matches = re.findall(pattern, content, re.DOTALL) + + entries = [{"title": m[0].strip(), "content": m[1].strip()[:200]} for m in matches] + + return { + "success": True, + "entries": entries, + "count": len(entries) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_context_load(projects: Optional[List[str]] = None) -> Dict[str, Any]: + """Load project context.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + + context = {} + + # Load AGENTS.md + agents_file = workspace / "AGENTS.md" + if agents_file.exists(): + context["agents"] = agents_file.read_text() + + # Load SOUL.md + soul_file = workspace / "SOUL.md" + if soul_file.exists(): + context["soul"] = soul_file.read_text() + + # Load TOOLS.md + tools_file = workspace / "TOOLS.md" + if tools_file.exists(): + context["tools"] = tools_file.read_text() + + return { + "success": True, + "context": context, + "loaded": list(context.keys()) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_project_scan(path: str = ".") -> Dict[str, Any]: + """Scan project structure.""" + try: + base = Path(path) + + if not base.exists(): + return {"success": False, "error": f"Path not found: {path}"} + + info = { + "name": base.name, + "files": [], + "dirs": [], + "has_git": (base / ".git").exists(), + "has_pyproject": (base / "pyproject.toml").exists(), + "has_package_json": (base / "package.json").exists(), + "has_dockerfile": (base / "Dockerfile").exists() + } + + for item in base.rglob("*"): + if len(info["files"]) + len(info["dirs"]) > 100: + break + + rel = item.relative_to(base) + if item.is_dir(): + info["dirs"].append(str(rel)) + else: + info["files"].append(str(rel)) + + return { + "success": True, + "project": info + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# TASK PLANNING TOOLS (Tools 33-37) +# ============================================================================ + +def tool_create_task(title: str, description: str = "", priority: str = "medium") -> Dict[str, Any]: + """Create a task.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + tasks_file = workspace / ".tasks.json" + + tasks = [] + if tasks_file.exists(): + tasks = json.loads(tasks_file.read_text()) + + task_id = hashlib.md5(f"{title}{datetime.now()}".encode()).hexdigest()[:8] + + task = { + "id": task_id, + "title": title, + "description": description, + "priority": priority, + "status": "pending", + "created": datetime.now().isoformat() + } + + tasks.append(task) + tasks_file.write_text(json.dumps(tasks, indent=2)) + + return { + "success": True, + "task": task + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_list_tasks(status: Optional[str] = None, priority: Optional[str] = None) -> Dict[str, Any]: + """List tasks.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + tasks_file = workspace / ".tasks.json" + + if not tasks_file.exists(): + return {"success": True, "tasks": []} + + tasks = json.loads(tasks_file.read_text()) + + if status: + tasks = [t for t in tasks if t.get("status") == status] + if priority: + tasks = [t for t in tasks if t.get("priority") == priority] + + return { + "success": True, + "tasks": tasks, + "count": len(tasks) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_update_task(task_id: str, status: Optional[str] = None, **kwargs) -> Dict[str, Any]: + """Update a task.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + tasks_file = workspace / ".tasks.json" + + if not tasks_file.exists(): + return {"success": False, "error": "No tasks found"} + + tasks = json.loads(tasks_file.read_text()) + + for task in tasks: + if task.get("id") == task_id: + if status: + task["status"] = status + task.update(kwargs) + task["updated"] = datetime.now().isoformat() + break + + tasks_file.write_text(json.dumps(tasks, indent=2)) + + return { + "success": True, + "task_id": task_id, + "updated": True + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_delete_task(task_id: str) -> Dict[str, Any]: + """Delete a task.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + tasks_file = workspace / ".tasks.json" + + if not tasks_file.exists(): + return {"success": False, "error": "No tasks found"} + + tasks = json.loads(tasks_file.read_text()) + tasks = [t for t in tasks if t.get("id") != task_id] + + tasks_file.write_text(json.dumps(tasks, indent=2)) + + return { + "success": True, + "task_id": task_id, + "deleted": True + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_create_plan(goal: str, steps: List[str]) -> Dict[str, Any]: + """Create an execution plan.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + plans_file = workspace / ".plans.json" + + plans = [] + if plans_file.exists(): + plans = json.loads(plans_file.read_text()) + + plan_id = hashlib.md5(f"{goal}{datetime.now()}".encode()).hexdigest()[:8] + + plan = { + "id": plan_id, + "goal": goal, + "steps": steps, + "status": "pending", + "created": datetime.now().isoformat() + } + + plans.append(plan) + plans_file.write_text(json.dumps(plans, indent=2)) + + return { + "success": True, + "plan": plan + } + except Exception as e: + return {"success": False, "error": str(e)} + + +def tool_execute_plan(plan_id: str) -> Dict[str, Any]: + """Execute a plan step by step.""" + try: + workspace = Path("/Users/walidsobhi/.openclaw/workspace") + plans_file = workspace / ".plans.json" + + if not plans_file.exists(): + return {"success": False, "error": "No plans found"} + + plans = json.loads(plans_file.read_text()) + + for plan in plans: + if plan.get("id") == plan_id: + plan["status"] = "in_progress" + plan["started"] = datetime.now().isoformat() + break + + plans_file.write_text(json.dumps(plans, indent=2)) + + return { + "success": True, + "plan_id": plan_id, + "status": "executing", + "steps": plan.get("steps", []) + } + except Exception as e: + return {"success": False, "error": str(e)} + + +# ============================================================================ +# TOOL REGISTRY +# ============================================================================ + +TOOLS: Dict[str, Callable] = { + # File operations (1-8) + "read": tool_read_file, + "write": tool_write_file, + "edit": tool_edit_file, + "search": tool_search_files, + "grep": tool_grep, + "copy": tool_copy_file, + "move": tool_move_file, + "delete": tool_delete_file, + + # Git operations (9-15) + "git_status": tool_git_status, + "git_commit": tool_git_commit, + "git_push": tool_git_push, + "git_pull": tool_git_pull, + "git_branch": tool_git_branch, + "git_log": tool_git_log, + "git_diff": tool_git_diff, + + # Code execution (16-22) + "run": tool_run_command, + "test": tool_run_tests, + "lint": tool_lint_code, + "format": tool_format_code, + "typecheck": tool_check_type, + "server": tool_start_server, + "install": tool_install_dependencies, + + # Web (23-27) + "web_search": tool_web_search, + "fetch": tool_web_fetch, + "download": tool_download_file, + "check_url": tool_check_url, + "screenshot": tool_screenshot, + + # Memory (28-32) + "memory_recall": tool_memory_recall, + "memory_save": tool_memory_save, + "memory_list": tool_memory_list, + "context_load": tool_context_load, + "project_scan": tool_project_scan, + + # Task planning (33-37) + "create_task": tool_create_task, + "list_tasks": tool_list_tasks, + "update_task": tool_update_task, + "delete_task": tool_delete_task, + "create_plan": tool_create_plan, + "execute_plan": tool_execute_plan, +} + + +def get_tool(name: str) -> Optional[Callable]: + """Get tool by name.""" + return TOOLS.get(name) + + +def list_tools() -> List[str]: + """List all available tools.""" + return list(TOOLS.keys()) + + +def get_tool_schemas() -> List[Dict[str, Any]]: + """Get tool schemas for LLM tool calling. + + Automatically generates JSON Schema from function signatures using inspect. + All 38 tools are included with accurate parameter types and descriptions. + """ + import inspect + from typing import get_type_hints + + schemas = [] + + for name, func in TOOLS.items(): + sig = inspect.signature(func) + doc = func.__doc__ or f"Tool: {name}" + + # Build parameters schema + properties = {} + required = [] + + for param_name, param in sig.parameters.items(): + # Skip self/cls + if param_name in ('self', 'cls'): + continue + + # Get type annotation + annotation = param.annotation + if annotation is inspect.Parameter.empty: + json_type = "string" # default + elif annotation is str: + json_type = "string" + elif annotation is int: + json_type = "integer" + elif annotation is bool: + json_type = "boolean" + elif annotation is float: + json_type = "number" + elif hasattr(annotation, '__origin__') and annotation.__origin__ is list: + json_type = "array" + elif hasattr(annotation, '__origin__') and annotation.__origin__ is dict: + json_type = "object" + else: + json_type = "string" # fallback + + # Build property definition + prop = {"type": json_type} + + # Extract description from docstring + param_desc = _extract_param_desc(doc, param_name) + if param_desc: + prop["description"] = param_desc + + # Add enum for restricted string values + if param_name in ('linter', 'formatter', 'package_manager') and hasattr(annotation, '__args__'): + prop["enum"] = list(annotation.__args__) + + properties[param_name] = prop + + # Mark as required if no default value + if param.default is inspect.Parameter.empty: + required.append(param_name) + + schema = { + "name": name, + "description": doc.strip().split('\n')[0], + "parameters": { + "type": "object", + "properties": properties, + "required": required + } + } + + schemas.append(schema) + + return schemas + + +def _extract_param_desc(docstring: str, param_name: str) -> Optional[str]: + """Extract parameter description from docstring. + + Looks for lines like: "- `param_name`: description" or "param_name: description". + """ + if not docstring: + return None + + lines = docstring.split('\n') + for i, line in enumerate(lines): + # Match: - `param`: description + if f"`{param_name}`" in line or f"{param_name}:" in line: + # Try to extract after colon or dash + parts = line.split(':', 1) + if len(parts) > 1: + return parts[1].strip().lstrip(' -').strip() + # Alternative: split on backtick + parts = line.split('`', 2) + if len(parts) > 2: + return parts[2].strip().lstrip(': -').strip() + + return None + + +if __name__ == "__main__": + print("Stack 2.9 Tools Module") + print(f"Available tools: {len(TOOLS)}") + print(list_tools()) diff --git a/audit_report.txt b/audits/audit_report.txt similarity index 100% rename from audit_report.txt rename to audits/audit_report.txt diff --git a/audit_results.json b/audits/audit_results.json similarity index 100% rename from audit_results.json rename to audits/audit_results.json diff --git a/Dockerfile b/config/Dockerfile similarity index 100% rename from Dockerfile rename to config/Dockerfile diff --git a/Dockerfile.gpu b/config/Dockerfile.gpu similarity index 100% rename from Dockerfile.gpu rename to config/Dockerfile.gpu diff --git a/MLproject b/config/MLproject similarity index 100% rename from MLproject rename to config/MLproject diff --git a/Makefile b/config/Makefile similarity index 100% rename from Makefile rename to config/Makefile diff --git a/docker-compose.gpu.yml b/config/docker-compose.gpu.yml similarity index 100% rename from docker-compose.gpu.yml rename to config/docker-compose.gpu.yml diff --git a/k8s/deployment.yaml b/config/k8s/deployment.yaml similarity index 100% rename from k8s/deployment.yaml rename to config/k8s/deployment.yaml diff --git a/k8s/pvc.yaml b/config/k8s/pvc.yaml similarity index 100% rename from k8s/pvc.yaml rename to config/k8s/pvc.yaml diff --git a/k8s/secret.yaml b/config/k8s/secret.yaml similarity index 100% rename from k8s/secret.yaml rename to config/k8s/secret.yaml diff --git a/k8s/service.yaml b/config/k8s/service.yaml similarity index 100% rename from k8s/service.yaml rename to config/k8s/service.yaml diff --git a/package-lock.json b/config/package-lock.json similarity index 100% rename from package-lock.json rename to config/package-lock.json diff --git a/package.json b/config/package.json similarity index 100% rename from package.json rename to config/package.json diff --git a/pyproject.toml b/config/pyproject.toml similarity index 100% rename from pyproject.toml rename to config/pyproject.toml diff --git a/tsconfig.json b/config/tsconfig.json similarity index 100% rename from tsconfig.json rename to config/tsconfig.json diff --git a/CHANGELOG.md b/docs/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to docs/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to docs/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md diff --git a/DIRECTORY_STRUCTURE.md b/docs/DIRECTORY_STRUCTURE.md similarity index 100% rename from DIRECTORY_STRUCTURE.md rename to docs/DIRECTORY_STRUCTURE.md diff --git a/GIT_PUSH.md b/docs/GIT_PUSH.md similarity index 100% rename from GIT_PUSH.md rename to docs/GIT_PUSH.md diff --git a/LAUNCH_CHECKLIST.md b/docs/LAUNCH_CHECKLIST.md similarity index 100% rename from LAUNCH_CHECKLIST.md rename to docs/LAUNCH_CHECKLIST.md diff --git a/LAUNCH_PLAN.md b/docs/LAUNCH_PLAN.md similarity index 100% rename from LAUNCH_PLAN.md rename to docs/LAUNCH_PLAN.md diff --git a/LICENSE b/docs/LICENSE similarity index 100% rename from LICENSE rename to docs/LICENSE diff --git a/MODEL_CARD.md b/docs/MODEL_CARD.md similarity index 100% rename from MODEL_CARD.md rename to docs/MODEL_CARD.md diff --git a/MODEL_REGISTRY.md b/docs/MODEL_REGISTRY.md similarity index 100% rename from MODEL_REGISTRY.md rename to docs/MODEL_REGISTRY.md diff --git a/README.md b/docs/README.md similarity index 91% rename from README.md rename to docs/README.md index 55d43ddf297c4982dafce08b9bd817bd82be5d82..ffc6f508101b6a1a0e9d671cc9a11e6b9b31c05f 100644 --- a/README.md +++ b/docs/README.md @@ -112,6 +112,30 @@ result = await registry.call("grep", {"pattern": "def main", "path": "./src"}) --- +## 🔌 OpenClaw Integration + +Stack 2.9 ships as an **MCP server**, exposing all 69 tools to [OpenClaw](https://github.com/openclaw) for seamless integration. + +### Register the MCP Server + +If not already configured, add it to OpenClaw: + +```bash +openclaw mcp set Stack2.9 '{"command":"python3","args":["src/mcp_server.py"],"cwd":"~/stack-2.9"}' +``` + +### Start the MCP Server + +```bash +cd ~/stack-2.9 && PYTHONPATH=. python3 src/mcp_server.py +``` + +### Use from OpenClaw + +Once registered, OpenClaw can directly call any tool: `file_read`, `grep`, `task_create`, `web_search`, `mcp_call`, and 65 more — no local GPU needed for the model. + +--- + ## 🛠️ Full Tool List (57 Tools) ### File Operations (5) diff --git a/README_QUICKSTART.md b/docs/README_QUICKSTART.md similarity index 100% rename from README_QUICKSTART.md rename to docs/README_QUICKSTART.md diff --git a/SECURITY.md b/docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to docs/SECURITY.md diff --git a/docs/tools.md b/docs/tools.md index 3bdd1ea4369f6388c78d52da79c683430be3dedb..917e2fa86ccb01bab7227e223555daa1f5a76ebc 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -1,162 +1,40 @@ -# Stack 2.9 Tools +# TOOLS.md - Local Notes -Python-native tool implementations compatible with the RTMP tool system. +Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup. -## Overview +## What Goes Here -Tools are implemented as Python classes extending `BaseTool`. Each tool has: -- A JSON-schema `input_schema` -- An `execute(input)` method returning `ToolResult[T]` -- Optional `validate_input()` for pre-execution checks +Things like: -## Available Tools +- Camera names and locations +- SSH hosts and aliases +- Preferred voices for TTS +- Speaker/room names +- Device nicknames +- Anything environment-specific -### Web Search +## Examples -**Tool:** `WebSearch` +```markdown +### Cameras -Search the web via DuckDuckGo. Results are cached for 5 minutes. +- living-room → Main area, 180° wide angle +- front-door → Entrance, motion-triggered -```python -from src.tools.web_search import WebSearchTool -tool = WebSearchTool() -result = tool.call({ - "query": "latest AI news", - "max_results": 10, - "allowed_domains": None, - "blocked_domains": None, -}) -``` - -**Parameters:** -| Name | Type | Required | Description | -|------|------|----------|-------------| -| `query` | string | Yes | Search query (min 2 chars) | -| `allowed_domains` | string[] | No | Restrict to domains | -| `blocked_domains` | string[] | No | Exclude domains | -| `max_results` | int | No | Max results (default 10, max 20) | - -**Output:** -```json -{ - "query": "...", - "results": [{"title": "...", "url": "...", "snippet": "..."}], - "duration_seconds": 0.5, - "source": "duckduckgo" -} -``` - ---- - -### Task Management - -Four tools for task lifecycle management. Tasks stored in `~/.stack-2.9/tasks.json`. - -#### TaskCreate - -```python -result = get_registry().call("TaskCreate", { - "subject": "Fix login bug", - "description": "Users cannot log in with SSO", - "status": "pending", - "priority": "high", - "tags": ["bug", "auth"], -}) -``` - -#### TaskList - -```python -result = get_registry().call("TaskList", { - "status": "pending", - "tag": "bug", - "limit": 50, -}) -``` - -#### TaskUpdate - -```python -result = get_registry().call("TaskUpdate", { - "id": "a1b2c3d4", - "status": "completed", -}) -``` - -#### TaskDelete - -```python -result = get_registry().call("TaskDelete", {"id": "a1b2c3d4"}) -``` - -**Task Status Values:** `pending`, `in_progress`, `completed`, `cancelled` - -**Priority Values:** `low`, `medium`, `high`, `urgent` - ---- +### SSH -### Scheduling / Cron +- home-server → 192.168.1.100, user: admin -Schedule prompts for later or recurring execution. Schedules stored in `~/.stack-2.9/schedules.json`. +### TTS -Uses standard 5-field cron in local time: `minute hour day-of-month month day-of-week` - -#### CronCreate - -```python -result = get_registry().call("CronCreate", { - "cron": "*/5 * * * *", # every 5 minutes - "prompt": "Check system status", - "recurring": True, - "durable": True, # persists across restarts -}) +- Preferred voice: "Nova" (warm, slightly British) +- Default speaker: Kitchen HomePod ``` -**Cron Examples:** -| Expression | Meaning | -|------------|---------| -| `*/5 * * * *` | Every 5 minutes | -| `0 * * * *` | Every hour | -| `0 9 * * 1-5` | Weekdays at 9am | -| `30 14 * * *` | 2:30pm daily | -| `0 0 1 * *` | Midnight on 1st of month | - -#### CronList - -```python -result = get_registry().call("CronList", {}) -``` +## Why Separate? -#### CronDelete - -```python -result = get_registry().call("CronDelete", {"id": "schedule-id"}) -``` +Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure. --- -## Tool Registry - -```python -from src.tools import get_registry - -registry = get_registry() -registry.list() # list all tool names -registry.get("WebSearch") # get a specific tool -registry.call("TaskCreate", {...}) # validate + execute in one step -``` - -## Adding New Tools - -1. Create `src/tools/your_tool.py` extending `BaseTool` -2. Define `name`, `description`, `input_schema`, and `execute()` -3. Import and register at the bottom of the file: `get_registry().register(YourTool())` -4. Add tests in `tests/test_your_tool.py` -5. Document in this file - -## Data Storage - -All tools store data under `~/.stack-2.9/`: -- `tasks.json` — task list -- `schedules.json` — cron schedules -- `web_search_cache.json` — cached search results +Add whatever helps you do your job. This is your cheat sheet. diff --git a/colab_train_stack29.ipynb b/notebooks/colab_train_stack29.ipynb similarity index 100% rename from colab_train_stack29.ipynb rename to notebooks/colab_train_stack29.ipynb diff --git a/kaggle_train_stack29_v5.ipynb b/notebooks/kaggle_train_stack29_v5.ipynb similarity index 100% rename from kaggle_train_stack29_v5.ipynb rename to notebooks/kaggle_train_stack29_v5.ipynb diff --git a/requirements.txt b/requirements/requirements.txt similarity index 100% rename from requirements.txt rename to requirements/requirements.txt diff --git a/requirements_api.txt b/requirements/requirements_api.txt similarity index 100% rename from requirements_api.txt rename to requirements/requirements_api.txt diff --git a/requirements_webui.txt b/requirements/requirements_webui.txt similarity index 100% rename from requirements_webui.txt rename to requirements/requirements_webui.txt diff --git a/install.sh b/scripts/deploy/install.sh similarity index 100% rename from install.sh rename to scripts/deploy/install.sh diff --git a/runpod_deploy.sh b/scripts/deploy/runpod_deploy.sh similarity index 100% rename from runpod_deploy.sh rename to scripts/deploy/runpod_deploy.sh diff --git a/setup.sh b/scripts/deploy/setup.sh similarity index 100% rename from setup.sh rename to scripts/deploy/setup.sh diff --git a/vastai_deploy.sh b/scripts/deploy/vastai_deploy.sh similarity index 100% rename from vastai_deploy.sh rename to scripts/deploy/vastai_deploy.sh diff --git a/src/mcp_server.py b/src/mcp_server.py index a70b62d7046a599eafef7ba49132deef63794c1c..e35fa53dcff053aeeb530c8c7e1bad9588b883d9 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -1,8 +1,15 @@ """MCP Server for Stack 2.9 - Exposes Stack tools via Model Context Protocol""" import asyncio +import os +import sys from typing import Any +# Ensure project root is on the path so 'from src.tools import' works +_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if _project_root not in sys.path: + sys.path.insert(0, _project_root) + from mcp.server.fastmcp import FastMCP # Import all Stack 2.9 tools (triggers auto-registration) diff --git a/src/tools/grep_tool.py b/src/tools/grep_tool.py index 73da8c8bbd34ac061fb7057f1ee917b461335d75..f7eec6b0e595415c0537a3e7cc4732b50ce10f5e 100644 --- a/src/tools/grep_tool.py +++ b/src/tools/grep_tool.py @@ -29,8 +29,15 @@ class GrepTool(BaseTool): "required": ["pattern", "path"] } - async def execute(self, pattern: str, path: str, recursive: bool = True, case_sensitive: bool = True, context_lines: int = 0, file_pattern: Optional[str] = None, max_results: int = 1000) -> ToolResult: + def execute(self, input_data: dict) -> ToolResult: """Search for pattern in files.""" + pattern = input_data.get('pattern') + path = input_data.get('path') + recursive = input_data.get('recursive', True) + case_sensitive = input_data.get('case_sensitive', True) + context_lines = input_data.get('context_lines', 0) + file_pattern = input_data.get('file_pattern') + max_results = input_data.get('max_results', 1000) search_path = Path(path) if not search_path.exists(): return ToolResult(success=False, error=f"Path not found: {path}") @@ -135,16 +142,9 @@ class GrepCountTool(BaseTool): "required": ["pattern", "path"] } - async def execute(self, pattern: str, path: str, recursive: bool = True, case_sensitive: bool = True, file_pattern: Optional[str] = None) -> ToolResult: + def execute(self, input_data: dict) -> ToolResult: """Count pattern matches.""" - grep_result = await GrepTool().execute( - pattern=pattern, - path=path, - recursive=recursive, - case_sensitive=case_sensitive, - file_pattern=file_pattern, - max_results=100000 - ) + grep_result = GrepTool().execute(input_data) counts = {} for match in grep_result.data.get("matches", []): @@ -152,8 +152,8 @@ class GrepCountTool(BaseTool): counts[file_path] = counts.get(file_path, 0) + 1 return ToolResult(success=True, data={ - "pattern": pattern, - "total_matches": len(matches) if (matches := grep_result.data.get("matches", [])) else 0, + "pattern": input_data.get('pattern'), + "total_matches": len(grep_result.data.get("matches", [])), "by_file": counts }) diff --git a/stack/training/patterns/feedback.json b/stack/training/patterns/feedback.json index 6fd7ff30b50f8713ad437b91f115bf7eee53c5af..45d8d18f381f026e21ceec01605ce6541ac9b212 100644 --- a/stack/training/patterns/feedback.json +++ b/stack/training/patterns/feedback.json @@ -1208,5 +1208,25 @@ "execution_time": 0.0, "timestamp": "2026-04-08T17:40:56.111571", "model_version": null + }, + { + "id": "034235bc464e647a", + "problem_type": "humaneval", + "solution": "accuracy=0.95", + "success": true, + "error_message": null, + "execution_time": 0.0, + "timestamp": "2026-04-09T21:51:36.160807", + "model_version": null + }, + { + "id": "671cde37e210031e", + "problem_type": "gsm8k", + "solution": "accuracy=0.55", + "success": true, + "error_message": null, + "execution_time": 0.0, + "timestamp": "2026-04-09T21:54:33.700093", + "model_version": null } ] \ No newline at end of file diff --git a/test_mcp.sh b/test_mcp.sh new file mode 100644 index 0000000000000000000000000000000000000000..47b76bb6de89b2f85e48e3de9b70efa484405c23 --- /dev/null +++ b/test_mcp.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# End-to-end MCP protocol test +cd /Users/walidsobhi/stack-2.9 + +# Start server in background, send JSON-RPC messages via stdin, capture responses +python3 src/mcp_server.py << 'EOF' +{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1} +{"jsonrpc":"2.0","method":"tools/call","params":{"name":"grep","arguments":{"pattern":"def main","path":"src","file_pattern":"*.py","max_results":3}},"id":2} +{"jsonrpc":"2.0","method":"tools/call","params":{"name":"WebSearch","arguments":{"query":"AI news","max_results":2}},"id":3} +EOF diff --git a/tests_core/stack2_9_enhanced.py b/tests/stack2_9_enhanced.py similarity index 100% rename from tests_core/stack2_9_enhanced.py rename to tests/stack2_9_enhanced.py diff --git a/tests/test_mcp_stdio.py b/tests/test_mcp_stdio.py new file mode 100644 index 0000000000000000000000000000000000000000..21c149a6708f1aecaddacb67d6223c95a05a3f4e --- /dev/null +++ b/tests/test_mcp_stdio.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Test MCP server stdio communication""" +import sys +import os + +project_root = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, project_root) + +# Send initialize + tools/list on stdin, read responses +import json + +init_msg = json.dumps({"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}) +list_msg = json.dumps({"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}) + +# Write both messages +for msg in [init_msg, list_msg]: + sys.stdout.write(msg + "\n") + sys.stdout.flush() + +# Read responses +sys.stderr.write("Waiting for responses...\n") +sys.stderr.flush() diff --git a/tests/test_tools.json b/tests/test_tools.json new file mode 100644 index 0000000000000000000000000000000000000000..d2ebd2ed68bf6bd9def88546e10cbf05471e9634 --- /dev/null +++ b/tests/test_tools.json @@ -0,0 +1 @@ +{"jsonrpc":"2.0","method":"tools/call","params":{"name":"web_search","arguments":{"query":"latest AI news","max_results":5}},"id":1} diff --git a/training-configs/7b-lora-config.yaml b/training/training-configs/7b-lora-config.yaml similarity index 100% rename from training-configs/7b-lora-config.yaml rename to training/training-configs/7b-lora-config.yaml diff --git a/training-configs/README.md b/training/training-configs/README.md similarity index 100% rename from training-configs/README.md rename to training/training-configs/README.md diff --git a/training-configs/kaggle-7b-qlora.sh b/training/training-configs/kaggle-7b-qlora.sh similarity index 100% rename from training-configs/kaggle-7b-qlora.sh rename to training/training-configs/kaggle-7b-qlora.sh diff --git a/training-configs/local-pretrain.sh b/training/training-configs/local-pretrain.sh similarity index 100% rename from training-configs/local-pretrain.sh rename to training/training-configs/local-pretrain.sh diff --git a/training-configs/recipes.md b/training/training-configs/recipes.md similarity index 100% rename from training-configs/recipes.md rename to training/training-configs/recipes.md diff --git a/training-configs/t4-qlora.yaml b/training/training-configs/t4-qlora.yaml similarity index 100% rename from training-configs/t4-qlora.yaml rename to training/training-configs/t4-qlora.yaml diff --git a/training-data/README.md b/training/training-data/README.md similarity index 100% rename from training-data/README.md rename to training/training-data/README.md diff --git a/training-data/tool_examples.json b/training/training-data/tool_examples.json similarity index 100% rename from training-data/tool_examples.json rename to training/training-data/tool_examples.json