claude-mpm 4.15.6__py3-none-any.whl → 4.21.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.
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 +6 -0
- claude_mpm/commands/mpm-init.md +112 -6
- claude_mpm/commands/mpm-resume.md +372 -0
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +2 -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/base.py +26 -11
- 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/event_bus/relay.py +23 -7
- 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/memory/failure_tracker.py +19 -4
- 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/tools/code_tree_analyzer/__init__.py +45 -0
- claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
- claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
- claude_mpm/tools/code_tree_analyzer/core.py +380 -0
- claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
- claude_mpm/tools/code_tree_analyzer/events.py +168 -0
- claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
- claude_mpm/tools/code_tree_analyzer/models.py +39 -0
- claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
- claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/METADATA +211 -33
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/RECORD +206 -64
- claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
- claude_mpm/cli/commands/mpm_init.py +0 -2008
- claude_mpm/tools/code_tree_analyzer.py +0 -1825
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Discovery Module
|
|
4
|
+
================
|
|
5
|
+
|
|
6
|
+
Handles directory traversal and file discovery for code analysis.
|
|
7
|
+
|
|
8
|
+
WHY: Separates file system operations from analysis logic,
|
|
9
|
+
providing efficient directory scanning with proper filtering.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import ClassVar, Dict, List, Optional, Set
|
|
15
|
+
|
|
16
|
+
from ...core.logging_config import get_logger
|
|
17
|
+
from ..code_tree_events import CodeTreeEventEmitter
|
|
18
|
+
from .gitignore import GitignoreManager
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DiscoveryManager:
|
|
22
|
+
"""Manages file and directory discovery for code analysis."""
|
|
23
|
+
|
|
24
|
+
# Define code file extensions at class level for directory filtering
|
|
25
|
+
CODE_EXTENSIONS: ClassVar[Set[str]] = {
|
|
26
|
+
".py",
|
|
27
|
+
".js",
|
|
28
|
+
".jsx",
|
|
29
|
+
".ts",
|
|
30
|
+
".tsx",
|
|
31
|
+
".mjs",
|
|
32
|
+
".cjs",
|
|
33
|
+
".java",
|
|
34
|
+
".cpp",
|
|
35
|
+
".c",
|
|
36
|
+
".h",
|
|
37
|
+
".hpp",
|
|
38
|
+
".cs",
|
|
39
|
+
".go",
|
|
40
|
+
".rs",
|
|
41
|
+
".rb",
|
|
42
|
+
".php",
|
|
43
|
+
".swift",
|
|
44
|
+
".kt",
|
|
45
|
+
".scala",
|
|
46
|
+
".r",
|
|
47
|
+
".m",
|
|
48
|
+
".mm",
|
|
49
|
+
".sh",
|
|
50
|
+
".bash",
|
|
51
|
+
".zsh",
|
|
52
|
+
".fish",
|
|
53
|
+
".ps1",
|
|
54
|
+
".bat",
|
|
55
|
+
".cmd",
|
|
56
|
+
".sql",
|
|
57
|
+
".html",
|
|
58
|
+
".css",
|
|
59
|
+
".scss",
|
|
60
|
+
".sass",
|
|
61
|
+
".less",
|
|
62
|
+
".xml",
|
|
63
|
+
".json",
|
|
64
|
+
".yaml",
|
|
65
|
+
".yml",
|
|
66
|
+
".toml",
|
|
67
|
+
".ini",
|
|
68
|
+
".cfg",
|
|
69
|
+
".conf",
|
|
70
|
+
".md",
|
|
71
|
+
".rst",
|
|
72
|
+
".txt",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# File extensions to language mapping
|
|
76
|
+
LANGUAGE_MAP: ClassVar[Dict[str, str]] = {
|
|
77
|
+
".py": "python",
|
|
78
|
+
".js": "javascript",
|
|
79
|
+
".jsx": "javascript",
|
|
80
|
+
".ts": "typescript",
|
|
81
|
+
".tsx": "typescript",
|
|
82
|
+
".mjs": "javascript",
|
|
83
|
+
".cjs": "javascript",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
gitignore_manager: GitignoreManager,
|
|
89
|
+
emitter: Optional[CodeTreeEventEmitter] = None,
|
|
90
|
+
):
|
|
91
|
+
"""Initialize discovery manager.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
gitignore_manager: GitignoreManager instance for filtering
|
|
95
|
+
emitter: Optional event emitter
|
|
96
|
+
"""
|
|
97
|
+
self.logger = get_logger(__name__)
|
|
98
|
+
self.gitignore_manager = gitignore_manager
|
|
99
|
+
self.emitter = emitter
|
|
100
|
+
self._last_working_dir = None
|
|
101
|
+
|
|
102
|
+
def has_code_files(
|
|
103
|
+
self, directory: Path, depth: int = 5, current_depth: int = 0
|
|
104
|
+
) -> bool:
|
|
105
|
+
"""Check if directory contains code files up to 5 levels deep.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
directory: Directory to check
|
|
109
|
+
depth: Maximum depth to search
|
|
110
|
+
current_depth: Current recursion depth
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if directory contains code files within depth levels
|
|
114
|
+
"""
|
|
115
|
+
if current_depth >= depth:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
# Skip checking these directories entirely
|
|
119
|
+
SKIP_DIRS = {
|
|
120
|
+
"node_modules",
|
|
121
|
+
"__pycache__",
|
|
122
|
+
".git",
|
|
123
|
+
".venv",
|
|
124
|
+
"venv",
|
|
125
|
+
"dist",
|
|
126
|
+
"build",
|
|
127
|
+
".tox",
|
|
128
|
+
"htmlcov",
|
|
129
|
+
".pytest_cache",
|
|
130
|
+
".mypy_cache",
|
|
131
|
+
"coverage",
|
|
132
|
+
".idea",
|
|
133
|
+
".vscode",
|
|
134
|
+
"env",
|
|
135
|
+
".coverage",
|
|
136
|
+
"__MACOSX",
|
|
137
|
+
".ipynb_checkpoints",
|
|
138
|
+
}
|
|
139
|
+
# Skip directories in the skip list or egg-info directories
|
|
140
|
+
if directory.name in SKIP_DIRS or directory.name.endswith(".egg-info"):
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
for item in directory.iterdir():
|
|
145
|
+
# Skip hidden items in scan
|
|
146
|
+
if item.name.startswith("."):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if item.is_file():
|
|
150
|
+
# Check if it's a code file
|
|
151
|
+
ext = item.suffix.lower()
|
|
152
|
+
if ext in self.CODE_EXTENSIONS:
|
|
153
|
+
return True
|
|
154
|
+
elif item.is_dir() and current_depth < depth - 1:
|
|
155
|
+
# Skip egg-info directories in the recursive check too
|
|
156
|
+
if item.name.endswith(".egg-info"):
|
|
157
|
+
continue
|
|
158
|
+
if self.has_code_files(item, depth, current_depth + 1):
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
except (PermissionError, OSError):
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
def discover_top_level(
|
|
167
|
+
self, directory: Path, ignore_patterns: Optional[List[str]] = None
|
|
168
|
+
) -> Dict[str, any]:
|
|
169
|
+
"""Discover only top-level directories and files for lazy loading.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
directory: Root directory to discover
|
|
173
|
+
ignore_patterns: Patterns to ignore
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dictionary with top-level structure
|
|
177
|
+
"""
|
|
178
|
+
# CRITICAL FIX: Use the directory parameter as the base for relative paths
|
|
179
|
+
# NOT the current working directory. This ensures we only show items
|
|
180
|
+
# within the requested directory, not parent directories.
|
|
181
|
+
Path(directory).absolute()
|
|
182
|
+
|
|
183
|
+
# Emit discovery start event
|
|
184
|
+
if self.emitter:
|
|
185
|
+
self.emitter.emit(
|
|
186
|
+
"info",
|
|
187
|
+
{
|
|
188
|
+
"type": "discovery.start",
|
|
189
|
+
"action": "scanning_directory",
|
|
190
|
+
"path": str(directory),
|
|
191
|
+
"message": f"Starting discovery of {directory.name}",
|
|
192
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
result = {
|
|
197
|
+
"path": str(directory),
|
|
198
|
+
"name": directory.name,
|
|
199
|
+
"type": "directory",
|
|
200
|
+
"children": [],
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Clear cache if working directory changed
|
|
205
|
+
if self._last_working_dir != directory:
|
|
206
|
+
self.gitignore_manager.clear_cache()
|
|
207
|
+
self._last_working_dir = directory
|
|
208
|
+
|
|
209
|
+
# Get immediate children only (no recursion)
|
|
210
|
+
files_count = 0
|
|
211
|
+
dirs_count = 0
|
|
212
|
+
ignored_count = 0
|
|
213
|
+
|
|
214
|
+
for item in directory.iterdir():
|
|
215
|
+
# Use gitignore manager for filtering with the directory as working dir
|
|
216
|
+
if self.gitignore_manager.should_ignore(item, directory):
|
|
217
|
+
if self.emitter:
|
|
218
|
+
self.emitter.emit(
|
|
219
|
+
"info",
|
|
220
|
+
{
|
|
221
|
+
"type": "filter.gitignore",
|
|
222
|
+
"path": str(item),
|
|
223
|
+
"reason": "gitignore pattern",
|
|
224
|
+
"message": f"Ignored by gitignore: {item.name}",
|
|
225
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
ignored_count += 1
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
# Also check additional patterns if provided
|
|
232
|
+
if ignore_patterns and any(p in str(item) for p in ignore_patterns):
|
|
233
|
+
if self.emitter:
|
|
234
|
+
self.emitter.emit(
|
|
235
|
+
"info",
|
|
236
|
+
{
|
|
237
|
+
"type": "filter.pattern",
|
|
238
|
+
"path": str(item),
|
|
239
|
+
"reason": "custom pattern",
|
|
240
|
+
"message": f"Ignored by pattern: {item.name}",
|
|
241
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
ignored_count += 1
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
if item.is_dir():
|
|
248
|
+
# Check if directory contains code files (recursively checking subdirectories)
|
|
249
|
+
# Important: We want to include directories even if they only have code
|
|
250
|
+
# in subdirectories (like src/claude_mpm/*.py)
|
|
251
|
+
if not self.has_code_files(item, depth=5):
|
|
252
|
+
if self.emitter:
|
|
253
|
+
self.emitter.emit(
|
|
254
|
+
"info",
|
|
255
|
+
{
|
|
256
|
+
"type": "filter.no_code",
|
|
257
|
+
"path": str(item.name),
|
|
258
|
+
"reason": "no code files",
|
|
259
|
+
"message": f"Skipped directory without code: {item.name}",
|
|
260
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
ignored_count += 1
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# Directory - return just the item name
|
|
267
|
+
# The frontend will construct the full path by combining parent path with child name
|
|
268
|
+
path_str = item.name
|
|
269
|
+
|
|
270
|
+
# Emit directory found event
|
|
271
|
+
if self.emitter:
|
|
272
|
+
self.emitter.emit(
|
|
273
|
+
"info",
|
|
274
|
+
{
|
|
275
|
+
"type": "discovery.directory",
|
|
276
|
+
"path": str(item),
|
|
277
|
+
"message": f"Found directory: {item.name}",
|
|
278
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
dirs_count += 1
|
|
282
|
+
|
|
283
|
+
child = {
|
|
284
|
+
"path": path_str,
|
|
285
|
+
"name": item.name,
|
|
286
|
+
"type": "directory",
|
|
287
|
+
"discovered": False,
|
|
288
|
+
"children": [],
|
|
289
|
+
}
|
|
290
|
+
result["children"].append(child)
|
|
291
|
+
|
|
292
|
+
elif item.is_file():
|
|
293
|
+
# Check if it's a supported code file or a special file we want to show
|
|
294
|
+
supported_extensions = {
|
|
295
|
+
".py",
|
|
296
|
+
".js",
|
|
297
|
+
".jsx",
|
|
298
|
+
".ts",
|
|
299
|
+
".tsx",
|
|
300
|
+
".mjs",
|
|
301
|
+
".cjs",
|
|
302
|
+
}
|
|
303
|
+
if item.suffix in supported_extensions or item.name in [
|
|
304
|
+
".gitignore",
|
|
305
|
+
".env.example",
|
|
306
|
+
".env.sample",
|
|
307
|
+
]:
|
|
308
|
+
# File - mark for lazy analysis
|
|
309
|
+
language = self.get_language(item)
|
|
310
|
+
|
|
311
|
+
# File path should be just the item name
|
|
312
|
+
# The frontend will construct the full path by combining parent path with child name
|
|
313
|
+
path_str = item.name
|
|
314
|
+
|
|
315
|
+
# Emit file found event
|
|
316
|
+
if self.emitter:
|
|
317
|
+
self.emitter.emit(
|
|
318
|
+
"info",
|
|
319
|
+
{
|
|
320
|
+
"type": "discovery.file",
|
|
321
|
+
"path": str(item),
|
|
322
|
+
"language": language,
|
|
323
|
+
"size": item.stat().st_size,
|
|
324
|
+
"message": f"Found file: {item.name} ({language})",
|
|
325
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
files_count += 1
|
|
329
|
+
|
|
330
|
+
child = {
|
|
331
|
+
"path": path_str,
|
|
332
|
+
"name": item.name,
|
|
333
|
+
"type": "file",
|
|
334
|
+
"language": language,
|
|
335
|
+
"size": item.stat().st_size,
|
|
336
|
+
"analyzed": False,
|
|
337
|
+
}
|
|
338
|
+
result["children"].append(child)
|
|
339
|
+
|
|
340
|
+
if self.emitter:
|
|
341
|
+
self.emitter.emit_file_discovered(
|
|
342
|
+
path_str, language, item.stat().st_size
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
except PermissionError as e:
|
|
346
|
+
self.logger.warning(f"Permission denied accessing {directory}: {e}")
|
|
347
|
+
if self.emitter:
|
|
348
|
+
self.emitter.emit_error(str(directory), f"Permission denied: {e}")
|
|
349
|
+
|
|
350
|
+
# Emit discovery complete event with stats
|
|
351
|
+
if self.emitter:
|
|
352
|
+
self.emitter.emit(
|
|
353
|
+
"info",
|
|
354
|
+
{
|
|
355
|
+
"type": "discovery.complete",
|
|
356
|
+
"path": str(directory),
|
|
357
|
+
"stats": {
|
|
358
|
+
"files": files_count,
|
|
359
|
+
"directories": dirs_count,
|
|
360
|
+
"ignored": ignored_count,
|
|
361
|
+
},
|
|
362
|
+
"message": f"Discovery complete: {files_count} files, {dirs_count} directories, {ignored_count} ignored",
|
|
363
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return result
|
|
368
|
+
|
|
369
|
+
def discover_directory(
|
|
370
|
+
self, dir_path: str, ignore_patterns: Optional[List[str]] = None
|
|
371
|
+
) -> Dict[str, any]:
|
|
372
|
+
"""Discover contents of a specific directory for lazy loading.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
dir_path: Directory path to discover
|
|
376
|
+
ignore_patterns: Patterns to ignore
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Dictionary with directory contents
|
|
380
|
+
"""
|
|
381
|
+
directory = Path(dir_path)
|
|
382
|
+
if not directory.exists() or not directory.is_dir():
|
|
383
|
+
return {"error": f"Invalid directory: {dir_path}"}
|
|
384
|
+
|
|
385
|
+
# Clear cache if working directory changed
|
|
386
|
+
if self._last_working_dir != directory.parent:
|
|
387
|
+
self.gitignore_manager.clear_cache()
|
|
388
|
+
self._last_working_dir = directory.parent
|
|
389
|
+
|
|
390
|
+
# The discover_top_level method will emit all the INFO events
|
|
391
|
+
return self.discover_top_level(directory, ignore_patterns)
|
|
392
|
+
|
|
393
|
+
def get_language(self, file_path: Path) -> str:
|
|
394
|
+
"""Determine language from file extension.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
file_path: Path to file
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Language string
|
|
401
|
+
"""
|
|
402
|
+
ext = file_path.suffix.lower()
|
|
403
|
+
return self.LANGUAGE_MAP.get(ext, "unknown")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Events Module
|
|
4
|
+
=============
|
|
5
|
+
|
|
6
|
+
Handles event emission for code tree analysis progress updates.
|
|
7
|
+
|
|
8
|
+
WHY: Decouples event emission from analysis logic, allowing
|
|
9
|
+
flexible event handling and real-time UI updates.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import List, Optional
|
|
15
|
+
|
|
16
|
+
from ...core.logging_config import get_logger
|
|
17
|
+
from ..code_tree_events import CodeTreeEventEmitter
|
|
18
|
+
from .models import CodeNode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EventManager:
|
|
22
|
+
"""Manages event emission during code tree analysis."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, emitter: Optional[CodeTreeEventEmitter] = None):
|
|
25
|
+
"""Initialize event manager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
emitter: Optional event emitter
|
|
29
|
+
"""
|
|
30
|
+
self.logger = get_logger(__name__)
|
|
31
|
+
self.emitter = emitter
|
|
32
|
+
|
|
33
|
+
def emit_analysis_start(self, path: Path, language: str) -> None:
|
|
34
|
+
"""Emit analysis start event.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path: File path being analyzed
|
|
38
|
+
language: Programming language
|
|
39
|
+
"""
|
|
40
|
+
if self.emitter:
|
|
41
|
+
self.emitter.emit(
|
|
42
|
+
"info",
|
|
43
|
+
{
|
|
44
|
+
"type": "analysis.start",
|
|
45
|
+
"file": str(path),
|
|
46
|
+
"language": language,
|
|
47
|
+
"message": f"Analyzing: {path.name}",
|
|
48
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def emit_cache_hit(self, path: Path) -> None:
|
|
53
|
+
"""Emit cache hit event.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
path: File path with cache hit
|
|
57
|
+
"""
|
|
58
|
+
if self.emitter:
|
|
59
|
+
self.emitter.emit(
|
|
60
|
+
"info",
|
|
61
|
+
{
|
|
62
|
+
"type": "cache.hit",
|
|
63
|
+
"file": str(path),
|
|
64
|
+
"message": f"Using cached analysis for {path.name}",
|
|
65
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def emit_cache_miss(self, path: Path) -> None:
|
|
70
|
+
"""Emit cache miss event.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
path: File path with cache miss
|
|
74
|
+
"""
|
|
75
|
+
if self.emitter:
|
|
76
|
+
self.emitter.emit(
|
|
77
|
+
"info",
|
|
78
|
+
{
|
|
79
|
+
"type": "cache.miss",
|
|
80
|
+
"file": str(path),
|
|
81
|
+
"message": f"Cache miss, analyzing fresh: {path.name}",
|
|
82
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def emit_parsing_start(self, path: Path) -> None:
|
|
87
|
+
"""Emit parsing start event.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
path: File path being parsed
|
|
91
|
+
"""
|
|
92
|
+
if self.emitter:
|
|
93
|
+
self.emitter.emit(
|
|
94
|
+
"info",
|
|
95
|
+
{
|
|
96
|
+
"type": "analysis.parse",
|
|
97
|
+
"file": str(path),
|
|
98
|
+
"message": f"Parsing file content: {path.name}",
|
|
99
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def emit_node_found(self, node: CodeNode, path: Path) -> None:
|
|
104
|
+
"""Emit node found event.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
node: CodeNode that was found
|
|
108
|
+
path: File path containing the node
|
|
109
|
+
"""
|
|
110
|
+
if self.emitter:
|
|
111
|
+
self.emitter.emit(
|
|
112
|
+
"info",
|
|
113
|
+
{
|
|
114
|
+
"type": f"analysis.{node.node_type}",
|
|
115
|
+
"name": node.name,
|
|
116
|
+
"file": str(path),
|
|
117
|
+
"line_start": node.line_start,
|
|
118
|
+
"complexity": node.complexity,
|
|
119
|
+
"message": f"Found {node.node_type}: {node.name}",
|
|
120
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def emit_analysis_complete(
|
|
125
|
+
self, path: Path, filtered_nodes: List[dict], duration: float
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Emit analysis complete event.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path: File path that was analyzed
|
|
131
|
+
filtered_nodes: List of filtered node dictionaries
|
|
132
|
+
duration: Analysis duration in seconds
|
|
133
|
+
"""
|
|
134
|
+
if not self.emitter:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
stats = self._calculate_node_stats(filtered_nodes)
|
|
138
|
+
self.emitter.emit(
|
|
139
|
+
"info",
|
|
140
|
+
{
|
|
141
|
+
"type": "analysis.complete",
|
|
142
|
+
"file": str(path),
|
|
143
|
+
"stats": stats,
|
|
144
|
+
"duration": duration,
|
|
145
|
+
"message": f"Analysis complete: {stats['classes']} classes, {stats['functions']} functions, {stats['methods']} methods",
|
|
146
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
self.emitter.emit_file_analyzed(str(path), filtered_nodes, duration)
|
|
150
|
+
|
|
151
|
+
def _calculate_node_stats(self, filtered_nodes: List[dict]) -> dict:
|
|
152
|
+
"""Calculate statistics from filtered nodes.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
filtered_nodes: List of filtered node dictionaries
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary of node statistics
|
|
159
|
+
"""
|
|
160
|
+
classes_count = sum(1 for n in filtered_nodes if n["type"] == "class")
|
|
161
|
+
functions_count = sum(1 for n in filtered_nodes if n["type"] == "function")
|
|
162
|
+
methods_count = sum(1 for n in filtered_nodes if n["type"] == "method")
|
|
163
|
+
return {
|
|
164
|
+
"classes": classes_count,
|
|
165
|
+
"functions": functions_count,
|
|
166
|
+
"methods": methods_count,
|
|
167
|
+
"total_nodes": len(filtered_nodes),
|
|
168
|
+
}
|