walidsobhie-code Claude Opus 4.6 commited on
Commit
b8e3e42
·
1 Parent(s): c7f1596

reorganize: consolidate root level to 20 folders

Browse files

- Moved config files to /config/ (Dockerfile, pyproject, k8s, etc.)
- Moved docs to /docs/ (all markdown files)
- Organized scripts into /scripts/ (data, training, eval, deploy)
- Moved requirements to /requirements/
- Moved notebooks to /notebooks/
- Merged training configs and data into /training/
- Created selective archive in /archived-selective/ (300KB)
- All imports verified working

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. TOOLS.md +0 -40
  2. archived-selective/BENCHMARK_BADGES.md +57 -0
  3. archived-selective/cli-backup/__init__.py +11 -0
  4. archived-selective/cli-backup/agent.py +660 -0
  5. archived-selective/cli-backup/cli.py +562 -0
  6. archived-selective/cli-backup/context.py +343 -0
  7. archived-selective/cli-backup/main.py +336 -0
  8. archived-selective/cli-backup/pyproject.toml +70 -0
  9. archived-selective/cli-backup/tools.py +1308 -0
  10. audit_report.txt → audits/audit_report.txt +0 -0
  11. audit_results.json → audits/audit_results.json +0 -0
  12. Dockerfile → config/Dockerfile +0 -0
  13. Dockerfile.gpu → config/Dockerfile.gpu +0 -0
  14. MLproject → config/MLproject +0 -0
  15. Makefile → config/Makefile +0 -0
  16. docker-compose.gpu.yml → config/docker-compose.gpu.yml +0 -0
  17. {k8s → config/k8s}/deployment.yaml +0 -0
  18. {k8s → config/k8s}/pvc.yaml +0 -0
  19. {k8s → config/k8s}/secret.yaml +0 -0
  20. {k8s → config/k8s}/service.yaml +0 -0
  21. package-lock.json → config/package-lock.json +0 -0
  22. package.json → config/package.json +0 -0
  23. pyproject.toml → config/pyproject.toml +0 -0
  24. tsconfig.json → config/tsconfig.json +0 -0
  25. CHANGELOG.md → docs/CHANGELOG.md +0 -0
  26. CODE_OF_CONDUCT.md → docs/CODE_OF_CONDUCT.md +0 -0
  27. CONTRIBUTING.md → docs/CONTRIBUTING.md +0 -0
  28. DIRECTORY_STRUCTURE.md → docs/DIRECTORY_STRUCTURE.md +0 -0
  29. GIT_PUSH.md → docs/GIT_PUSH.md +0 -0
  30. LAUNCH_CHECKLIST.md → docs/LAUNCH_CHECKLIST.md +0 -0
  31. LAUNCH_PLAN.md → docs/LAUNCH_PLAN.md +0 -0
  32. LICENSE → docs/LICENSE +0 -0
  33. MODEL_CARD.md → docs/MODEL_CARD.md +0 -0
  34. MODEL_REGISTRY.md → docs/MODEL_REGISTRY.md +0 -0
  35. README.md → docs/README.md +24 -0
  36. README_QUICKSTART.md → docs/README_QUICKSTART.md +0 -0
  37. SECURITY.md → docs/SECURITY.md +0 -0
  38. docs/tools.md +23 -145
  39. colab_train_stack29.ipynb → notebooks/colab_train_stack29.ipynb +0 -0
  40. kaggle_train_stack29_v5.ipynb → notebooks/kaggle_train_stack29_v5.ipynb +0 -0
  41. requirements.txt → requirements/requirements.txt +0 -0
  42. requirements_api.txt → requirements/requirements_api.txt +0 -0
  43. requirements_webui.txt → requirements/requirements_webui.txt +0 -0
  44. install.sh → scripts/deploy/install.sh +0 -0
  45. runpod_deploy.sh → scripts/deploy/runpod_deploy.sh +0 -0
  46. setup.sh → scripts/deploy/setup.sh +0 -0
  47. vastai_deploy.sh → scripts/deploy/vastai_deploy.sh +0 -0
  48. src/mcp_server.py +7 -0
  49. src/tools/grep_tool.py +12 -12
  50. stack/training/patterns/feedback.json +20 -0
TOOLS.md DELETED
@@ -1,40 +0,0 @@
1
- # TOOLS.md - Local Notes
2
-
3
- Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
4
-
5
- ## What Goes Here
6
-
7
- Things like:
8
-
9
- - Camera names and locations
10
- - SSH hosts and aliases
11
- - Preferred voices for TTS
12
- - Speaker/room names
13
- - Device nicknames
14
- - Anything environment-specific
15
-
16
- ## Examples
17
-
18
- ```markdown
19
- ### Cameras
20
-
21
- - living-room → Main area, 180° wide angle
22
- - front-door → Entrance, motion-triggered
23
-
24
- ### SSH
25
-
26
- - home-server → 192.168.1.100, user: admin
27
-
28
- ### TTS
29
-
30
- - Preferred voice: "Nova" (warm, slightly British)
31
- - Default speaker: Kitchen HomePod
32
- ```
33
-
34
- ## Why Separate?
35
-
36
- Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
37
-
38
- ---
39
-
40
- Add whatever helps you do your job. This is your cheat sheet.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
archived-selective/BENCHMARK_BADGES.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benchmark Badges for README.md
2
+
3
+ Copy these badge markdown lines into your main README to display evaluation scores.
4
+
5
+ ## HumanEval Pass@k Badges
6
+
7
+ Replace the static badges with dynamic ones after evaluation completes.
8
+
9
+ ### Template (placeholders):
10
+
11
+ ```markdown
12
+ [![HumanEval Pass@1](https://img.shields.io/static/v1?label=HumanEval&message=Pass%401&color=yellow)](https://github.com/your-repo)
13
+ [![HumanEval Pass@10](https://img.shields.io/static/v1?label=HumanEval&message=Pass%4010&color=yellow)](https://github.com/your-repo)
14
+ [![HumanEval Pass@100](https://img.shields.io/static/v1?label=HumanEval&message=Pass%40100&color=orange)](https://github.com/your-repo)
15
+ ```
16
+
17
+ ### Example with actual scores (update after running evaluation):
18
+
19
+ If you get 82% Pass@1:
20
+ ```markdown
21
+ [![HumanEval Pass@1: 82%](https://img.shields.io/badge/HumanEval-Pass%401-82%25-yellow?logo=python)](https://github.com/your-repo)
22
+ [![HumanEval Pass@10: 89%](https://img.shields.io/badge/HumanEval-Pass%4010-89%25-yellow?logo=python)](https://github.com/your-repo)
23
+ [![HumanEval Pass@100: 92%](https://img.shields.io/badge/HumanEval-Pass%40100-92%25-orange?logo=python)](https://github.com/your-repo)
24
+ ```
25
+
26
+ ## MBPP Badges
27
+
28
+ ### Template:
29
+ ```markdown
30
+ [![MBPP Pass@1](https://img.shields.io/static/v1?label=MBPP&message=Pass%401&color=blue)](https://github.com/your-repo)
31
+ [![MBPP Pass@10](https://img.shields.io/static/v1?label=MBPP&message=Pass%4010&color=blue)](https://github.com/your-repo)
32
+ [![MBPP Pass@100](https://img.shields.io/static/v1?label=MBPP&message=Pass%40100&color=blue)](https://github.com/your-repo)
33
+ ```
34
+
35
+ ### Example with actual scores:
36
+ If you get 80% Pass@1:
37
+ ```markdown
38
+ [![MBPP Pass@1: 80%](https://img.shields.io/badge/MBPP-Pass%401-80%25-blue?logo=python)](https://github.com/your-repo)
39
+ [![MBPP Pass@10: 85%](https://img.shields.io/badge/MBPP-Pass%4010-85%25-blue?logo=python)](https://github.com/your-repo)
40
+ [![MBPP Pass@100: 88%](https://img.shields.io/badge/MBPP-Pass%40100-88%25-blue?logo=python)](https://github.com/your-repo)
41
+ ```
42
+
43
+ ## Auto-generating Badges
44
+
45
+ After running evaluation, use the scores from the generated summary files:
46
+
47
+ - `results/humaneval_summary.json` → contains `pass@k` value for HumanEval
48
+ - `results/mbpp_summary.json` → contains `pass@k` value for MBPP
49
+
50
+ You can create a script to auto-update README.md with the latest scores.
51
+
52
+ ## Combined Badges
53
+
54
+ ```markdown
55
+ [![HumanEval](https://img.shields.io/badge/HumanEval-164%20problems-green)](https://github.com/your-repo)
56
+ [![MBPP](https://img.shields.io/badge/MBPP-500%20problems-green)](https://github.com/your-repo)
57
+ ```
archived-selective/cli-backup/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
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"]
archived-selective/cli-backup/agent.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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]}")
archived-selective/cli-backup/cli.py ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
archived-selective/cli-backup/context.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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())
archived-selective/cli-backup/main.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
archived-selective/cli-backup/pyproject.toml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
archived-selective/cli-backup/tools.py ADDED
@@ -0,0 +1,1308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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())
audit_report.txt → audits/audit_report.txt RENAMED
File without changes
audit_results.json → audits/audit_results.json RENAMED
File without changes
Dockerfile → config/Dockerfile RENAMED
File without changes
Dockerfile.gpu → config/Dockerfile.gpu RENAMED
File without changes
MLproject → config/MLproject RENAMED
File without changes
Makefile → config/Makefile RENAMED
File without changes
docker-compose.gpu.yml → config/docker-compose.gpu.yml RENAMED
File without changes
{k8s → config/k8s}/deployment.yaml RENAMED
File without changes
{k8s → config/k8s}/pvc.yaml RENAMED
File without changes
{k8s → config/k8s}/secret.yaml RENAMED
File without changes
{k8s → config/k8s}/service.yaml RENAMED
File without changes
package-lock.json → config/package-lock.json RENAMED
File without changes
package.json → config/package.json RENAMED
File without changes
pyproject.toml → config/pyproject.toml RENAMED
File without changes
tsconfig.json → config/tsconfig.json RENAMED
File without changes
CHANGELOG.md → docs/CHANGELOG.md RENAMED
File without changes
CODE_OF_CONDUCT.md → docs/CODE_OF_CONDUCT.md RENAMED
File without changes
CONTRIBUTING.md → docs/CONTRIBUTING.md RENAMED
File without changes
DIRECTORY_STRUCTURE.md → docs/DIRECTORY_STRUCTURE.md RENAMED
File without changes
GIT_PUSH.md → docs/GIT_PUSH.md RENAMED
File without changes
LAUNCH_CHECKLIST.md → docs/LAUNCH_CHECKLIST.md RENAMED
File without changes
LAUNCH_PLAN.md → docs/LAUNCH_PLAN.md RENAMED
File without changes
LICENSE → docs/LICENSE RENAMED
File without changes
MODEL_CARD.md → docs/MODEL_CARD.md RENAMED
File without changes
MODEL_REGISTRY.md → docs/MODEL_REGISTRY.md RENAMED
File without changes
README.md → docs/README.md RENAMED
@@ -112,6 +112,30 @@ result = await registry.call("grep", {"pattern": "def main", "path": "./src"})
112
 
113
  ---
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  ## 🛠️ Full Tool List (57 Tools)
116
 
117
  ### File Operations (5)
 
112
 
113
  ---
114
 
115
+ ## 🔌 OpenClaw Integration
116
+
117
+ Stack 2.9 ships as an **MCP server**, exposing all 69 tools to [OpenClaw](https://github.com/openclaw) for seamless integration.
118
+
119
+ ### Register the MCP Server
120
+
121
+ If not already configured, add it to OpenClaw:
122
+
123
+ ```bash
124
+ openclaw mcp set Stack2.9 '{"command":"python3","args":["src/mcp_server.py"],"cwd":"~/stack-2.9"}'
125
+ ```
126
+
127
+ ### Start the MCP Server
128
+
129
+ ```bash
130
+ cd ~/stack-2.9 && PYTHONPATH=. python3 src/mcp_server.py
131
+ ```
132
+
133
+ ### Use from OpenClaw
134
+
135
+ Once registered, OpenClaw can directly call any tool: `file_read`, `grep`, `task_create`, `web_search`, `mcp_call`, and 65 more — no local GPU needed for the model.
136
+
137
+ ---
138
+
139
  ## 🛠️ Full Tool List (57 Tools)
140
 
141
  ### File Operations (5)
README_QUICKSTART.md → docs/README_QUICKSTART.md RENAMED
File without changes
SECURITY.md → docs/SECURITY.md RENAMED
File without changes
docs/tools.md CHANGED
@@ -1,162 +1,40 @@
1
- # Stack 2.9 Tools
2
 
3
- Python-native tool implementations compatible with the RTMP tool system.
4
 
5
- ## Overview
6
 
7
- Tools are implemented as Python classes extending `BaseTool`. Each tool has:
8
- - A JSON-schema `input_schema`
9
- - An `execute(input)` method returning `ToolResult[T]`
10
- - Optional `validate_input()` for pre-execution checks
11
 
12
- ## Available Tools
 
 
 
 
 
13
 
14
- ### Web Search
15
 
16
- **Tool:** `WebSearch`
 
17
 
18
- Search the web via DuckDuckGo. Results are cached for 5 minutes.
 
19
 
20
- ```python
21
- from src.tools.web_search import WebSearchTool
22
- tool = WebSearchTool()
23
- result = tool.call({
24
- "query": "latest AI news",
25
- "max_results": 10,
26
- "allowed_domains": None,
27
- "blocked_domains": None,
28
- })
29
- ```
30
-
31
- **Parameters:**
32
- | Name | Type | Required | Description |
33
- |------|------|----------|-------------|
34
- | `query` | string | Yes | Search query (min 2 chars) |
35
- | `allowed_domains` | string[] | No | Restrict to domains |
36
- | `blocked_domains` | string[] | No | Exclude domains |
37
- | `max_results` | int | No | Max results (default 10, max 20) |
38
-
39
- **Output:**
40
- ```json
41
- {
42
- "query": "...",
43
- "results": [{"title": "...", "url": "...", "snippet": "..."}],
44
- "duration_seconds": 0.5,
45
- "source": "duckduckgo"
46
- }
47
- ```
48
-
49
- ---
50
-
51
- ### Task Management
52
-
53
- Four tools for task lifecycle management. Tasks stored in `~/.stack-2.9/tasks.json`.
54
-
55
- #### TaskCreate
56
-
57
- ```python
58
- result = get_registry().call("TaskCreate", {
59
- "subject": "Fix login bug",
60
- "description": "Users cannot log in with SSO",
61
- "status": "pending",
62
- "priority": "high",
63
- "tags": ["bug", "auth"],
64
- })
65
- ```
66
-
67
- #### TaskList
68
-
69
- ```python
70
- result = get_registry().call("TaskList", {
71
- "status": "pending",
72
- "tag": "bug",
73
- "limit": 50,
74
- })
75
- ```
76
-
77
- #### TaskUpdate
78
-
79
- ```python
80
- result = get_registry().call("TaskUpdate", {
81
- "id": "a1b2c3d4",
82
- "status": "completed",
83
- })
84
- ```
85
-
86
- #### TaskDelete
87
-
88
- ```python
89
- result = get_registry().call("TaskDelete", {"id": "a1b2c3d4"})
90
- ```
91
-
92
- **Task Status Values:** `pending`, `in_progress`, `completed`, `cancelled`
93
-
94
- **Priority Values:** `low`, `medium`, `high`, `urgent`
95
-
96
- ---
97
 
98
- ### Scheduling / Cron
99
 
100
- Schedule prompts for later or recurring execution. Schedules stored in `~/.stack-2.9/schedules.json`.
101
 
102
- Uses standard 5-field cron in local time: `minute hour day-of-month month day-of-week`
103
-
104
- #### CronCreate
105
-
106
- ```python
107
- result = get_registry().call("CronCreate", {
108
- "cron": "*/5 * * * *", # every 5 minutes
109
- "prompt": "Check system status",
110
- "recurring": True,
111
- "durable": True, # persists across restarts
112
- })
113
  ```
114
 
115
- **Cron Examples:**
116
- | Expression | Meaning |
117
- |------------|---------|
118
- | `*/5 * * * *` | Every 5 minutes |
119
- | `0 * * * *` | Every hour |
120
- | `0 9 * * 1-5` | Weekdays at 9am |
121
- | `30 14 * * *` | 2:30pm daily |
122
- | `0 0 1 * *` | Midnight on 1st of month |
123
-
124
- #### CronList
125
-
126
- ```python
127
- result = get_registry().call("CronList", {})
128
- ```
129
 
130
- #### CronDelete
131
-
132
- ```python
133
- result = get_registry().call("CronDelete", {"id": "schedule-id"})
134
- ```
135
 
136
  ---
137
 
138
- ## Tool Registry
139
-
140
- ```python
141
- from src.tools import get_registry
142
-
143
- registry = get_registry()
144
- registry.list() # list all tool names
145
- registry.get("WebSearch") # get a specific tool
146
- registry.call("TaskCreate", {...}) # validate + execute in one step
147
- ```
148
-
149
- ## Adding New Tools
150
-
151
- 1. Create `src/tools/your_tool.py` extending `BaseTool`
152
- 2. Define `name`, `description`, `input_schema`, and `execute()`
153
- 3. Import and register at the bottom of the file: `get_registry().register(YourTool())`
154
- 4. Add tests in `tests/test_your_tool.py`
155
- 5. Document in this file
156
-
157
- ## Data Storage
158
-
159
- All tools store data under `~/.stack-2.9/`:
160
- - `tasks.json` — task list
161
- - `schedules.json` — cron schedules
162
- - `web_search_cache.json` — cached search results
 
1
+ # TOOLS.md - Local Notes
2
 
3
+ Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
4
 
5
+ ## What Goes Here
6
 
7
+ Things like:
 
 
 
8
 
9
+ - Camera names and locations
10
+ - SSH hosts and aliases
11
+ - Preferred voices for TTS
12
+ - Speaker/room names
13
+ - Device nicknames
14
+ - Anything environment-specific
15
 
16
+ ## Examples
17
 
18
+ ```markdown
19
+ ### Cameras
20
 
21
+ - living-room Main area, 180° wide angle
22
+ - front-door → Entrance, motion-triggered
23
 
24
+ ### SSH
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ - home-server 192.168.1.100, user: admin
27
 
28
+ ### TTS
29
 
30
+ - Preferred voice: "Nova" (warm, slightly British)
31
+ - Default speaker: Kitchen HomePod
 
 
 
 
 
 
 
 
 
32
  ```
33
 
34
+ ## Why Separate?
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
 
 
 
 
37
 
38
  ---
39
 
40
+ Add whatever helps you do your job. This is your cheat sheet.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
colab_train_stack29.ipynb → notebooks/colab_train_stack29.ipynb RENAMED
File without changes
kaggle_train_stack29_v5.ipynb → notebooks/kaggle_train_stack29_v5.ipynb RENAMED
File without changes
requirements.txt → requirements/requirements.txt RENAMED
File without changes
requirements_api.txt → requirements/requirements_api.txt RENAMED
File without changes
requirements_webui.txt → requirements/requirements_webui.txt RENAMED
File without changes
install.sh → scripts/deploy/install.sh RENAMED
File without changes
runpod_deploy.sh → scripts/deploy/runpod_deploy.sh RENAMED
File without changes
setup.sh → scripts/deploy/setup.sh RENAMED
File without changes
vastai_deploy.sh → scripts/deploy/vastai_deploy.sh RENAMED
File without changes
src/mcp_server.py CHANGED
@@ -1,8 +1,15 @@
1
  """MCP Server for Stack 2.9 - Exposes Stack tools via Model Context Protocol"""
2
 
3
  import asyncio
 
 
4
  from typing import Any
5
 
 
 
 
 
 
6
  from mcp.server.fastmcp import FastMCP
7
 
8
  # Import all Stack 2.9 tools (triggers auto-registration)
 
1
  """MCP Server for Stack 2.9 - Exposes Stack tools via Model Context Protocol"""
2
 
3
  import asyncio
4
+ import os
5
+ import sys
6
  from typing import Any
7
 
8
+ # Ensure project root is on the path so 'from src.tools import' works
9
+ _project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
10
+ if _project_root not in sys.path:
11
+ sys.path.insert(0, _project_root)
12
+
13
  from mcp.server.fastmcp import FastMCP
14
 
15
  # Import all Stack 2.9 tools (triggers auto-registration)
src/tools/grep_tool.py CHANGED
@@ -29,8 +29,15 @@ class GrepTool(BaseTool):
29
  "required": ["pattern", "path"]
30
  }
31
 
32
- async def execute(self, pattern: str, path: str, recursive: bool = True, case_sensitive: bool = True, context_lines: int = 0, file_pattern: Optional[str] = None, max_results: int = 1000) -> ToolResult:
33
  """Search for pattern in files."""
 
 
 
 
 
 
 
34
  search_path = Path(path)
35
  if not search_path.exists():
36
  return ToolResult(success=False, error=f"Path not found: {path}")
@@ -135,16 +142,9 @@ class GrepCountTool(BaseTool):
135
  "required": ["pattern", "path"]
136
  }
137
 
138
- async def execute(self, pattern: str, path: str, recursive: bool = True, case_sensitive: bool = True, file_pattern: Optional[str] = None) -> ToolResult:
139
  """Count pattern matches."""
140
- grep_result = await GrepTool().execute(
141
- pattern=pattern,
142
- path=path,
143
- recursive=recursive,
144
- case_sensitive=case_sensitive,
145
- file_pattern=file_pattern,
146
- max_results=100000
147
- )
148
 
149
  counts = {}
150
  for match in grep_result.data.get("matches", []):
@@ -152,8 +152,8 @@ class GrepCountTool(BaseTool):
152
  counts[file_path] = counts.get(file_path, 0) + 1
153
 
154
  return ToolResult(success=True, data={
155
- "pattern": pattern,
156
- "total_matches": len(matches) if (matches := grep_result.data.get("matches", [])) else 0,
157
  "by_file": counts
158
  })
159
 
 
29
  "required": ["pattern", "path"]
30
  }
31
 
32
+ def execute(self, input_data: dict) -> ToolResult:
33
  """Search for pattern in files."""
34
+ pattern = input_data.get('pattern')
35
+ path = input_data.get('path')
36
+ recursive = input_data.get('recursive', True)
37
+ case_sensitive = input_data.get('case_sensitive', True)
38
+ context_lines = input_data.get('context_lines', 0)
39
+ file_pattern = input_data.get('file_pattern')
40
+ max_results = input_data.get('max_results', 1000)
41
  search_path = Path(path)
42
  if not search_path.exists():
43
  return ToolResult(success=False, error=f"Path not found: {path}")
 
142
  "required": ["pattern", "path"]
143
  }
144
 
145
+ def execute(self, input_data: dict) -> ToolResult:
146
  """Count pattern matches."""
147
+ grep_result = GrepTool().execute(input_data)
 
 
 
 
 
 
 
148
 
149
  counts = {}
150
  for match in grep_result.data.get("matches", []):
 
152
  counts[file_path] = counts.get(file_path, 0) + 1
153
 
154
  return ToolResult(success=True, data={
155
+ "pattern": input_data.get('pattern'),
156
+ "total_matches": len(grep_result.data.get("matches", [])),
157
  "by_file": counts
158
  })
159
 
stack/training/patterns/feedback.json CHANGED
@@ -1208,5 +1208,25 @@
1208
  "execution_time": 0.0,
1209
  "timestamp": "2026-04-08T17:40:56.111571",
1210
  "model_version": null
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
  }
1212
  ]
 
1208
  "execution_time": 0.0,
1209
  "timestamp": "2026-04-08T17:40:56.111571",
1210
  "model_version": null
1211
+ },
1212
+ {
1213
+ "id": "034235bc464e647a",
1214
+ "problem_type": "humaneval",
1215
+ "solution": "accuracy=0.95",
1216
+ "success": true,
1217
+ "error_message": null,
1218
+ "execution_time": 0.0,
1219
+ "timestamp": "2026-04-09T21:51:36.160807",
1220
+ "model_version": null
1221
+ },
1222
+ {
1223
+ "id": "671cde37e210031e",
1224
+ "problem_type": "gsm8k",
1225
+ "solution": "accuracy=0.55",
1226
+ "success": true,
1227
+ "error_message": null,
1228
+ "execution_time": 0.0,
1229
+ "timestamp": "2026-04-09T21:54:33.700093",
1230
+ "model_version": null
1231
  }
1232
  ]