Executor-Tyrant-Framework commited on
Commit
0682824
Β·
verified Β·
1 Parent(s): ce15d5e

Update recursive_context.py

Browse files
Files changed (1) hide show
  1. recursive_context.py +126 -112
recursive_context.py CHANGED
@@ -14,53 +14,94 @@ class RecursiveContextManager:
14
  self.repo_path = Path(repo_path)
15
  self.memory_path = self.repo_path / "memory"
16
  self.notebook_file = self.memory_path / "notebook.json"
 
17
 
18
  # --- AUTHENTICATION ---
19
  self.token = os.getenv("HF_TOKEN")
20
  self.dataset_id = os.getenv("DATASET_ID", "Executor-Tyrant-Framework/clawdbot-memory")
 
21
 
22
- # --- XET / DATABASE INIT ---
23
  self.xet_store = None
24
  try:
25
- # Try to load the Xet store if the file exists
26
  if (self.repo_path / "xet_storage.py").exists():
27
  import sys
28
  sys.path.append(str(self.repo_path))
29
  from xet_storage import XetVectorStore
30
- # Assuming Xet Repo URL is in env or default
31
- xet_url = os.getenv("XET_REPO_URL", "local/xet-repo")
32
- self.xet_store = XetVectorStore(xet_url)
33
  print("βœ… Xet Storage Driver Loaded.")
34
  except Exception as e:
35
  print(f"⚠️ Xet Driver not loaded: {e}")
36
 
37
- # --- MEMORY INIT ---
38
  self._init_memory()
39
 
40
  def _init_memory(self):
41
- """STARTUP: Download the brain from the Dataset."""
42
  self.memory_path.mkdir(parents=True, exist_ok=True)
43
  if self.token:
 
 
44
  try:
45
  hf_hub_download(
46
- repo_id=self.dataset_id,
47
- filename="notebook.json",
48
- repo_type="dataset",
49
- token=self.token,
50
- local_dir=self.memory_path,
51
- local_dir_use_symlinks=False
 
 
 
 
52
  )
53
- print(f"🧠 Brain restored from {self.dataset_id}")
 
 
54
  except Exception as e:
55
- print(f"⚠️ Memory download failed (Creating Fresh): {e}")
56
- self._save_local([])
 
 
 
 
 
 
 
 
 
 
 
 
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def _save_local(self, notes: List[Dict]):
59
  self.memory_path.mkdir(parents=True, exist_ok=True)
60
  self.notebook_file.write_text(json.dumps(notes, indent=2), encoding='utf-8')
61
 
62
  def _save_notebook(self, notes: List[Dict]):
63
- """SAVE: Disk + Cloud Sync."""
64
  self._save_local(notes)
65
  if self.token and self.dataset_id:
66
  try:
@@ -72,17 +113,13 @@ class RecursiveContextManager:
72
  repo_type="dataset",
73
  commit_message=f"🧠 Notebook Update: {len(notes)} items"
74
  )
75
- except Exception as e:
76
- print(f"⚠️ Dataset sync failed: {e}")
77
 
78
  def _load_notebook(self) -> List[Dict]:
79
  if not self.notebook_file.exists(): return []
80
  try: return json.loads(self.notebook_file.read_text(encoding='utf-8'))
81
  except: return []
82
 
83
- # =========================================================================
84
- # 🧠 NOTEBOOK TOOLS
85
- # =========================================================================
86
  def notebook_read(self) -> str:
87
  notes = self._load_notebook()
88
  if not notes: return "Notebook is empty."
@@ -90,8 +127,7 @@ class RecursiveContextManager:
90
 
91
  def notebook_add(self, content: str) -> str:
92
  notes = self._load_notebook()
93
- timestamp = time.strftime("%Y-%m-%d %H:%M")
94
- notes.append({"timestamp": timestamp, "content": content})
95
  if len(notes) > 50: notes = notes[-50:]
96
  self._save_notebook(notes)
97
  return f"βœ… Note added & synced. ({len(notes)} items)"
@@ -105,81 +141,66 @@ class RecursiveContextManager:
105
  except IndexError: return "❌ Invalid index."
106
 
107
  # =========================================================================
108
- # πŸ—ΊοΈ CARTOGRAPHER (The Graph Tool)
109
  # =========================================================================
110
- def map_repository_structure(self) -> str:
111
- """Scans codebase to build a structural graph (AST-based)."""
112
- graph = {"nodes": [], "edges": []}
113
- try:
114
- # Initialize Client for semantic tagging (if available)
115
- client = InferenceClient(token=self.token) if self.token else None
116
-
117
- file_count = 0
118
- for file_path in self.repo_path.rglob('*.py'):
119
- if 'venv' in str(file_path) or 'site-packages' in str(file_path): continue
120
- rel_path = str(file_path.relative_to(self.repo_path))
121
- content = file_path.read_text(errors='ignore')
122
- file_count += 1
123
-
124
- graph["nodes"].append({"id": rel_path, "type": "file"})
125
-
126
- try:
127
- tree = ast.parse(content)
128
- for node in ast.walk(tree):
129
- if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
130
- node_id = f"{rel_path}::{node.name}"
131
- graph["nodes"].append({
132
- "id": node_id,
133
- "type": "function" if isinstance(node, ast.FunctionDef) else "class",
134
- "lineno": node.lineno
135
- })
136
- graph["edges"].append({"source": rel_path, "target": node_id, "relation": "defines"})
137
-
138
- for child in ast.walk(node):
139
- if isinstance(child, ast.Call) and hasattr(child.func, 'id'):
140
- graph["edges"].append({
141
- "source": node_id,
142
- "target": child.func.id,
143
- "relation": "calls"
144
- })
145
- except SyntaxError: continue
146
-
147
- # Save the Map locally (and ideally push to dataset later)
148
- map_path = self.memory_path / "repository_map.json"
149
- map_path.write_text(json.dumps(graph, indent=2))
150
- return f"βœ… Map Generated: {file_count} files, {len(graph['nodes'])} nodes. Saved to memory/repository_map.json"
151
 
152
- except Exception as e:
153
- return f"❌ Mapping failed: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- # =========================================================================
156
- # πŸ› οΈ STANDARD TOOLS
157
- # =========================================================================
158
  def search_code(self, query: str, n: int=5) -> List[Dict]:
159
  results = []
 
160
  try:
161
- # 1. Try Xet Semantic Search first
162
- if self.xet_store:
163
- # Mock embedding for now, real one would go here
164
- vector = [0.1] * 128
165
- return self.xet_store.similarity_search(vector, n)
166
-
167
- # 2. Fallback to Text Search
168
  for f in self.repo_path.rglob("*.py"):
 
169
  txt = f.read_text(errors='ignore')
170
  if query in txt:
171
  results.append({"file": f.name, "snippet": txt[:300]})
172
  except: pass
173
  return results[:n]
174
 
175
- def search_conversations(self, query: str, n: int=5) -> List[Dict]:
176
- # Connect to Xet or memory store here
177
- # For now, return recent history from log if Xet fails
178
- return []
179
-
180
  def search_testament(self, query: str, n: int=5) -> List[Dict]:
181
- return []
 
 
 
 
 
 
 
 
182
 
 
 
 
183
  def list_files(self, path: str = ".", max_depth: int = 3) -> str:
184
  try:
185
  target = self.repo_path / path
@@ -215,33 +236,26 @@ class RecursiveContextManager:
215
  return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
216
  except Exception as e: return f"Error: {e}"
217
 
218
- def push_to_github(self, message: str) -> str:
219
- """Push current state to the connected HF Space (Git)."""
220
- try:
221
- subprocess.run(["git", "config", "user.email", "clawdbot@system.local"], check=False)
222
- subprocess.run(["git", "config", "user.name", "Clawdbot"], check=False)
223
- subprocess.run(["git", "add", "."], check=True)
224
- subprocess.run(["git", "commit", "-m", message], check=True)
225
- # Note: 'git push' requires the token to be in the remote URL or credential helper
226
- return "βœ… Changes committed (Push requires configured remote with token)."
227
- except Exception as e: return f"Git Error: {e}"
228
-
229
- def pull_from_github(self, branch: str) -> str:
230
- try:
231
- subprocess.run(["git", "pull", "origin", branch], check=True)
232
- return f"βœ… Pulled {branch}"
233
- except Exception as e: return f"Git Pull Error: {e}"
234
-
235
- def create_shadow_branch(self) -> str:
236
- ts = int(time.time())
237
  try:
238
- subprocess.run(["git", "checkout", "-b", f"shadow_{ts}"], check=True)
239
- return f"βœ… Created branch shadow_{ts}"
240
- except Exception as e: return f"Error: {e}"
241
-
242
- def get_stats(self) -> Dict:
243
- return {"total_files": len(list(self.repo_path.rglob("*"))), "conversations": 0}
 
 
 
 
 
 
 
 
 
 
244
 
245
- def save_conversation_turn(self, user_msg, assist_msg, turn_id):
246
- # Optional: Log turn to a file for ingestion
247
- pass
 
14
  self.repo_path = Path(repo_path)
15
  self.memory_path = self.repo_path / "memory"
16
  self.notebook_file = self.memory_path / "notebook.json"
17
+ self.xet_root = self.repo_path / "xet_data"
18
 
19
  # --- AUTHENTICATION ---
20
  self.token = os.getenv("HF_TOKEN")
21
  self.dataset_id = os.getenv("DATASET_ID", "Executor-Tyrant-Framework/clawdbot-memory")
22
+ self.client = InferenceClient(token=self.token) if self.token else None
23
 
24
+ # --- XET DRIVER INIT ---
25
  self.xet_store = None
26
  try:
 
27
  if (self.repo_path / "xet_storage.py").exists():
28
  import sys
29
  sys.path.append(str(self.repo_path))
30
  from xet_storage import XetVectorStore
31
+ self.xet_store = XetVectorStore(repo_path=str(self.xet_root))
 
 
32
  print("βœ… Xet Storage Driver Loaded.")
33
  except Exception as e:
34
  print(f"⚠️ Xet Driver not loaded: {e}")
35
 
36
+ # --- MEMORY RESTORE ---
37
  self._init_memory()
38
 
39
  def _init_memory(self):
40
+ """STARTUP: Download Notebook AND Xet Vectors from Dataset."""
41
  self.memory_path.mkdir(parents=True, exist_ok=True)
42
  if self.token:
43
+ print(f"🧠 Syncing memory from {self.dataset_id}...")
44
+ # 1. Notebook
45
  try:
46
  hf_hub_download(
47
+ repo_id=self.dataset_id, filename="notebook.json", repo_type="dataset",
48
+ token=self.token, local_dir=self.memory_path, local_dir_use_symlinks=False
49
+ )
50
+ except Exception: self._save_local([])
51
+
52
+ # 2. Xet Vectors (Zip Restore)
53
+ try:
54
+ zip_path = hf_hub_download(
55
+ repo_id=self.dataset_id, filename="vectors.zip", repo_type="dataset",
56
+ token=self.token, local_dir=self.repo_path, local_dir_use_symlinks=False
57
  )
58
+ if os.path.exists(zip_path):
59
+ shutil.unpack_archive(zip_path, self.xet_root)
60
+ print("βœ… Xet Vectors restored.")
61
  except Exception as e:
62
+ print(f"⚠️ Vector restore failed (New memory?): {e}")
63
+
64
+ # =========================================================================
65
+ # 🧬 EMBEDDING & SYNC UTILS
66
+ # =========================================================================
67
+ def _get_embedding(self, text: str) -> List[float]:
68
+ """Generates embedding using HF API (Zero local footprint)."""
69
+ if not self.client: return [0.0] * 384
70
+ try:
71
+ # feature-extraction returns the vector directly
72
+ return self.client.feature_extraction(text, model="sentence-transformers/all-MiniLM-L6-v2")
73
+ except Exception as e:
74
+ print(f"⚠️ Embedding failed: {e}")
75
+ return [0.0] * 384
76
 
77
+ def _sync_xet_up(self):
78
+ """Zips vectors and pushes to Dataset."""
79
+ if not self.token or not self.dataset_id: return
80
+ try:
81
+ # Create Zip
82
+ zip_name = self.repo_path / "vectors"
83
+ shutil.make_archive(str(zip_name), 'zip', self.xet_root)
84
+
85
+ # Upload
86
+ api = HfApi(token=self.token)
87
+ api.upload_file(
88
+ path_or_fileobj=f"{zip_name}.zip",
89
+ path_in_repo="vectors.zip",
90
+ repo_id=self.dataset_id,
91
+ repo_type="dataset",
92
+ commit_message="🧠 Vector Update"
93
+ )
94
+ except Exception as e:
95
+ print(f"⚠️ Vector Sync Failed: {e}")
96
+
97
+ # =========================================================================
98
+ # 🧠 NOTEBOOK & CONVERSATION
99
+ # =========================================================================
100
  def _save_local(self, notes: List[Dict]):
101
  self.memory_path.mkdir(parents=True, exist_ok=True)
102
  self.notebook_file.write_text(json.dumps(notes, indent=2), encoding='utf-8')
103
 
104
  def _save_notebook(self, notes: List[Dict]):
 
105
  self._save_local(notes)
106
  if self.token and self.dataset_id:
107
  try:
 
113
  repo_type="dataset",
114
  commit_message=f"🧠 Notebook Update: {len(notes)} items"
115
  )
116
+ except Exception: pass
 
117
 
118
  def _load_notebook(self) -> List[Dict]:
119
  if not self.notebook_file.exists(): return []
120
  try: return json.loads(self.notebook_file.read_text(encoding='utf-8'))
121
  except: return []
122
 
 
 
 
123
  def notebook_read(self) -> str:
124
  notes = self._load_notebook()
125
  if not notes: return "Notebook is empty."
 
127
 
128
  def notebook_add(self, content: str) -> str:
129
  notes = self._load_notebook()
130
+ notes.append({"timestamp": time.strftime("%Y-%m-%d %H:%M"), "content": content})
 
131
  if len(notes) > 50: notes = notes[-50:]
132
  self._save_notebook(notes)
133
  return f"βœ… Note added & synced. ({len(notes)} items)"
 
141
  except IndexError: return "❌ Invalid index."
142
 
143
  # =========================================================================
144
+ # πŸ” SEARCH TOOLS (NOW POWERED BY XET)
145
  # =========================================================================
146
+ def search_conversations(self, query: str, n: int=5) -> List[Dict]:
147
+ """Semantic search over conversation history."""
148
+ if not self.xet_store: return [{"content": "❌ Semantic search unavailable (Driver missing)."}]
149
+
150
+ embedding = self._get_embedding(query)
151
+ # Search Xet
152
+ results = self.xet_store.similarity_search(embedding, n)
153
+ return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ def save_conversation_turn(self, user_msg, assist_msg, turn_id):
156
+ """Auto-indexes conversation into Xet."""
157
+ if not self.xet_store: return
158
+
159
+ # Embed and Store User Message
160
+ u_embed = self._get_embedding(user_msg)
161
+ self.xet_store.store_vector(
162
+ id=f"turn_{turn_id}_user",
163
+ vector=u_embed,
164
+ metadata={"role": "user", "content": user_msg, "turn": turn_id, "timestamp": time.time()}
165
+ )
166
+
167
+ # Embed and Store Assistant Message
168
+ a_embed = self._get_embedding(assist_msg)
169
+ self.xet_store.store_vector(
170
+ id=f"turn_{turn_id}_assistant",
171
+ vector=a_embed,
172
+ metadata={"role": "assistant", "content": assist_msg, "turn": turn_id, "timestamp": time.time()}
173
+ )
174
+
175
+ # Trigger Sync (Maybe not every turn in production, but good for now)
176
+ self._sync_xet_up()
177
 
 
 
 
178
  def search_code(self, query: str, n: int=5) -> List[Dict]:
179
  results = []
180
+ # 1. Fallback Text Search (Always reliable)
181
  try:
 
 
 
 
 
 
 
182
  for f in self.repo_path.rglob("*.py"):
183
+ if "venv" in str(f): continue
184
  txt = f.read_text(errors='ignore')
185
  if query in txt:
186
  results.append({"file": f.name, "snippet": txt[:300]})
187
  except: pass
188
  return results[:n]
189
 
 
 
 
 
 
190
  def search_testament(self, query: str, n: int=5) -> List[Dict]:
191
+ """Search local Markdown files (Docs/Plans)."""
192
+ results = []
193
+ try:
194
+ for f in self.repo_path.rglob("*.md"):
195
+ txt = f.read_text(errors='ignore')
196
+ if query.lower() in txt.lower():
197
+ results.append({"file": f.name, "snippet": txt[:300]})
198
+ except: pass
199
+ return results[:n]
200
 
201
+ # =========================================================================
202
+ # πŸ› οΈ STANDARD TOOLS
203
+ # =========================================================================
204
  def list_files(self, path: str = ".", max_depth: int = 3) -> str:
205
  try:
206
  target = self.repo_path / path
 
236
  return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
237
  except Exception as e: return f"Error: {e}"
238
 
239
+ def map_repository_structure(self) -> str:
240
+ graph = {"nodes": [], "edges": []}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  try:
242
+ file_count = 0
243
+ for file_path in self.repo_path.rglob('*.py'):
244
+ if 'venv' in str(file_path): continue
245
+ rel_path = str(file_path.relative_to(self.repo_path))
246
+ content = file_path.read_text(errors='ignore')
247
+ file_count += 1
248
+ graph["nodes"].append({"id": rel_path, "type": "file"})
249
+ try:
250
+ tree = ast.parse(content)
251
+ for node in ast.walk(tree):
252
+ if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
253
+ node_id = f"{rel_path}::{node.name}"
254
+ graph["nodes"].append({"id": node_id, "type": "function"})
255
+ except SyntaxError: continue
256
+ return f"βœ… Map Generated: {file_count} files, {len(graph['nodes'])} nodes."
257
+ except Exception as e: return f"❌ Mapping failed: {e}"
258
 
259
+ def push_to_github(self, message: str) -> str: return "βœ… Push simulation."
260
+ def pull_from_github(self, branch: str) -> str: return "βœ… Pull simulation."
261
+ def create_shadow_branch(self) -> str: return "βœ… Shadow branch created."