claude-mpm 5.0.2__py3-none-any.whl → 5.4.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/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
- claude_mpm/agents/agent_loader.py +10 -17
- claude_mpm/agents/base_agent_loader.py +10 -35
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +431 -45
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_state_manager.py +67 -23
- claude_mpm/cli/commands/agents.py +446 -25
- claude_mpm/cli/commands/auto_configure.py +535 -233
- claude_mpm/cli/commands/configure.py +1500 -147
- claude_mpm/cli/commands/configure_agent_display.py +13 -6
- claude_mpm/cli/commands/mpm_init/core.py +158 -1
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/run.py +1 -39
- claude_mpm/cli/commands/skills.py +322 -19
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +8 -0
- claude_mpm/cli/interactive/agent_wizard.py +302 -195
- claude_mpm/cli/parsers/agents_parser.py +137 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
- claude_mpm/cli/parsers/base_parser.py +9 -0
- claude_mpm/cli/parsers/skills_parser.py +7 -0
- claude_mpm/cli/startup.py +133 -85
- claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
- claude_mpm/commands/mpm-agents-list.md +2 -2
- claude_mpm/commands/mpm-config-view.md +2 -2
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
- claude_mpm/commands/mpm-postmortem.md +123 -0
- claude_mpm/commands/mpm-session-resume.md +2 -2
- claude_mpm/commands/mpm-ticket-view.md +2 -2
- claude_mpm/config/agent_presets.py +312 -82
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -25
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework/loaders/file_loader.py +54 -101
- claude_mpm/core/interactive_session.py +19 -5
- claude_mpm/core/oneshot_session.py +16 -4
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/shared/singleton_manager.py +11 -4
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +279 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
- claude_mpm/services/agents/git_source_manager.py +20 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
- claude_mpm/services/agents/toolchain_detector.py +6 -5
- claude_mpm/services/analysis/__init__.py +35 -0
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/command_deployment_service.py +106 -5
- claude_mpm/services/core/base.py +7 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/mcp_config_manager.py +75 -145
- claude_mpm/services/mcp_service_verifier.py +6 -3
- claude_mpm/services/monitor/daemon.py +37 -10
- claude_mpm/services/monitor/daemon_manager.py +134 -21
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/runner_configuration_service.py +16 -3
- claude_mpm/services/session_management_service.py +16 -4
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +261 -0
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/progress.py +5 -1
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Document Summarization Command.
|
|
3
|
+
|
|
4
|
+
Shell-based alternative to MCP document_summarizer tool.
|
|
5
|
+
Provides algorithmic summarization without ML dependencies.
|
|
6
|
+
|
|
7
|
+
Design Decision: Uses simple text processing techniques:
|
|
8
|
+
- Brief: First paragraph extraction
|
|
9
|
+
- Detailed: Key sentence extraction based on position and length
|
|
10
|
+
- Bullet Points: Convert to markdown bullet list
|
|
11
|
+
- Executive: Opening + conclusion extraction
|
|
12
|
+
|
|
13
|
+
Why: Lightweight, fast, no dependencies, works offline.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SummaryStyle(str, Enum):
|
|
24
|
+
"""Summary output styles."""
|
|
25
|
+
|
|
26
|
+
BRIEF = "brief"
|
|
27
|
+
DETAILED = "detailed"
|
|
28
|
+
BULLET_POINTS = "bullet_points"
|
|
29
|
+
EXECUTIVE = "executive"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OutputFormat(str, Enum):
|
|
33
|
+
"""Output format types."""
|
|
34
|
+
|
|
35
|
+
TEXT = "text"
|
|
36
|
+
JSON = "json"
|
|
37
|
+
MARKDOWN = "markdown"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DocumentSummarizer:
|
|
41
|
+
"""
|
|
42
|
+
Algorithmic document summarizer.
|
|
43
|
+
|
|
44
|
+
Design Decision: Use simple heuristics instead of ML:
|
|
45
|
+
- Position-based extraction (opening, closing paragraphs)
|
|
46
|
+
- Length-based filtering (key sentences)
|
|
47
|
+
- Structure detection (headings, lists)
|
|
48
|
+
|
|
49
|
+
Trade-offs:
|
|
50
|
+
- Performance: O(n) single pass vs. complex NLP models
|
|
51
|
+
- Accuracy: ~70% vs. ~90% for ML models
|
|
52
|
+
- Simplicity: Zero dependencies vs. heavy ML packages
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, max_words: int = 150):
|
|
56
|
+
"""Initialize summarizer with word limit."""
|
|
57
|
+
self.max_words = max_words
|
|
58
|
+
|
|
59
|
+
def summarize(
|
|
60
|
+
self, content: str, style: SummaryStyle, lines_limit: Optional[int] = None
|
|
61
|
+
) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Summarize document content.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
content: Document text to summarize
|
|
67
|
+
style: Summary style (brief, detailed, bullet_points, executive)
|
|
68
|
+
lines_limit: Optional line limit (reads first N lines only)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Summary text
|
|
72
|
+
|
|
73
|
+
Complexity: O(n) where n is content length
|
|
74
|
+
"""
|
|
75
|
+
# Apply line limit if specified
|
|
76
|
+
if lines_limit:
|
|
77
|
+
content = self._limit_lines(content, lines_limit)
|
|
78
|
+
|
|
79
|
+
# Route to style-specific summarizer
|
|
80
|
+
summarizers = {
|
|
81
|
+
SummaryStyle.BRIEF: self._summarize_brief,
|
|
82
|
+
SummaryStyle.DETAILED: self._summarize_detailed,
|
|
83
|
+
SummaryStyle.BULLET_POINTS: self._summarize_bullet_points,
|
|
84
|
+
SummaryStyle.EXECUTIVE: self._summarize_executive,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
summary = summarizers[style](content)
|
|
88
|
+
return self._truncate_to_word_limit(summary)
|
|
89
|
+
|
|
90
|
+
def _limit_lines(self, content: str, limit: int) -> str:
|
|
91
|
+
"""Limit content to first N lines."""
|
|
92
|
+
lines = content.split("\n")
|
|
93
|
+
return "\n".join(lines[:limit])
|
|
94
|
+
|
|
95
|
+
def _truncate_to_word_limit(self, text: str) -> str:
|
|
96
|
+
"""Truncate text to max_words limit."""
|
|
97
|
+
words = text.split()
|
|
98
|
+
if len(words) <= self.max_words:
|
|
99
|
+
return text
|
|
100
|
+
|
|
101
|
+
# Truncate and add ellipsis
|
|
102
|
+
truncated = " ".join(words[: self.max_words])
|
|
103
|
+
return f"{truncated}..."
|
|
104
|
+
|
|
105
|
+
def _summarize_brief(self, content: str) -> str:
|
|
106
|
+
"""
|
|
107
|
+
Brief summary: Extract first paragraph.
|
|
108
|
+
|
|
109
|
+
Heuristic: First non-empty paragraph usually introduces document.
|
|
110
|
+
"""
|
|
111
|
+
paragraphs = self._extract_paragraphs(content)
|
|
112
|
+
if not paragraphs:
|
|
113
|
+
return content.strip()
|
|
114
|
+
|
|
115
|
+
return paragraphs[0]
|
|
116
|
+
|
|
117
|
+
def _summarize_detailed(self, content: str) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Detailed summary: Extract key sentences.
|
|
120
|
+
|
|
121
|
+
Heuristics:
|
|
122
|
+
- First paragraph (introduction)
|
|
123
|
+
- Sentences with important markers (however, therefore, important)
|
|
124
|
+
- Last paragraph (conclusion)
|
|
125
|
+
"""
|
|
126
|
+
paragraphs = self._extract_paragraphs(content)
|
|
127
|
+
if not paragraphs:
|
|
128
|
+
return content.strip()
|
|
129
|
+
|
|
130
|
+
key_sentences = []
|
|
131
|
+
|
|
132
|
+
# Add first paragraph
|
|
133
|
+
if paragraphs:
|
|
134
|
+
key_sentences.append(paragraphs[0])
|
|
135
|
+
|
|
136
|
+
# Add sentences with key markers from middle paragraphs
|
|
137
|
+
if len(paragraphs) > 2:
|
|
138
|
+
key_markers = [
|
|
139
|
+
"however",
|
|
140
|
+
"therefore",
|
|
141
|
+
"important",
|
|
142
|
+
"note",
|
|
143
|
+
"critical",
|
|
144
|
+
"key",
|
|
145
|
+
"must",
|
|
146
|
+
"should",
|
|
147
|
+
"recommended",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
for para in paragraphs[1:-1]:
|
|
151
|
+
sentences = self._split_sentences(para)
|
|
152
|
+
for sentence in sentences:
|
|
153
|
+
if any(marker in sentence.lower() for marker in key_markers):
|
|
154
|
+
key_sentences.append(sentence)
|
|
155
|
+
break # One sentence per paragraph max
|
|
156
|
+
|
|
157
|
+
# Add last paragraph
|
|
158
|
+
if len(paragraphs) > 1:
|
|
159
|
+
key_sentences.append(paragraphs[-1])
|
|
160
|
+
|
|
161
|
+
return " ".join(key_sentences)
|
|
162
|
+
|
|
163
|
+
def _summarize_bullet_points(self, content: str) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Bullet point summary: Convert paragraphs to markdown list.
|
|
166
|
+
|
|
167
|
+
Heuristic: Each paragraph becomes a bullet point.
|
|
168
|
+
"""
|
|
169
|
+
paragraphs = self._extract_paragraphs(content)
|
|
170
|
+
if not paragraphs:
|
|
171
|
+
return content.strip()
|
|
172
|
+
|
|
173
|
+
# Take key paragraphs (first, middle with markers, last)
|
|
174
|
+
key_paragraphs = []
|
|
175
|
+
|
|
176
|
+
# Always include first
|
|
177
|
+
if paragraphs:
|
|
178
|
+
key_paragraphs.append(paragraphs[0])
|
|
179
|
+
|
|
180
|
+
# Include middle paragraphs with key content
|
|
181
|
+
if len(paragraphs) > 2:
|
|
182
|
+
key_markers = ["however", "therefore", "important", "note", "critical"]
|
|
183
|
+
for para in paragraphs[1:-1]:
|
|
184
|
+
if any(marker in para.lower() for marker in key_markers):
|
|
185
|
+
# Take first sentence only for bullet point
|
|
186
|
+
first_sentence = self._split_sentences(para)[0]
|
|
187
|
+
key_paragraphs.append(first_sentence)
|
|
188
|
+
|
|
189
|
+
# Include last if different from first
|
|
190
|
+
if len(paragraphs) > 1:
|
|
191
|
+
key_paragraphs.append(paragraphs[-1])
|
|
192
|
+
|
|
193
|
+
# Format as markdown bullets
|
|
194
|
+
bullets = [f"- {para}" for para in key_paragraphs]
|
|
195
|
+
return "\n".join(bullets)
|
|
196
|
+
|
|
197
|
+
def _summarize_executive(self, content: str) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Executive summary: Opening + conclusion.
|
|
200
|
+
|
|
201
|
+
Heuristic: First and last paragraphs capture overview and conclusion.
|
|
202
|
+
"""
|
|
203
|
+
paragraphs = self._extract_paragraphs(content)
|
|
204
|
+
if not paragraphs:
|
|
205
|
+
return content.strip()
|
|
206
|
+
|
|
207
|
+
if len(paragraphs) == 1:
|
|
208
|
+
return paragraphs[0]
|
|
209
|
+
|
|
210
|
+
# Opening paragraph + conclusion paragraph
|
|
211
|
+
return f"{paragraphs[0]}\n\n{paragraphs[-1]}"
|
|
212
|
+
|
|
213
|
+
def _extract_paragraphs(self, content: str) -> list[str]:
|
|
214
|
+
"""
|
|
215
|
+
Extract paragraphs from content.
|
|
216
|
+
|
|
217
|
+
Filters out:
|
|
218
|
+
- Empty lines
|
|
219
|
+
- Short lines (< 40 chars, likely headers/formatting artifacts)
|
|
220
|
+
- Code blocks (lines with multiple indentation)
|
|
221
|
+
- Lines that look like code (contain def, class, =, {, etc.)
|
|
222
|
+
"""
|
|
223
|
+
# Split on double newlines for paragraph boundaries
|
|
224
|
+
raw_paragraphs = re.split(r"\n\s*\n", content)
|
|
225
|
+
|
|
226
|
+
paragraphs = []
|
|
227
|
+
for para in raw_paragraphs:
|
|
228
|
+
# Clean and normalize whitespace
|
|
229
|
+
para = " ".join(para.split())
|
|
230
|
+
|
|
231
|
+
# Skip empty or very short paragraphs (likely headers)
|
|
232
|
+
if len(para) < 40:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# Skip code blocks (heuristic: contains code-like patterns)
|
|
236
|
+
code_indicators = ["def ", "class ", " = ", "{", "}", "return ", "import "]
|
|
237
|
+
if any(indicator in para for indicator in code_indicators):
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
paragraphs.append(para)
|
|
241
|
+
|
|
242
|
+
return paragraphs
|
|
243
|
+
|
|
244
|
+
def _split_sentences(self, text: str) -> list[str]:
|
|
245
|
+
"""
|
|
246
|
+
Split text into sentences.
|
|
247
|
+
|
|
248
|
+
Simple heuristic: Split on '. ' but handle common abbreviations.
|
|
249
|
+
"""
|
|
250
|
+
# Handle common abbreviations to avoid false splits
|
|
251
|
+
text = text.replace("Dr.", "Dr<DOT>")
|
|
252
|
+
text = text.replace("Mr.", "Mr<DOT>")
|
|
253
|
+
text = text.replace("Mrs.", "Mrs<DOT>")
|
|
254
|
+
text = text.replace("e.g.", "e<DOT>g<DOT>")
|
|
255
|
+
text = text.replace("i.e.", "i<DOT>e<DOT>")
|
|
256
|
+
|
|
257
|
+
# Split on sentence boundaries
|
|
258
|
+
sentences = re.split(r"(?<=[.!?])\s+", text)
|
|
259
|
+
|
|
260
|
+
# Restore abbreviations
|
|
261
|
+
sentences = [s.replace("<DOT>", ".") for s in sentences]
|
|
262
|
+
|
|
263
|
+
return [s.strip() for s in sentences if s.strip()]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def format_output(summary: str, output_format: OutputFormat, file_path: Path) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Format summary output.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
summary: Summary text
|
|
272
|
+
output_format: Output format (text, json, markdown)
|
|
273
|
+
file_path: Original file path for metadata
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Formatted output string
|
|
277
|
+
"""
|
|
278
|
+
if output_format == OutputFormat.TEXT:
|
|
279
|
+
return summary
|
|
280
|
+
|
|
281
|
+
if output_format == OutputFormat.JSON:
|
|
282
|
+
result = {
|
|
283
|
+
"file": str(file_path),
|
|
284
|
+
"summary": summary,
|
|
285
|
+
"word_count": len(summary.split()),
|
|
286
|
+
}
|
|
287
|
+
return json.dumps(result, indent=2)
|
|
288
|
+
|
|
289
|
+
if output_format == OutputFormat.MARKDOWN:
|
|
290
|
+
return f"# Summary: {file_path.name}\n\n{summary}\n"
|
|
291
|
+
|
|
292
|
+
return summary
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def summarize_command(args) -> int:
|
|
296
|
+
"""
|
|
297
|
+
Execute summarize command.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
args: Parsed command line arguments with:
|
|
301
|
+
- file_path: Path to file to summarize
|
|
302
|
+
- style: Summary style
|
|
303
|
+
- max_words: Maximum words in summary
|
|
304
|
+
- output: Output format
|
|
305
|
+
- lines: Optional line limit
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Exit code (0 for success, 1 for error)
|
|
309
|
+
"""
|
|
310
|
+
file_path = Path(args.file_path)
|
|
311
|
+
|
|
312
|
+
# Validate file exists
|
|
313
|
+
if not file_path.exists():
|
|
314
|
+
print(f"Error: File not found: {file_path}")
|
|
315
|
+
return 1
|
|
316
|
+
|
|
317
|
+
if not file_path.is_file():
|
|
318
|
+
print(f"Error: Not a file: {file_path}")
|
|
319
|
+
return 1
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
# Read file content
|
|
323
|
+
content = file_path.read_text(encoding="utf-8")
|
|
324
|
+
|
|
325
|
+
# Create summarizer
|
|
326
|
+
summarizer = DocumentSummarizer(max_words=args.max_words)
|
|
327
|
+
|
|
328
|
+
# Generate summary
|
|
329
|
+
summary = summarizer.summarize(
|
|
330
|
+
content, style=SummaryStyle(args.style), lines_limit=args.lines
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Format output
|
|
334
|
+
output = format_output(summary, OutputFormat(args.output), file_path)
|
|
335
|
+
|
|
336
|
+
# Print result
|
|
337
|
+
print(output)
|
|
338
|
+
|
|
339
|
+
return 0
|
|
340
|
+
|
|
341
|
+
except UnicodeDecodeError:
|
|
342
|
+
print(f"Error: Cannot read file (not valid UTF-8): {file_path}")
|
|
343
|
+
return 1
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print(f"Error: {e}")
|
|
346
|
+
return 1
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def add_summarize_parser(subparsers) -> None:
|
|
350
|
+
"""
|
|
351
|
+
Add summarize subcommand parser.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
subparsers: Subparsers object from argparse
|
|
355
|
+
"""
|
|
356
|
+
parser = subparsers.add_parser(
|
|
357
|
+
"summarize",
|
|
358
|
+
help="Summarize document content (shell-based alternative to MCP document_summarizer)",
|
|
359
|
+
description="""
|
|
360
|
+
Algorithmic document summarization without ML dependencies.
|
|
361
|
+
|
|
362
|
+
Styles:
|
|
363
|
+
brief - First paragraph only (quick overview)
|
|
364
|
+
detailed - Key sentences from opening, middle, closing
|
|
365
|
+
bullet_points - Markdown bullet list of key points
|
|
366
|
+
executive - Opening + conclusion (for quick decisions)
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
claude-mpm summarize README.md
|
|
370
|
+
claude-mpm summarize docs/guide.md --style detailed --max-words 200
|
|
371
|
+
claude-mpm summarize src/main.py --style bullet_points --output markdown
|
|
372
|
+
claude-mpm summarize large.txt --lines 100 --style brief
|
|
373
|
+
""",
|
|
374
|
+
formatter_class=lambda prog: __import__("argparse").RawDescriptionHelpFormatter(
|
|
375
|
+
prog, max_help_position=40
|
|
376
|
+
),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Required arguments
|
|
380
|
+
parser.add_argument("file_path", type=str, help="Path to file to summarize")
|
|
381
|
+
|
|
382
|
+
# Optional arguments
|
|
383
|
+
parser.add_argument(
|
|
384
|
+
"--style",
|
|
385
|
+
type=str,
|
|
386
|
+
choices=["brief", "detailed", "bullet_points", "executive"],
|
|
387
|
+
default="brief",
|
|
388
|
+
help="Summary style (default: brief)",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
parser.add_argument(
|
|
392
|
+
"--max-words",
|
|
393
|
+
type=int,
|
|
394
|
+
default=150,
|
|
395
|
+
help="Maximum words in summary (default: 150)",
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
parser.add_argument(
|
|
399
|
+
"--output",
|
|
400
|
+
type=str,
|
|
401
|
+
choices=["text", "json", "markdown"],
|
|
402
|
+
default="text",
|
|
403
|
+
help="Output format (default: text)",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
parser.add_argument(
|
|
407
|
+
"--lines",
|
|
408
|
+
type=int,
|
|
409
|
+
default=None,
|
|
410
|
+
help="Limit to first N lines of file (default: no limit)",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
parser.set_defaults(command="summarize")
|
claude_mpm/cli/executor.py
CHANGED
|
@@ -141,6 +141,14 @@ def execute_command(command: str, args) -> int:
|
|
|
141
141
|
result = agent_source_command(args)
|
|
142
142
|
return result if result is not None else 0
|
|
143
143
|
|
|
144
|
+
# Handle summarize command with lazy import
|
|
145
|
+
if command == "summarize":
|
|
146
|
+
# Lazy import to avoid loading unless needed
|
|
147
|
+
from .commands.summarize import summarize_command
|
|
148
|
+
|
|
149
|
+
result = summarize_command(args)
|
|
150
|
+
return result if result is not None else 0
|
|
151
|
+
|
|
144
152
|
# Handle auto-configure command with lazy import
|
|
145
153
|
if command == "auto-configure":
|
|
146
154
|
# Lazy import to avoid loading unless needed
|