claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  8. claude_mpm/cli/__main__.py +4 -0
  9. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  11. claude_mpm/cli/commands/agents.py +169 -31
  12. claude_mpm/cli/commands/auto_configure.py +210 -25
  13. claude_mpm/cli/commands/config.py +88 -2
  14. claude_mpm/cli/commands/configure.py +1111 -161
  15. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  16. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  17. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  18. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  19. claude_mpm/cli/commands/skills.py +214 -189
  20. claude_mpm/cli/commands/summarize.py +413 -0
  21. claude_mpm/cli/executor.py +11 -3
  22. claude_mpm/cli/parsers/agents_parser.py +54 -9
  23. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  24. claude_mpm/cli/parsers/base_parser.py +5 -0
  25. claude_mpm/cli/parsers/config_parser.py +153 -83
  26. claude_mpm/cli/parsers/skills_parser.py +3 -2
  27. claude_mpm/cli/startup.py +550 -94
  28. claude_mpm/commands/mpm-config.md +265 -0
  29. claude_mpm/commands/mpm-help.md +14 -95
  30. claude_mpm/commands/mpm-organize.md +500 -0
  31. claude_mpm/config/agent_sources.py +27 -0
  32. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  33. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  34. claude_mpm/core/framework_loader.py +4 -2
  35. claude_mpm/core/logger.py +13 -0
  36. claude_mpm/core/output_style_manager.py +173 -43
  37. claude_mpm/core/socketio_pool.py +3 -3
  38. claude_mpm/core/unified_agent_registry.py +134 -16
  39. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  42. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  45. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  46. claude_mpm/hooks/memory_integration_hook.py +46 -1
  47. claude_mpm/init.py +0 -19
  48. claude_mpm/models/agent_definition.py +7 -0
  49. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  50. claude_mpm/scripts/launch_monitor.py +93 -13
  51. claude_mpm/scripts/start_activity_logging.py +0 -0
  52. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  53. claude_mpm/services/agents/agent_review_service.py +280 -0
  54. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  55. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
  57. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
  58. claude_mpm/services/agents/git_source_manager.py +34 -0
  59. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  60. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  61. claude_mpm/services/agents/toolchain_detector.py +10 -6
  62. claude_mpm/services/analysis/__init__.py +11 -1
  63. claude_mpm/services/analysis/clone_detector.py +1030 -0
  64. claude_mpm/services/command_deployment_service.py +81 -10
  65. claude_mpm/services/event_bus/config.py +3 -1
  66. claude_mpm/services/git/git_operations_service.py +93 -8
  67. claude_mpm/services/monitor/daemon.py +9 -2
  68. claude_mpm/services/monitor/daemon_manager.py +39 -3
  69. claude_mpm/services/monitor/server.py +225 -19
  70. claude_mpm/services/self_upgrade_service.py +120 -12
  71. claude_mpm/services/skills/__init__.py +3 -0
  72. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  73. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  74. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  75. claude_mpm/services/skills_deployer.py +126 -9
  76. claude_mpm/services/socketio/event_normalizer.py +15 -1
  77. claude_mpm/services/socketio/server/core.py +160 -21
  78. claude_mpm/services/version_control/git_operations.py +103 -0
  79. claude_mpm/utils/agent_filters.py +17 -44
  80. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  81. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
  82. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  83. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  84. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  85. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  86. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  87. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  88. claude_mpm/agents/BASE_OPS.md +0 -219
  89. claude_mpm/agents/BASE_PM.md +0 -480
  90. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  91. claude_mpm/agents/BASE_QA.md +0 -167
  92. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  93. claude_mpm/agents/base_agent.json +0 -31
  94. claude_mpm/agents/base_agent_loader.py +0 -601
  95. claude_mpm/cli/commands/agents_detect.py +0 -380
  96. claude_mpm/cli/commands/agents_recommend.py +0 -309
  97. claude_mpm/cli/ticket_cli.py +0 -35
  98. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  99. claude_mpm/commands/mpm-agents-detect.md +0 -177
  100. claude_mpm/commands/mpm-agents-list.md +0 -131
  101. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  102. claude_mpm/commands/mpm-config-view.md +0 -150
  103. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  104. claude_mpm/dashboard/analysis_runner.py +0 -455
  105. claude_mpm/dashboard/index.html +0 -13
  106. claude_mpm/dashboard/open_dashboard.py +0 -66
  107. claude_mpm/dashboard/static/css/activity.css +0 -1958
  108. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  109. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  110. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  111. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  112. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  113. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  114. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  115. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  116. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  117. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  118. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  119. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  120. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  121. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  122. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  123. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  124. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  125. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  126. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  127. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  128. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  129. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  130. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  131. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  132. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  133. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  134. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  135. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  136. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  137. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  138. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  139. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  140. claude_mpm/dashboard/templates/code_simple.html +0 -153
  141. claude_mpm/dashboard/templates/index.html +0 -606
  142. claude_mpm/dashboard/test_dashboard.html +0 -372
  143. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  154. claude_mpm/scripts/mcp_server.py +0 -75
  155. claude_mpm/scripts/mcp_wrapper.py +0 -39
  156. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  157. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  158. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  159. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  160. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  161. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  162. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  163. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  164. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  165. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  166. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  167. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  168. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  169. claude_mpm/services/mcp_gateway/main.py +0 -589
  170. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  171. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  172. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  173. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  174. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  175. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  176. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  177. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  178. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  179. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  180. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  181. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  182. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  183. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  184. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  185. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  186. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  187. claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
  188. claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
  189. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  190. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  191. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
@@ -23,10 +23,14 @@ class CommandDeploymentService(BaseService):
23
23
  DEPRECATED_COMMANDS = [
24
24
  "mpm-agents.md", # Replaced by mpm-agents-list.md
25
25
  "mpm-auto-configure.md", # Replaced by mpm-agents-auto-configure.md
26
- "mpm-config.md", # Replaced by mpm-config-view.md
27
- "mpm-organize.md", # Replaced by mpm-ticket-organize.md
26
+ "mpm-config-view.md", # Replaced by mpm-config.md
28
27
  "mpm-resume.md", # Replaced by mpm-session-resume.md
29
28
  "mpm-ticket.md", # Replaced by mpm-ticket-view.md
29
+ # Removed - consolidated into /mpm-configure
30
+ "mpm-agents-list.md", # Consolidated into /mpm-configure
31
+ "mpm-agents-detect.md", # Consolidated into /mpm-configure
32
+ "mpm-agents-auto-configure.md", # Consolidated into /mpm-configure
33
+ "mpm-agents-recommend.md", # Consolidated into /mpm-configure
30
34
  ]
31
35
 
32
36
  def __init__(self):
@@ -315,10 +319,14 @@ class CommandDeploymentService(BaseService):
315
319
  replacement_map = {
316
320
  "mpm-agents.md": "mpm-agents-list.md",
317
321
  "mpm-auto-configure.md": "mpm-agents-auto-configure.md",
318
- "mpm-config.md": "mpm-config-view.md",
319
- "mpm-organize.md": "mpm-ticket-organize.md",
322
+ "mpm-config-view.md": "mpm-config.md",
320
323
  "mpm-resume.md": "mpm-session-resume.md",
321
324
  "mpm-ticket.md": "mpm-ticket-view.md",
325
+ # Removed commands - consolidated into /mpm-configure
326
+ "mpm-agents-list.md": "mpm-configure (use /mpm-configure)",
327
+ "mpm-agents-detect.md": "mpm-configure (use /mpm-configure)",
328
+ "mpm-agents-auto-configure.md": "mpm-configure (use /mpm-configure)",
329
+ "mpm-agents-recommend.md": "mpm-configure (use /mpm-configure)",
322
330
  }
323
331
 
324
332
  for deprecated_cmd in self.DEPRECATED_COMMANDS:
@@ -345,13 +353,71 @@ class CommandDeploymentService(BaseService):
345
353
 
346
354
  return removed
347
355
 
356
+ def remove_stale_commands(self) -> int:
357
+ """Remove stale MPM commands that no longer exist in source.
358
+
359
+ This method cleans up deployed commands that have been deleted or renamed
360
+ in the source directory. It's called automatically on startup to ensure
361
+ deployed commands stay in sync with source.
362
+
363
+ Returns:
364
+ Number of stale files removed
365
+ """
366
+ if not self.target_dir.exists():
367
+ self.logger.debug(
368
+ f"Target directory does not exist: {self.target_dir}, skipping stale command cleanup"
369
+ )
370
+ return 0
371
+
372
+ if not self.source_dir.exists():
373
+ self.logger.warning(
374
+ f"Source directory does not exist: {self.source_dir}, cannot detect stale commands"
375
+ )
376
+ return 0
377
+
378
+ # Get current source commands (ground truth)
379
+ source_commands = {f.name for f in self.source_dir.glob("mpm*.md")}
380
+
381
+ # Get deployed commands
382
+ deployed_commands = {f.name for f in self.target_dir.glob("mpm*.md")}
383
+
384
+ # Find stale commands (deployed but not in source, excluding deprecated)
385
+ stale_commands = (
386
+ deployed_commands - source_commands - set(self.DEPRECATED_COMMANDS)
387
+ )
388
+
389
+ if not stale_commands:
390
+ self.logger.debug("No stale commands found to remove")
391
+ return 0
392
+
393
+ removed = 0
394
+ self.logger.info(f"Cleaning up {len(stale_commands)} stale command(s)...")
395
+
396
+ for stale_cmd in stale_commands:
397
+ stale_file = self.target_dir / stale_cmd
398
+ try:
399
+ stale_file.unlink()
400
+ self.logger.info(
401
+ f"Removed stale command: {stale_cmd} (no longer in source)"
402
+ )
403
+ removed += 1
404
+ except Exception as e:
405
+ # Log error but don't fail startup - this is non-critical
406
+ self.logger.warning(f"Failed to remove stale command {stale_cmd}: {e}")
407
+
408
+ if removed > 0:
409
+ self.logger.info(f"Removed {removed} stale command(s)")
410
+
411
+ return removed
412
+
348
413
 
349
414
  def deploy_commands_on_startup(force: bool = False) -> None:
350
415
  """Convenience function to deploy commands during startup.
351
416
 
352
417
  This function:
353
418
  1. Removes deprecated commands that have been replaced
354
- 2. Deploys current command files
419
+ 2. Removes stale commands that no longer exist in source
420
+ 3. Deploys current command files
355
421
 
356
422
  Args:
357
423
  force: Force deployment even if files exist
@@ -359,12 +425,17 @@ def deploy_commands_on_startup(force: bool = False) -> None:
359
425
  service = CommandDeploymentService()
360
426
  logger = get_logger("startup")
361
427
 
362
- # Clean up deprecated commands BEFORE deploying new ones
363
- removed_count = service.remove_deprecated_commands()
364
- if removed_count > 0:
365
- logger.info(f"Cleaned up {removed_count} deprecated command(s)")
428
+ # Clean up deprecated commands FIRST (known old commands)
429
+ deprecated_count = service.remove_deprecated_commands()
430
+ if deprecated_count > 0:
431
+ logger.info(f"Cleaned up {deprecated_count} deprecated command(s)")
432
+
433
+ # Clean up stale commands SECOND (deployed but not in source anymore)
434
+ stale_count = service.remove_stale_commands()
435
+ if stale_count > 0:
436
+ logger.info(f"Cleaned up {stale_count} stale command(s)")
366
437
 
367
- # Deploy current commands
438
+ # Deploy current commands LAST
368
439
  result = service.deploy_commands(force=force)
369
440
 
370
441
  if result["deployed"]:
@@ -52,9 +52,11 @@ class EventBusConfig:
52
52
  )
53
53
 
54
54
  # Relay configuration
55
+ # DirectSocketIORelay disabled by default - events already emit via direct sio.emit()
56
+ # Enable with CLAUDE_MPM_RELAY_ENABLED=true if needed for external consumers
55
57
  relay_enabled: bool = field(
56
58
  default_factory=lambda: os.environ.get(
57
- "CLAUDE_MPM_RELAY_ENABLED", "true"
59
+ "CLAUDE_MPM_RELAY_ENABLED", "false"
58
60
  ).lower()
59
61
  == "true"
60
62
  )
@@ -18,10 +18,13 @@ Example:
18
18
  ... service.commit(Path("~/.claude-mpm/cache/remote-agents"), "feat: improve research agent memory handling")
19
19
  """
20
20
 
21
+ import logging
21
22
  import subprocess
22
23
  from pathlib import Path
23
24
  from typing import List, Optional, Tuple
24
25
 
26
+ logger = logging.getLogger(__name__)
27
+
25
28
 
26
29
  # Custom Exceptions
27
30
  class GitOperationError(Exception):
@@ -314,7 +317,12 @@ class GitOperationsService:
314
317
 
315
318
  def pull(self, repo_path: Path, branch: str = "main") -> bool:
316
319
  """
317
- Pull latest changes from remote.
320
+ Pull latest changes from remote with automatic divergent branch recovery.
321
+
322
+ For cache/sync repositories, automatically handles divergent branches by:
323
+ 1. First attempting git pull --rebase (cleaner history)
324
+ 2. If rebase fails or conflicts exist, hard reset to match remote exactly
325
+ (cache repos should mirror remote state, local changes are discarded)
318
326
 
319
327
  Args:
320
328
  repo_path: Repository path
@@ -325,21 +333,98 @@ class GitOperationsService:
325
333
 
326
334
  Raises:
327
335
  GitOperationError: If pull fails
328
- GitConflictError: If merge conflicts detected
336
+ GitConflictError: If merge conflicts detected and cannot be auto-resolved
329
337
  """
330
338
  self._validate_repo(repo_path)
331
339
 
340
+ # First, try standard pull
332
341
  returncode, _stdout, stderr = self._run_git_command(
333
342
  ["git", "pull", "origin", branch], cwd=repo_path
334
343
  )
335
344
 
336
- if returncode != 0:
337
- # Check for merge conflicts
338
- if "conflict" in stderr.lower():
339
- raise GitConflictError(
340
- f"Merge conflicts detected when pulling {branch}: {stderr}"
345
+ if returncode == 0:
346
+ return True
347
+
348
+ # Check if this is a divergent branch situation
349
+ is_divergent = any(
350
+ phrase in stderr.lower()
351
+ for phrase in [
352
+ "divergent branches",
353
+ "need to specify how to reconcile",
354
+ "have diverged",
355
+ ]
356
+ )
357
+
358
+ if is_divergent:
359
+ logger.warning(
360
+ f"Divergent branches detected in cache repository at {repo_path}. "
361
+ "Attempting automatic recovery with rebase..."
362
+ )
363
+
364
+ # Strategy 1: Try rebase for cleaner history
365
+ returncode, _stdout, stderr = self._run_git_command(
366
+ ["git", "pull", "--rebase", "origin", branch], cwd=repo_path
367
+ )
368
+
369
+ if returncode == 0:
370
+ logger.info(
371
+ f"Successfully resolved divergent branches with rebase for {branch}"
372
+ )
373
+ return True
374
+
375
+ # Check if rebase had conflicts
376
+ has_rebase_conflict = any(
377
+ phrase in stderr.lower()
378
+ for phrase in ["conflict", "rebase in progress"]
379
+ )
380
+
381
+ if has_rebase_conflict:
382
+ logger.warning(
383
+ "Rebase conflicts detected. Aborting rebase and resetting to remote state..."
341
384
  )
342
- raise GitOperationError(f"Failed to pull {branch}: {stderr}")
385
+ # Abort the rebase
386
+ self._run_git_command(["git", "rebase", "--abort"], cwd=repo_path)
387
+
388
+ # Strategy 2: Hard reset to match remote exactly (cache repos should mirror remote)
389
+ logger.warning(
390
+ f"Discarding local changes and resetting to origin/{branch} "
391
+ "(cache repositories should match remote exactly)"
392
+ )
393
+
394
+ # Fetch latest to ensure we have the remote state
395
+ returncode, _stdout, stderr = self._run_git_command(
396
+ ["git", "fetch", "origin", branch], cwd=repo_path
397
+ )
398
+
399
+ if returncode != 0:
400
+ raise GitOperationError(
401
+ f"Failed to fetch from remote during divergent branch recovery: {stderr}"
402
+ )
403
+
404
+ # Hard reset to remote branch
405
+ returncode, _stdout, stderr = self._run_git_command(
406
+ ["git", "reset", "--hard", f"origin/{branch}"], cwd=repo_path
407
+ )
408
+
409
+ if returncode != 0:
410
+ raise GitOperationError(
411
+ f"Failed to reset to origin/{branch} during divergent branch recovery: {stderr}"
412
+ )
413
+
414
+ logger.info(
415
+ f"Successfully reset cache repository to origin/{branch} "
416
+ "(local changes discarded)"
417
+ )
418
+ return True
419
+
420
+ # Check for merge conflicts (non-divergent case)
421
+ if "conflict" in stderr.lower():
422
+ raise GitConflictError(
423
+ f"Merge conflicts detected when pulling {branch}: {stderr}"
424
+ )
425
+
426
+ # Other pull errors
427
+ raise GitOperationError(f"Failed to pull {branch}: {stderr}")
343
428
 
344
429
  return True
345
430
 
@@ -44,6 +44,7 @@ class UnifiedMonitorDaemon:
44
44
  daemon_mode: bool = False,
45
45
  pid_file: Optional[str] = None,
46
46
  log_file: Optional[str] = None,
47
+ enable_hot_reload: bool = False,
47
48
  ):
48
49
  """Initialize the unified monitor daemon.
49
50
 
@@ -53,10 +54,12 @@ class UnifiedMonitorDaemon:
53
54
  daemon_mode: Whether to run as background daemon
54
55
  pid_file: Path to PID file for daemon mode
55
56
  log_file: Path to log file for daemon mode
57
+ enable_hot_reload: Enable file watching and hot reload for development
56
58
  """
57
59
  self.host = host
58
60
  self.port = port
59
61
  self.daemon_mode = daemon_mode
62
+ self.enable_hot_reload = enable_hot_reload
60
63
  self.logger = get_logger(__name__)
61
64
 
62
65
  # Use new consolidated DaemonManager for all daemon operations
@@ -75,7 +78,9 @@ class UnifiedMonitorDaemon:
75
78
  )
76
79
 
77
80
  # Core server
78
- self.server = UnifiedMonitorServer(host=host, port=port)
81
+ self.server = UnifiedMonitorServer(
82
+ host=host, port=port, enable_hot_reload=enable_hot_reload
83
+ )
79
84
 
80
85
  # Health monitoring
81
86
  self.health_monitor = HealthMonitor(port=port)
@@ -510,7 +515,9 @@ class UnifiedMonitorDaemon:
510
515
 
511
516
  # Recreate the server and health monitor after stop() sets them to None
512
517
  self.logger.info(f"Recreating server components for {self.host}:{self.port}")
513
- self.server = UnifiedMonitorServer(host=self.host, port=self.port)
518
+ self.server = UnifiedMonitorServer(
519
+ host=self.host, port=self.port, enable_hot_reload=self.enable_hot_reload
520
+ )
514
521
  self.health_monitor = HealthMonitor(port=self.port)
515
522
 
516
523
  # Reset the shutdown event for the new run
@@ -34,6 +34,11 @@ from typing import Optional, Tuple
34
34
  from ...core.enums import OperationResult
35
35
  from ...core.logging_config import get_logger
36
36
 
37
+ # Exit code constants for signal handling
38
+ EXIT_NORMAL = 0
39
+ EXIT_SIGKILL = 137 # 128 + SIGKILL(9) - forced termination
40
+ EXIT_SIGTERM = 143 # 128 + SIGTERM(15) - graceful shutdown
41
+
37
42
 
38
43
  class DaemonManager:
39
44
  """Centralized manager for all daemon lifecycle operations.
@@ -556,7 +561,7 @@ class DaemonManager:
556
561
  # Use subprocess for clean daemon startup (v4.2.40)
557
562
  # This avoids fork() issues with Python threading
558
563
  if self.use_subprocess_daemon():
559
- return self.start_daemon_subprocess()
564
+ return self.start_daemon_subprocess(force_restart=force_restart)
560
565
  # Fallback to traditional fork (kept for compatibility)
561
566
  return self.daemonize()
562
567
 
@@ -574,12 +579,15 @@ class DaemonManager:
574
579
  # Otherwise, use subprocess for monitor daemon to avoid threading issues
575
580
  return True
576
581
 
577
- def start_daemon_subprocess(self) -> bool:
582
+ def start_daemon_subprocess(self, force_restart: bool = False) -> bool:
578
583
  """Start daemon using subprocess.Popen for clean process isolation.
579
584
 
580
585
  This avoids all the fork() + threading issues by starting the monitor
581
586
  in a completely fresh process with no inherited threads or locks.
582
587
 
588
+ Args:
589
+ force_restart: Whether this is a force restart (helps interpret exit codes)
590
+
583
591
  Returns:
584
592
  True if daemon started successfully
585
593
  """
@@ -652,7 +660,35 @@ class DaemonManager:
652
660
  # Check if process is still running
653
661
  returncode = process.poll()
654
662
  if returncode is not None:
655
- # Process exited - this is the bug we're fixing!
663
+ # Process exited - interpret exit code with context
664
+ # Exit codes 137 (SIGKILL) and 143 (SIGTERM) are common during daemon replacement
665
+ if returncode == EXIT_SIGKILL:
666
+ # SIGKILL - process was forcefully terminated
667
+ if force_restart:
668
+ # This is expected during force restart - old daemon was killed
669
+ self.logger.info(
670
+ f"Previous monitor instance replaced (exit {EXIT_SIGKILL}: SIGKILL during force restart)"
671
+ )
672
+ else:
673
+ # Unexpected SIGKILL - something else killed our new daemon
674
+ self.logger.warning(
675
+ f"Monitor subprocess terminated unexpectedly (exit {EXIT_SIGKILL}: SIGKILL). "
676
+ f"Check {self.log_file} for details."
677
+ )
678
+ return False
679
+ if returncode == EXIT_SIGTERM:
680
+ # SIGTERM - graceful shutdown requested
681
+ self.logger.info(
682
+ f"Monitor subprocess cleanly terminated (exit {EXIT_SIGTERM}: SIGTERM, graceful shutdown)"
683
+ )
684
+ return False
685
+ if returncode == EXIT_NORMAL:
686
+ # Normal exit
687
+ self.logger.info(
688
+ f"Monitor subprocess exited normally (exit code {EXIT_NORMAL})"
689
+ )
690
+ return False
691
+ # Unexpected exit code - this IS an error
656
692
  self.logger.error(
657
693
  f"Monitor daemon subprocess exited prematurely with code {returncode}"
658
694
  )