claude-mpm 4.15.6__py3-none-any.whl → 4.21.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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +272 -23
- claude_mpm/agents/PM_INSTRUCTIONS.md +49 -0
- claude_mpm/agents/agent_loader.py +4 -4
- claude_mpm/agents/templates/engineer.json +5 -1
- claude_mpm/agents/templates/php-engineer.json +10 -4
- claude_mpm/agents/templates/python_engineer.json +8 -3
- claude_mpm/agents/templates/rust_engineer.json +12 -7
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +525 -0
- claude_mpm/cli/commands/mpm_init/display.py +341 -0
- claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
- claude_mpm/cli/commands/mpm_init/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +442 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +67 -1
- claude_mpm/cli/commands/skills.py +488 -0
- claude_mpm/cli/executor.py +2 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +42 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/startup.py +57 -0
- claude_mpm/commands/mpm-auto-configure.md +52 -0
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-init.md +112 -6
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/constants.py +12 -0
- claude_mpm/core/config.py +42 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/interfaces.py +56 -1
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/hooks/__init__.py +8 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/recommender.py +47 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +87 -0
- claude_mpm/services/cli/session_pause_manager.py +504 -0
- claude_mpm/services/cli/session_resume_helper.py +372 -0
- claude_mpm/services/core/interfaces.py +56 -1
- claude_mpm/services/core/models/agent_config.py +3 -0
- claude_mpm/services/core/models/process.py +4 -0
- claude_mpm/services/core/path_resolver.py +1 -1
- claude_mpm/services/diagnostics/models.py +21 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +2 -0
- claude_mpm/services/mcp_config_manager.py +7 -131
- claude_mpm/services/mcp_gateway/auto_configure.py +31 -25
- claude_mpm/services/mcp_gateway/core/process_pool.py +19 -10
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +26 -21
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/unified/deployment_strategies/local.py +1 -1
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/agent_skills_injector.py +324 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +97 -9
- claude_mpm/skills/skills_registry.py +348 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.dist-info}/METADATA +211 -33
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.dist-info}/RECORD +192 -60
- claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
- claude_mpm/cli/commands/mpm_init.py +0 -2008
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.0.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,504 @@
|
|
|
1
|
+
"""Session Pause Manager Service.
|
|
2
|
+
|
|
3
|
+
WHY: This service creates session pause documents that capture complete conversation
|
|
4
|
+
context, git state, todos, and working directory for seamless resume.
|
|
5
|
+
|
|
6
|
+
DESIGN DECISIONS:
|
|
7
|
+
- Three format output (JSON, YAML, Markdown) for different use cases
|
|
8
|
+
- Atomic file operations using StateStorage
|
|
9
|
+
- Git integration for automatic commits
|
|
10
|
+
- Compatible with SessionResumeHelper for resume workflow
|
|
11
|
+
- LATEST-SESSION.txt pointer for quick access
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import subprocess
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, Optional
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from claude_mpm.core.logger import get_logger
|
|
22
|
+
from claude_mpm.storage.state_storage import StateStorage
|
|
23
|
+
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionPauseManager:
|
|
28
|
+
"""Manages creating pause sessions and capturing state."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, project_path: Optional[Path] = None):
|
|
31
|
+
"""Initialize session pause manager.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
project_path: Project root path (default: current directory)
|
|
35
|
+
"""
|
|
36
|
+
self.project_path = (project_path or Path.cwd()).resolve()
|
|
37
|
+
# Use flattened structure: .claude-mpm/sessions/ instead of sessions/pause/
|
|
38
|
+
self.pause_dir = self.project_path / ".claude-mpm" / "sessions"
|
|
39
|
+
self.pause_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
self.storage = StateStorage(self.pause_dir)
|
|
41
|
+
|
|
42
|
+
def create_pause_session(
|
|
43
|
+
self,
|
|
44
|
+
message: Optional[str] = None,
|
|
45
|
+
skip_commit: bool = False,
|
|
46
|
+
export_path: Optional[str] = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Create a pause session with captured state.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: Optional pause reason/context message
|
|
52
|
+
skip_commit: Skip git commit of session state
|
|
53
|
+
export_path: Optional export location for session file
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Session ID
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
Exception: If session creation fails
|
|
60
|
+
"""
|
|
61
|
+
logger.info("Creating pause session")
|
|
62
|
+
|
|
63
|
+
# Generate session ID
|
|
64
|
+
session_id = f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
65
|
+
|
|
66
|
+
# Capture state
|
|
67
|
+
state = self._capture_state(session_id, message)
|
|
68
|
+
|
|
69
|
+
# Save JSON format
|
|
70
|
+
json_path = self.pause_dir / f"{session_id}.json"
|
|
71
|
+
if not self.storage.write_json(state, json_path, atomic=True):
|
|
72
|
+
raise RuntimeError(f"Failed to write JSON to {json_path}")
|
|
73
|
+
logger.debug(f"Saved JSON: {json_path}")
|
|
74
|
+
|
|
75
|
+
# Save YAML format
|
|
76
|
+
yaml_path = self.pause_dir / f"{session_id}.yaml"
|
|
77
|
+
self._save_yaml(state, yaml_path)
|
|
78
|
+
logger.debug(f"Saved YAML: {yaml_path}")
|
|
79
|
+
|
|
80
|
+
# Save Markdown format
|
|
81
|
+
md_path = self.pause_dir / f"{session_id}.md"
|
|
82
|
+
md_content = self._generate_markdown(state)
|
|
83
|
+
md_path.write_text(md_content)
|
|
84
|
+
logger.debug(f"Saved Markdown: {md_path}")
|
|
85
|
+
|
|
86
|
+
# Update LATEST-SESSION.txt pointer
|
|
87
|
+
self._update_latest_pointer(session_id)
|
|
88
|
+
|
|
89
|
+
# Optional export
|
|
90
|
+
if export_path:
|
|
91
|
+
export_file = Path(export_path).resolve()
|
|
92
|
+
if not self.storage.write_json(state, export_file, atomic=True):
|
|
93
|
+
logger.warning(f"Failed to export to {export_file}")
|
|
94
|
+
else:
|
|
95
|
+
logger.info(f"Exported session to {export_file}")
|
|
96
|
+
|
|
97
|
+
# Optional git commit
|
|
98
|
+
if not skip_commit and self._is_git_repo():
|
|
99
|
+
self._commit_pause_session(session_id, message)
|
|
100
|
+
|
|
101
|
+
logger.info(f"Pause session created: {session_id}")
|
|
102
|
+
return session_id
|
|
103
|
+
|
|
104
|
+
def _capture_state(self, session_id: str, message: Optional[str]) -> Dict[str, Any]:
|
|
105
|
+
"""Capture current session state.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
session_id: Session identifier
|
|
109
|
+
message: Optional context message
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Complete state dictionary
|
|
113
|
+
"""
|
|
114
|
+
# Get git context
|
|
115
|
+
git_context = self._get_git_context()
|
|
116
|
+
|
|
117
|
+
# Build state dictionary
|
|
118
|
+
return {
|
|
119
|
+
"session_id": session_id,
|
|
120
|
+
"paused_at": datetime.now(timezone.utc).isoformat(),
|
|
121
|
+
"duration_hours": 0, # Can be calculated if session start time known
|
|
122
|
+
"context_usage": {
|
|
123
|
+
"tokens_used": 0, # Would need Claude API integration
|
|
124
|
+
"tokens_total": 200000,
|
|
125
|
+
"percentage": 0,
|
|
126
|
+
},
|
|
127
|
+
"conversation": {
|
|
128
|
+
"primary_task": "Manual pause - see message below",
|
|
129
|
+
"current_phase": "In progress",
|
|
130
|
+
"summary": message or "No summary provided",
|
|
131
|
+
"accomplishments": [],
|
|
132
|
+
"next_steps": [],
|
|
133
|
+
},
|
|
134
|
+
"git_context": git_context,
|
|
135
|
+
"active_context": {
|
|
136
|
+
"working_directory": str(self.project_path),
|
|
137
|
+
},
|
|
138
|
+
"important_reminders": [],
|
|
139
|
+
"resume_instructions": {
|
|
140
|
+
"quick_start": [
|
|
141
|
+
f"Read {session_id}.md for full context",
|
|
142
|
+
"Run: git status to check current state",
|
|
143
|
+
"Run: cat .claude-mpm/sessions/LATEST-SESSION.txt",
|
|
144
|
+
],
|
|
145
|
+
"files_to_review": [],
|
|
146
|
+
"validation_commands": {
|
|
147
|
+
"check_git": "git status && git log -1 --stat",
|
|
148
|
+
"check_session": f"cat .claude-mpm/sessions/{session_id}.md",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
"open_questions": [],
|
|
152
|
+
"performance_metrics": {},
|
|
153
|
+
"todos": {"active": [], "completed": []},
|
|
154
|
+
"version": self._get_project_version(),
|
|
155
|
+
"build": "current",
|
|
156
|
+
"project_path": str(self.project_path),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
def _get_git_context(self) -> Dict[str, Any]:
|
|
160
|
+
"""Get git repository context.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Git context dictionary
|
|
164
|
+
"""
|
|
165
|
+
if not self._is_git_repo():
|
|
166
|
+
return {
|
|
167
|
+
"is_git_repo": False,
|
|
168
|
+
"branch": None,
|
|
169
|
+
"recent_commits": [],
|
|
170
|
+
"status": {
|
|
171
|
+
"clean": True,
|
|
172
|
+
"modified_files": [],
|
|
173
|
+
"untracked_files": [],
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
# Get current branch
|
|
179
|
+
branch = subprocess.check_output(
|
|
180
|
+
["git", "branch", "--show-current"],
|
|
181
|
+
cwd=self.project_path,
|
|
182
|
+
text=True,
|
|
183
|
+
stderr=subprocess.DEVNULL,
|
|
184
|
+
).strip()
|
|
185
|
+
|
|
186
|
+
# Get recent commits (last 5)
|
|
187
|
+
commit_log = subprocess.check_output(
|
|
188
|
+
["git", "log", "-5", "--pretty=format:%h|%an|%ai|%s"],
|
|
189
|
+
cwd=self.project_path,
|
|
190
|
+
text=True,
|
|
191
|
+
stderr=subprocess.DEVNULL,
|
|
192
|
+
).strip()
|
|
193
|
+
|
|
194
|
+
recent_commits = []
|
|
195
|
+
for line in commit_log.split("\n"):
|
|
196
|
+
if line:
|
|
197
|
+
parts = line.split("|", 3)
|
|
198
|
+
if len(parts) == 4:
|
|
199
|
+
recent_commits.append(
|
|
200
|
+
{
|
|
201
|
+
"sha": parts[0],
|
|
202
|
+
"author": parts[1],
|
|
203
|
+
"timestamp": parts[2],
|
|
204
|
+
"message": parts[3],
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Get status
|
|
209
|
+
status_output = subprocess.check_output(
|
|
210
|
+
["git", "status", "--porcelain"],
|
|
211
|
+
cwd=self.project_path,
|
|
212
|
+
text=True,
|
|
213
|
+
stderr=subprocess.DEVNULL,
|
|
214
|
+
).strip()
|
|
215
|
+
|
|
216
|
+
modified_files = []
|
|
217
|
+
untracked_files = []
|
|
218
|
+
if status_output:
|
|
219
|
+
for line in status_output.split("\n"):
|
|
220
|
+
if line.startswith("??"):
|
|
221
|
+
untracked_files.append(line[3:])
|
|
222
|
+
elif line:
|
|
223
|
+
modified_files.append(line[3:])
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"is_git_repo": True,
|
|
227
|
+
"branch": branch,
|
|
228
|
+
"recent_commits": recent_commits,
|
|
229
|
+
"status": {
|
|
230
|
+
"clean": len(modified_files) == 0 and len(untracked_files) == 0,
|
|
231
|
+
"modified_files": modified_files,
|
|
232
|
+
"untracked_files": untracked_files,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
except subprocess.CalledProcessError as e:
|
|
237
|
+
logger.warning(f"Git command failed: {e}")
|
|
238
|
+
return {
|
|
239
|
+
"is_git_repo": True,
|
|
240
|
+
"branch": "unknown",
|
|
241
|
+
"recent_commits": [],
|
|
242
|
+
"status": {"clean": True, "modified_files": [], "untracked_files": []},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
def _is_git_repo(self) -> bool:
|
|
246
|
+
"""Check if directory is a git repository.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if git repository exists
|
|
250
|
+
"""
|
|
251
|
+
return (self.project_path / ".git").exists()
|
|
252
|
+
|
|
253
|
+
def _save_yaml(self, state: Dict[str, Any], yaml_path: Path) -> None:
|
|
254
|
+
"""Save state as YAML format.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
state: State dictionary
|
|
258
|
+
yaml_path: Target YAML file path
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
with yaml_path.open("w") as f:
|
|
262
|
+
yaml.dump(
|
|
263
|
+
state,
|
|
264
|
+
f,
|
|
265
|
+
default_flow_style=False,
|
|
266
|
+
allow_unicode=True,
|
|
267
|
+
sort_keys=False,
|
|
268
|
+
)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.error(f"Failed to write YAML to {yaml_path}: {e}")
|
|
271
|
+
raise
|
|
272
|
+
|
|
273
|
+
def _generate_markdown(self, state: Dict[str, Any]) -> str:
|
|
274
|
+
"""Generate human-readable markdown format.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
state: State dictionary
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Markdown formatted string
|
|
281
|
+
"""
|
|
282
|
+
session_id = state["session_id"]
|
|
283
|
+
paused_at = state["paused_at"]
|
|
284
|
+
conversation = state["conversation"]
|
|
285
|
+
git_context = state["git_context"]
|
|
286
|
+
active_context = state["active_context"]
|
|
287
|
+
|
|
288
|
+
lines = [
|
|
289
|
+
"# Claude MPM Session Pause Document",
|
|
290
|
+
"",
|
|
291
|
+
"## Session Metadata",
|
|
292
|
+
"",
|
|
293
|
+
f"**Session ID**: `{session_id}`",
|
|
294
|
+
f"**Paused At**: {paused_at}",
|
|
295
|
+
f"**Project**: `{state['project_path']}`",
|
|
296
|
+
f"**Version**: {state.get('version', 'unknown')}",
|
|
297
|
+
"",
|
|
298
|
+
"## What You Were Working On",
|
|
299
|
+
"",
|
|
300
|
+
f"**Primary Task**: {conversation['primary_task']}",
|
|
301
|
+
f"**Current Phase**: {conversation['current_phase']}",
|
|
302
|
+
"",
|
|
303
|
+
"**Summary**:",
|
|
304
|
+
f"{conversation['summary']}",
|
|
305
|
+
"",
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
# Accomplishments
|
|
309
|
+
if conversation.get("accomplishments"):
|
|
310
|
+
lines.append("## Accomplishments This Session")
|
|
311
|
+
lines.append("")
|
|
312
|
+
for item in conversation["accomplishments"]:
|
|
313
|
+
lines.append(f"- {item}")
|
|
314
|
+
lines.append("")
|
|
315
|
+
|
|
316
|
+
# Next steps
|
|
317
|
+
if conversation.get("next_steps"):
|
|
318
|
+
lines.append("## Next Steps (Priority Order)")
|
|
319
|
+
lines.append("")
|
|
320
|
+
for i, step in enumerate(conversation["next_steps"], 1):
|
|
321
|
+
if isinstance(step, dict):
|
|
322
|
+
lines.append(
|
|
323
|
+
f"{i}. **{step.get('task', 'Unknown task')}** (Priority: {step.get('priority', '?')})"
|
|
324
|
+
)
|
|
325
|
+
if step.get("estimated_hours"):
|
|
326
|
+
lines.append(f" - Est. time: {step['estimated_hours']}")
|
|
327
|
+
if step.get("status"):
|
|
328
|
+
lines.append(f" - Status: {step['status']}")
|
|
329
|
+
if step.get("notes"):
|
|
330
|
+
lines.append(f" - Notes: {step['notes']}")
|
|
331
|
+
else:
|
|
332
|
+
lines.append(f"{i}. {step}")
|
|
333
|
+
lines.append("")
|
|
334
|
+
|
|
335
|
+
# Active context
|
|
336
|
+
lines.extend(
|
|
337
|
+
[
|
|
338
|
+
"## Active Context",
|
|
339
|
+
"",
|
|
340
|
+
f"**Working Directory**: `{active_context['working_directory']}`",
|
|
341
|
+
"",
|
|
342
|
+
]
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Git context
|
|
346
|
+
lines.append("## Git Context")
|
|
347
|
+
lines.append("")
|
|
348
|
+
if git_context["is_git_repo"]:
|
|
349
|
+
lines.append(f"**Branch**: `{git_context['branch']}`")
|
|
350
|
+
lines.append(
|
|
351
|
+
f"**Status**: {'Clean' if git_context['status']['clean'] else 'Modified'}"
|
|
352
|
+
)
|
|
353
|
+
lines.append("")
|
|
354
|
+
|
|
355
|
+
if git_context["status"]["modified_files"]:
|
|
356
|
+
lines.append("**Modified files**:")
|
|
357
|
+
for f in git_context["status"]["modified_files"][:10]:
|
|
358
|
+
lines.append(f"- `{f}`")
|
|
359
|
+
lines.append("")
|
|
360
|
+
|
|
361
|
+
if git_context["recent_commits"]:
|
|
362
|
+
lines.append("**Recent commits**:")
|
|
363
|
+
for commit in git_context["recent_commits"]:
|
|
364
|
+
lines.append(
|
|
365
|
+
f"- `{commit['sha']}` - {commit['message']} ({commit['author']})"
|
|
366
|
+
)
|
|
367
|
+
lines.append("")
|
|
368
|
+
else:
|
|
369
|
+
lines.append("*Not a git repository*")
|
|
370
|
+
lines.append("")
|
|
371
|
+
|
|
372
|
+
# Important reminders
|
|
373
|
+
if state.get("important_reminders"):
|
|
374
|
+
lines.append("## Important Reminders")
|
|
375
|
+
lines.append("")
|
|
376
|
+
for reminder in state["important_reminders"]:
|
|
377
|
+
lines.append(f"- {reminder}")
|
|
378
|
+
lines.append("")
|
|
379
|
+
|
|
380
|
+
# Resume instructions
|
|
381
|
+
lines.extend(
|
|
382
|
+
[
|
|
383
|
+
"## Resume Instructions",
|
|
384
|
+
"",
|
|
385
|
+
"### Quick Resume (5 minutes)",
|
|
386
|
+
"",
|
|
387
|
+
]
|
|
388
|
+
)
|
|
389
|
+
for instruction in state["resume_instructions"]["quick_start"]:
|
|
390
|
+
lines.append(f"1. {instruction}")
|
|
391
|
+
lines.append("")
|
|
392
|
+
|
|
393
|
+
if state["resume_instructions"]["validation_commands"]:
|
|
394
|
+
lines.append("### Validation Commands")
|
|
395
|
+
lines.append("")
|
|
396
|
+
lines.append("```bash")
|
|
397
|
+
for cmd in state["resume_instructions"]["validation_commands"].values():
|
|
398
|
+
lines.append(cmd)
|
|
399
|
+
lines.append("```")
|
|
400
|
+
lines.append("")
|
|
401
|
+
|
|
402
|
+
# Footer
|
|
403
|
+
lines.extend(
|
|
404
|
+
[
|
|
405
|
+
"---",
|
|
406
|
+
"",
|
|
407
|
+
"Resume with: `/mpm-init resume` or `cat .claude-mpm/sessions/LATEST-SESSION.txt`",
|
|
408
|
+
"",
|
|
409
|
+
]
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return "\n".join(lines)
|
|
413
|
+
|
|
414
|
+
def _update_latest_pointer(self, session_id: str) -> None:
|
|
415
|
+
"""Update LATEST-SESSION.txt pointer.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
session_id: Session identifier
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
latest_file = self.pause_dir / "LATEST-SESSION.txt"
|
|
422
|
+
content = f"""Latest Session: {session_id}
|
|
423
|
+
Paused At: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S %Z')}
|
|
424
|
+
Project: {self.project_path}
|
|
425
|
+
|
|
426
|
+
Files:
|
|
427
|
+
- {session_id}.json (machine-readable)
|
|
428
|
+
- {session_id}.yaml (human-readable config)
|
|
429
|
+
- {session_id}.md (documentation)
|
|
430
|
+
|
|
431
|
+
Quick Resume:
|
|
432
|
+
/mpm-init resume
|
|
433
|
+
|
|
434
|
+
Full Context:
|
|
435
|
+
cat .claude-mpm/sessions/{session_id}.md
|
|
436
|
+
|
|
437
|
+
Validation:
|
|
438
|
+
git status && git log -1 --stat
|
|
439
|
+
"""
|
|
440
|
+
latest_file.write_text(content)
|
|
441
|
+
logger.debug(f"Updated LATEST-SESSION.txt: {session_id}")
|
|
442
|
+
except Exception as e:
|
|
443
|
+
logger.warning(f"Failed to update LATEST-SESSION.txt: {e}")
|
|
444
|
+
|
|
445
|
+
def _commit_pause_session(self, session_id: str, message: Optional[str]) -> None:
|
|
446
|
+
"""Create git commit for pause session.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
session_id: Session identifier
|
|
450
|
+
message: Optional context message
|
|
451
|
+
"""
|
|
452
|
+
try:
|
|
453
|
+
# Add session files
|
|
454
|
+
subprocess.run(
|
|
455
|
+
["git", "add", ".claude-mpm/sessions/"],
|
|
456
|
+
cwd=self.project_path,
|
|
457
|
+
check=True,
|
|
458
|
+
capture_output=True,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Build commit message
|
|
462
|
+
commit_msg = f"session: pause at {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}\n\nSession ID: {session_id}"
|
|
463
|
+
if message:
|
|
464
|
+
commit_msg += f"\nContext: {message}"
|
|
465
|
+
|
|
466
|
+
# Create commit
|
|
467
|
+
subprocess.run(
|
|
468
|
+
["git", "commit", "-m", commit_msg],
|
|
469
|
+
cwd=self.project_path,
|
|
470
|
+
check=True,
|
|
471
|
+
capture_output=True,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
logger.info(f"Created git commit for pause session: {session_id}")
|
|
475
|
+
|
|
476
|
+
except subprocess.CalledProcessError as e:
|
|
477
|
+
# Non-fatal - pause still succeeded
|
|
478
|
+
logger.warning(f"Failed to create git commit: {e.stderr.decode()}")
|
|
479
|
+
|
|
480
|
+
def _get_project_version(self) -> str:
|
|
481
|
+
"""Get project version from pyproject.toml or package.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Version string or 'unknown'
|
|
485
|
+
"""
|
|
486
|
+
try:
|
|
487
|
+
# Try pyproject.toml
|
|
488
|
+
pyproject = self.project_path / "pyproject.toml"
|
|
489
|
+
if pyproject.exists():
|
|
490
|
+
content = pyproject.read_text()
|
|
491
|
+
for line in content.split("\n"):
|
|
492
|
+
if line.startswith("version"):
|
|
493
|
+
return line.split("=")[1].strip().strip('"')
|
|
494
|
+
|
|
495
|
+
# Try package __version__
|
|
496
|
+
import claude_mpm
|
|
497
|
+
|
|
498
|
+
if hasattr(claude_mpm, "__version__"):
|
|
499
|
+
return claude_mpm.__version__
|
|
500
|
+
|
|
501
|
+
except Exception:
|
|
502
|
+
pass
|
|
503
|
+
|
|
504
|
+
return "unknown"
|