claude-mpm 4.15.2__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 (203) 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/agentic-coder-optimizer.json +9 -2
  7. claude_mpm/agents/templates/api_qa.json +7 -1
  8. claude_mpm/agents/templates/clerk-ops.json +8 -1
  9. claude_mpm/agents/templates/code_analyzer.json +4 -1
  10. claude_mpm/agents/templates/dart_engineer.json +11 -1
  11. claude_mpm/agents/templates/data_engineer.json +11 -1
  12. claude_mpm/agents/templates/documentation.json +6 -1
  13. claude_mpm/agents/templates/engineer.json +18 -1
  14. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  15. claude_mpm/agents/templates/golang_engineer.json +11 -1
  16. claude_mpm/agents/templates/java_engineer.json +12 -2
  17. claude_mpm/agents/templates/local_ops_agent.json +216 -37
  18. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  19. claude_mpm/agents/templates/ops.json +8 -1
  20. claude_mpm/agents/templates/php-engineer.json +11 -1
  21. claude_mpm/agents/templates/project_organizer.json +9 -2
  22. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  23. claude_mpm/agents/templates/python_engineer.json +19 -4
  24. claude_mpm/agents/templates/qa.json +7 -1
  25. claude_mpm/agents/templates/react_engineer.json +11 -1
  26. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  27. claude_mpm/agents/templates/research.json +4 -1
  28. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  29. claude_mpm/agents/templates/rust_engineer.json +23 -8
  30. claude_mpm/agents/templates/security.json +6 -1
  31. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  32. claude_mpm/agents/templates/ticketing.json +6 -1
  33. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  34. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  35. claude_mpm/agents/templates/version_control.json +8 -1
  36. claude_mpm/agents/templates/web_qa.json +7 -1
  37. claude_mpm/agents/templates/web_ui.json +11 -1
  38. claude_mpm/cli/commands/__init__.py +2 -0
  39. claude_mpm/cli/commands/configure.py +164 -16
  40. claude_mpm/cli/commands/configure_agent_display.py +6 -6
  41. claude_mpm/cli/commands/configure_behavior_manager.py +8 -8
  42. claude_mpm/cli/commands/configure_navigation.py +20 -18
  43. claude_mpm/cli/commands/configure_startup_manager.py +14 -14
  44. claude_mpm/cli/commands/configure_template_editor.py +8 -8
  45. claude_mpm/cli/commands/mpm_init.py +109 -24
  46. claude_mpm/cli/commands/skills.py +434 -0
  47. claude_mpm/cli/executor.py +2 -0
  48. claude_mpm/cli/interactive/__init__.py +3 -0
  49. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  50. claude_mpm/cli/parsers/base_parser.py +7 -0
  51. claude_mpm/cli/parsers/skills_parser.py +137 -0
  52. claude_mpm/cli/startup.py +83 -0
  53. claude_mpm/commands/mpm-auto-configure.md +52 -0
  54. claude_mpm/commands/mpm-help.md +3 -0
  55. claude_mpm/commands/mpm-init.md +112 -6
  56. claude_mpm/commands/mpm-version.md +113 -0
  57. claude_mpm/commands/mpm.md +1 -0
  58. claude_mpm/config/agent_config.py +2 -2
  59. claude_mpm/constants.py +12 -0
  60. claude_mpm/core/config.py +42 -0
  61. claude_mpm/core/enums.py +18 -0
  62. claude_mpm/core/factories.py +1 -1
  63. claude_mpm/core/optimized_agent_loader.py +3 -3
  64. claude_mpm/core/types.py +2 -9
  65. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  66. claude_mpm/dashboard/templates/index.html +3 -41
  67. claude_mpm/hooks/__init__.py +8 -0
  68. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  69. claude_mpm/hooks/session_resume_hook.py +121 -0
  70. claude_mpm/models/resume_log.py +340 -0
  71. claude_mpm/services/agents/auto_config_manager.py +1 -1
  72. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  73. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  74. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  75. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  76. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  77. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  78. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  79. claude_mpm/services/agents/local_template_manager.py +1 -1
  80. claude_mpm/services/agents/recommender.py +47 -0
  81. claude_mpm/services/cli/resume_service.py +617 -0
  82. claude_mpm/services/cli/session_manager.py +87 -0
  83. claude_mpm/services/cli/session_resume_helper.py +352 -0
  84. claude_mpm/services/core/models/health.py +1 -28
  85. claude_mpm/services/core/path_resolver.py +1 -1
  86. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  87. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  88. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  89. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  90. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  91. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  92. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  93. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  94. claude_mpm/services/local_ops/__init__.py +1 -1
  95. claude_mpm/services/local_ops/crash_detector.py +1 -1
  96. claude_mpm/services/local_ops/health_checks/http_check.py +2 -1
  97. claude_mpm/services/local_ops/health_checks/process_check.py +2 -1
  98. claude_mpm/services/local_ops/health_checks/resource_check.py +2 -1
  99. claude_mpm/services/local_ops/health_manager.py +1 -1
  100. claude_mpm/services/local_ops/restart_manager.py +1 -1
  101. claude_mpm/services/mcp_config_manager.py +7 -131
  102. claude_mpm/services/session_manager.py +205 -1
  103. claude_mpm/services/shared/async_service_base.py +16 -27
  104. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  105. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  106. claude_mpm/services/socketio/handlers/hook.py +10 -0
  107. claude_mpm/services/socketio/handlers/registry.py +4 -2
  108. claude_mpm/services/socketio/server/main.py +7 -7
  109. claude_mpm/services/unified/deployment_strategies/local.py +1 -1
  110. claude_mpm/services/version_service.py +104 -1
  111. claude_mpm/skills/__init__.py +42 -0
  112. claude_mpm/skills/agent_skills_injector.py +331 -0
  113. claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
  114. claude_mpm/skills/bundled/__init__.py +6 -0
  115. claude_mpm/skills/bundled/api-documentation.md +393 -0
  116. claude_mpm/skills/bundled/async-testing.md +571 -0
  117. claude_mpm/skills/bundled/code-review.md +143 -0
  118. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +75 -0
  119. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +184 -0
  120. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +107 -0
  121. claude_mpm/skills/bundled/collaboration/requesting-code-review/code-reviewer.md +146 -0
  122. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +118 -0
  123. claude_mpm/skills/bundled/database-migration.md +199 -0
  124. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +177 -0
  125. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  126. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  127. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  128. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  129. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  130. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  131. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  132. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  133. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  134. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  135. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +175 -0
  136. claude_mpm/skills/bundled/debugging/verification-before-completion/references/common-failures.md +213 -0
  137. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +314 -0
  138. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +227 -0
  139. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  140. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  141. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  142. claude_mpm/skills/bundled/git-workflow.md +414 -0
  143. claude_mpm/skills/bundled/imagemagick.md +204 -0
  144. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  145. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +74 -0
  146. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +32 -0
  147. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  148. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  149. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  150. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  151. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +328 -0
  152. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  153. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  154. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  155. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  156. claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +150 -0
  157. claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +372 -0
  158. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +209 -0
  159. claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +302 -0
  160. claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +111 -0
  161. claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +65 -0
  162. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  163. claude_mpm/skills/bundled/pdf.md +141 -0
  164. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  165. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  166. claude_mpm/skills/bundled/security-scanning.md +327 -0
  167. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  168. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  169. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +123 -0
  170. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  171. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  172. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  173. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  174. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  175. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  176. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +304 -0
  177. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +96 -0
  178. claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
  179. claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +40 -0
  180. claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
  181. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +107 -0
  182. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  183. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  184. claude_mpm/skills/bundled/xlsx.md +157 -0
  185. claude_mpm/skills/registry.py +286 -0
  186. claude_mpm/skills/skill_manager.py +310 -0
  187. claude_mpm/skills/skills_registry.py +351 -0
  188. claude_mpm/skills/skills_service.py +730 -0
  189. claude_mpm/utils/agent_dependency_loader.py +2 -2
  190. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/METADATA +211 -33
  191. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/RECORD +195 -115
  192. claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
  193. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  194. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  195. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  196. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  197. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  198. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  199. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  200. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/WHEEL +0 -0
  201. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/entry_points.txt +0 -0
  202. {claude_mpm-4.15.2.dist-info → claude_mpm-4.20.3.dist-info}/licenses/LICENSE +0 -0
  203. {claude_mpm-4.15.2.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
@@ -16,36 +16,9 @@ ARCHITECTURE:
16
16
 
17
17
  from dataclasses import asdict, dataclass, field
18
18
  from datetime import datetime
19
- from enum import Enum
20
19
  from typing import Any, Dict, List
21
20
 
22
-
23
- class HealthStatus(Enum):
24
- """
25
- Health status levels.
26
-
27
- WHY: Provides granular health states to distinguish between different
28
- levels of service degradation.
29
-
30
- States:
31
- HEALTHY: All checks passing, process operating normally
32
- DEGRADED: Process running but with issues (high resource usage, slow responses)
33
- UNHEALTHY: Critical failure (process dead, crashed, or unresponsive)
34
- UNKNOWN: Cannot determine health status
35
- """
36
-
37
- HEALTHY = "healthy"
38
- DEGRADED = "degraded"
39
- UNHEALTHY = "unhealthy"
40
- UNKNOWN = "unknown"
41
-
42
- def is_operational(self) -> bool:
43
- """Check if status indicates operational service."""
44
- return self in (HealthStatus.HEALTHY, HealthStatus.DEGRADED)
45
-
46
- def is_critical(self) -> bool:
47
- """Check if status indicates critical failure."""
48
- return self == HealthStatus.UNHEALTHY
21
+ from ....core.enums import HealthStatus
49
22
 
50
23
 
51
24
  @dataclass
@@ -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 = (
@@ -3,12 +3,12 @@
3
3
  Exports main monitoring components for backward compatibility.
4
4
  """
5
5
 
6
+ from ....core.enums import HealthStatus
6
7
  from .aggregator import MonitoringAggregatorService
7
8
  from .base import (
8
9
  HealthChecker,
9
10
  HealthCheckResult,
10
11
  HealthMetric,
11
- HealthStatus,
12
12
  )
13
13
 
14
14
  # Legacy exports for backward compatibility
@@ -9,12 +9,12 @@ import time
9
9
  from collections import deque
10
10
  from typing import Any, Callable, Dict, List, Optional
11
11
 
12
+ from ....core.enums import HealthStatus
12
13
  from .base import (
13
14
  BaseMonitoringService,
14
15
  HealthChecker,
15
16
  HealthCheckResult,
16
17
  HealthMetric,
17
- HealthStatus,
18
18
  )
19
19
 
20
20
 
@@ -212,14 +212,14 @@ class MonitoringAggregatorService(BaseMonitoringService):
212
212
 
213
213
  total_metrics = len(metrics)
214
214
 
215
- # Critical if any critical metrics
216
- if status_counts[HealthStatus.CRITICAL] > 0:
217
- return HealthStatus.CRITICAL
215
+ # Unhealthy if any unhealthy metrics
216
+ if status_counts[HealthStatus.UNHEALTHY] > 0:
217
+ return HealthStatus.UNHEALTHY
218
218
 
219
- # Warning if >30% warning metrics
220
- warning_ratio = status_counts[HealthStatus.WARNING] / total_metrics
221
- if warning_ratio > 0.3:
222
- return HealthStatus.WARNING
219
+ # Degraded if >30% degraded metrics
220
+ degraded_ratio = status_counts[HealthStatus.DEGRADED] / total_metrics
221
+ if degraded_ratio > 0.3:
222
+ return HealthStatus.DEGRADED
223
223
 
224
224
  # Unknown if >50% unknown metrics
225
225
  unknown_ratio = status_counts[HealthStatus.UNKNOWN] / total_metrics
@@ -375,10 +375,10 @@ class MonitoringAggregatorService(BaseMonitoringService):
375
375
  checks_count = len(recent_results)
376
376
 
377
377
  # Determine aggregated status
378
- if status_counts[HealthStatus.CRITICAL] > 0:
379
- aggregated_status = HealthStatus.CRITICAL
380
- elif status_counts[HealthStatus.WARNING] > checks_count * 0.3:
381
- aggregated_status = HealthStatus.WARNING
378
+ if status_counts[HealthStatus.UNHEALTHY] > 0:
379
+ aggregated_status = HealthStatus.UNHEALTHY
380
+ elif status_counts[HealthStatus.DEGRADED] > checks_count * 0.3:
381
+ aggregated_status = HealthStatus.DEGRADED
382
382
  elif status_counts[HealthStatus.UNKNOWN] > checks_count * 0.5:
383
383
  aggregated_status = HealthStatus.UNKNOWN
384
384
  else:
@@ -7,17 +7,9 @@ import time
7
7
  from abc import ABC, abstractmethod
8
8
  from dataclasses import asdict, dataclass
9
9
  from datetime import datetime, timezone
10
- from enum import Enum
11
10
  from typing import Any, Dict, List, Optional, Union
12
11
 
13
-
14
- class HealthStatus(Enum):
15
- """Health status levels for monitoring."""
16
-
17
- HEALTHY = "healthy"
18
- WARNING = "warning"
19
- CRITICAL = "critical"
20
- UNKNOWN = "unknown"
12
+ from ....core.enums import HealthStatus
21
13
 
22
14
 
23
15
  @dataclass
@@ -75,11 +67,11 @@ class HealthCheckResult:
75
67
  "healthy_metrics": len(
76
68
  [m for m in self.metrics if m.status == HealthStatus.HEALTHY]
77
69
  ),
78
- "warning_metrics": len(
79
- [m for m in self.metrics if m.status == HealthStatus.WARNING]
70
+ "degraded_metrics": len(
71
+ [m for m in self.metrics if m.status == HealthStatus.DEGRADED]
80
72
  ),
81
- "critical_metrics": len(
82
- [m for m in self.metrics if m.status == HealthStatus.CRITICAL]
73
+ "unhealthy_metrics": len(
74
+ [m for m in self.metrics if m.status == HealthStatus.UNHEALTHY]
83
75
  ),
84
76
  }
85
77
 
@@ -6,7 +6,8 @@ Monitors network connectivity, port availability, and socket health.
6
6
  import socket
7
7
  from typing import Dict, List, Optional
8
8
 
9
- from .base import BaseMonitoringService, HealthMetric, HealthStatus
9
+ from ....core.enums import HealthStatus
10
+ from .base import BaseMonitoringService, HealthMetric
10
11
 
11
12
 
12
13
  class NetworkHealthService(BaseMonitoringService):
@@ -98,7 +99,7 @@ class NetworkHealthService(BaseMonitoringService):
98
99
  HealthMetric(
99
100
  name="socket_creation",
100
101
  value=False,
101
- status=HealthStatus.CRITICAL,
102
+ status=HealthStatus.UNHEALTHY,
102
103
  message=f"Failed to create socket: {e}",
103
104
  )
104
105
  )
@@ -142,11 +143,11 @@ class NetworkHealthService(BaseMonitoringService):
142
143
  )
143
144
  )
144
145
  else:
145
- # Determine if this is critical or warning based on endpoint type
146
+ # Determine if this is unhealthy or degraded based on endpoint type
146
147
  status = (
147
- HealthStatus.WARNING
148
+ HealthStatus.DEGRADED
148
149
  if "optional" in name.lower()
149
- else HealthStatus.CRITICAL
150
+ else HealthStatus.UNHEALTHY
150
151
  )
151
152
  metrics.append(
152
153
  HealthMetric(
@@ -161,7 +162,7 @@ class NetworkHealthService(BaseMonitoringService):
161
162
  HealthMetric(
162
163
  name=metric_name,
163
164
  value=False,
164
- status=HealthStatus.WARNING,
165
+ status=HealthStatus.DEGRADED,
165
166
  message=f"Connection to {host}:{port} timed out after {timeout}s",
166
167
  )
167
168
  )