walidsobhie-code commited on
Commit
fd9b9a2
·
1 Parent(s): a9b0b85

fix: update benchmark paths in stack-2.9-cli.py and remove deprecated cli/ module

Browse files

- Fixed path resolution: stack-2.9-eval -> stack/eval, stack-2.9-training -> stack/training
- Added __init__.py to benchmarks directory
- Fixed ChatMessage timestamp default value
- Removed src/cli/ (archived to ~/stack-2.9-backups/) - use src/stack-2.9-cli.py instead

Users: Run with 'python3 src/stack-2.9-cli.py'

src/cli/__init__.py DELETED
@@ -1,11 +0,0 @@
1
- """
2
- Stack 2.9 CLI Package
3
- Terminal user interface for Stack 2.9
4
- """
5
-
6
- __version__ = "2.9.0"
7
- __author__ = "my-ai-stack"
8
-
9
- from .main import Stack29CLI, main
10
-
11
- __all__ = ["Stack29CLI", "main"]
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/agent.py DELETED
@@ -1,660 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Stack 2.9 - Core Agent Logic Module
4
- Query understanding, tool selection, response generation, and self-reflection loop.
5
- """
6
-
7
- import os
8
- import json
9
- import re
10
- import asyncio
11
- from pathlib import Path
12
- from typing import Any, Dict, List, Optional, Union, Callable
13
- from dataclasses import dataclass, field
14
- from datetime import datetime
15
- from enum import Enum
16
-
17
- from .tools import TOOLS, get_tool, list_tools, get_tool_schemas
18
- from .context import ContextManager, create_context_manager
19
-
20
-
21
- class QueryIntent(Enum):
22
- """Intents recognized by the agent."""
23
- FILE_READ = "file_read"
24
- FILE_WRITE = "file_write"
25
- FILE_EDIT = "file_edit"
26
- FILE_SEARCH = "file_search"
27
- GIT_OPERATION = "git_operation"
28
- CODE_EXECUTION = "code_execution"
29
- WEB_SEARCH = "web_search"
30
- MEMORY = "memory"
31
- TASK = "task"
32
- QUESTION = "question"
33
- GENERAL = "general"
34
- GENERAL_HELP = "general_help"
35
-
36
-
37
- @dataclass
38
- class ToolCall:
39
- """Represents a tool call."""
40
- tool_name: str
41
- arguments: Dict[str, Any]
42
- result: Optional[Dict[str, Any]] = None
43
- success: bool = False
44
- error: Optional[str] = None
45
-
46
-
47
- @dataclass
48
- class AgentResponse:
49
- """Represents the agent's response."""
50
- content: str
51
- tool_calls: List[ToolCall] = field(default_factory=list)
52
- context_used: List[str] = field(default_factory=list)
53
- confidence: float = 1.0
54
- needs_clarification: bool = False
55
- clarification_needed: Optional[str] = None
56
-
57
-
58
- class QueryUnderstanding:
59
- """Understands user queries and maps them to intents and tools."""
60
-
61
- # Intent patterns
62
- PATTERNS = {
63
- QueryIntent.FILE_READ: [
64
- r"read\s+(?:the\s+)?(?:file\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
65
- r"show\s+(?:me\s+)?(?:the\s+)?(?:content\s+of\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
66
- r"what('s| is)\s+in\s+(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
67
- r"cat\s+(.+)",
68
- r"view\s+(.+)",
69
- ],
70
- QueryIntent.FILE_WRITE: [
71
- r"write\s+(?:to\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
72
- r"create\s+(?:file\s+)?(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
73
- r"save\s+(?:to\s+)?(.+)",
74
- ],
75
- QueryIntent.FILE_EDIT: [
76
- r"edit\s+(.+\.py|.+\.js|.+\.txt|.+\.md|.+\.json)",
77
- r"modify\s+(.+)",
78
- r"change\s+(.+)",
79
- r"replace\s+(.+)",
80
- ],
81
- QueryIntent.FILE_SEARCH: [
82
- r"find\s+(?:files?\s+)?(?:named\s+)?(.+)",
83
- r"search\s+for\s+(?:files?\s+)?(.+)",
84
- r"grep\s+for\s+(.+)",
85
- r"where\s+is\s+(.+)",
86
- r"locate\s+(.+)",
87
- ],
88
- QueryIntent.GIT_OPERATION: [
89
- r"git\s+(commit|push|pull|branch|status|log|diff)",
90
- r"(commit|push|pull|branch)\s+(?:to\s+)?(?:the\s+)?(?:repo|repository)?",
91
- ],
92
- QueryIntent.CODE_EXECUTION: [
93
- r"^run\s+(?:the\s+)?(?:command\s+)?(.+)",
94
- r"^execute\s+(.+)",
95
- r"^start\s+(?:the\s+)?(?:server\s+)?(.+)",
96
- r"^test\s+(?:the\s+)?(.+)",
97
- r"^lint\s+(.+)",
98
- r"^format\s+(.+)",
99
- ],
100
- QueryIntent.WEB_SEARCH: [
101
- r"^search\s+(?:the\s+)?web\s+for\s+(.+)",
102
- r"^google\s+(.+)",
103
- r"^look\s+up\s+(.+)",
104
- r"^find\s+information\s+about\s+(.+)",
105
- r"latest\s+ai\s+news",
106
- r"what('s|\s+is)\s+new\s+in\s+ai",
107
- ],
108
- QueryIntent.MEMORY: [
109
- r"(remember|recall|what do you remember)\s+(.+)",
110
- r"(save|store)\s+(?:to\s+)?memory\s+(.+)",
111
- r"what('s| is)\s+in\s+(?:the\s+)?memory",
112
- ],
113
- QueryIntent.GENERAL_HELP: [
114
- r"list\s+(?:all\s+)?tools?",
115
- r"what\s+tools\s+(?:do\s+you\s+have|can\s+you\s+do)",
116
- r"help\s+me",
117
- r"what\s+can\s+you\s+do",
118
- r"how\s+to\s+use\s+you",
119
- ],
120
- QueryIntent.TASK: [
121
- r"(create|add|new)\s+task\s+(.+)",
122
- r"list\s+(?:my\s+)?tasks?",
123
- r"(complete|finish|done)\s+task\s+(.+)",
124
- ],
125
- QueryIntent.QUESTION: [
126
- r"what\s+is\s+(.+)",
127
- r"how\s+(?:do|does)\s+(.+)",
128
- r"why\s+(.+)",
129
- r"can\s+(.+)",
130
- r"(?:help|explain)\s+(.+)",
131
- ],
132
- }
133
-
134
- def __init__(self):
135
- self.tools = list_tools()
136
-
137
- def parse(self, query: str) -> Dict[str, Any]:
138
- """Parse query and determine intent."""
139
- query = query.strip().lower()
140
-
141
- # Check each intent pattern
142
- for intent, patterns in self.PATTERNS.items():
143
- for pattern in patterns:
144
- match = re.search(pattern, query, re.IGNORECASE)
145
- if match:
146
- return {
147
- "intent": intent.value,
148
- "matched": match.group(0),
149
- "extracted": match.groups() if match.groups() else None,
150
- "confidence": 0.8
151
- }
152
-
153
- return {
154
- "intent": QueryIntent.GENERAL.value,
155
- "matched": None,
156
- "extracted": None,
157
- "confidence": 0.5
158
- }
159
-
160
- def extract_file_path(self, text: str) -> Optional[str]:
161
- """Extract file path from text."""
162
- # Common patterns for file paths
163
- patterns = [
164
- r"([a-zA-Z0-9_/\-\.]+\.py)",
165
- r"([a-zA-Z0-9_/\-\.]+\.js)",
166
- r"([a-zA-Z0-9_/\-\.]+\.ts)",
167
- r"([a-zA-Z0-9_/\-\.]+\.md)",
168
- r"([a-zA-Z0-9_/\-\.]+\.json)",
169
- r"([a-zA-Z0-9_/\-\.]+\.txt)",
170
- r"([a-zA-Z0-9_/\-\.]+\.yaml|\.yml)",
171
- r"([a-zA-Z0-9_/\-\.]+)",
172
- ]
173
-
174
- for pattern in patterns:
175
- match = re.search(pattern, text)
176
- if match:
177
- return match.group(1)
178
-
179
- return None
180
-
181
-
182
- class ToolSelector:
183
- """Selects appropriate tools based on query intent."""
184
-
185
- # Intent to tool mapping
186
- INTENT_TOOLS = {
187
- QueryIntent.FILE_READ: ["read"],
188
- QueryIntent.FILE_WRITE: ["write"],
189
- QueryIntent.FILE_EDIT: ["edit"],
190
- QueryIntent.FILE_SEARCH: ["search", "grep"],
191
- QueryIntent.GIT_OPERATION: ["git_status", "git_commit", "git_push", "git_pull", "git_branch", "git_log"],
192
- QueryIntent.CODE_EXECUTION: ["run", "test", "lint", "format"],
193
- QueryIntent.WEB_SEARCH: ["web_search", "fetch"],
194
- QueryIntent.MEMORY: ["memory_recall", "memory_save", "memory_list"],
195
- QueryIntent.TASK: ["create_task", "list_tasks", "update_task"],
196
- QueryIntent.GENERAL_HELP: [],
197
- }
198
-
199
- def select(self, intent: str, context: Dict[str, Any]) -> List[str]:
200
- """Select tools for given intent."""
201
- # Map string to QueryIntent enum
202
- INTENT_MAP = {
203
- "file_read": QueryIntent.FILE_READ,
204
- "file_write": QueryIntent.FILE_WRITE,
205
- "file_edit": QueryIntent.FILE_EDIT,
206
- "file_search": QueryIntent.FILE_SEARCH,
207
- "git_operation": QueryIntent.GIT_OPERATION,
208
- "code_execution": QueryIntent.CODE_EXECUTION,
209
- "web_search": QueryIntent.WEB_SEARCH,
210
- "memory": QueryIntent.MEMORY,
211
- "task": QueryIntent.TASK,
212
- "general": QueryIntent.GENERAL,
213
- "general_help": QueryIntent.GENERAL_HELP,
214
- }
215
-
216
- tools = []
217
- intent_enum = INTENT_MAP.get(intent)
218
- if intent_enum:
219
- tools = list(self.INTENT_TOOLS.get(intent_enum, []))
220
-
221
- # For git operations, filter based on query keywords
222
- if intent == "git_operation" and context.get("query"):
223
- query = context["query"].lower()
224
- git_keyword_tools = {
225
- "status": ["git_status"],
226
- "commit": ["git_commit"],
227
- "push": ["git_push"],
228
- "pull": ["git_pull"],
229
- "branch": ["git_branch"],
230
- "log": ["git_log"],
231
- "diff": ["git_diff"],
232
- }
233
- filtered = []
234
- for kw, tool_list in git_keyword_tools.items():
235
- if kw in query:
236
- filtered.extend(tool_list)
237
- # Default to git_status if no specific keyword found but query mentions git
238
- if not filtered and "git" in query:
239
- filtered = ["git_status"]
240
- if filtered:
241
- tools = filtered
242
-
243
- return tools
244
-
245
- def get_tool_parameters(self, tool_name: str, query: str, context: Dict[str, Any]) -> Dict[str, Any]:
246
- """Extract parameters for a tool from query and context."""
247
- params = {}
248
-
249
- query_lower = query.lower()
250
-
251
- if tool_name == "read":
252
- path = re.search(r"(?:read|show|cat|view)\s+(?:the\s+)?(?:file\s+)?(.+)", query, re.IGNORECASE)
253
- if path:
254
- params["path"] = path.group(1).strip()
255
-
256
- elif tool_name == "write":
257
- path = re.search(r"write\s+(?:to\s+)?(.+?)(?:\s+with|\s+content|$)", query, re.IGNORECASE)
258
- if path:
259
- params["path"] = path.group(1).strip()
260
- # Try to extract content
261
- content_match = re.search(r"(?:content|with):\s*(.+)$", query, re.IGNORECASE)
262
- if content_match:
263
- params["content"] = content_match.group(1)
264
-
265
- elif tool_name == "git_commit":
266
- msg = re.search(r"commit(?:\s+with)?\s+(?:message\s+)?[\"']?(.+)[\"']?", query, re.IGNORECASE)
267
- if msg:
268
- params["message"] = msg.group(1).strip()
269
-
270
- elif tool_name == "web_search":
271
- # Extract search query
272
- patterns = [
273
- r"search\s+(?:the\s+)?web\s+for\s+(.+)",
274
- r"google\s+(.+)",
275
- r"look\s+up\s+(.+)",
276
- r"latest\s+ai\s+news",
277
- r"what('s|\s+is)\s+new\s+in\s+ai",
278
- ]
279
- for pattern in patterns:
280
- match = re.search(pattern, query, re.IGNORECASE)
281
- if match:
282
- # Only extract group(1) if it exists
283
- if match.groups():
284
- params["query"] = match.group(1).strip()
285
- else:
286
- # For patterns without capture groups, use full match
287
- params["query"] = match.group(0).strip()
288
- break
289
-
290
- elif tool_name in ("grep", "search"):
291
- # Extract pattern to search for - capture full phrase
292
- # Strategy: split by " in " and take second part as path
293
- parts = query.split(' in ')
294
- if len(parts) >= 2:
295
- # Last part is the path
296
- path_part = ' in '.join(parts[1:])
297
- # Clean up path
298
- if path_part.strip() in ['project', 'this project']:
299
- path_part = '/Users/walidsobhi/stack-2.9/src'
300
- elif path_part.strip() == 'src':
301
- path_part = '/Users/walidsobhi/stack-2.9/src'
302
- elif path_part.startswith('~') or path_part.startswith('/') or path_part.startswith('./'):
303
- pass # Keep as-is
304
- else:
305
- path_part = '/Users/walidsobhi/stack-2.9/' + path_part.strip()
306
- params["path"] = path_part
307
- # First part is the pattern (remove grep/for/search)
308
- pattern_part = parts[0]
309
- for prefix in ['grep for', 'search for', 'find for', 'grep', 'search', 'find']:
310
- if pattern_part.strip().lower().startswith(prefix):
311
- pattern_part = pattern_part.strip()[len(prefix):].strip()
312
- break
313
- params["pattern"] = pattern_part
314
- else:
315
- # Default path is workspace root
316
- params["path"] = "/Users/walidsobhi/stack-2.9/src"
317
-
318
- return params
319
-
320
-
321
- class ResponseGenerator:
322
- """Generates natural language responses."""
323
-
324
- GREETING_VARIATIONS = [
325
- "Sure! I can help with that.",
326
- "Got it! Let me assist with that.",
327
- "No problem! Here's what I found:",
328
- "Alright! Here you go:",
329
- "Sure thing! Let me show you:",
330
- ]
331
-
332
- HELP_RESPONSES = [
333
- "I support these operations:",
334
- "Here are some things I can do:",
335
- "Here's my toolkit:",
336
- "I can help with the following:",
337
- ]
338
-
339
- def __init__(self):
340
- self.context_manager = create_context_manager()
341
- self.last_intent = None
342
- self.last_query = None
343
-
344
- def generate(
345
- self,
346
- tool_results: List[ToolCall],
347
- intent: str,
348
- context: Dict[str, Any]
349
- ) -> str:
350
- """Generate response from tool results."""
351
- import random
352
-
353
- # Track intent for conversation flow
354
- previous_intent = self.last_intent
355
- self.last_intent = intent
356
-
357
- if not tool_results:
358
- # Handle queries that don't need tools
359
- if intent == "question":
360
- return ("I can help with reading/writing files, running commands, "
361
- "git operations, web search, and more. "
362
- "Try asking me something like 'read the file README.md' "
363
- "or 'check git status'.")
364
- elif intent == "general_help":
365
- greeting = random.choice(self.HELP_RESPONSES)
366
- return (f"{greeting}\n"
367
- "- Read/write/edit files\n"
368
- "- Run commands and code\n"
369
- "- Git operations (status, commit, push, pull)\n"
370
- "- Code search with grep\n"
371
- "- Web search\n"
372
- "- Manage tasks\n\n"
373
- "Examples:\n"
374
- "- 'read the file /path/to/file'\n"
375
- "- 'check git status'\n"
376
- "- 'grep for def main in ~/project/src'\n"
377
- "- 'run the command ls'\n"
378
- "- 'what is 2 + 2'")
379
- elif intent == "general":
380
- # Don't repeat the same greeting
381
- if previous_intent == "general":
382
- return "What would you like me to help you with?"
383
- return "What can I help you with?"
384
- return None
385
-
386
- responses = []
387
- greeting = random.choice(self.GREETING_VARIATIONS) if tool_results else None
388
-
389
- for call in tool_results:
390
- if call.result is None:
391
- responses.append(f"Hmm, {call.tool_name} didn't return anything.")
392
- continue
393
-
394
- if call.result.get("success"):
395
- result = call.result
396
-
397
- # Format based on tool
398
- if call.tool_name == "read":
399
- if "content" in result:
400
- content = result["content"]
401
- if len(content) > 500:
402
- content = content[:500] + "..."
403
- responses.append(f"Here's the content:\n```\n{content}\n```")
404
-
405
- elif call.tool_name == "search":
406
- # Skip search tool if it has no matches - grep will show results
407
- if "matches" in result and result["matches"]:
408
- matches = result["matches"]
409
- resp = f"Found {len(matches)} matches:\n"
410
- for m in matches[:10]:
411
- resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n"
412
- responses.append(resp)
413
- # else: skip - grep will show results
414
-
415
- elif call.tool_name == "grep":
416
- if "matches" in result:
417
- matches = result["matches"]
418
- if matches:
419
- resp = f"Found {len(matches)} matches:\n"
420
- for m in matches[:10]:
421
- resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n"
422
- responses.append(resp)
423
- else:
424
- responses.append("Didn't find any matches for that.")
425
-
426
- elif call.tool_name in ["git_status", "git_log"]:
427
- if "files" in result:
428
- files = result["files"]
429
- if files:
430
- responses.append(f"Changed files ({len(files)}):\n" + "\n".join(f" - {f}" for f in files))
431
- else:
432
- responses.append("No changes detected.")
433
- elif "commits" in result:
434
- commits = result["commits"]
435
- if commits:
436
- responses.append("Recent commits:\n" + "\n".join(f" - {c}" for c in commits[:5]))
437
-
438
- elif call.tool_name == "web_search":
439
- if "results" in result:
440
- results = result["results"]
441
- resp = "Search results:\n"
442
- for r in results[:5]:
443
- resp += f"- {r.get('title', 'Untitled')}\n"
444
- responses.append(resp)
445
-
446
- elif call.tool_name == "run":
447
- stdout = result.get("stdout", "")
448
- stderr = result.get("stderr", "")
449
- if stdout:
450
- responses.append(f"Output:\n```\n{stdout[:500]}\n```")
451
- if stderr:
452
- responses.append(f"Errors:\n```\n{stderr[:500]}\n```")
453
- if not stdout and not stderr:
454
- responses.append("Command executed successfully.")
455
-
456
- elif call.tool_name == "memory_recall":
457
- if "matches" in result:
458
- matches = result["matches"]
459
- if matches:
460
- responses.append(f"Found {len(matches)} memory entries.")
461
- else:
462
- responses.append("No matching memories found.")
463
-
464
- else:
465
- # Generic success response
466
- responses.append(f"{call.tool_name}: {json.dumps(result)[:200]}")
467
- else:
468
- error = call.result.get("error", "Unknown error")
469
- responses.append(f"Error in {call.tool_name}: {error}")
470
-
471
- return "\n\n".join(responses) or "I processed your request but have no results to show."
472
-
473
- def generate_clarification(self, question: str) -> str:
474
- """Generate clarification question."""
475
- return f"I need some clarification: {question}"
476
-
477
-
478
- class SelfReflection:
479
- """Self-reflection loop for improving responses."""
480
-
481
- def __init__(self):
482
- self.max_iterations = 3
483
- self.min_confidence = 0.7
484
-
485
- def reflect(
486
- self,
487
- query: str,
488
- tool_calls: List[ToolCall],
489
- response: str
490
- ) -> Dict[str, Any]:
491
- """Reflect on the response and determine if improvement is needed."""
492
- # Check if any tool call failed
493
- failed_calls = [c for c in tool_calls if not c.success]
494
-
495
- # Calculate confidence
496
- success_rate = len(tool_calls) / max(len(tool_calls), 1)
497
- confidence = success_rate
498
-
499
- needs_reflection = (
500
- len(failed_calls) > 0 or
501
- confidence < self.min_confidence or
502
- len(response) < 20
503
- )
504
-
505
- return {
506
- "needs_reflection": needs_reflection,
507
- "confidence": confidence,
508
- "failed_calls": len(failed_calls),
509
- "response_length": len(response),
510
- "suggestion": self._get_suggestion(failed_calls, confidence) if needs_reflection else None
511
- }
512
-
513
- def _get_suggestion(self, failed_calls: List[ToolCall], confidence: float) -> str:
514
- """Get improvement suggestion."""
515
- if not failed_calls:
516
- return "Try providing more context in your query."
517
-
518
- return f"Failed tool calls: {', '.join(c.tool_name for c in failed_calls)}"
519
-
520
-
521
- class StackAgent:
522
- """
523
- Core agent that combines all components for intelligent assistance.
524
- """
525
-
526
- def __init__(self, workspace: Optional[str] = None):
527
- self.query_understanding = QueryUnderstanding()
528
- self.tool_selector = ToolSelector()
529
- self.response_generator = ResponseGenerator()
530
- self.self_reflection = SelfReflection()
531
- self.context_manager = create_context_manager(workspace)
532
- self.conversation_history: List[Dict[str, Any]] = []
533
-
534
- def process(self, query: str, context: Optional[Dict] = None) -> AgentResponse:
535
- """Process a user query."""
536
- context = context or {}
537
-
538
- # Step 1: Understand query
539
- parsed = self.query_understanding.parse(query)
540
- intent = parsed["intent"]
541
- confidence = parsed["confidence"]
542
-
543
- # Step 2: Select tools (pass query in context for smart filtering)
544
- selected_tools = self.tool_selector.select(intent, {"query": query, **context})
545
- tool_params = {}
546
-
547
- for tool_name in selected_tools:
548
- tool_params[tool_name] = self.tool_selector.get_tool_parameters(tool_name, query, context)
549
-
550
- # Step 3: Execute tools
551
- tool_calls = []
552
- for tool_name in selected_tools:
553
- tool = get_tool(tool_name)
554
- if tool is None:
555
- continue
556
-
557
- params = tool_params.get(tool_name, {})
558
- try:
559
- result = tool(**params)
560
- call = ToolCall(
561
- tool_name=tool_name,
562
- arguments=params,
563
- result=result,
564
- success=result.get("success", False) if isinstance(result, dict) else True
565
- )
566
- except Exception as e:
567
- call = ToolCall(
568
- tool_name=tool_name,
569
- arguments=params,
570
- error=str(e),
571
- success=False
572
- )
573
-
574
- tool_calls.append(call)
575
-
576
- # Record in session
577
- self.context_manager.session.add_tool_usage(tool_name, call.result)
578
-
579
- # Step 4: Generate response
580
- response_content = self.response_generator.generate(tool_calls, intent, context)
581
-
582
- # Step 5: Self-reflect
583
- reflection = self.self_reflection.reflect(query, tool_calls, response_content)
584
-
585
- # Step 6: Add to conversation history
586
- self.conversation_history.append({
587
- "query": query,
588
- "intent": intent,
589
- "tool_calls": [c.tool_name for c in tool_calls],
590
- "response": response_content,
591
- "reflection": reflection,
592
- "timestamp": datetime.now().isoformat()
593
- })
594
-
595
- return AgentResponse(
596
- content=response_content,
597
- tool_calls=tool_calls,
598
- confidence=reflection.get("confidence", confidence),
599
- needs_clarification=reflection.get("needs_reflection", False),
600
- clarification_needed=reflection.get("suggestion")
601
- )
602
-
603
- def process_with_tools(self, query: str, forced_tools: List[str]) -> AgentResponse:
604
- """Process query with explicitly specified tools."""
605
- tool_calls = []
606
-
607
- for tool_name in forced_tools:
608
- tool = get_tool(tool_name)
609
- if tool is None:
610
- continue
611
-
612
- try:
613
- result = tool()
614
- call = ToolCall(
615
- tool_name=tool_name,
616
- arguments={},
617
- result=result,
618
- success=result.get("success", False) if isinstance(result, dict) else True
619
- )
620
- except Exception as e:
621
- call = ToolCall(
622
- tool_name=tool_name,
623
- arguments={},
624
- error=str(e),
625
- success=False
626
- )
627
-
628
- tool_calls.append(call)
629
-
630
- response_content = self.response_generator.generate(tool_calls, "general", {})
631
-
632
- return AgentResponse(
633
- content=response_content,
634
- tool_calls=tool_calls,
635
- confidence=1.0
636
- )
637
-
638
- def get_context(self) -> str:
639
- """Get current context as string."""
640
- return self.context_manager.get_workspace_context()
641
-
642
- def get_schemas(self) -> List[Dict[str, Any]]:
643
- """Get tool schemas for tool calling."""
644
- return get_tool_schemas()
645
-
646
-
647
- def create_agent(workspace: Optional[str] = None) -> StackAgent:
648
- """Factory function to create agent."""
649
- return StackAgent(workspace)
650
-
651
-
652
- if __name__ == "__main__":
653
- print("Stack 2.9 Agent Module")
654
- agent = create_agent()
655
- print(f"Agent initialized with {len(list_tools())} tools")
656
-
657
- # Test query
658
- response = agent.process("list my tasks")
659
- print(f"\nQuery: 'list my tasks'")
660
- print(f"Response: {response.content[:200]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/cli.py DELETED
@@ -1,562 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Stack 2.9 - CLI Entry Point
4
- A complete CLI and agent interface showcasing Stack 2.9 capabilities.
5
- """
6
-
7
- import os
8
- import sys
9
- import cmd
10
- import json
11
- import argparse
12
- import asyncio
13
- import subprocess
14
- from pathlib import Path
15
- from typing import Any, Dict, List, Optional
16
-
17
- from .agent import create_agent, StackAgent
18
- from .context import create_context_manager
19
-
20
-
21
- # ============================================================================
22
- # UTILITIES
23
- # ============================================================================
24
-
25
- def print_banner():
26
- """Print the Stack 2.9 banner."""
27
- banner = r"""
28
- ____ _ _ _
29
- | _ \ ___ _ __ __| |_ __ ___ (_)_ __ | | __
30
- | |_) / _ \ '_ \ / _` | '__/ _ \| | '_ \ | |/ /
31
- | _ < __/ | | | (_| | | | (_) | | | | | | <
32
- |_| \_\___|_| |_|\__,_|_| \___/|_|_| |_| |_|\_\
33
-
34
- Stack 2.9 CLI & Agent Interface
35
- """
36
- print(banner)
37
-
38
-
39
- def print_colored(text: str, color: str = "white", bold: bool = False):
40
- """Print colored text."""
41
- colors = {
42
- "red": "\033[91m",
43
- "green": "\033[92m",
44
- "yellow": "\033[93m",
45
- "blue": "\033[94m",
46
- "magenta": "\033[95m",
47
- "cyan": "\033[96m",
48
- "white": "\033[97m",
49
- "reset": "\033[0m"
50
- }
51
-
52
- if bold:
53
- text = f"\033[1m{text}\033[0m"
54
-
55
- sys.stdout.write(colors.get(color, "white"))
56
- print(text)
57
- sys.stdout.write(colors["reset"])
58
-
59
-
60
- def format_output(data: Any, format: str = "text") -> str:
61
- """Format output for display."""
62
- if format == "json":
63
- return json.dumps(data, indent=2)
64
- elif isinstance(data, dict):
65
- return "\n".join(f" {k}: {v}" for k, v in data.items())
66
- elif isinstance(data, list):
67
- return "\n".join(f" - {item}" for item in data)
68
- else:
69
- return str(data)
70
-
71
-
72
- # ============================================================================
73
- # MODE 1: INTERACTIVE CHAT
74
- # ============================================================================
75
-
76
- class ChatMode(cmd.Cmd):
77
- """Interactive chat mode with the agent."""
78
-
79
- intro = """
80
- Welcome to Stack 2.9 Interactive Chat!
81
- Type your queries or use commands:
82
- /tools - List available tools
83
- /schema - Show tool schemas
84
- /context - Show current context
85
- /history - Show conversation history
86
- /clear - Clear chat history
87
- /exit - Exit chat mode
88
-
89
- Just type your question or task to get started!
90
- """
91
- prompt = "\n[Stack]> "
92
-
93
- def __init__(self, agent: StackAgent):
94
- super().__init__()
95
- self.agent = agent
96
- self.history: List[Dict[str, Any]] = []
97
- self.show_tool_calls = False
98
- self.output_format = "text"
99
- self.voice_input = False
100
-
101
- def default(self, line: str):
102
- """Handle user input as a query to the agent."""
103
- if line.startswith('/'):
104
- return
105
-
106
- print_colored("\nThinking...", "cyan")
107
-
108
- try:
109
- response = self.agent.process(line)
110
- self.history.append({
111
- "query": line,
112
- "response": response.content,
113
- "tool_calls": [tc.tool_name for tc in response.tool_calls],
114
- "timestamp": "now"
115
- })
116
-
117
- # Display response
118
- print("\n" + "="*50)
119
- print_colored("Response:", "green", bold=True)
120
- print(response.content)
121
-
122
- # Show tool usage if enabled
123
- if self.show_tool_calls and response.tool_calls:
124
- print("\n" + "-"*50)
125
- print_colored("Tools Used:", "yellow")
126
- for tc in response.tool_calls:
127
- status = "✓" if tc.success else "✗"
128
- print(f" {status} {tc.tool_name}")
129
- if not tc.success and tc.error:
130
- print(f" Error: {tc.error}")
131
-
132
- if response.needs_clarification:
133
- print_colored(f"\nNote: {response.clarification_needed}", "yellow")
134
-
135
- except KeyboardInterrupt:
136
- print_colored("\nInterrupted.", "red")
137
- except Exception as e:
138
- print_colored(f"\nError: {e}", "red")
139
-
140
- def do_tools(self, arg):
141
- """List all available tools."""
142
- tools = self.agent.context_manager.session.tools_used
143
- if tools:
144
- print(f"\nUsed {len(tools)} tools this session:")
145
- for tool in set(tools):
146
- print(f" - {tool}")
147
- else:
148
- print("\nNo tools used yet this session.")
149
-
150
- def do_schema(self, arg):
151
- """Show tool schemas for tool calling."""
152
- schemas = self.agent.get_schemas()
153
- print(f"\nTool Schemas ({len(schemas)} tools):")
154
- for schema in schemas[:10]:
155
- print(f"\n {schema['name']}: {schema['description']}")
156
- print(f"\n ... and {len(schemas) - 10} more")
157
-
158
- def do_context(self, arg):
159
- """Show current context."""
160
- context = self.agent.get_context()
161
- print("\n" + context)
162
-
163
- def do_history(self, arg):
164
- """Show conversation history."""
165
- print(f"\nChat History ({len(self.history)} messages):")
166
- for i, entry in enumerate(self.history[-10:], 1):
167
- print(f"\n{i}. Query: {entry['query'][:50]}...")
168
- print(f" Tools: {', '.join(entry['tool_calls'])}")
169
-
170
- def do_clear(self, arg):
171
- """Clear chat history."""
172
- self.history.clear()
173
- self.agent.context_manager.session.messages.clear()
174
- print_colored("Chat history cleared.", "green")
175
-
176
- def do_voice(self, arg):
177
- """Toggle voice input."""
178
- self.voice_input = not self.voice_input
179
- status = "enabled" if self.voice_input else "disabled"
180
- print_colored(f"Voice input {status} (note: requires additional setup)", "cyan")
181
-
182
- def do_exit(self, arg):
183
- """Exit chat mode."""
184
- print_colored("Goodbye!", "green")
185
- return True
186
-
187
- def do_EOF(self, arg):
188
- """Handle Ctrl+D."""
189
- print()
190
- return self.do_exit(arg)
191
-
192
- def postcmd(self, stop: bool, line: str) -> bool:
193
- """Save session after each command."""
194
- return stop
195
-
196
- def run(self):
197
- """Run the chat loop."""
198
- print_banner()
199
- print(self.intro)
200
-
201
- try:
202
- self.cmdloop()
203
- except KeyboardInterrupt:
204
- print("\nExiting...")
205
-
206
-
207
- # ============================================================================
208
- # MODE 2: COMMAND EXECUTION
209
- # ============================================================================
210
-
211
- class CommandMode:
212
- """Execute single commands."""
213
-
214
- def __init__(self, agent: StackAgent):
215
- self.agent = agent
216
- self.context_manager = create_context_manager()
217
-
218
- def execute(
219
- self,
220
- query: str,
221
- output_file: Optional[str] = None,
222
- output_format: str = "text"
223
- ) -> str:
224
- """Execute a query and return result."""
225
- print(f"Executing: {query}")
226
-
227
- response = self.agent.process(query)
228
-
229
- result = format_output(response.content, output_format)
230
-
231
- # Save to file if requested
232
- if output_file:
233
- with open(output_file, 'w') as f:
234
- f.write(result)
235
- print(f"Output saved to: {output_file}")
236
-
237
- return result
238
-
239
- def execute_tools(
240
- self,
241
- tools: List[str],
242
- output_file: Optional[str] = None
243
- ) -> str:
244
- """Execute specific tools directly."""
245
- print(f"Executing tools: {', '.join(tools)}")
246
-
247
- response = self.agent.process_with_tools("", tools)
248
-
249
- if output_file:
250
- with open(output_file, 'w') as f:
251
- f.write(response.content)
252
- print(f"Output saved to: {output_file}")
253
-
254
- return response.content
255
-
256
-
257
- # ============================================================================
258
- # MODE 3: VOICE INTERFACE (PLACEHOLDER)
259
- # ============================================================================
260
-
261
- class VoiceInterface:
262
- """Voice input/output interface (requires additional setup)."""
263
-
264
- def __init__(self):
265
- self.available = self._check_availability()
266
-
267
- def _check_availability(self) -> bool:
268
- """Check if voice dependencies are available."""
269
- try:
270
- import speech_recognition as sr
271
- import pyttsx3
272
- return True
273
- except ImportError:
274
- return False
275
-
276
- def listen(self) -> Optional[str]:
277
- """Listen for voice input."""
278
- if not self.available:
279
- print("Voice recognition not available. Install with: pip install SpeechRecognition pyttsx3 pyaudio")
280
- return None
281
-
282
- try:
283
- import speech_recognition as sr
284
-
285
- r = sr.Recognizer()
286
- with sr.Microphone() as source:
287
- print("Listening... (speak now)")
288
- audio = r.listen(source, timeout=5)
289
-
290
- text = r.recognize_google(audio)
291
- print(f"Heard: {text}")
292
- return text
293
- except Exception as e:
294
- print(f"Voice error: {e}")
295
- return None
296
-
297
- def speak(self, text: str):
298
- """Speak text output."""
299
- if not self.available:
300
- print(f"(TTS not available): {text}")
301
- return
302
-
303
- try:
304
- import pyttsx3
305
-
306
- engine = pyttsx3.init()
307
- engine.say(text)
308
- engine.runAndWait()
309
- except Exception as e:
310
- print(f"TTS error: {e}")
311
-
312
-
313
- # ============================================================================
314
- # MAIN CLI
315
- # ============================================================================
316
-
317
- class StackCLI:
318
- """Main CLI entry point."""
319
-
320
- def __init__(self):
321
- self.agent = create_agent()
322
- self.chat_mode = ChatMode(self.agent)
323
- self.command_mode = CommandMode(self.agent)
324
- self.voice = VoiceInterface()
325
-
326
- def run_interactive(self):
327
- """Run interactive chat mode."""
328
- self.chat_mode.run()
329
-
330
- def run_command(self, query: str, out_file: Optional[str] = None, fmt: str = "text"):
331
- """Run a single command."""
332
- result = self.command_mode.execute(query, out_file, fmt)
333
- print(result)
334
- return result
335
-
336
- def run_tools(self, tools: List[str], out_file: Optional[str] = None):
337
- """Run specific tools."""
338
- result = self.command_mode.execute_tools(tools, out_file)
339
- print(result)
340
- return result
341
-
342
- def run_eval(self, benchmark: str, provider: str = 'ollama', model: str = None):
343
- """Run evaluation benchmarks."""
344
- print_colored(f"\n=== Running {benchmark} benchmark ===", "blue")
345
-
346
- import sys
347
- from pathlib import Path
348
- eval_dir = Path(__file__).parent.parent / "stack-2.9-eval"
349
- if eval_dir.exists():
350
- sys.path.insert(0, str(eval_dir))
351
-
352
- try:
353
- if benchmark == 'mbpp':
354
- from benchmarks.mbpp import MBPP
355
- b = MBPP(model_provider=provider, model_name=model)
356
- elif benchmark == 'human_eval':
357
- from benchmarks.human_eval import HumanEval
358
- b = HumanEval(model_provider=provider, model_name=model)
359
- elif benchmark == 'gsm8k':
360
- from benchmarks.gsm8k import GSM8K
361
- b = GSM8K(model_provider=provider, model_name=model)
362
- elif benchmark == 'all':
363
- from benchmarks.mbpp import MBPP
364
- from benchmarks.human_eval import HumanEval
365
- from benchmarks.gsm8k import GSM8K
366
- for name, Benchmark in [('MBPP', MBPP), ('HumanEval', HumanEval), ('GSM8K', GSM8K)]:
367
- print_colored(f"\n--- {name} ---", "yellow")
368
- b = Benchmark(model_provider=provider, model_name=model)
369
- results = b.evaluate()
370
- print(f" Accuracy: {results['accuracy']*100:.1f}%")
371
- return
372
-
373
- results = b.evaluate()
374
- print_colored(f"\nResults:", "green")
375
- print(f" Accuracy: {results['accuracy']*100:.1f}%")
376
- print(f" Passed: {results['pass_at_1']}/{results['total_cases']}")
377
- print(f" Model: {results['model']}")
378
- except Exception as e:
379
- print_colored(f"Error: {e}", "red")
380
-
381
- def run_patterns(self, action: str):
382
- """Manage learned patterns."""
383
- print_colored(f"\n=== Pattern Management ===", "blue")
384
-
385
- import sys
386
- from pathlib import Path
387
- train_dir = Path(__file__).parent.parent / "stack-2.9-training"
388
- if train_dir.exists():
389
- sys.path.insert(0, str(train_dir))
390
-
391
- try:
392
- from pattern_miner import PatternMiner
393
- miner = PatternMiner()
394
-
395
- if action == 'list':
396
- patterns = miner.get_relevant_patterns(limit=20)
397
- print_colored(f"\nStored Patterns:", "yellow")
398
- for p in patterns:
399
- print(f" [{p.pattern_type}] {p.code_snippet[:50]}...")
400
- elif action == 'stats':
401
- stats = miner.get_statistics()
402
- print_colored(f"\nStatistics:", "yellow")
403
- print(f" Total Feedback: {stats['total_feedback']}")
404
- print(f" Success Rate: {stats['success_rate']:.1%}")
405
- print(f" Total Patterns: {stats['total_patterns']}")
406
- except Exception as e:
407
- print_colored(f"Error: {e}", "red")
408
-
409
- def run_voice(self):
410
- """Run voice mode loop."""
411
- if not self.voice.available:
412
- print_colored("Voice interface not available.", "red")
413
- return
414
-
415
- print_banner()
416
- print("Voice Mode - Say 'exit' to quit")
417
-
418
- try:
419
- while True:
420
- text = self.voice.listen()
421
- if not text:
422
- continue
423
-
424
- if "exit" in text.lower():
425
- print("Exiting...")
426
- break
427
-
428
- # Process with agent
429
- response = self.agent.process(text)
430
- print(f"\nResponse: {response.content[:200]}")
431
-
432
- # Speak response
433
- self.voice.speak(response.content[:200])
434
-
435
- except KeyboardInterrupt:
436
- print("\nExiting...")
437
-
438
-
439
- def main():
440
- """Main entry point."""
441
- parser = argparse.ArgumentParser(
442
- description="Stack 2.9 CLI and Agent Interface",
443
- formatter_class=argparse.RawDescriptionHelpFormatter,
444
- epilog="""
445
- Examples:
446
- %(prog)s # Interactive chat mode
447
- %(prog)s -c "read README.md" # Execute single command
448
- %(prog)s -t read write # Execute specific tools
449
- %(prog)s -v # Voice mode
450
- """
451
- )
452
-
453
- parser.add_argument(
454
- '-c', '--command',
455
- help="Execute a single query/command"
456
- )
457
-
458
- parser.add_argument(
459
- '-t', '--tools',
460
- nargs='+',
461
- help="Execute specific tools"
462
- )
463
-
464
- parser.add_argument(
465
- '-o', '--output',
466
- help="Output file for results"
467
- )
468
-
469
- parser.add_argument(
470
- '-f', '--format',
471
- choices=['text', 'json'],
472
- default='text',
473
- help="Output format"
474
- )
475
-
476
- parser.add_argument(
477
- '-v', '--voice',
478
- action='store_true',
479
- help="Enable voice mode"
480
- )
481
-
482
- parser.add_argument(
483
- '-w', '--workspace',
484
- default="/Users/walidsobhi/.openclaw/workspace",
485
- help="Workspace path"
486
- )
487
-
488
- # Evaluation options
489
- parser.add_argument(
490
- '-e', '--eval',
491
- choices=['mbpp', 'human_eval', 'gsm8k', 'all'],
492
- help="Run evaluation benchmark"
493
- )
494
-
495
- parser.add_argument(
496
- '--eval-provider',
497
- default='ollama',
498
- choices=['ollama', 'openai', 'anthropic', 'together'],
499
- help="Model provider for evaluation"
500
- )
501
-
502
- parser.add_argument(
503
- '--eval-model',
504
- type=str,
505
- help="Model name for evaluation"
506
- )
507
-
508
- # Pattern management
509
- parser.add_argument(
510
- '--patterns',
511
- choices=['list', 'stats', 'clear'],
512
- help="Manage learned patterns"
513
- )
514
-
515
- # Training
516
- parser.add_argument(
517
- '--train',
518
- action='store_true',
519
- help="Run LoRA training"
520
- )
521
-
522
- args = parser.parse_args()
523
-
524
- try:
525
- # Create CLI with custom workspace if provided
526
- cli = StackCLI()
527
-
528
- # Handle evaluation
529
- if args.eval:
530
- cli.run_eval(args.eval, args.eval_provider, args.eval_model)
531
- return
532
-
533
- # Handle pattern management
534
- if args.patterns:
535
- cli.run_patterns(args.patterns)
536
- return
537
-
538
- # Handle training
539
- if args.train:
540
- cli.run_train()
541
- return
542
-
543
- if args.voice:
544
- cli.run_voice()
545
- elif args.tools:
546
- cli.run_tools(args.tools, args.output)
547
- elif args.command:
548
- exit(0 if cli.run_command(args.command, args.output, args.format) else 1)
549
- else:
550
- # Interactive mode
551
- cli.run_interactive()
552
-
553
- except KeyboardInterrupt:
554
- print("\nGoodbye!")
555
- sys.exit(0)
556
- except Exception as e:
557
- print(f"Error: {e}", file=sys.stderr)
558
- sys.exit(1)
559
-
560
-
561
- if __name__ == "__main__":
562
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/context.py DELETED
@@ -1,343 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Stack 2.9 - Context Management Module
4
- Handles project awareness, session memory, and long-term memory integration.
5
- """
6
-
7
- import os
8
- import json
9
- import re
10
- from pathlib import Path
11
- from typing import Any, Dict, List, Optional, Set
12
- from datetime import datetime, timedelta
13
- from dataclasses import dataclass, field
14
- from collections import defaultdict
15
-
16
-
17
- @dataclass
18
- class ProjectContext:
19
- """Represents a project's context."""
20
- name: str
21
- path: str
22
- language: Optional[str] = None
23
- framework: Optional[str] = None
24
- files: List[str] = field(default_factory=list)
25
- dirs: List[str] = field(default_factory=list)
26
- has_git: bool = False
27
- dependencies: List[str] = field(default_factory=list)
28
- entry_points: List[str] = field(default_factory=list)
29
- metadata: Dict[str, Any] = field(default_factory=dict)
30
-
31
-
32
- @dataclass
33
- class SessionMemory:
34
- """Represents the current session's memory."""
35
- messages: List[Dict[str, Any]] = field(default_factory=list)
36
- tools_used: List[str] = field(default_factory=list)
37
- files_touched: List[str] = field(default_factory=list)
38
- commands_run: List[str] = field(default_factory=list)
39
- created_at: datetime = field(default_factory=datetime.now)
40
- last_updated: datetime = field(default_factory=datetime.now)
41
-
42
- def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
43
- """Add a message to session memory."""
44
- self.messages.append({
45
- "role": role,
46
- "content": content,
47
- "timestamp": datetime.now().isoformat(),
48
- "metadata": metadata or {}
49
- })
50
- self.last_updated = datetime.now()
51
-
52
- def add_tool_usage(self, tool_name: str, result: Any):
53
- """Record tool usage."""
54
- self.tools_used.append({
55
- "tool": tool_name,
56
- "timestamp": datetime.now().isoformat(),
57
- "success": result.get("success", False) if isinstance(result, dict) else True
58
- })
59
- self.last_updated = datetime.now()
60
-
61
- def add_file_touched(self, file_path: str, action: str):
62
- """Record file access."""
63
- self.files_touched.append({
64
- "path": file_path,
65
- "action": action,
66
- "timestamp": datetime.now().isoformat()
67
- })
68
- self.last_updated = datetime.now()
69
-
70
- def add_command(self, command: str, result: Optional[Dict] = None):
71
- """Record command execution."""
72
- self.commands_run.append({
73
- "command": command,
74
- "result": result,
75
- "timestamp": datetime.now().isoformat()
76
- })
77
- self.last_updated = datetime.now()
78
-
79
- def get_summary(self) -> Dict[str, Any]:
80
- """Get session summary."""
81
- return {
82
- "messages_count": len(self.messages),
83
- "tools_used_count": len(self.tools_used),
84
- "files_touched_count": len(self.files_touched),
85
- "commands_run_count": len(self.commands_run),
86
- "duration_minutes": (datetime.now() - self.created_at).total_seconds() / 60,
87
- "created_at": self.created_at.isoformat(),
88
- "last_updated": self.last_updated.isoformat()
89
- }
90
-
91
-
92
- class ContextManager:
93
- """Manages context across projects, sessions, and long-term memory."""
94
-
95
- def __init__(self, workspace_path: str = "/Users/walidsobhi/.openclaw/workspace"):
96
- self.workspace = Path(workspace_path)
97
- self.session = SessionMemory()
98
- self.projects: Dict[str, ProjectContext] = {}
99
- self.current_project: Optional[ProjectContext] = None
100
- self._load_context()
101
-
102
- def _load_context(self):
103
- """Load existing context files."""
104
- # Load workspace context files
105
- context_files = {
106
- "AGENTS.md": "agents",
107
- "SOUL.md": "soul",
108
- "TOOLS.md": "tools",
109
- "USER.md": "user",
110
- "MEMORY.md": "memory"
111
- }
112
-
113
- self.context = {}
114
- for filename, key in context_files.items():
115
- file_path = self.workspace / filename
116
- if file_path.exists():
117
- self.context[key] = file_path.read_text(encoding='utf-8')
118
-
119
- # Scan for projects
120
- self._scan_projects()
121
-
122
- def _scan_projects(self):
123
- """Scan workspace for projects."""
124
- for item in self.workspace.iterdir():
125
- if item.is_dir() and not item.name.startswith('.'):
126
- # Check if it's a project
127
- if (item / "pyproject.toml").exists() or (item / "package.json").exists():
128
- self.projects[item.name] = ProjectContext(
129
- name=item.name,
130
- path=str(item)
131
- )
132
-
133
- def load_project(self, project_name: str) -> Optional[ProjectContext]:
134
- """Load a specific project."""
135
- project_path = self.workspace / project_name
136
-
137
- if not project_path.exists():
138
- return None
139
-
140
- # Create project context
141
- ctx = ProjectContext(
142
- name=project_name,
143
- path=str(project_path)
144
- )
145
-
146
- # Detect language/framework
147
- if (project_path / "pyproject.toml").exists():
148
- ctx.language = "python"
149
- try:
150
- content = (project_path / "pyproject.toml").read_text()
151
- if "fastapi" in content:
152
- ctx.framework = "fastapi"
153
- elif "django" in content:
154
- ctx.framework = "django"
155
- elif "flask" in content:
156
- ctx.framework = "flask"
157
- except:
158
- pass
159
-
160
- if (project_path / "package.json").exists():
161
- ctx.language = "javascript"
162
- try:
163
- content = json.loads((project_path / "package.json").read_text())
164
- ctx.dependencies = list(content.get("dependencies", {}).keys())
165
- if "next" in content.get("dependencies", {}):
166
- ctx.framework = "next"
167
- elif "react" in content.get("dependencies", {}):
168
- ctx.framework = "react"
169
- except:
170
- pass
171
-
172
- # Check for git
173
- ctx.has_git = (project_path / ".git").exists()
174
-
175
- # Scan files
176
- try:
177
- for item in project_path.rglob("*"):
178
- if len(ctx.files) > 100:
179
- break
180
- rel = item.relative_to(project_path)
181
- if item.is_file():
182
- ctx.files.append(str(rel))
183
- elif item.is_dir() and not item.name.startswith('.'):
184
- ctx.dirs.append(str(rel))
185
- except:
186
- pass
187
-
188
- # Find entry points
189
- entry_patterns = ["main.py", "app.py", "index.js", "main.js", "server.py"]
190
- for pattern in entry_patterns:
191
- for f in ctx.files:
192
- if f.endswith(pattern):
193
- ctx.entry_points.append(f)
194
-
195
- self.projects[project_name] = ctx
196
- self.current_project = ctx
197
- return ctx
198
-
199
- def get_context_summary(self) -> Dict[str, Any]:
200
- """Get context summary."""
201
- return {
202
- "workspace": str(self.workspace),
203
- "projects": list(self.projects.keys()),
204
- "current_project": self.current_project.name if self.current_project else None,
205
- "session": self.session.get_summary(),
206
- "has_agents": "agents" in self.context,
207
- "has_soul": "soul" in self.context,
208
- "has_tools": "tools" in self.context,
209
- "has_memory": "memory" in self.context
210
- }
211
-
212
- def get_workspace_context(self) -> str:
213
- """Get formatted workspace context."""
214
- lines = ["# Workspace Context"]
215
- lines.append(f"\n## Projects ({len(self.projects)})")
216
-
217
- for name, proj in self.projects.items():
218
- lines.append(f"- **{name}** ({proj.language or 'unknown'})")
219
- if proj.framework:
220
- lines.append(f" - Framework: {proj.framework}")
221
- lines.append(f" - Path: {proj.path}")
222
- if proj.has_git:
223
- lines.append(" - Git: ✓")
224
-
225
- if self.current_project:
226
- lines.append(f"\n## Current Project: {self.current_project.name}")
227
- lines.append(f"- Files: {len(self.current_project.files)}")
228
- lines.append(f"- Dirs: {len(self.current_project.dirs)}")
229
- if self.current_project.entry_points:
230
- lines.append(f"- Entry: {self.current_project.entry_points[0]}")
231
-
232
- lines.append(f"\n## Session")
233
- summary = self.session.get_summary()
234
- lines.append(f"- Messages: {summary['messages_count']}")
235
- lines.append(f"- Tools used: {summary['tools_used_count']}")
236
- lines.append(f"- Files touched: {summary['files_touched_count']}")
237
-
238
- return "\n".join(lines)
239
-
240
- def search_memory(self, query: str, max_results: int = 5) -> List[Dict[str, Any]]:
241
- """Search long-term memory."""
242
- results = []
243
-
244
- # Search MEMORY.md
245
- memory_file = self.workspace / "MEMORY.md"
246
- if memory_file.exists():
247
- content = memory_file.read_text()
248
- if query.lower() in content.lower():
249
- results.append({
250
- "file": str(memory_file),
251
- "type": "memory",
252
- "content": content[:500]
253
- })
254
-
255
- # Search memory folder
256
- memory_dir = self.workspace / "memory"
257
- if memory_dir.exists():
258
- for f in memory_dir.rglob("*.md"):
259
- try:
260
- content = f.read_text()
261
- if query.lower() in content.lower():
262
- results.append({
263
- "file": str(f),
264
- "type": "daily",
265
- "content": content[:500]
266
- })
267
- except:
268
- continue
269
-
270
- return results[:max_results]
271
-
272
- def save_to_memory(self, key: str, value: str):
273
- """Save to long-term memory."""
274
- memory_file = self.workspace / "MEMORY.md"
275
-
276
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
277
- entry = f"\n### {key}\n_{timestamp}_\n{value}\n"
278
-
279
- with open(memory_file, "a") as f:
280
- f.write(entry)
281
-
282
- def get_recent_context(self, days: int = 7) -> List[Dict[str, Any]]:
283
- """Get recent context from memory."""
284
- results = []
285
-
286
- memory_dir = self.workspace / "memory"
287
- if memory_dir.exists():
288
- # Get files from last N days
289
- cutoff = datetime.now() - timedelta(days=days)
290
-
291
- for f in sorted(memory_dir.glob("*.md"), key=lambda x: x.stat().st_mtime, reverse=True):
292
- try:
293
- mtime = datetime.fromtimestamp(f.stat().st_mtime)
294
- if mtime > cutoff:
295
- content = f.read_text()
296
- results.append({
297
- "file": str(f),
298
- "date": mtime.isoformat(),
299
- "content": content[:1000]
300
- })
301
- except:
302
- continue
303
-
304
- return results
305
-
306
-
307
- class ProjectAware:
308
- """Mixin for project-aware functionality."""
309
-
310
- def __init__(self):
311
- self.context_manager = ContextManager()
312
-
313
- def detect_project(self, path: str) -> Optional[str]:
314
- """Detect project from path."""
315
- p = Path(path).resolve()
316
-
317
- # Walk up to find project root
318
- while p != p.parent:
319
- for name in ["pyproject.toml", "package.json", "Cargo.toml", "go.mod"]:
320
- if (p / name).exists():
321
- return p.name
322
- p = p.parent
323
-
324
- return None
325
-
326
- def get_project_context(self, project_name: str) -> Optional[ProjectContext]:
327
- """Get project context."""
328
- return self.context_manager.load_project(project_name)
329
-
330
- def format_context_for_prompt(self) -> str:
331
- """Format context for LLM prompt."""
332
- return self.context_manager.get_workspace_context()
333
-
334
-
335
- def create_context_manager(workspace: Optional[str] = None) -> ContextManager:
336
- """Factory function to create context manager."""
337
- return ContextManager(workspace or "/Users/walidsobhi/.openclaw/workspace")
338
-
339
-
340
- if __name__ == "__main__":
341
- print("Stack 2.9 Context Module")
342
- cm = ContextManager()
343
- print(cm.get_context_summary())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/main.py DELETED
@@ -1,336 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Stack 2.9 CLI - Terminal User Interface
4
- Main entry point for interacting with Stack 2.9
5
- """
6
-
7
- import os
8
- import sys
9
- import argparse
10
- from pathlib import Path
11
- from typing import Optional
12
-
13
- # Add parent directories to path
14
- sys.path.insert(0, str(Path(__file__).parent))
15
- sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "eval"))
16
- sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "training"))
17
-
18
- from model_client import create_model_client, ChatMessage
19
- from pattern_miner import PatternMiner
20
- from data_quality import DataQualityAnalyzer
21
-
22
-
23
- class Stack29CLI:
24
- """Stack 2.9 Terminal User Interface"""
25
-
26
- def __init__(self, provider: str = None, model: str = None):
27
- self.provider = provider or os.environ.get("MODEL_PROVIDER", "ollama")
28
- self.model = model or os.environ.get("MODEL_NAME", "")
29
- self.client = None
30
- self.agent = None
31
- self.miner = PatternMiner()
32
- self.chat_history = []
33
-
34
- # Colors
35
- self.BLUE = '\033[94m'
36
- self.GREEN = '\033[92m'
37
- self.YELLOW = '\033[93m'
38
- self.RED = '\033[91m'
39
- self.END = '\033[0m'
40
- self.BOLD = '\033[1m'
41
-
42
- def print_header(self):
43
- """Print CLI header"""
44
- print(f"""
45
- {self.BLUE}╔═══════════════════════════════════════════════════════╗
46
- ║ {self.BOLD}Stack 2.9 - Self-Evolving AI{self.END}{self.BLUE} ║
47
- ║ {self.YELLOW}Your AI coding companion{self.END}{self.BLUE} ║
48
- ╚═══════════════════════════════════════════════════════╝{self.END}
49
- """)
50
-
51
- def print_menu(self):
52
- """Print main menu"""
53
- print(f"""
54
- {self.BOLD}Main Menu:{self.END}
55
- {self.GREEN}[1]{self.END} Chat with Stack 2.9
56
- {self.GREEN}[2]{self.END} Run Evaluation (Benchmarks)
57
- {self.GREEN}[3]{self.END} Manage Patterns (Self-Evolution)
58
- {self.GREEN}[4]{self.END} Train Model (LoRA Fine-tuning)
59
- {self.GREEN}[5]{self.END} Settings
60
- {self.GREEN}[0]{self.END} Exit
61
-
62
- """)
63
-
64
- def init_client(self):
65
- """Initialize model client"""
66
- try:
67
- self.client = create_model_client(self.provider, self.model)
68
- print(f"{self.GREEN}✓{self.END} Connected to {self.provider}: {self.client.get_model_name()}")
69
- except Exception as e:
70
- print(f"{self.RED}✗{self.END} Failed to connect: {e}")
71
- print(f"{self.YELLOW}!{self.END} Make sure {self.provider} is running")
72
- self.client = None
73
-
74
- def chat_mode(self):
75
- """Interactive chat mode using agent with tool calling"""
76
- if not self.client:
77
- print(f"{self.RED}No model connected!{self.END}")
78
- return
79
-
80
- print(f"\n{self.BLUE}=== Chat Mode ==={self.END}")
81
- print("Type 'exit' to return to menu, 'clear' to clear history\n")
82
-
83
- # Initialize agent if not done
84
- if not hasattr(self, 'agent') or self.agent is None:
85
- from cli.agent import StackAgent
86
- self.agent = StackAgent(workspace='/Users/walidsobhi/stack-2.9')
87
- print(f"{self.GREEN}✓{self.END} Agent initialized")
88
-
89
- while True:
90
- try:
91
- user_input = input(f"{self.GREEN}You:{self.END} ").strip()
92
-
93
- if not user_input:
94
- continue
95
-
96
- if user_input.lower() in ['exit', 'quit', 'q']:
97
- break
98
-
99
- if user_input.lower() == 'clear':
100
- print("Chat cleared.\n")
101
- continue
102
-
103
- # Process through agent (handles tool calling)
104
- print(f"{self.BLUE}Stack 2.9:{self.END} ", end="", flush=True)
105
-
106
- try:
107
- response = self.agent.process(user_input)
108
- print(response.content)
109
-
110
- except Exception as e:
111
- print(f"{self.RED}Error: {e}{self.END}")
112
-
113
- except Exception as e:
114
- print(f"{self.RED}Error: {e}{self.END}")
115
-
116
- print()
117
-
118
- except KeyboardInterrupt:
119
- print("\n")
120
- break
121
-
122
- def eval_mode(self):
123
- """Run evaluation benchmarks"""
124
- print(f"\n{self.BLUE}=== Evaluation ==={self.END}")
125
- print(f"{self.GREEN}[1]{self.END} MBPP (Code Generation)")
126
- print(f"{self.GREEN}[2]{self.END} HumanEval (Python)")
127
- print(f"{self.GREEN}[3]{self.END} GSM8K (Math)")
128
- print(f"{self.GREEN}[4]{self.END} Run All")
129
- print(f"{self.GREEN}[0]{self.END} Back")
130
-
131
- choice = input("\nSelect: ").strip()
132
-
133
- if choice == '0':
134
- return
135
-
136
- benchmarks = {
137
- '1': ('mbpp', 'MBPP'),
138
- '2': ('human_eval', 'HumanEval'),
139
- '3': ('gsm8k', 'GSM8K'),
140
- '4': ('all', 'All')
141
- }
142
-
143
- if choice not in benchmarks:
144
- print(f"{self.RED}Invalid choice{self.END}")
145
- return
146
-
147
- bench_name, bench_label = benchmarks[choice]
148
-
149
- print(f"\n{self.YELLOW}Running {bench_label} benchmark...{self.END}")
150
-
151
- try:
152
- if bench_name == 'all':
153
- from benchmarks.mbpp import MBPP
154
- from benchmarks.human_eval import HumanEval
155
- from benchmarks.gsm8k import GSM8K
156
-
157
- for name, Benchmark in [('MBPP', MBPP), ('HumanEval', HumanEval), ('GSM8K', GSM8K)]:
158
- print(f"\n--- {name} ---")
159
- b = Benchmark(model_provider=self.provider, model_name=self.model)
160
- results = b.evaluate()
161
- print(f" Accuracy: {results['accuracy']*100:.1f}%")
162
- else:
163
- module = __import__(f'benchmarks.{bench_name}', fromlist=['MBPP', 'HumanEval', 'GSM8K'])
164
- Benchmark = getattr(module, bench_name.upper() if bench_name != 'mbpp' else 'MBPP')
165
- b = Benchmark(model_provider=self.provider, model_name=self.model)
166
- results = b.evaluate()
167
- print(f"\n{self.GREEN}Results:{self.END}")
168
- print(f" Accuracy: {results['accuracy']*100:.1f}%")
169
- print(f" Passed: {results['pass_at_1']}/{results['total_cases']}")
170
-
171
- except Exception as e:
172
- print(f"{self.RED}Error: {e}{self.END}")
173
-
174
- input("\nPress Enter to continue...")
175
-
176
- def pattern_mode(self):
177
- """Manage patterns for self-evolution"""
178
- print(f"\n{self.BLUE}=== Pattern Manager ==={self.END}")
179
- print(f"{self.GREEN}[1]{self.END} View Patterns")
180
- print(f"{self.GREEN}[2]{self.END} View Statistics")
181
- print(f"{self.GREEN}[3]{self.END} Generate Synthetic Data")
182
- print(f"{self.GREEN}[4]{self.END} Clear Patterns")
183
- print(f"{self.GREEN}[0]{self.END} Back")
184
-
185
- choice = input("\nSelect: ").strip()
186
-
187
- if choice == '0':
188
- return
189
-
190
- if choice == '1':
191
- patterns = self.miner.get_relevant_patterns(limit=20)
192
- print(f"\n{self.YELLOW}Stored Patterns ({len(patterns)}):{self.END}")
193
- for p in patterns:
194
- print(f" [{p.pattern_type}] {p.code_snippet[:50]}... (rate: {p.success_rate:.0%})")
195
-
196
- elif choice == '2':
197
- stats = self.miner.get_statistics()
198
- print(f"\n{self.YELLOW}Statistics:{self.END}")
199
- print(f" Total Feedback: {stats['total_feedback']}")
200
- print(f" Success Rate: {stats['success_rate']:.1%}")
201
- print(f" Total Patterns: {stats['total_patterns']}")
202
- print(f" By Type: {stats['patterns_by_type']}")
203
-
204
- elif choice == '3':
205
- try:
206
- count = int(input("Number of examples: ").strip())
207
- self.miner.store_feedback(
208
- problem_type="synthetic",
209
- solution="# Synthetic pattern",
210
- success=True
211
- )
212
- print(f"{self.GREEN}✓{self.END} Generated {count} synthetic patterns")
213
- except ValueError:
214
- print(f"{self.RED}Invalid number{self.END}")
215
-
216
- elif choice == '4':
217
- confirm = input("Clear all patterns? (y/n): ").strip().lower()
218
- if confirm == 'y':
219
- # Note: This would need a clear method in PatternMiner
220
- print(f"{self.YELLOW}Feature not implemented{self.END}")
221
-
222
- input("\nPress Enter to continue...")
223
-
224
- def train_mode(self):
225
- """Train model with LoRA"""
226
- print(f"\n{self.BLUE}=== Training ==={self.END}")
227
- print(f"{self.YELLOW}Note: Requires GPU and training data{self.END}")
228
- print(f"\n{self.GREEN}[1]{self.END} Prepare Data")
229
- print(f"{self.GREEN}[2]{self.END} Train LoRA")
230
- print(f"{self.GREEN}[3]{self.END} Merge Adapter")
231
- print(f"{self.GREEN}[0]{self.END} Back")
232
-
233
- choice = input("\nSelect: ").strip()
234
-
235
- if choice == '0':
236
- return
237
-
238
- if choice == '1':
239
- print(f"\n{self.YELLOW}Preparing training data...{self.END}")
240
- try:
241
- from prepare_data import prepare_data
242
- result = prepare_data()
243
- print(f"{self.GREEN}✓{self.END} Prepared {result['train_samples']} training samples")
244
- except Exception as e:
245
- print(f"{self.RED}Error: {e}{self.END}")
246
-
247
- elif choice == '2':
248
- print(f"\n{YELLOW}Training LoRA...{self.END}")
249
- print(f"{self.YELLOW}Note: This requires significant GPU resources{self.END}")
250
- confirm = input("Continue? (y/n): ").strip().lower()
251
- if confirm == 'y':
252
- try:
253
- from train_lora import train_lora
254
- trainer = train_lora()
255
- print(f"{self.GREEN}✓{self.END} Training complete")
256
- except Exception as e:
257
- print(f"{self.RED}Error: {e}{self.END}")
258
-
259
- input("\nPress Enter to continue...")
260
-
261
- def settings_mode(self):
262
- """Configure settings"""
263
- print(f"\n{self.BLUE}=== Settings ==={self.END}")
264
- print(f"Provider: {self.provider}")
265
- print(f"Model: {self.model}")
266
- print(f"\n{self.GREEN}[1]{self.END} Change Provider")
267
- print(f"{self.GREEN}[2]{self.END} Change Model")
268
- print(f"{self.GREEN}[0]{self.END} Back")
269
-
270
- choice = input("\nSelect: ").strip()
271
-
272
- if choice == '1':
273
- print("Providers: ollama, openai, anthropic")
274
- new_provider = input("Provider: ").strip()
275
- if new_provider in ['ollama', 'openai', 'anthropic']:
276
- self.provider = new_provider
277
- self.init_client()
278
-
279
- elif choice == '2':
280
- new_model = input("Model name: ").strip()
281
- if new_model:
282
- self.model = new_model
283
- self.init_client()
284
-
285
- def run(self):
286
- """Run the CLI"""
287
- self.print_header()
288
- self.init_client()
289
-
290
- while True:
291
- self.print_menu()
292
- choice = input(f"{self.GREEN}Select>{self.END} ").strip()
293
-
294
- if choice == '0':
295
- print(f"\n{self.BLUE}Thanks for using Stack 2.9!{self.END}\n")
296
- break
297
-
298
- if choice == '1':
299
- self.chat_mode()
300
- elif choice == '2':
301
- self.eval_mode()
302
- elif choice == '3':
303
- self.pattern_mode()
304
- elif choice == '4':
305
- self.train_mode()
306
- elif choice == '5':
307
- self.settings_mode()
308
- else:
309
- print(f"{self.RED}Invalid option{self.END}")
310
-
311
-
312
- def main():
313
- parser = argparse.ArgumentParser(description="Stack 2.9 CLI")
314
- parser.add_argument("--provider", "-p", choices=["ollama", "openai", "anthropic"],
315
- help="Model provider")
316
- parser.add_argument("--model", "-m", type=str, help="Model name")
317
- parser.add_argument("--chat", "-c", action="store_true", help="Start in chat mode")
318
- parser.add_argument("--eval", "-e", choices=["mbpp", "human_eval", "gsm8k", "all"],
319
- help="Run evaluation")
320
-
321
- args = parser.parse_args()
322
-
323
- cli = Stack29CLI(provider=args.provider, model=args.model)
324
-
325
- if args.chat:
326
- cli.init_client()
327
- cli.chat_mode()
328
- elif args.eval:
329
- cli.init_client()
330
- cli.eval_mode()
331
- else:
332
- cli.run()
333
-
334
-
335
- if __name__ == "__main__":
336
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/pyproject.toml DELETED
@@ -1,70 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "stack-cli"
7
- version = "2.9.0"
8
- description = "Stack 2.9 CLI and Agent Interface"
9
- readme = "../STACK_CLI_README.md"
10
- license = {text = "MIT"}
11
- authors = [
12
- {name = "Walid Sobhi", email = "walid@example.com"}
13
- ]
14
- keywords = ["cli", "agent", "ai", "automation", "development"]
15
- classifiers = [
16
- "Development Status :: 3 - Alpha",
17
- "Intended Audience :: Developers",
18
- "License :: OSI Approved :: MIT License",
19
- "Programming Language :: Python :: 3",
20
- "Programming Language :: Python :: 3.8",
21
- "Programming Language :: Python :: 3.9",
22
- "Programming Language :: Python :: 3.10",
23
- "Programming Language :: Python :: 3.11",
24
- "Topic :: Software Development :: Tools",
25
- ]
26
- requires-python = ">=3.8"
27
- dependencies = [
28
- "openai>=1.0.0",
29
- "openrouter>=1.0.0",
30
- "anthropic>=0.8.0",
31
- "aiohttp>=3.9.0",
32
- "python-dotenv>=1.0.0",
33
- "prompt-toolkit>=3.0.0",
34
- "rich>=13.0.0",
35
- "click>=8.0.0",
36
- ]
37
-
38
- [project.optional-dependencies]
39
- dev = [
40
- "pytest>=7.0.0",
41
- "pytest-cov>=4.0.0",
42
- "ruff>=0.1.0",
43
- "mypy>=1.0.0",
44
- ]
45
- voice = [
46
- "SpeechRecognition>=3.10.0",
47
- "pyttsx3>=2.90",
48
- ]
49
-
50
- [project.scripts]
51
- stack = "stack_cli.cli:main"
52
-
53
- [tool.setuptools.packages.find]
54
- where = ["."]
55
- include = ["stack_cli*"]
56
-
57
- [tool.black]
58
- line-length = 100
59
- target-version = ['py38', 'py39', 'py310', 'py311']
60
- include = '\.pyi?$'
61
-
62
- [tool.ruff]
63
- line-length = 100
64
- target-version = "py38"
65
-
66
- [tool.mypy]
67
- python_version = "3.8"
68
- warn_return_any = true
69
- warn_unused_configs = true
70
- disallow_untyped_defs = false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/cli/tools.py DELETED
@@ -1,1308 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Stack 2.9 - Built-in Tools Module
4
- 38 powerful tools for file operations, git, code execution, web, memory, and planning.
5
- """
6
-
7
- import os
8
- import re
9
- import json
10
- import subprocess
11
- import shutil
12
- from pathlib import Path
13
- from typing import Any, Callable, Dict, List, Optional, Union
14
- from datetime import datetime, timedelta
15
- import hashlib
16
-
17
-
18
- # ============================================================================
19
- # FILE OPERATIONS TOOLS (Tools 1-8)
20
- # ============================================================================
21
-
22
- def tool_read_file(path: str, offset: int = 0, limit: int = -1) -> Dict[str, Any]:
23
- """Read file contents with optional offset and limit."""
24
- try:
25
- p = Path(path)
26
- if not p.exists():
27
- return {"success": False, "error": f"File not found: {path}"}
28
-
29
- content = p.read_text(encoding='utf-8')
30
- lines = content.split('\n')
31
-
32
- if limit > 0:
33
- lines = lines[offset:offset + limit]
34
-
35
- return {
36
- "success": True,
37
- "content": '\n'.join(lines),
38
- "total_lines": len(content.split('\n')),
39
- "path": path
40
- }
41
- except Exception as e:
42
- return {"success": False, "error": str(e)}
43
-
44
-
45
- def tool_write_file(path: str, content: str, append: bool = False) -> Dict[str, Any]:
46
- """Write content to file (create or overwrite)."""
47
- try:
48
- p = Path(path)
49
- p.parent.mkdir(parents=True, exist_ok=True)
50
-
51
- if append:
52
- p.write_text(content, encoding='utf-8')
53
- else:
54
- p.write_text(content, encoding='utf-8')
55
-
56
- return {
57
- "success": True,
58
- "path": path,
59
- "lines_written": len(content.split('\n'))
60
- }
61
- except Exception as e:
62
- return {"success": False, "error": str(e)}
63
-
64
-
65
- def tool_edit_file(path: str, old_text: str, new_text: str) -> Dict[str, Any]:
66
- """Edit file using exact text replacement."""
67
- try:
68
- p = Path(path)
69
- if not p.exists():
70
- return {"success": False, "error": f"File not found: {path}"}
71
-
72
- content = p.read_text(encoding='utf-8')
73
- if old_text not in content:
74
- return {"success": False, "error": "Text to replace not found"}
75
-
76
- new_content = content.replace(old_text, new_text, 1)
77
- p.write_text(new_content, encoding='utf-8')
78
-
79
- return {
80
- "success": True,
81
- "path": path,
82
- "edits_made": 1
83
- }
84
- except Exception as e:
85
- return {"success": False, "error": str(e)}
86
-
87
-
88
- def tool_search_files(
89
- path: str,
90
- pattern: str,
91
- exclude: Optional[List[str]] = None
92
- ) -> Dict[str, Any]:
93
- """Recursively search for files matching a pattern."""
94
- try:
95
- # Expand ~ to home directory
96
- base_path = Path(os.path.expanduser(path))
97
- if not base_path.exists():
98
- return {"success": False, "error": f"Path not found: {path}"}
99
-
100
- results = []
101
- exclude = exclude or []
102
-
103
- for p in base_path.rglob(pattern):
104
- # Check if any exclusion pattern matches
105
- skip = False
106
- for exc in exclude:
107
- if exc in str(p):
108
- skip = True
109
- break
110
- if not skip:
111
- results.append(str(p))
112
-
113
- return {
114
- "success": True,
115
- "matches": results,
116
- "count": len(results)
117
- }
118
- except Exception as e:
119
- return {"success": False, "error": str(e)}
120
-
121
-
122
- def tool_grep(path: str, pattern: str, context: int = 0) -> Dict[str, Any]:
123
- """Search for pattern in file(s)."""
124
- try:
125
- # Expand ~ to home directory
126
- base_path = Path(os.path.expanduser(path))
127
- results = []
128
-
129
- if base_path.is_file():
130
- files = [base_path]
131
- elif base_path.is_dir():
132
- files = list(base_path.rglob('*'))
133
- files = [f for f in files if f.is_file()]
134
- else:
135
- return {"success": False, "error": f"Invalid path: {path}"}
136
-
137
- for f in files:
138
- try:
139
- content = f.read_text(encoding='utf-8', errors='ignore')
140
- lines = content.split('\n')
141
-
142
- for i, line in enumerate(lines):
143
- if re.search(pattern, line):
144
- result = {
145
- "file": str(f),
146
- "line": i + 1,
147
- "content": line.strip()
148
- }
149
- if context > 0:
150
- start = max(0, i - context)
151
- end = min(len(lines), i + context + 1)
152
- result["context"] = lines[start:end]
153
- results.append(result)
154
- except:
155
- continue
156
-
157
- return {
158
- "success": True,
159
- "matches": results,
160
- "count": len(results)
161
- }
162
- except Exception as e:
163
- return {"success": False, "error": str(e)}
164
-
165
-
166
- def tool_copy_file(source: str, destination: str) -> Dict[str, Any]:
167
- """Copy file or directory."""
168
- try:
169
- src = Path(source)
170
- dst = Path(destination)
171
-
172
- if not src.exists():
173
- return {"success": False, "error": f"Source not found: {source}"}
174
-
175
- if src.is_dir():
176
- shutil.copytree(src, dst)
177
- else:
178
- dst.parent.mkdir(parents=True, exist_ok=True)
179
- shutil.copy2(src, dst)
180
-
181
- return {
182
- "success": True,
183
- "source": source,
184
- "destination": destination
185
- }
186
- except Exception as e:
187
- return {"success": False, "error": str(e)}
188
-
189
-
190
- def tool_move_file(source: str, destination: str) -> Dict[str, Any]:
191
- """Move or rename file or directory."""
192
- try:
193
- src = Path(source)
194
- dst = Path(destination)
195
-
196
- if not src.exists():
197
- return {"success": False, "error": f"Source not found: {source}"}
198
-
199
- dst.parent.mkdir(parents=True, exist_ok=True)
200
- shutil.move(str(src), str(dst))
201
-
202
- return {
203
- "success": True,
204
- "source": source,
205
- "destination": destination
206
- }
207
- except Exception as e:
208
- return {"success": False, "error": str(e)}
209
-
210
-
211
- def tool_delete_file(path: str, force: bool = False) -> Dict[str, Any]:
212
- """Delete file or directory (use trash for safe delete)."""
213
- try:
214
- p = Path(path)
215
-
216
- if not p.exists():
217
- return {"success": False, "error": f"Path not found: {path}"}
218
-
219
- # For safety, require force=True for destructive delete
220
- if not force:
221
- # Just report what would be deleted
222
- return {
223
- "success": True,
224
- "would_delete": str(p),
225
- "warning": "Set force=True to actually delete"
226
- }
227
-
228
- if p.is_dir():
229
- shutil.rmtree(p)
230
- else:
231
- p.unlink()
232
-
233
- return {
234
- "success": True,
235
- "deleted": str(p)
236
- }
237
- except Exception as e:
238
- return {"success": False, "error": str(e)}
239
-
240
-
241
- # ============================================================================
242
- # GIT OPERATIONS TOOLS (Tools 9-15)
243
- # ============================================================================
244
-
245
- def tool_git_status(repo_path: str = ".") -> Dict[str, Any]:
246
- """Get git status."""
247
- try:
248
- result = subprocess.run(
249
- ["git", "-C", repo_path, "status", "--porcelain"],
250
- capture_output=True,
251
- text=True,
252
- timeout=30
253
- )
254
-
255
- files = [line[3:] for line in result.stdout.strip().split('\n') if line]
256
-
257
- return {
258
- "success": True,
259
- "files": files,
260
- "count": len(files),
261
- "repo": repo_path
262
- }
263
- except Exception as e:
264
- return {"success": False, "error": str(e)}
265
-
266
-
267
- def tool_git_commit(repo_path: str, message: str, files: Optional[List[str]] = None) -> Dict[str, Any]:
268
- """Create a git commit."""
269
- try:
270
- # Stage files if provided
271
- if files:
272
- for f in files:
273
- subprocess.run(
274
- ["git", "-C", repo_path, "add", f],
275
- capture_output=True,
276
- timeout=30
277
- )
278
- else:
279
- subprocess.run(
280
- ["git", "-C", repo_path, "add", "-A"],
281
- capture_output=True,
282
- timeout=30
283
- )
284
-
285
- # Check if there are changes to commit
286
- result = subprocess.run(
287
- ["git", "-C", repo_path, "status", "--porcelain"],
288
- capture_output=True,
289
- text=True,
290
- timeout=30
291
- )
292
-
293
- if not result.stdout.strip():
294
- return {"success": True, "message": "No changes to commit"}
295
-
296
- # Commit
297
- result = subprocess.run(
298
- ["git", "-C", repo_path, "commit", "-m", message],
299
- capture_output=True,
300
- text=True,
301
- timeout=30
302
- )
303
-
304
- return {
305
- "success": True,
306
- "message": message,
307
- "output": result.stdout + result.stderr
308
- }
309
- except Exception as e:
310
- return {"success": False, "error": str(e)}
311
-
312
-
313
- def tool_git_push(repo_path: str = ".", remote: str = "origin", branch: Optional[str] = None) -> Dict[str, Any]:
314
- """Push to remote."""
315
- try:
316
- cmd = ["git", "-C", repo_path, "push", remote]
317
- if branch:
318
- cmd.append(branch)
319
-
320
- result = subprocess.run(
321
- cmd,
322
- capture_output=True,
323
- text=True,
324
- timeout=60
325
- )
326
-
327
- return {
328
- "success": True,
329
- "remote": remote,
330
- "branch": branch,
331
- "output": result.stdout + result.stderr
332
- }
333
- except Exception as e:
334
- return {"success": False, "error": str(e)}
335
-
336
-
337
- def tool_git_pull(repo_path: str = ".", remote: str = "origin", branch: Optional[str] = None) -> Dict[str, Any]:
338
- """Pull from remote."""
339
- try:
340
- cmd = ["git", "-C", repo_path, "pull", remote]
341
- if branch:
342
- cmd.append(branch)
343
-
344
- result = subprocess.run(
345
- cmd,
346
- capture_output=True,
347
- text=True,
348
- timeout=60
349
- )
350
-
351
- return {
352
- "success": True,
353
- "remote": remote,
354
- "branch": branch,
355
- "output": result.stdout + result.stderr
356
- }
357
- except Exception as e:
358
- return {"success": False, "error": str(e)}
359
-
360
-
361
- def tool_git_branch(repo_path: str = ".", create: Optional[str] = None, delete: Optional[str] = None) -> Dict[str, Any]:
362
- """List, create, or delete branches."""
363
- try:
364
- if create:
365
- result = subprocess.run(
366
- ["git", "-C", repo_path, "checkout", "-b", create],
367
- capture_output=True,
368
- text=True,
369
- timeout=30
370
- )
371
- return {"success": True, "created": create}
372
-
373
- if delete:
374
- result = subprocess.run(
375
- ["git", "-C", repo_path, "branch", "-D", delete],
376
- capture_output=True,
377
- text=True,
378
- timeout=30
379
- )
380
- return {"success": True, "deleted": delete}
381
-
382
- # List branches
383
- result = subprocess.run(
384
- ["git", "-C", repo_path, "branch", "-a"],
385
- capture_output=True,
386
- text=True,
387
- timeout=30
388
- )
389
-
390
- branches = [b.strip().replace('* ', '') for b in result.stdout.strip().split('\n') if b]
391
-
392
- return {
393
- "success": True,
394
- "branches": branches,
395
- "count": len(branches)
396
- }
397
- except Exception as e:
398
- return {"success": False, "error": str(e)}
399
-
400
-
401
- def tool_git_log(repo_path: str = ".", limit: int = 10) -> Dict[str, Any]:
402
- """Get git log."""
403
- try:
404
- result = subprocess.run(
405
- ["git", "-C", repo_path, "log", f"--max-count={limit}", "--oneline"],
406
- capture_output=True,
407
- text=True,
408
- timeout=30
409
- )
410
-
411
- commits = result.stdout.strip().split('\n')
412
-
413
- return {
414
- "success": True,
415
- "commits": commits,
416
- "count": len([c for c in commits if c])
417
- }
418
- except Exception as e:
419
- return {"success": False, "error": str(e)}
420
-
421
-
422
- def tool_git_diff(repo_path: str = ".", file: Optional[str] = None, staged: bool = False) -> Dict[str, Any]:
423
- """Get git diff."""
424
- try:
425
- cmd = ["git", "-C", repo_path, "diff"]
426
- if staged:
427
- cmd.append("--staged")
428
- if file:
429
- cmd.append(file)
430
-
431
- result = subprocess.run(
432
- cmd,
433
- capture_output=True,
434
- text=True,
435
- timeout=30
436
- )
437
-
438
- return {
439
- "success": True,
440
- "diff": result.stdout,
441
- "has_changes": bool(result.stdout.strip())
442
- }
443
- except Exception as e:
444
- return {"success": False, "error": str(e)}
445
-
446
-
447
- # ============================================================================
448
- # CODE EXECUTION TOOLS (Tools 16-22)
449
- # ============================================================================
450
-
451
- def tool_run_command(
452
- command: str,
453
- timeout: int = 60,
454
- cwd: Optional[str] = None,
455
- env: Optional[Dict[str, str]] = None
456
- ) -> Dict[str, Any]:
457
- """Run shell command."""
458
- try:
459
- result = subprocess.run(
460
- command,
461
- shell=True,
462
- capture_output=True,
463
- text=True,
464
- timeout=timeout,
465
- cwd=cwd,
466
- env={**os.environ, **(env or {})}
467
- )
468
-
469
- return {
470
- "success": result.returncode == 0,
471
- "returncode": result.returncode,
472
- "stdout": result.stdout,
473
- "stderr": result.stderr,
474
- "command": command
475
- }
476
- except subprocess.TimeoutExpired:
477
- return {"success": False, "error": "Command timed out"}
478
- except Exception as e:
479
- return {"success": False, "error": str(e)}
480
-
481
-
482
- def tool_run_tests(path: str = ".", pattern: str = "test*.py", verbose: bool = True) -> Dict[str, Any]:
483
- """Run tests using pytest."""
484
- try:
485
- cmd = ["pytest", path, "-k", pattern]
486
- if verbose:
487
- cmd.append("-v")
488
-
489
- result = subprocess.run(
490
- cmd,
491
- capture_output=True,
492
- text=True,
493
- timeout=300,
494
- cwd=path
495
- )
496
-
497
- return {
498
- "success": result.returncode == 0,
499
- "output": result.stdout,
500
- "errors": result.stderr,
501
- "returncode": result.returncode
502
- }
503
- except FileNotFoundError:
504
- return {"success": False, "error": "pytest not found"}
505
- except Exception as e:
506
- return {"success": False, "error": str(e)}
507
-
508
-
509
- def tool_lint_code(path: str = ".", linter: str = "ruff") -> Dict[str, Any]:
510
- """Lint code."""
511
- try:
512
- if linter == "ruff":
513
- result = subprocess.run(
514
- ["ruff", "check", path],
515
- capture_output=True,
516
- text=True,
517
- timeout=120
518
- )
519
- elif linter == "pylint":
520
- result = subprocess.run(
521
- ["pylint", path],
522
- capture_output=True,
523
- text=True,
524
- timeout=120
525
- )
526
- elif linter == "mypy":
527
- result = subprocess.run(
528
- ["mypy", path],
529
- capture_output=True,
530
- text=True,
531
- timeout=120
532
- )
533
- else:
534
- return {"success": False, "error": f"Unknown linter: {linter}"}
535
-
536
- return {
537
- "success": result.returncode == 0,
538
- "output": result.stdout,
539
- "errors": result.stderr
540
- }
541
- except FileNotFoundError:
542
- return {"success": False, "error": f"{linter} not found"}
543
- except Exception as e:
544
- return {"success": False, "error": str(e)}
545
-
546
-
547
- def tool_format_code(path: str = ".", formatter: str = "ruff") -> Dict[str, Any]:
548
- """Format code."""
549
- try:
550
- if formatter == "ruff":
551
- result = subprocess.run(
552
- ["ruff", "format", path],
553
- capture_output=True,
554
- text=True,
555
- timeout=120
556
- )
557
- elif formatter == "black":
558
- result = subprocess.run(
559
- ["black", path],
560
- capture_output=True,
561
- text=True,
562
- timeout=120
563
- )
564
- else:
565
- return {"success": False, "error": f"Unknown formatter: {formatter}"}
566
-
567
- return {
568
- "success": result.returncode == 0,
569
- "output": result.stdout,
570
- "errors": result.stderr
571
- }
572
- except FileNotFoundError:
573
- return {"success": False, "error": f"{formatter} not found"}
574
- except Exception as e:
575
- return {"success": False, "error": str(e)}
576
-
577
-
578
- def tool_check_type(path: str = ".") -> Dict[str, Any]:
579
- """Type check with mypy."""
580
- try:
581
- result = subprocess.run(
582
- ["mypy", path, "--ignore-missing-imports"],
583
- capture_output=True,
584
- text=True,
585
- timeout=180
586
- )
587
-
588
- return {
589
- "success": result.returncode == 0,
590
- "output": result.stdout,
591
- "errors": result.stderr
592
- }
593
- except FileNotFoundError:
594
- return {"success": False, "error": "mypy not found"}
595
- except Exception as e:
596
- return {"success": False, "error": str(e)}
597
-
598
-
599
- def tool_start_server(
600
- command: str,
601
- port: int,
602
- cwd: Optional[str] = None,
603
- background: bool = False
604
- ) -> Dict[str, Any]:
605
- """Start a development server."""
606
- try:
607
- if background:
608
- proc = subprocess.Popen(
609
- command,
610
- shell=True,
611
- cwd=cwd,
612
- stdout=subprocess.PIPE,
613
- stderr=subprocess.PIPE
614
- )
615
- return {
616
- "success": True,
617
- "pid": proc.pid,
618
- "port": port,
619
- "message": f"Server started on port {port}"
620
- }
621
- else:
622
- result = subprocess.run(
623
- command,
624
- shell=True,
625
- capture_output=True,
626
- text=True,
627
- cwd=cwd
628
- )
629
- return {
630
- "success": result.returncode == 0,
631
- "output": result.stdout
632
- }
633
- except Exception as e:
634
- return {"success": False, "error": str(e)}
635
-
636
-
637
- def tool_install_dependencies(path: str = ".", package_manager: str = "pip") -> Dict[str, Any]:
638
- """Install dependencies."""
639
- try:
640
- if package_manager == "pip":
641
- result = subprocess.run(
642
- ["pip", "install", "-r", "requirements.txt"],
643
- capture_output=True,
644
- text=True,
645
- timeout=300,
646
- cwd=path
647
- )
648
- elif package_manager == "poetry":
649
- result = subprocess.run(
650
- ["poetry", "install"],
651
- capture_output=True,
652
- text=True,
653
- timeout=300,
654
- cwd=path
655
- )
656
- elif package_manager == "npm":
657
- result = subprocess.run(
658
- ["npm", "install"],
659
- capture_output=True,
660
- text=True,
661
- timeout=300,
662
- cwd=path
663
- )
664
- else:
665
- return {"success": False, "error": f"Unknown package manager: {package_manager}"}
666
-
667
- return {
668
- "success": result.returncode == 0,
669
- "output": result.stdout,
670
- "errors": result.stderr
671
- }
672
- except Exception as e:
673
- return {"success": False, "error": str(e)}
674
-
675
-
676
- # ============================================================================
677
- # WEB TOOLS (Tools 23-27)
678
- # ============================================================================
679
-
680
- def tool_web_search(
681
- query: str,
682
- count: int = 5,
683
- freshness: Optional[str] = None,
684
- language: Optional[str] = None
685
- ) -> Dict[str, Any]:
686
- """Search the web using DuckDuckGo."""
687
- try:
688
- import urllib.request
689
- import urllib.parse
690
- import re
691
- from html import unescape
692
-
693
- # DuckDuckGo Lite
694
- encoded_query = urllib.parse.quote(query)
695
- url = f"https://lite.duckduckgo.com/lite/?q={encoded_query}"
696
-
697
- req = urllib.request.Request(url, headers={
698
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
699
- })
700
- with urllib.request.urlopen(req, timeout=30) as response:
701
- html = response.read().decode('utf-8')
702
-
703
- results = []
704
- # Find links - look for anchor tags with titles
705
- all_links = re.findall(r'<a[^>]*href="(https?://[^"]+)"[^>]*>([^<]+)</a>', html)
706
-
707
- for url, title in all_links[:count]:
708
- title = unescape(title).strip()
709
- if title and len(title) > 3:
710
- results.append({"title": title, "url": url})
711
-
712
- return {
713
- "success": True,
714
- "query": query,
715
- "results": results[:count],
716
- "count": len(results)
717
- }
718
- except Exception as e:
719
- return {"success": False, "error": str(e)}
720
-
721
-
722
- def tool_web_fetch(url: str, max_chars: int = 10000) -> Dict[str, Any]:
723
- """Fetch and extract content from URL."""
724
- try:
725
- result = subprocess.run(
726
- ["curl", "-s", url],
727
- capture_output=True,
728
- text=True,
729
- timeout=30
730
- )
731
-
732
- content = result.stdout[:max_chars]
733
-
734
- return {
735
- "success": True,
736
- "url": url,
737
- "content": content,
738
- "length": len(content)
739
- }
740
- except Exception as e:
741
- return {"success": False, "error": str(e)}
742
-
743
-
744
- def tool_download_file(url: str, destination: str) -> Dict[str, Any]:
745
- """Download file from URL."""
746
- try:
747
- result = subprocess.run(
748
- ["curl", "-L", "-o", destination, url],
749
- capture_output=True,
750
- text=True,
751
- timeout=120
752
- )
753
-
754
- size = Path(destination).stat().st_size if Path(destination).exists() else 0
755
-
756
- return {
757
- "success": result.returncode == 0,
758
- "url": url,
759
- "destination": destination,
760
- "size": size
761
- }
762
- except Exception as e:
763
- return {"success": False, "error": str(e)}
764
-
765
-
766
- def tool_check_url(url: str) -> Dict[str, Any]:
767
- """Check if URL is accessible."""
768
- try:
769
- result = subprocess.run(
770
- ["curl", "-I", "-s", "-o", "/dev/null", "-w", "%{http_code}", url],
771
- capture_output=True,
772
- text=True,
773
- timeout=15
774
- )
775
-
776
- code = result.stdout.strip()
777
-
778
- return {
779
- "success": code in ["200", "301", "302"],
780
- "url": url,
781
- "status_code": code
782
- }
783
- except Exception as e:
784
- return {"success": False, "error": str(e)}
785
-
786
-
787
- def tool_screenshot(url: str, destination: str = "screenshot.png") -> Dict[str, Any]:
788
- """Take screenshot of webpage."""
789
- try:
790
- # Try playwright or puppeteer
791
- result = subprocess.run(
792
- ["npx", "puppeteer", url, "--output", destination],
793
- capture_output=True,
794
- text=True,
795
- timeout=60
796
- )
797
-
798
- if result.returncode != 0:
799
- return {"success": False, "error": "Failed to take screenshot"}
800
-
801
- return {
802
- "success": True,
803
- "url": url,
804
- "destination": destination
805
- }
806
- except Exception as e:
807
- return {"success": False, "error": str(e)}
808
-
809
-
810
- # ============================================================================
811
- # MEMORY TOOLS (Tools 28-32)
812
- # ============================================================================
813
-
814
- def tool_memory_recall(query: str, max_results: int = 5) -> Dict[str, Any]:
815
- """Recall from memory (searches memory files)."""
816
- try:
817
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
818
-
819
- # Search in memory files
820
- results = []
821
-
822
- # Search MEMORY.md
823
- memory_file = workspace / "MEMORY.md"
824
- if memory_file.exists():
825
- content = memory_file.read_text()
826
- if query.lower() in content.lower():
827
- results.append(str(memory_file))
828
-
829
- # Search memory folder
830
- memory_dir = workspace / "memory"
831
- if memory_dir.exists():
832
- for f in memory_dir.rglob("*.md"):
833
- try:
834
- content = f.read_text()
835
- if query.lower() in content.lower():
836
- results.append(str(f))
837
- except:
838
- continue
839
-
840
- return {
841
- "success": True,
842
- "query": query,
843
- "matches": results[:max_results],
844
- "count": len(results)
845
- }
846
- except Exception as e:
847
- return {"success": False, "error": str(e)}
848
-
849
-
850
- def tool_memory_save(key: str, value: str) -> Dict[str, Any]:
851
- """Save to memory."""
852
- try:
853
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
854
- memory_file = workspace / "MEMORY.md"
855
-
856
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
857
- entry = f"\n### {key}\n_{timestamp}_\n{value}\n"
858
-
859
- with open(memory_file, "a") as f:
860
- f.write(entry)
861
-
862
- return {
863
- "success": True,
864
- "key": key,
865
- "saved": True
866
- }
867
- except Exception as e:
868
- return {"success": False, "error": str(e)}
869
-
870
-
871
- def tool_memory_list() -> Dict[str, Any]:
872
- """List memory entries."""
873
- try:
874
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
875
- memory_file = workspace / "MEMORY.md"
876
-
877
- if not memory_file.exists():
878
- return {"success": True, "entries": []}
879
-
880
- content = memory_file.read_text()
881
-
882
- # Extract sections
883
- pattern = r"### (.+?)\n.*?\n(.*?)(?=### |$)"
884
- matches = re.findall(pattern, content, re.DOTALL)
885
-
886
- entries = [{"title": m[0].strip(), "content": m[1].strip()[:200]} for m in matches]
887
-
888
- return {
889
- "success": True,
890
- "entries": entries,
891
- "count": len(entries)
892
- }
893
- except Exception as e:
894
- return {"success": False, "error": str(e)}
895
-
896
-
897
- def tool_context_load(projects: Optional[List[str]] = None) -> Dict[str, Any]:
898
- """Load project context."""
899
- try:
900
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
901
-
902
- context = {}
903
-
904
- # Load AGENTS.md
905
- agents_file = workspace / "AGENTS.md"
906
- if agents_file.exists():
907
- context["agents"] = agents_file.read_text()
908
-
909
- # Load SOUL.md
910
- soul_file = workspace / "SOUL.md"
911
- if soul_file.exists():
912
- context["soul"] = soul_file.read_text()
913
-
914
- # Load TOOLS.md
915
- tools_file = workspace / "TOOLS.md"
916
- if tools_file.exists():
917
- context["tools"] = tools_file.read_text()
918
-
919
- return {
920
- "success": True,
921
- "context": context,
922
- "loaded": list(context.keys())
923
- }
924
- except Exception as e:
925
- return {"success": False, "error": str(e)}
926
-
927
-
928
- def tool_project_scan(path: str = ".") -> Dict[str, Any]:
929
- """Scan project structure."""
930
- try:
931
- base = Path(path)
932
-
933
- if not base.exists():
934
- return {"success": False, "error": f"Path not found: {path}"}
935
-
936
- info = {
937
- "name": base.name,
938
- "files": [],
939
- "dirs": [],
940
- "has_git": (base / ".git").exists(),
941
- "has_pyproject": (base / "pyproject.toml").exists(),
942
- "has_package_json": (base / "package.json").exists(),
943
- "has_dockerfile": (base / "Dockerfile").exists()
944
- }
945
-
946
- for item in base.rglob("*"):
947
- if len(info["files"]) + len(info["dirs"]) > 100:
948
- break
949
-
950
- rel = item.relative_to(base)
951
- if item.is_dir():
952
- info["dirs"].append(str(rel))
953
- else:
954
- info["files"].append(str(rel))
955
-
956
- return {
957
- "success": True,
958
- "project": info
959
- }
960
- except Exception as e:
961
- return {"success": False, "error": str(e)}
962
-
963
-
964
- # ============================================================================
965
- # TASK PLANNING TOOLS (Tools 33-37)
966
- # ============================================================================
967
-
968
- def tool_create_task(title: str, description: str = "", priority: str = "medium") -> Dict[str, Any]:
969
- """Create a task."""
970
- try:
971
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
972
- tasks_file = workspace / ".tasks.json"
973
-
974
- tasks = []
975
- if tasks_file.exists():
976
- tasks = json.loads(tasks_file.read_text())
977
-
978
- task_id = hashlib.md5(f"{title}{datetime.now()}".encode()).hexdigest()[:8]
979
-
980
- task = {
981
- "id": task_id,
982
- "title": title,
983
- "description": description,
984
- "priority": priority,
985
- "status": "pending",
986
- "created": datetime.now().isoformat()
987
- }
988
-
989
- tasks.append(task)
990
- tasks_file.write_text(json.dumps(tasks, indent=2))
991
-
992
- return {
993
- "success": True,
994
- "task": task
995
- }
996
- except Exception as e:
997
- return {"success": False, "error": str(e)}
998
-
999
-
1000
- def tool_list_tasks(status: Optional[str] = None, priority: Optional[str] = None) -> Dict[str, Any]:
1001
- """List tasks."""
1002
- try:
1003
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
1004
- tasks_file = workspace / ".tasks.json"
1005
-
1006
- if not tasks_file.exists():
1007
- return {"success": True, "tasks": []}
1008
-
1009
- tasks = json.loads(tasks_file.read_text())
1010
-
1011
- if status:
1012
- tasks = [t for t in tasks if t.get("status") == status]
1013
- if priority:
1014
- tasks = [t for t in tasks if t.get("priority") == priority]
1015
-
1016
- return {
1017
- "success": True,
1018
- "tasks": tasks,
1019
- "count": len(tasks)
1020
- }
1021
- except Exception as e:
1022
- return {"success": False, "error": str(e)}
1023
-
1024
-
1025
- def tool_update_task(task_id: str, status: Optional[str] = None, **kwargs) -> Dict[str, Any]:
1026
- """Update a task."""
1027
- try:
1028
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
1029
- tasks_file = workspace / ".tasks.json"
1030
-
1031
- if not tasks_file.exists():
1032
- return {"success": False, "error": "No tasks found"}
1033
-
1034
- tasks = json.loads(tasks_file.read_text())
1035
-
1036
- for task in tasks:
1037
- if task.get("id") == task_id:
1038
- if status:
1039
- task["status"] = status
1040
- task.update(kwargs)
1041
- task["updated"] = datetime.now().isoformat()
1042
- break
1043
-
1044
- tasks_file.write_text(json.dumps(tasks, indent=2))
1045
-
1046
- return {
1047
- "success": True,
1048
- "task_id": task_id,
1049
- "updated": True
1050
- }
1051
- except Exception as e:
1052
- return {"success": False, "error": str(e)}
1053
-
1054
-
1055
- def tool_delete_task(task_id: str) -> Dict[str, Any]:
1056
- """Delete a task."""
1057
- try:
1058
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
1059
- tasks_file = workspace / ".tasks.json"
1060
-
1061
- if not tasks_file.exists():
1062
- return {"success": False, "error": "No tasks found"}
1063
-
1064
- tasks = json.loads(tasks_file.read_text())
1065
- tasks = [t for t in tasks if t.get("id") != task_id]
1066
-
1067
- tasks_file.write_text(json.dumps(tasks, indent=2))
1068
-
1069
- return {
1070
- "success": True,
1071
- "task_id": task_id,
1072
- "deleted": True
1073
- }
1074
- except Exception as e:
1075
- return {"success": False, "error": str(e)}
1076
-
1077
-
1078
- def tool_create_plan(goal: str, steps: List[str]) -> Dict[str, Any]:
1079
- """Create an execution plan."""
1080
- try:
1081
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
1082
- plans_file = workspace / ".plans.json"
1083
-
1084
- plans = []
1085
- if plans_file.exists():
1086
- plans = json.loads(plans_file.read_text())
1087
-
1088
- plan_id = hashlib.md5(f"{goal}{datetime.now()}".encode()).hexdigest()[:8]
1089
-
1090
- plan = {
1091
- "id": plan_id,
1092
- "goal": goal,
1093
- "steps": steps,
1094
- "status": "pending",
1095
- "created": datetime.now().isoformat()
1096
- }
1097
-
1098
- plans.append(plan)
1099
- plans_file.write_text(json.dumps(plans, indent=2))
1100
-
1101
- return {
1102
- "success": True,
1103
- "plan": plan
1104
- }
1105
- except Exception as e:
1106
- return {"success": False, "error": str(e)}
1107
-
1108
-
1109
- def tool_execute_plan(plan_id: str) -> Dict[str, Any]:
1110
- """Execute a plan step by step."""
1111
- try:
1112
- workspace = Path("/Users/walidsobhi/.openclaw/workspace")
1113
- plans_file = workspace / ".plans.json"
1114
-
1115
- if not plans_file.exists():
1116
- return {"success": False, "error": "No plans found"}
1117
-
1118
- plans = json.loads(plans_file.read_text())
1119
-
1120
- for plan in plans:
1121
- if plan.get("id") == plan_id:
1122
- plan["status"] = "in_progress"
1123
- plan["started"] = datetime.now().isoformat()
1124
- break
1125
-
1126
- plans_file.write_text(json.dumps(plans, indent=2))
1127
-
1128
- return {
1129
- "success": True,
1130
- "plan_id": plan_id,
1131
- "status": "executing",
1132
- "steps": plan.get("steps", [])
1133
- }
1134
- except Exception as e:
1135
- return {"success": False, "error": str(e)}
1136
-
1137
-
1138
- # ============================================================================
1139
- # TOOL REGISTRY
1140
- # ============================================================================
1141
-
1142
- TOOLS: Dict[str, Callable] = {
1143
- # File operations (1-8)
1144
- "read": tool_read_file,
1145
- "write": tool_write_file,
1146
- "edit": tool_edit_file,
1147
- "search": tool_search_files,
1148
- "grep": tool_grep,
1149
- "copy": tool_copy_file,
1150
- "move": tool_move_file,
1151
- "delete": tool_delete_file,
1152
-
1153
- # Git operations (9-15)
1154
- "git_status": tool_git_status,
1155
- "git_commit": tool_git_commit,
1156
- "git_push": tool_git_push,
1157
- "git_pull": tool_git_pull,
1158
- "git_branch": tool_git_branch,
1159
- "git_log": tool_git_log,
1160
- "git_diff": tool_git_diff,
1161
-
1162
- # Code execution (16-22)
1163
- "run": tool_run_command,
1164
- "test": tool_run_tests,
1165
- "lint": tool_lint_code,
1166
- "format": tool_format_code,
1167
- "typecheck": tool_check_type,
1168
- "server": tool_start_server,
1169
- "install": tool_install_dependencies,
1170
-
1171
- # Web (23-27)
1172
- "web_search": tool_web_search,
1173
- "fetch": tool_web_fetch,
1174
- "download": tool_download_file,
1175
- "check_url": tool_check_url,
1176
- "screenshot": tool_screenshot,
1177
-
1178
- # Memory (28-32)
1179
- "memory_recall": tool_memory_recall,
1180
- "memory_save": tool_memory_save,
1181
- "memory_list": tool_memory_list,
1182
- "context_load": tool_context_load,
1183
- "project_scan": tool_project_scan,
1184
-
1185
- # Task planning (33-37)
1186
- "create_task": tool_create_task,
1187
- "list_tasks": tool_list_tasks,
1188
- "update_task": tool_update_task,
1189
- "delete_task": tool_delete_task,
1190
- "create_plan": tool_create_plan,
1191
- "execute_plan": tool_execute_plan,
1192
- }
1193
-
1194
-
1195
- def get_tool(name: str) -> Optional[Callable]:
1196
- """Get tool by name."""
1197
- return TOOLS.get(name)
1198
-
1199
-
1200
- def list_tools() -> List[str]:
1201
- """List all available tools."""
1202
- return list(TOOLS.keys())
1203
-
1204
-
1205
- def get_tool_schemas() -> List[Dict[str, Any]]:
1206
- """Get tool schemas for LLM tool calling.
1207
-
1208
- Automatically generates JSON Schema from function signatures using inspect.
1209
- All 38 tools are included with accurate parameter types and descriptions.
1210
- """
1211
- import inspect
1212
- from typing import get_type_hints
1213
-
1214
- schemas = []
1215
-
1216
- for name, func in TOOLS.items():
1217
- sig = inspect.signature(func)
1218
- doc = func.__doc__ or f"Tool: {name}"
1219
-
1220
- # Build parameters schema
1221
- properties = {}
1222
- required = []
1223
-
1224
- for param_name, param in sig.parameters.items():
1225
- # Skip self/cls
1226
- if param_name in ('self', 'cls'):
1227
- continue
1228
-
1229
- # Get type annotation
1230
- annotation = param.annotation
1231
- if annotation is inspect.Parameter.empty:
1232
- json_type = "string" # default
1233
- elif annotation is str:
1234
- json_type = "string"
1235
- elif annotation is int:
1236
- json_type = "integer"
1237
- elif annotation is bool:
1238
- json_type = "boolean"
1239
- elif annotation is float:
1240
- json_type = "number"
1241
- elif hasattr(annotation, '__origin__') and annotation.__origin__ is list:
1242
- json_type = "array"
1243
- elif hasattr(annotation, '__origin__') and annotation.__origin__ is dict:
1244
- json_type = "object"
1245
- else:
1246
- json_type = "string" # fallback
1247
-
1248
- # Build property definition
1249
- prop = {"type": json_type}
1250
-
1251
- # Extract description from docstring
1252
- param_desc = _extract_param_desc(doc, param_name)
1253
- if param_desc:
1254
- prop["description"] = param_desc
1255
-
1256
- # Add enum for restricted string values
1257
- if param_name in ('linter', 'formatter', 'package_manager') and hasattr(annotation, '__args__'):
1258
- prop["enum"] = list(annotation.__args__)
1259
-
1260
- properties[param_name] = prop
1261
-
1262
- # Mark as required if no default value
1263
- if param.default is inspect.Parameter.empty:
1264
- required.append(param_name)
1265
-
1266
- schema = {
1267
- "name": name,
1268
- "description": doc.strip().split('\n')[0],
1269
- "parameters": {
1270
- "type": "object",
1271
- "properties": properties,
1272
- "required": required
1273
- }
1274
- }
1275
-
1276
- schemas.append(schema)
1277
-
1278
- return schemas
1279
-
1280
-
1281
- def _extract_param_desc(docstring: str, param_name: str) -> Optional[str]:
1282
- """Extract parameter description from docstring.
1283
-
1284
- Looks for lines like: "- `param_name`: description" or "param_name: description".
1285
- """
1286
- if not docstring:
1287
- return None
1288
-
1289
- lines = docstring.split('\n')
1290
- for i, line in enumerate(lines):
1291
- # Match: - `param`: description
1292
- if f"`{param_name}`" in line or f"{param_name}:" in line:
1293
- # Try to extract after colon or dash
1294
- parts = line.split(':', 1)
1295
- if len(parts) > 1:
1296
- return parts[1].strip().lstrip(' -').strip()
1297
- # Alternative: split on backtick
1298
- parts = line.split('`', 2)
1299
- if len(parts) > 2:
1300
- return parts[2].strip().lstrip(': -').strip()
1301
-
1302
- return None
1303
-
1304
-
1305
- if __name__ == "__main__":
1306
- print("Stack 2.9 Tools Module")
1307
- print(f"Available tools: {len(TOOLS)}")
1308
- print(list_tools())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/stack-2.9-cli.py CHANGED
@@ -11,10 +11,11 @@ from pathlib import Path
11
  from typing import Optional, List, Dict
12
  from dataclasses import dataclass
13
 
14
- # Add eval and training to path
15
  sys.path.insert(0, str(Path(__file__).parent))
16
- sys.path.insert(0, str(Path(__file__).parent / "stack-2.9-eval"))
17
- sys.path.insert(0, str(Path(__file__).parent / "stack-2.9-training"))
 
18
 
19
  from model_client import create_model_client, ChatMessage
20
  from benchmarks.mbpp import MBPP
@@ -29,7 +30,7 @@ class ChatMessage:
29
  """Chat message for display."""
30
  role: str
31
  content: str
32
- timestamp: str
33
 
34
 
35
  class Stack29TUI:
 
11
  from typing import Optional, List, Dict
12
  from dataclasses import dataclass
13
 
14
+ # Add eval and training to path - go up from src/ to project root, then into stack/
15
  sys.path.insert(0, str(Path(__file__).parent))
16
+ sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "eval"))
17
+ sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "training"))
18
+ sys.path.insert(0, str(Path(__file__).parent.parent / "stack" / "eval" / "benchmarks"))
19
 
20
  from model_client import create_model_client, ChatMessage
21
  from benchmarks.mbpp import MBPP
 
30
  """Chat message for display."""
31
  role: str
32
  content: str
33
+ timestamp: str = ""
34
 
35
 
36
  class Stack29TUI:
stack/eval/benchmarks/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Benchmarks package
stack/training/patterns/feedback.json CHANGED
@@ -1198,5 +1198,15 @@
1198
  "execution_time": 0.0,
1199
  "timestamp": "2026-04-08T02:22:38.004558",
1200
  "model_version": null
 
 
 
 
 
 
 
 
 
 
1201
  }
1202
  ]
 
1198
  "execution_time": 0.0,
1199
  "timestamp": "2026-04-08T02:22:38.004558",
1200
  "model_version": null
1201
+ },
1202
+ {
1203
+ "id": "6e39a2b1839be0df",
1204
+ "problem_type": "humaneval",
1205
+ "solution": "accuracy=1.0",
1206
+ "success": true,
1207
+ "error_message": null,
1208
+ "execution_time": 0.0,
1209
+ "timestamp": "2026-04-08T17:40:56.111571",
1210
+ "model_version": null
1211
  }
1212
  ]