claude-mpm 4.16.0__py3-none-any.whl → 4.20.3__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 (131) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  3. claude_mpm/agents/BASE_PM.md +255 -23
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +40 -0
  5. claude_mpm/agents/agent_loader.py +4 -4
  6. claude_mpm/agents/templates/engineer.json +5 -1
  7. claude_mpm/agents/templates/python_engineer.json +8 -3
  8. claude_mpm/agents/templates/rust_engineer.json +12 -7
  9. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  10. claude_mpm/cli/commands/__init__.py +2 -0
  11. claude_mpm/cli/commands/mpm_init.py +109 -24
  12. claude_mpm/cli/commands/skills.py +434 -0
  13. claude_mpm/cli/executor.py +2 -0
  14. claude_mpm/cli/parsers/base_parser.py +7 -0
  15. claude_mpm/cli/parsers/skills_parser.py +137 -0
  16. claude_mpm/cli/startup.py +57 -0
  17. claude_mpm/commands/mpm-auto-configure.md +52 -0
  18. claude_mpm/commands/mpm-help.md +3 -0
  19. claude_mpm/commands/mpm-init.md +112 -6
  20. claude_mpm/commands/mpm-version.md +113 -0
  21. claude_mpm/commands/mpm.md +1 -0
  22. claude_mpm/config/agent_config.py +2 -2
  23. claude_mpm/constants.py +12 -0
  24. claude_mpm/core/config.py +42 -0
  25. claude_mpm/core/factories.py +1 -1
  26. claude_mpm/core/optimized_agent_loader.py +3 -3
  27. claude_mpm/hooks/__init__.py +8 -0
  28. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  29. claude_mpm/hooks/session_resume_hook.py +121 -0
  30. claude_mpm/models/resume_log.py +340 -0
  31. claude_mpm/services/agents/auto_config_manager.py +1 -1
  32. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  33. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  34. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  35. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  36. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  37. claude_mpm/services/agents/local_template_manager.py +1 -1
  38. claude_mpm/services/agents/recommender.py +47 -0
  39. claude_mpm/services/cli/resume_service.py +617 -0
  40. claude_mpm/services/cli/session_manager.py +87 -0
  41. claude_mpm/services/cli/session_resume_helper.py +352 -0
  42. claude_mpm/services/core/path_resolver.py +1 -1
  43. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  44. claude_mpm/services/mcp_config_manager.py +7 -131
  45. claude_mpm/services/session_manager.py +205 -1
  46. claude_mpm/services/unified/deployment_strategies/local.py +1 -1
  47. claude_mpm/services/version_service.py +104 -1
  48. claude_mpm/skills/__init__.py +21 -0
  49. claude_mpm/skills/agent_skills_injector.py +331 -0
  50. claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
  51. claude_mpm/skills/bundled/api-documentation.md +393 -0
  52. claude_mpm/skills/bundled/async-testing.md +571 -0
  53. claude_mpm/skills/bundled/code-review.md +143 -0
  54. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +75 -0
  55. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +184 -0
  56. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +107 -0
  57. claude_mpm/skills/bundled/collaboration/requesting-code-review/code-reviewer.md +146 -0
  58. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +118 -0
  59. claude_mpm/skills/bundled/database-migration.md +199 -0
  60. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +177 -0
  61. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  62. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  63. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  64. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  65. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  66. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  67. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  68. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  69. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  70. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  71. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +175 -0
  72. claude_mpm/skills/bundled/debugging/verification-before-completion/references/common-failures.md +213 -0
  73. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +314 -0
  74. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +227 -0
  75. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  76. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  77. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  78. claude_mpm/skills/bundled/git-workflow.md +414 -0
  79. claude_mpm/skills/bundled/imagemagick.md +204 -0
  80. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  81. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +74 -0
  82. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +32 -0
  83. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  84. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  85. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  86. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  87. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +328 -0
  88. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  89. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  90. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  91. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  92. claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +150 -0
  93. claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +372 -0
  94. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +209 -0
  95. claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +302 -0
  96. claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +111 -0
  97. claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +65 -0
  98. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  99. claude_mpm/skills/bundled/pdf.md +141 -0
  100. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  101. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  102. claude_mpm/skills/bundled/security-scanning.md +327 -0
  103. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  104. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  105. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +123 -0
  106. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  107. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  108. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  109. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  110. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  111. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  112. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +304 -0
  113. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +96 -0
  114. claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
  115. claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +40 -0
  116. claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
  117. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +107 -0
  118. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  119. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  120. claude_mpm/skills/bundled/xlsx.md +157 -0
  121. claude_mpm/skills/registry.py +97 -9
  122. claude_mpm/skills/skills_registry.py +351 -0
  123. claude_mpm/skills/skills_service.py +730 -0
  124. claude_mpm/utils/agent_dependency_loader.py +2 -2
  125. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/METADATA +181 -32
  126. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/RECORD +130 -48
  127. claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
  128. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/WHEEL +0 -0
  129. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/entry_points.txt +0 -0
  130. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/licenses/LICENSE +0 -0
  131. {claude_mpm-4.16.0.dist-info → claude_mpm-4.20.3.dist-info}/top_level.txt +0 -0
@@ -11,8 +11,10 @@ DESIGN DECISIONS:
11
11
  - Automatic session cleanup and archiving
12
12
  - Thread-safe session operations
13
13
  - Non-blocking validation with structured warnings
14
+ - Async-first design with periodic auto-save task
14
15
  """
15
16
 
17
+ import asyncio
16
18
  import gzip
17
19
  import json
18
20
  import uuid
@@ -217,8 +219,39 @@ class SessionManager(ISessionManager):
217
219
  self.config_service = config_service
218
220
  self.logger = get_logger("SessionManager")
219
221
  self._sessions_cache: Dict[str, SessionInfo] = {}
222
+ self._auto_save_task: Optional[asyncio.Task] = None
223
+ self._running = False
220
224
  self._load_sessions()
221
225
 
226
+ # Start auto-save task if enabled and event loop is running
227
+ if config_service:
228
+ auto_save_enabled = config_service.get("session.auto_save", True)
229
+ if auto_save_enabled:
230
+ self._start_auto_save()
231
+ else:
232
+ self.logger.info("Auto-save disabled by configuration")
233
+ else:
234
+ self.logger.debug("No config service provided, auto-save not started")
235
+
236
+ def _start_auto_save(self) -> None:
237
+ """Start the auto-save background task.
238
+
239
+ WHY: Separated from __init__ to allow safe initialization without event loop.
240
+ Can be called when event loop is available.
241
+ """
242
+ try:
243
+ loop = asyncio.get_running_loop()
244
+ self._running = True
245
+ self._auto_save_task = loop.create_task(self._periodic_session_save())
246
+ self.logger.info("Auto-save task started")
247
+ except RuntimeError:
248
+ # No event loop running, schedule for later
249
+ self.logger.debug(
250
+ "No event loop running, auto-save will start when loop is available"
251
+ )
252
+ # Set flag so we know to start it later
253
+ self._running = True
254
+
222
255
  def create_session(
223
256
  self, context: str = "default", options: Optional[Dict[str, Any]] = None
224
257
  ) -> SessionInfo:
@@ -477,6 +510,60 @@ class SessionManager(ISessionManager):
477
510
  self.logger.error(f"Failed to load sessions: {e}")
478
511
  self._sessions_cache = {}
479
512
 
513
+ async def _periodic_session_save(self) -> None:
514
+ """Periodically save sessions to disk.
515
+
516
+ WHY: Ensures sessions are persisted regularly to prevent data loss.
517
+ Follows the async pattern from EventAggregator._periodic_cleanup().
518
+ """
519
+ if not self.config_service:
520
+ self.logger.warning("No config service, cannot determine save interval")
521
+ return
522
+
523
+ save_interval = self.config_service.get("session.save_interval", 300)
524
+ self.logger.info(f"Starting periodic session save (interval: {save_interval}s)")
525
+
526
+ while self._running:
527
+ try:
528
+ await asyncio.sleep(save_interval)
529
+
530
+ if self._sessions_cache:
531
+ self._save_sessions()
532
+ self.logger.debug(
533
+ f"Auto-saved {len(self._sessions_cache)} session(s)"
534
+ )
535
+ else:
536
+ self.logger.debug("No sessions to save")
537
+
538
+ except asyncio.CancelledError:
539
+ self.logger.info("Auto-save task cancelled")
540
+ break
541
+ except Exception as e:
542
+ self.logger.error(f"Error in auto-save task: {e}")
543
+
544
+ async def cleanup(self) -> None:
545
+ """Clean up resources and stop background tasks.
546
+
547
+ WHY: Ensures graceful shutdown of the SessionManager and all background tasks.
548
+ """
549
+ self.logger.info("Shutting down SessionManager...")
550
+ self._running = False
551
+
552
+ # Cancel auto-save task
553
+ if self._auto_save_task and not self._auto_save_task.done():
554
+ self._auto_save_task.cancel()
555
+ try:
556
+ await self._auto_save_task
557
+ except asyncio.CancelledError:
558
+ pass
559
+
560
+ # Final save before shutdown
561
+ if self._sessions_cache:
562
+ self._save_sessions()
563
+ self.logger.info(f"Final save: {len(self._sessions_cache)} session(s)")
564
+
565
+ self.logger.info("SessionManager shutdown complete")
566
+
480
567
 
481
568
  # Context manager for session management
482
569
  class ManagedSession:
@@ -0,0 +1,352 @@
1
+ """Session Resume Helper Service.
2
+
3
+ WHY: This service provides automatic session resume detection and prompting for PM startup.
4
+ It detects paused sessions, calculates git changes since pause, and presents resumption
5
+ context to users.
6
+
7
+ DESIGN DECISIONS:
8
+ - Project-specific session storage (.claude-mpm/sessions/pause/)
9
+ - Non-blocking detection with graceful degradation
10
+ - Git change detection for context updates
11
+ - User-friendly prompts with time elapsed information
12
+ - Integration with existing SessionManager infrastructure
13
+ """
14
+
15
+ import json
16
+ import subprocess
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Tuple
20
+
21
+ from claude_mpm.core.logger import get_logger
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class SessionResumeHelper:
27
+ """Helper for automatic session resume detection and prompting."""
28
+
29
+ def __init__(self, project_path: Optional[Path] = None):
30
+ """Initialize session resume helper.
31
+
32
+ Args:
33
+ project_path: Project root path (default: current directory)
34
+ """
35
+ self.project_path = project_path or Path.cwd()
36
+ self.pause_dir = self.project_path / ".claude-mpm" / "sessions" / "pause"
37
+
38
+ def has_paused_sessions(self) -> bool:
39
+ """Check if there are any paused sessions.
40
+
41
+ Returns:
42
+ True if paused sessions exist, False otherwise
43
+ """
44
+ if not self.pause_dir.exists():
45
+ return False
46
+
47
+ # Look for session JSON files
48
+ session_files = list(self.pause_dir.glob("session-*.json"))
49
+ return len(session_files) > 0
50
+
51
+ def get_most_recent_session(self) -> Optional[Dict[str, Any]]:
52
+ """Get the most recent paused session.
53
+
54
+ Returns:
55
+ Session data dictionary or None if no sessions found
56
+ """
57
+ if not self.pause_dir.exists():
58
+ return None
59
+
60
+ # Find all session files
61
+ session_files = list(self.pause_dir.glob("session-*.json"))
62
+ if not session_files:
63
+ return None
64
+
65
+ # Sort by modification time (most recent first)
66
+ session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
67
+
68
+ # Load the most recent session
69
+ try:
70
+ with session_files[0].open("r") as f:
71
+ session_data = json.load(f)
72
+ session_data["file_path"] = session_files[0]
73
+ return session_data
74
+ except Exception as e:
75
+ logger.error(f"Failed to load session file {session_files[0]}: {e}")
76
+ return None
77
+
78
+ def get_git_changes_since_pause(
79
+ self, paused_at: str, recent_commits: List[Dict[str, str]]
80
+ ) -> Tuple[int, List[Dict[str, str]]]:
81
+ """Calculate git changes since session was paused.
82
+
83
+ Args:
84
+ paused_at: ISO-8601 timestamp when session was paused
85
+ recent_commits: List of recent commits from session data
86
+
87
+ Returns:
88
+ Tuple of (new_commit_count, new_commits_list)
89
+ """
90
+ try:
91
+ # Parse pause timestamp
92
+ pause_time = datetime.fromisoformat(paused_at)
93
+
94
+ # Get commits since pause time
95
+ cmd = [
96
+ "git",
97
+ "log",
98
+ f'--since="{pause_time.isoformat()}"',
99
+ "--pretty=format:%h|%an|%ai|%s",
100
+ "--all",
101
+ ]
102
+
103
+ result = subprocess.run(
104
+ cmd,
105
+ cwd=self.project_path,
106
+ capture_output=True,
107
+ text=True,
108
+ check=False,
109
+ )
110
+
111
+ if result.returncode != 0:
112
+ logger.warning(f"Git log command failed: {result.stderr}")
113
+ return 0, []
114
+
115
+ # Parse commit output
116
+ new_commits = []
117
+ for line in result.stdout.strip().split("\n"):
118
+ if line:
119
+ parts = line.split("|", 3)
120
+ if len(parts) == 4:
121
+ new_commits.append(
122
+ {
123
+ "sha": parts[0],
124
+ "author": parts[1],
125
+ "timestamp": parts[2],
126
+ "message": parts[3],
127
+ }
128
+ )
129
+
130
+ return len(new_commits), new_commits
131
+
132
+ except Exception as e:
133
+ logger.error(f"Failed to get git changes: {e}")
134
+ return 0, []
135
+
136
+ def get_time_elapsed(self, paused_at: str) -> str:
137
+ """Calculate human-readable time elapsed since pause.
138
+
139
+ Args:
140
+ paused_at: ISO-8601 timestamp when session was paused
141
+
142
+ Returns:
143
+ Human-readable time string (e.g., "2 hours ago", "3 days ago")
144
+ """
145
+ try:
146
+ pause_time = datetime.fromisoformat(paused_at)
147
+ now = datetime.now(timezone.utc)
148
+
149
+ # Ensure pause_time is timezone-aware
150
+ if pause_time.tzinfo is None:
151
+ pause_time = pause_time.replace(tzinfo=timezone.utc)
152
+
153
+ delta = now - pause_time
154
+
155
+ # Calculate time components
156
+ days = delta.days
157
+ hours = delta.seconds // 3600
158
+ minutes = (delta.seconds % 3600) // 60
159
+
160
+ # Format human-readable string
161
+ if days > 0:
162
+ if days == 1:
163
+ return "1 day ago"
164
+ return f"{days} days ago"
165
+ if hours > 0:
166
+ if hours == 1:
167
+ return "1 hour ago"
168
+ return f"{hours} hours ago"
169
+ if minutes > 0:
170
+ if minutes == 1:
171
+ return "1 minute ago"
172
+ return f"{minutes} minutes ago"
173
+ return "just now"
174
+
175
+ except Exception as e:
176
+ logger.error(f"Failed to calculate time elapsed: {e}")
177
+ return "unknown time ago"
178
+
179
+ def format_resume_prompt(self, session_data: Dict[str, Any]) -> str:
180
+ """Format a user-friendly resume prompt.
181
+
182
+ Args:
183
+ session_data: Session data dictionary
184
+
185
+ Returns:
186
+ Formatted prompt string for display
187
+ """
188
+ try:
189
+ # Extract session information
190
+ paused_at = session_data.get("paused_at", "")
191
+ conversation = session_data.get("conversation", {})
192
+ git_context = session_data.get("git_context", {})
193
+
194
+ summary = conversation.get("summary", "No summary available")
195
+ accomplishments = conversation.get("accomplishments", [])
196
+ next_steps = conversation.get("next_steps", [])
197
+
198
+ # Calculate time elapsed
199
+ time_ago = self.get_time_elapsed(paused_at)
200
+
201
+ # Get git changes
202
+ recent_commits = git_context.get("recent_commits", [])
203
+ new_commit_count, new_commits = self.get_git_changes_since_pause(
204
+ paused_at, recent_commits
205
+ )
206
+
207
+ # Build prompt
208
+ lines = []
209
+ lines.append("\n" + "=" * 80)
210
+ lines.append("📋 PAUSED SESSION FOUND")
211
+ lines.append("=" * 80)
212
+ lines.append(f"\nPaused: {time_ago}")
213
+ lines.append(f"\nLast working on: {summary}")
214
+
215
+ if accomplishments:
216
+ lines.append("\nCompleted:")
217
+ for item in accomplishments[:5]: # Limit to first 5
218
+ lines.append(f" ✓ {item}")
219
+ if len(accomplishments) > 5:
220
+ lines.append(f" ... and {len(accomplishments) - 5} more")
221
+
222
+ if next_steps:
223
+ lines.append("\nNext steps:")
224
+ for item in next_steps[:5]: # Limit to first 5
225
+ lines.append(f" • {item}")
226
+ if len(next_steps) > 5:
227
+ lines.append(f" ... and {len(next_steps) - 5} more")
228
+
229
+ # Git changes information
230
+ if new_commit_count > 0:
231
+ lines.append(f"\nGit changes since pause: {new_commit_count} commits")
232
+ if new_commits:
233
+ lines.append("\nRecent commits:")
234
+ for commit in new_commits[:3]: # Show first 3
235
+ lines.append(
236
+ f" {commit['sha']} - {commit['message']} ({commit['author']})"
237
+ )
238
+ if len(new_commits) > 3:
239
+ lines.append(f" ... and {len(new_commits) - 3} more")
240
+ else:
241
+ lines.append("\nNo git changes since pause")
242
+
243
+ lines.append("\n" + "=" * 80)
244
+ lines.append(
245
+ "Use this context to resume work, or start fresh if not relevant."
246
+ )
247
+ lines.append("=" * 80 + "\n")
248
+
249
+ return "\n".join(lines)
250
+
251
+ except Exception as e:
252
+ logger.error(f"Failed to format resume prompt: {e}")
253
+ return "\n📋 Paused session found, but failed to format details.\n"
254
+
255
+ def check_and_display_resume_prompt(self) -> Optional[Dict[str, Any]]:
256
+ """Check for paused sessions and display resume prompt if found.
257
+
258
+ This is the main entry point for PM startup integration.
259
+
260
+ Returns:
261
+ Session data if found and user should resume, None otherwise
262
+ """
263
+ if not self.has_paused_sessions():
264
+ logger.debug("No paused sessions found")
265
+ return None
266
+
267
+ # Get most recent session
268
+ session_data = self.get_most_recent_session()
269
+ if not session_data:
270
+ logger.debug("Failed to load paused session data")
271
+ return None
272
+
273
+ # Display resume prompt
274
+ prompt_text = self.format_resume_prompt(session_data)
275
+ print(prompt_text)
276
+
277
+ # Return session data for PM to use
278
+ return session_data
279
+
280
+ def clear_session(self, session_data: Dict[str, Any]) -> bool:
281
+ """Clear a paused session after successful resume.
282
+
283
+ Args:
284
+ session_data: Session data dictionary with 'file_path' key
285
+
286
+ Returns:
287
+ True if successfully cleared, False otherwise
288
+ """
289
+ try:
290
+ file_path = session_data.get("file_path")
291
+ if not file_path or not isinstance(file_path, Path):
292
+ logger.error("Invalid session file path")
293
+ return False
294
+
295
+ if file_path.exists():
296
+ file_path.unlink()
297
+ logger.info(f"Cleared paused session: {file_path}")
298
+
299
+ # Also remove SHA256 checksum file if exists
300
+ sha_file = file_path.parent / f".{file_path.name}.sha256"
301
+ if sha_file.exists():
302
+ sha_file.unlink()
303
+ logger.debug(f"Cleared session checksum: {sha_file}")
304
+
305
+ return True
306
+ logger.warning(f"Session file not found: {file_path}")
307
+ return False
308
+
309
+ except Exception as e:
310
+ logger.error(f"Failed to clear session: {e}")
311
+ return False
312
+
313
+ def get_session_count(self) -> int:
314
+ """Get count of paused sessions.
315
+
316
+ Returns:
317
+ Number of paused sessions
318
+ """
319
+ if not self.pause_dir.exists():
320
+ return 0
321
+
322
+ session_files = list(self.pause_dir.glob("session-*.json"))
323
+ return len(session_files)
324
+
325
+ def list_all_sessions(self) -> List[Dict[str, Any]]:
326
+ """List all paused sessions sorted by most recent.
327
+
328
+ Returns:
329
+ List of session data dictionaries
330
+ """
331
+ if not self.pause_dir.exists():
332
+ return []
333
+
334
+ session_files = list(self.pause_dir.glob("session-*.json"))
335
+ if not session_files:
336
+ return []
337
+
338
+ # Sort by modification time (most recent first)
339
+ session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
340
+
341
+ sessions = []
342
+ for session_file in session_files:
343
+ try:
344
+ with session_file.open("r") as f:
345
+ session_data = json.load(f)
346
+ session_data["file_path"] = session_file
347
+ sessions.append(session_data)
348
+ except Exception as e:
349
+ logger.error(f"Failed to load session {session_file}: {e}")
350
+ continue
351
+
352
+ return sessions
@@ -281,7 +281,7 @@ class PathResolver(IPathResolver):
281
281
 
282
282
  if agents_dir and agents_dir.exists():
283
283
  discovered_agents_dir = agents_dir
284
- self.logger.info(f"Using custom agents directory: {discovered_agents_dir}")
284
+ self.logger.debug(f"Using custom agents directory: {discovered_agents_dir}")
285
285
  elif framework_path and framework_path != Path("__PACKAGED__"):
286
286
  # Prioritize templates directory over main agents directory
287
287
  templates_dir = (