minion-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. examples/advance_tui.py +508 -0
  2. examples/agent_with_todos.py +165 -0
  3. examples/file_freshness_example.py +97 -0
  4. examples/file_watching_example.py +110 -0
  5. examples/interruptible_tui.py +5 -0
  6. examples/message_response_children_demo.py +226 -0
  7. examples/rich_example.py +4 -0
  8. examples/simple_file_watching.py +57 -0
  9. examples/simple_tui.py +267 -0
  10. examples/simple_usage.py +69 -0
  11. minion_code/__init__.py +16 -0
  12. minion_code/agents/__init__.py +11 -0
  13. minion_code/agents/code_agent.py +320 -0
  14. minion_code/cli.py +502 -0
  15. minion_code/commands/__init__.py +90 -0
  16. minion_code/commands/clear_command.py +70 -0
  17. minion_code/commands/help_command.py +90 -0
  18. minion_code/commands/history_command.py +104 -0
  19. minion_code/commands/quit_command.py +32 -0
  20. minion_code/commands/status_command.py +115 -0
  21. minion_code/commands/tools_command.py +86 -0
  22. minion_code/commands/version_command.py +104 -0
  23. minion_code/components/Message.py +304 -0
  24. minion_code/components/MessageResponse.py +188 -0
  25. minion_code/components/PromptInput.py +534 -0
  26. minion_code/components/__init__.py +29 -0
  27. minion_code/screens/REPL.py +925 -0
  28. minion_code/screens/__init__.py +4 -0
  29. minion_code/services/__init__.py +50 -0
  30. minion_code/services/event_system.py +108 -0
  31. minion_code/services/file_freshness_service.py +582 -0
  32. minion_code/tools/__init__.py +69 -0
  33. minion_code/tools/bash_tool.py +58 -0
  34. minion_code/tools/file_edit_tool.py +238 -0
  35. minion_code/tools/file_read_tool.py +73 -0
  36. minion_code/tools/file_write_tool.py +36 -0
  37. minion_code/tools/glob_tool.py +58 -0
  38. minion_code/tools/grep_tool.py +105 -0
  39. minion_code/tools/ls_tool.py +65 -0
  40. minion_code/tools/multi_edit_tool.py +271 -0
  41. minion_code/tools/python_interpreter_tool.py +105 -0
  42. minion_code/tools/todo_read_tool.py +100 -0
  43. minion_code/tools/todo_write_tool.py +234 -0
  44. minion_code/tools/user_input_tool.py +53 -0
  45. minion_code/types.py +88 -0
  46. minion_code/utils/__init__.py +44 -0
  47. minion_code/utils/mcp_loader.py +211 -0
  48. minion_code/utils/todo_file_utils.py +110 -0
  49. minion_code/utils/todo_storage.py +149 -0
  50. minion_code-0.1.0.dist-info/METADATA +350 -0
  51. minion_code-0.1.0.dist-info/RECORD +59 -0
  52. minion_code-0.1.0.dist-info/WHEEL +5 -0
  53. minion_code-0.1.0.dist-info/entry_points.txt +4 -0
  54. minion_code-0.1.0.dist-info/licenses/LICENSE +661 -0
  55. minion_code-0.1.0.dist-info/top_level.txt +3 -0
  56. tests/__init__.py +1 -0
  57. tests/test_basic.py +20 -0
  58. tests/test_readonly_tools.py +102 -0
  59. tests/test_tools.py +83 -0
@@ -0,0 +1,582 @@
1
+ """File freshness tracking service."""
2
+
3
+ import os
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Dict, Optional, Union, Set, List, NamedTuple
7
+ from dataclasses import dataclass
8
+ import logging
9
+ import threading
10
+
11
+ from .event_system import EventDispatcher, EventContext, emit_event, add_event_listener
12
+ from ..utils.todo_file_utils import get_todo_file_path
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Try to import watchdog, fall back to polling if not available
17
+ try:
18
+ from watchdog.observers import Observer
19
+ from watchdog.events import FileSystemEventHandler, FileModifiedEvent
20
+ WATCHDOG_AVAILABLE = True
21
+ except ImportError:
22
+ WATCHDOG_AVAILABLE = False
23
+ # Create dummy classes for when watchdog is not available
24
+ class FileSystemEventHandler:
25
+ pass
26
+ class Observer:
27
+ pass
28
+ logger.warning("watchdog library not available, file watching will use polling fallback")
29
+
30
+
31
+ @dataclass
32
+ class FileTimestamp:
33
+ """Information about a file's timestamp tracking."""
34
+ path: str
35
+ last_read: float
36
+ last_modified: float
37
+ size: int
38
+ last_agent_edit: Optional[float] = None
39
+
40
+
41
+ class FreshnessResult(NamedTuple):
42
+ """Result of file freshness check."""
43
+ is_fresh: bool
44
+ last_read: Optional[float] = None
45
+ current_modified: Optional[float] = None
46
+ conflict: bool = False
47
+
48
+
49
+ class TodoFileWatcher(FileSystemEventHandler):
50
+ """File system event handler for todo files."""
51
+
52
+ def __init__(self, agent_id: str, file_path: str, service: 'FileFreshnessService'):
53
+ self.agent_id = agent_id
54
+ self.file_path = file_path
55
+ self.service = service
56
+ super().__init__()
57
+
58
+ def on_modified(self, event):
59
+ """Handle file modification events."""
60
+ if event.is_directory:
61
+ return
62
+
63
+ # Check if the modified file is our watched file
64
+ if os.path.abspath(event.src_path) == os.path.abspath(self.file_path):
65
+ logger.debug(f"Todo file modified: {self.file_path} for agent {self.agent_id}")
66
+
67
+ # Check if this was an external modification
68
+ reminder = self.service.generate_file_modification_reminder(self.file_path)
69
+ if reminder:
70
+ # File was modified externally, emit todo change reminder
71
+ emit_event('todo:file_changed', {
72
+ 'agent_id': self.agent_id,
73
+ 'file_path': self.file_path,
74
+ 'reminder': reminder,
75
+ 'timestamp': time.time(),
76
+ 'current_stats': self._get_file_stats(),
77
+ })
78
+
79
+ def _get_file_stats(self) -> Dict[str, Union[float, int]]:
80
+ """Get current file statistics."""
81
+ try:
82
+ if os.path.exists(self.file_path):
83
+ stats = os.stat(self.file_path)
84
+ return {
85
+ 'mtime': stats.st_mtime,
86
+ 'size': stats.st_size
87
+ }
88
+ except Exception:
89
+ pass
90
+ return {'mtime': 0, 'size': 0}
91
+
92
+
93
+ class PollingWatcher:
94
+ """Fallback polling-based file watcher."""
95
+
96
+ def __init__(self, agent_id: str, file_path: str, service: 'FileFreshnessService', interval: float = 1.0):
97
+ self.agent_id = agent_id
98
+ self.file_path = file_path
99
+ self.service = service
100
+ self.interval = interval
101
+ self.running = False
102
+ self.thread = None
103
+ self.last_mtime = None
104
+
105
+ # Get initial modification time
106
+ if os.path.exists(file_path):
107
+ self.last_mtime = os.path.getmtime(file_path)
108
+
109
+ def start(self):
110
+ """Start polling for file changes."""
111
+ if self.running:
112
+ return
113
+
114
+ self.running = True
115
+ self.thread = threading.Thread(target=self._poll_loop, daemon=True)
116
+ self.thread.start()
117
+ logger.debug(f"Started polling watcher for {self.file_path}")
118
+
119
+ def stop(self):
120
+ """Stop polling for file changes."""
121
+ self.running = False
122
+ if self.thread and self.thread.is_alive():
123
+ self.thread.join(timeout=2.0)
124
+ logger.debug(f"Stopped polling watcher for {self.file_path}")
125
+
126
+ def _poll_loop(self):
127
+ """Main polling loop."""
128
+ while self.running:
129
+ try:
130
+ if os.path.exists(self.file_path):
131
+ current_mtime = os.path.getmtime(self.file_path)
132
+
133
+ if self.last_mtime is not None and current_mtime > self.last_mtime:
134
+ # File was modified
135
+ logger.debug(f"Polling detected modification: {self.file_path}")
136
+
137
+ reminder = self.service.generate_file_modification_reminder(self.file_path)
138
+ if reminder:
139
+ emit_event('todo:file_changed', {
140
+ 'agent_id': self.agent_id,
141
+ 'file_path': self.file_path,
142
+ 'reminder': reminder,
143
+ 'timestamp': time.time(),
144
+ })
145
+
146
+ self.last_mtime = current_mtime
147
+ elif self.last_mtime is not None:
148
+ # File was deleted
149
+ logger.debug(f"Polling detected deletion: {self.file_path}")
150
+ self.last_mtime = None
151
+
152
+ time.sleep(self.interval)
153
+
154
+ except Exception as error:
155
+ logger.error(f"Error in polling loop for {self.file_path}: {error}")
156
+ time.sleep(self.interval)
157
+
158
+
159
+ @dataclass
160
+ class FileFreshnessState:
161
+ """State container for file freshness tracking."""
162
+ read_timestamps: Dict[str, FileTimestamp]
163
+ edit_conflicts: Set[str]
164
+ session_files: Set[str]
165
+ watched_todo_files: Dict[str, str] # agent_id -> file_path
166
+ file_watchers: Dict[str, Union[Observer, PollingWatcher]] # agent_id -> watcher
167
+ todo_handlers: Dict[str, TodoFileWatcher] # agent_id -> handler
168
+
169
+
170
+ class FileFreshnessService:
171
+ """Service for tracking file freshness and changes."""
172
+
173
+ def __init__(self):
174
+ self.state = FileFreshnessState(
175
+ read_timestamps={},
176
+ edit_conflicts=set(),
177
+ session_files=set(),
178
+ watched_todo_files={},
179
+ file_watchers={},
180
+ todo_handlers={}
181
+ )
182
+ self.setup_event_listeners()
183
+
184
+ def setup_event_listeners(self) -> None:
185
+ """Setup event listeners for session management."""
186
+ # Listen for session startup events
187
+ add_event_listener('session:startup', self._handle_session_startup)
188
+
189
+ # Listen for todo change events
190
+ add_event_listener('todo:changed', self._handle_todo_changed)
191
+
192
+ # Listen for file access events
193
+ add_event_listener('file:read', self._handle_file_read)
194
+
195
+ # Listen for file edit events
196
+ add_event_listener('file:edited', self._handle_file_edited)
197
+
198
+ def _handle_session_startup(self, context: EventContext) -> None:
199
+ """Handle session startup event."""
200
+ self.reset_session()
201
+ logger.info("File freshness session reset on startup")
202
+
203
+ def _handle_todo_changed(self, context: EventContext) -> None:
204
+ """Handle todo change event."""
205
+ # Update last todo update time if needed
206
+ logger.debug("Todo changed event received")
207
+
208
+ def _handle_file_read(self, context: EventContext) -> None:
209
+ """Handle file read event."""
210
+ # This handler is for external events, not for self-generated events
211
+ # We don't need to do anything here as the event was already processed
212
+ pass
213
+
214
+ def _handle_file_edited(self, context: EventContext) -> None:
215
+ """Handle file edit event."""
216
+ # This handler is for external events, not for self-generated events
217
+ # We don't need to do anything here as the event was already processed
218
+ pass
219
+
220
+ def record_file_read(self, file_path: Union[str, Path]) -> None:
221
+ """Record file read operation with timestamp tracking."""
222
+ path_str = str(file_path)
223
+
224
+ try:
225
+ if not os.path.exists(path_str):
226
+ return
227
+
228
+ stats = os.stat(path_str)
229
+ timestamp = FileTimestamp(
230
+ path=path_str,
231
+ last_read=time.time(),
232
+ last_modified=stats.st_mtime,
233
+ size=stats.st_size
234
+ )
235
+
236
+ self.state.read_timestamps[path_str] = timestamp
237
+ self.state.session_files.add(path_str)
238
+
239
+ # Emit file read event
240
+ emit_event('file:read', {
241
+ 'file_path': path_str,
242
+ 'timestamp': timestamp.last_read,
243
+ 'size': timestamp.size,
244
+ 'modified': timestamp.last_modified
245
+ })
246
+
247
+ logger.debug(f"Recorded file read: {path_str}")
248
+
249
+ except Exception as error:
250
+ logger.error(f"Error recording file read for {path_str}: {error}")
251
+
252
+ def check_file_freshness(self, file_path: Union[str, Path]) -> FreshnessResult:
253
+ """Check if file has been modified since last read."""
254
+ path_str = str(file_path)
255
+ recorded = self.state.read_timestamps.get(path_str)
256
+
257
+ if not recorded:
258
+ return FreshnessResult(is_fresh=True, conflict=False)
259
+
260
+ try:
261
+ if not os.path.exists(path_str):
262
+ return FreshnessResult(is_fresh=False, conflict=True)
263
+
264
+ current_stats = os.stat(path_str)
265
+ is_fresh = current_stats.st_mtime <= recorded.last_modified
266
+ conflict = not is_fresh
267
+
268
+ if conflict:
269
+ self.state.edit_conflicts.add(path_str)
270
+
271
+ # Emit file conflict event
272
+ emit_event('file:conflict', {
273
+ 'file_path': path_str,
274
+ 'last_read': recorded.last_read,
275
+ 'last_modified': recorded.last_modified,
276
+ 'current_modified': current_stats.st_mtime,
277
+ 'size_diff': current_stats.st_size - recorded.size
278
+ })
279
+
280
+ logger.warning(f"File conflict detected: {path_str}")
281
+
282
+ return FreshnessResult(
283
+ is_fresh=is_fresh,
284
+ last_read=recorded.last_read,
285
+ current_modified=current_stats.st_mtime,
286
+ conflict=conflict
287
+ )
288
+
289
+ except Exception as error:
290
+ logger.error(f"Error checking freshness for {path_str}: {error}")
291
+ return FreshnessResult(is_fresh=False, conflict=True)
292
+
293
+ def record_file_edit(self, file_path: Union[str, Path], content: Optional[str] = None) -> None:
294
+ """Record file edit operation by Agent."""
295
+ path_str = str(file_path)
296
+
297
+ try:
298
+ now = time.time()
299
+
300
+ # Update recorded timestamp after edit
301
+ if os.path.exists(path_str):
302
+ stats = os.stat(path_str)
303
+ existing = self.state.read_timestamps.get(path_str)
304
+
305
+ if existing:
306
+ existing.last_modified = stats.st_mtime
307
+ existing.size = stats.st_size
308
+ existing.last_agent_edit = now
309
+ self.state.read_timestamps[path_str] = existing
310
+ else:
311
+ # Create new record for Agent-edited file
312
+ timestamp = FileTimestamp(
313
+ path=path_str,
314
+ last_read=now,
315
+ last_modified=stats.st_mtime,
316
+ size=stats.st_size,
317
+ last_agent_edit=now
318
+ )
319
+ self.state.read_timestamps[path_str] = timestamp
320
+
321
+ # Remove from conflicts since we just edited it
322
+ self.state.edit_conflicts.discard(path_str)
323
+
324
+ # Emit file edit event
325
+ emit_event('file:edited', {
326
+ 'file_path': path_str,
327
+ 'timestamp': now,
328
+ 'content_length': len(content) if content else 0,
329
+ 'source': 'agent'
330
+ })
331
+
332
+ logger.debug(f"Recorded file edit: {path_str}")
333
+
334
+ except Exception as error:
335
+ logger.error(f"Error recording file edit for {path_str}: {error}")
336
+
337
+ def generate_file_modification_reminder(self, file_path: Union[str, Path]) -> Optional[str]:
338
+ """Generate reminder message for externally modified files."""
339
+ path_str = str(file_path)
340
+ recorded = self.state.read_timestamps.get(path_str)
341
+
342
+ if not recorded:
343
+ return None
344
+
345
+ try:
346
+ if not os.path.exists(path_str):
347
+ return f"Note: {path_str} was deleted since last read."
348
+
349
+ current_stats = os.stat(path_str)
350
+ is_modified = current_stats.st_mtime > recorded.last_modified
351
+
352
+ if not is_modified:
353
+ return None
354
+
355
+ # Check if this was an Agent-initiated change
356
+ # Use small time tolerance to handle filesystem timestamp precision issues
357
+ TIME_TOLERANCE_MS = 0.1 # 100ms in seconds
358
+ if (recorded.last_agent_edit and
359
+ recorded.last_agent_edit >= recorded.last_modified - TIME_TOLERANCE_MS):
360
+ # Agent modified this file recently, no reminder needed
361
+ return None
362
+
363
+ # External modification detected - generate reminder
364
+ return f"Note: {path_str} was modified externally since last read. The file may have changed outside of this session."
365
+
366
+ except Exception as error:
367
+ logger.error(f"Error checking modification for {path_str}: {error}")
368
+ return None
369
+
370
+ def get_conflicted_files(self) -> List[str]:
371
+ """Get list of files with edit conflicts."""
372
+ return list(self.state.edit_conflicts)
373
+
374
+ def get_session_files(self) -> List[str]:
375
+ """Get list of files accessed in current session."""
376
+ return list(self.state.session_files)
377
+
378
+ def reset_session(self) -> None:
379
+ """Reset session state."""
380
+ # Clean up existing todo file watchers
381
+ for agent_id in list(self.state.watched_todo_files.keys()):
382
+ self.stop_watching_todo_file(agent_id)
383
+
384
+ self.state = FileFreshnessState(
385
+ read_timestamps={},
386
+ edit_conflicts=set(),
387
+ session_files=set(),
388
+ watched_todo_files={},
389
+ file_watchers={},
390
+ todo_handlers={}
391
+ )
392
+ logger.info("File freshness session reset")
393
+
394
+ def get_file_info(self, file_path: Union[str, Path]) -> Optional[FileTimestamp]:
395
+ """Get file timestamp information."""
396
+ path_str = str(file_path)
397
+ return self.state.read_timestamps.get(path_str)
398
+
399
+ def is_file_tracked(self, file_path: Union[str, Path]) -> bool:
400
+ """Check if file is being tracked."""
401
+ path_str = str(file_path)
402
+ return path_str in self.state.read_timestamps
403
+
404
+ def get_important_files(self, max_files: int = 5) -> List[Dict[str, Union[str, float, int]]]:
405
+ """
406
+ Retrieves files prioritized for recovery during conversation compression.
407
+
408
+ Selects recently accessed files based on:
409
+ - File access recency (most recent first)
410
+ - File type relevance (excludes dependencies, build artifacts)
411
+ - Development workflow importance
412
+ """
413
+ files = []
414
+ for path, info in self.state.read_timestamps.items():
415
+ if self._is_valid_for_recovery(path):
416
+ files.append({
417
+ 'path': path,
418
+ 'timestamp': info.last_read,
419
+ 'size': info.size
420
+ })
421
+
422
+ # Sort by timestamp (newest first) and limit results
423
+ files.sort(key=lambda x: x['timestamp'], reverse=True)
424
+ return files[:max_files]
425
+
426
+ def _is_valid_for_recovery(self, file_path: str) -> bool:
427
+ """
428
+ Determines which files are suitable for automatic recovery.
429
+
430
+ Excludes files that are typically not relevant for development context:
431
+ - Build artifacts and generated files
432
+ - Dependencies and cached files
433
+ - Temporary files and system directories
434
+ """
435
+ return (
436
+ 'node_modules' not in file_path and
437
+ '.git' not in file_path and
438
+ not file_path.startswith('/tmp') and
439
+ '.cache' not in file_path and
440
+ 'dist/' not in file_path and
441
+ 'build/' not in file_path and
442
+ '__pycache__' not in file_path and
443
+ '.pyc' not in file_path
444
+ )
445
+
446
+ def start_watching_todo_file(self, agent_id: str, file_path: Optional[str] = None) -> None:
447
+ """Start watching todo file for an agent."""
448
+ try:
449
+ # Use provided file_path or generate default path using todo_file_utils
450
+ if file_path is None:
451
+ file_path = get_todo_file_path(agent_id)
452
+
453
+ # Don't watch if already watching
454
+ if agent_id in self.state.watched_todo_files:
455
+ logger.debug(f"Already watching todo file for agent {agent_id}")
456
+ return
457
+
458
+ self.state.watched_todo_files[agent_id] = file_path
459
+
460
+ # Record initial state if file exists
461
+ if os.path.exists(file_path):
462
+ self.record_file_read(file_path)
463
+
464
+ # Start watching for changes
465
+ if WATCHDOG_AVAILABLE:
466
+ self._start_watchdog_watcher(agent_id, file_path)
467
+ else:
468
+ self._start_polling_watcher(agent_id, file_path)
469
+
470
+ logger.info(f"Started watching todo file for agent {agent_id}: {file_path}")
471
+
472
+ except Exception as error:
473
+ logger.error(f"Error starting todo file watch for agent {agent_id}: {error}")
474
+
475
+ def stop_watching_todo_file(self, agent_id: str) -> None:
476
+ """Stop watching todo file for an agent."""
477
+ try:
478
+ if agent_id not in self.state.watched_todo_files:
479
+ logger.debug(f"Not watching todo file for agent {agent_id}")
480
+ return
481
+
482
+ # Stop the appropriate watcher
483
+ if agent_id in self.state.file_watchers:
484
+ watcher = self.state.file_watchers[agent_id]
485
+
486
+ if isinstance(watcher, Observer):
487
+ watcher.stop()
488
+ watcher.join(timeout=2.0)
489
+ elif isinstance(watcher, PollingWatcher):
490
+ watcher.stop()
491
+
492
+ del self.state.file_watchers[agent_id]
493
+
494
+ # Clean up handler
495
+ if agent_id in self.state.todo_handlers:
496
+ del self.state.todo_handlers[agent_id]
497
+
498
+ # Remove from watched files
499
+ file_path = self.state.watched_todo_files.pop(agent_id, None)
500
+
501
+ logger.info(f"Stopped watching todo file for agent {agent_id}: {file_path}")
502
+
503
+ except Exception as error:
504
+ logger.error(f"Error stopping todo file watch for agent {agent_id}: {error}")
505
+
506
+ def _start_watchdog_watcher(self, agent_id: str, file_path: str) -> None:
507
+ """Start watchdog-based file watcher."""
508
+ try:
509
+ # Create event handler
510
+ handler = TodoFileWatcher(agent_id, file_path, self)
511
+ self.state.todo_handlers[agent_id] = handler
512
+
513
+ # Create observer and watch the directory containing the file
514
+ observer = Observer()
515
+ watch_dir = os.path.dirname(os.path.abspath(file_path))
516
+
517
+ # Create directory if it doesn't exist
518
+ os.makedirs(watch_dir, exist_ok=True)
519
+
520
+ observer.schedule(handler, watch_dir, recursive=False)
521
+ observer.start()
522
+
523
+ self.state.file_watchers[agent_id] = observer
524
+ logger.debug(f"Started watchdog watcher for {file_path}")
525
+
526
+ except Exception as error:
527
+ logger.error(f"Error starting watchdog watcher for {file_path}: {error}")
528
+ # Fall back to polling
529
+ self._start_polling_watcher(agent_id, file_path)
530
+
531
+ def _start_polling_watcher(self, agent_id: str, file_path: str) -> None:
532
+ """Start polling-based file watcher."""
533
+ try:
534
+ watcher = PollingWatcher(agent_id, file_path, self)
535
+ watcher.start()
536
+
537
+ self.state.file_watchers[agent_id] = watcher
538
+ logger.debug(f"Started polling watcher for {file_path}")
539
+
540
+ except Exception as error:
541
+ logger.error(f"Error starting polling watcher for {file_path}: {error}")
542
+
543
+ def get_watched_files(self) -> Dict[str, str]:
544
+ """Get currently watched todo files."""
545
+ return self.state.watched_todo_files.copy()
546
+
547
+ def is_watching_agent(self, agent_id: str) -> bool:
548
+ """Check if we're watching todo file for an agent."""
549
+ return agent_id in self.state.watched_todo_files
550
+
551
+
552
+ # Global service instance
553
+ file_freshness_service = FileFreshnessService()
554
+
555
+ # Convenience functions for external use
556
+ def record_file_read(file_path: Union[str, Path]) -> None:
557
+ """Record file read operation."""
558
+ file_freshness_service.record_file_read(file_path)
559
+
560
+ def record_file_edit(file_path: Union[str, Path], content: Optional[str] = None) -> None:
561
+ """Record file edit operation."""
562
+ file_freshness_service.record_file_edit(file_path, content)
563
+
564
+ def check_file_freshness(file_path: Union[str, Path]) -> FreshnessResult:
565
+ """Check file freshness."""
566
+ return file_freshness_service.check_file_freshness(file_path)
567
+
568
+ def generate_file_modification_reminder(file_path: Union[str, Path]) -> Optional[str]:
569
+ """Generate file modification reminder."""
570
+ return file_freshness_service.generate_file_modification_reminder(file_path)
571
+
572
+ def reset_file_freshness_session() -> None:
573
+ """Reset file freshness session."""
574
+ file_freshness_service.reset_session()
575
+
576
+ def start_watching_todo_file(agent_id: str, file_path: Optional[str] = None) -> None:
577
+ """Start watching todo file for an agent."""
578
+ file_freshness_service.start_watching_todo_file(agent_id, file_path)
579
+
580
+ def stop_watching_todo_file(agent_id: str) -> None:
581
+ """Stop watching todo file for an agent."""
582
+ file_freshness_service.stop_watching_todo_file(agent_id)
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Minion Code Tools Package
5
+ A collection of development tools for code analysis and manipulation.
6
+ """
7
+
8
+ # Import base classes from minion framework
9
+ from minion.tools import BaseTool, tool, ToolCollection
10
+
11
+ # Import individual tools
12
+ from .file_read_tool import FileReadTool
13
+ from .file_write_tool import FileWriteTool
14
+ from .file_edit_tool import FileEditTool
15
+ from .multi_edit_tool import MultiEditTool
16
+ from .bash_tool import BashTool
17
+ from .grep_tool import GrepTool
18
+ from .glob_tool import GlobTool
19
+ from .ls_tool import LsTool
20
+ from .python_interpreter_tool import PythonInterpreterTool
21
+ from .user_input_tool import UserInputTool
22
+
23
+ from .todo_write_tool import TodoWriteTool
24
+ from .todo_read_tool import TodoReadTool
25
+
26
+ # Tool mapping
27
+ TOOL_MAPPING = {
28
+ tool_class.name: tool_class
29
+ for tool_class in [
30
+ FileReadTool,
31
+ FileWriteTool,
32
+ FileEditTool,
33
+ MultiEditTool,
34
+ BashTool,
35
+ GrepTool,
36
+ GlobTool,
37
+ LsTool,
38
+ PythonInterpreterTool,
39
+ UserInputTool,
40
+
41
+ TodoWriteTool,
42
+ TodoReadTool,
43
+ ]
44
+ }
45
+
46
+ __all__ = [
47
+ # Base classes
48
+ # File system tools
49
+ "FileReadTool",
50
+ "FileWriteTool",
51
+ "FileEditTool",
52
+ "FileEditToolNew",
53
+ "MultiEditTool",
54
+ "BashTool",
55
+ "GrepTool",
56
+ "GlobTool",
57
+ "LsTool",
58
+ # Execution tools
59
+ "PythonInterpreterTool",
60
+ # Web tools
61
+ # Interactive tools
62
+ "UserInputTool",
63
+
64
+ # Todo tools
65
+ "TodoWriteTool",
66
+ "TodoReadTool",
67
+ # Utilities
68
+ "TOOL_MAPPING",
69
+ ]