claude-mpm 3.4.2__py3-none-any.whl → 3.4.5__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.
- claude_mpm/agents/INSTRUCTIONS.md +20 -3
- claude_mpm/agents/backups/INSTRUCTIONS.md +119 -5
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +36 -0
- claude_mpm/cli/__init__.py +3 -1
- claude_mpm/cli/commands/__init__.py +3 -1
- claude_mpm/cli/commands/monitor.py +328 -0
- claude_mpm/cli/commands/run.py +9 -17
- claude_mpm/cli/parser.py +69 -1
- claude_mpm/constants.py +9 -0
- claude_mpm/services/memory_router.py +99 -6
- claude_mpm/ticket_wrapper.py +29 -0
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/METADATA +1 -1
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/RECORD +17 -24
- claude_mpm/scripts/__init__.py +0 -1
- claude_mpm/scripts/claude-mpm-socketio +0 -32
- claude_mpm/scripts/claude_mpm_monitor.html +0 -567
- claude_mpm/scripts/install_socketio_server.py +0 -407
- claude_mpm/scripts/launch_monitor.py +0 -132
- claude_mpm/scripts/launch_socketio_dashboard.py +0 -261
- claude_mpm/scripts/manage_version.py +0 -479
- claude_mpm/scripts/socketio_daemon.py +0 -221
- claude_mpm/scripts/socketio_server_manager.py +0 -753
- claude_mpm/scripts/ticket.py +0 -269
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.2.dist-info → claude_mpm-3.4.5.dist-info}/top_level.txt +0 -0
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Version management script for Claude MPM.
|
|
4
|
-
|
|
5
|
-
This script implements a comprehensive version management system that:
|
|
6
|
-
1. Uses setuptools-scm for version detection from git tags
|
|
7
|
-
2. Updates VERSION file to maintain version synchronization
|
|
8
|
-
3. Generates/updates CHANGELOG.md from git commits automatically
|
|
9
|
-
4. Supports semantic versioning with conventional commits
|
|
10
|
-
|
|
11
|
-
OPERATIONAL PURPOSE:
|
|
12
|
-
Central version management for consistent releases across all distribution channels.
|
|
13
|
-
Automates version bumping, changelog generation, and release preparation.
|
|
14
|
-
|
|
15
|
-
VERSION MANAGEMENT STRATEGY:
|
|
16
|
-
- Primary source of truth: Git tags (format: v1.0.0)
|
|
17
|
-
- setuptools-scm derives version from git state
|
|
18
|
-
- VERSION file kept in sync for quick access
|
|
19
|
-
- package.json synchronized via release.py script
|
|
20
|
-
- Conventional commits determine version bump type automatically
|
|
21
|
-
|
|
22
|
-
SEMANTIC VERSIONING IMPLEMENTATION:
|
|
23
|
-
- MAJOR (X.0.0): Breaking changes (BREAKING CHANGE: or feat!)
|
|
24
|
-
- MINOR (0.X.0): New features (feat:)
|
|
25
|
-
- PATCH (0.0.X): Bug fixes (fix:) or performance improvements (perf:)
|
|
26
|
-
- Development versions between releases: X.Y.Z.postN+gHASH[.dirty]
|
|
27
|
-
|
|
28
|
-
VERSION COMPATIBILITY:
|
|
29
|
-
- Backward compatible with manual version management
|
|
30
|
-
- Forward compatible with CI/CD automation
|
|
31
|
-
- Supports both manual and automatic version bumping
|
|
32
|
-
- Handles migration from old version formats gracefully
|
|
33
|
-
|
|
34
|
-
DEPLOYMENT PIPELINE INTEGRATION:
|
|
35
|
-
1. Developer commits with conventional format
|
|
36
|
-
2. CI runs manage_version.py to determine bump
|
|
37
|
-
3. Version updated and changelog generated
|
|
38
|
-
4. Git tag created for release
|
|
39
|
-
5. PyPI and npm packages built and published
|
|
40
|
-
|
|
41
|
-
MONITORING AND TROUBLESHOOTING:
|
|
42
|
-
- Check git tags: git tag -l | sort -V
|
|
43
|
-
- Verify VERSION file matches latest tag
|
|
44
|
-
- Review CHANGELOG.md for missing commits
|
|
45
|
-
- Ensure conventional commit format compliance
|
|
46
|
-
- Monitor version sync across package files
|
|
47
|
-
|
|
48
|
-
ROLLBACK PROCEDURES:
|
|
49
|
-
- Delete incorrect git tag: git tag -d vX.Y.Z
|
|
50
|
-
- Reset VERSION file to previous version
|
|
51
|
-
- Revert CHANGELOG.md changes
|
|
52
|
-
- Force push tag updates carefully
|
|
53
|
-
- Coordinate with PyPI/npm for package yanking
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
import subprocess
|
|
57
|
-
import re
|
|
58
|
-
import sys
|
|
59
|
-
from pathlib import Path
|
|
60
|
-
from datetime import datetime
|
|
61
|
-
from typing import List, Tuple, Optional
|
|
62
|
-
import argparse
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Conventional commit types and their changelog sections
|
|
66
|
-
# These map commit types to human-readable changelog categories
|
|
67
|
-
# Following the Conventional Commits specification v1.0.0
|
|
68
|
-
COMMIT_TYPES = {
|
|
69
|
-
"feat": "Features", # New features
|
|
70
|
-
"fix": "Bug Fixes", # Bug fixes
|
|
71
|
-
"docs": "Documentation", # Documentation only changes
|
|
72
|
-
"style": "Code Style", # Code style changes (formatting, etc)
|
|
73
|
-
"refactor": "Code Refactoring", # Code changes that neither fix bugs nor add features
|
|
74
|
-
"perf": "Performance Improvements", # Performance improvements
|
|
75
|
-
"test": "Tests", # Adding or updating tests
|
|
76
|
-
"build": "Build System", # Build system or dependency changes
|
|
77
|
-
"ci": "Continuous Integration", # CI configuration changes
|
|
78
|
-
"chore": "Chores", # Other changes that don't modify src or test files
|
|
79
|
-
"revert": "Reverts" # Reverting previous commits
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
# Types that trigger version bumps based on semantic versioning rules
|
|
83
|
-
# These determine how the version number changes based on commit types
|
|
84
|
-
MAJOR_TYPES = ["breaking", "major"] # Keywords in commit message that trigger major bump
|
|
85
|
-
MINOR_TYPES = ["feat"] # Commit types that trigger minor version bump
|
|
86
|
-
PATCH_TYPES = ["fix", "perf"] # Commit types that trigger patch version bump
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def run_command(cmd: List[str]) -> str:
|
|
90
|
-
"""Run a command and return its output.
|
|
91
|
-
|
|
92
|
-
This is a utility function that executes shell commands safely and returns
|
|
93
|
-
their output. Used throughout the script for git operations.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
cmd: List of command arguments (e.g., ['git', 'tag', '-l'])
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
String output from the command, stripped of whitespace
|
|
100
|
-
Empty string if command fails
|
|
101
|
-
"""
|
|
102
|
-
try:
|
|
103
|
-
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
104
|
-
return result.stdout.strip()
|
|
105
|
-
except subprocess.CalledProcessError as e:
|
|
106
|
-
print(f"Error running command {' '.join(cmd)}: {e}")
|
|
107
|
-
return ""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def get_current_version() -> str:
|
|
111
|
-
"""Get current version from VERSION file.
|
|
112
|
-
|
|
113
|
-
Version Detection:
|
|
114
|
-
1. VERSION file: Primary source of truth
|
|
115
|
-
2. Default: Returns 0.0.0 if VERSION file is missing
|
|
116
|
-
|
|
117
|
-
The VERSION file is the single source of truth for version information.
|
|
118
|
-
Git tags are used for releases, but VERSION file contains the current version.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
Current version string from VERSION file
|
|
122
|
-
"""
|
|
123
|
-
# Read from VERSION file - single source of truth
|
|
124
|
-
version_file = Path("VERSION")
|
|
125
|
-
if version_file.exists():
|
|
126
|
-
return version_file.read_text().strip()
|
|
127
|
-
|
|
128
|
-
# Default version when VERSION file is missing
|
|
129
|
-
print("WARNING: VERSION file not found, using default version 0.0.0", file=sys.stderr)
|
|
130
|
-
return "0.0.0"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def parse_conventional_commit(message: str) -> Tuple[Optional[str], Optional[str], str, bool]:
|
|
134
|
-
"""Parse a conventional commit message following the Conventional Commits spec.
|
|
135
|
-
|
|
136
|
-
Conventional Commits Format:
|
|
137
|
-
<type>[optional scope]: <description>
|
|
138
|
-
|
|
139
|
-
[optional body]
|
|
140
|
-
|
|
141
|
-
[optional footer(s)]
|
|
142
|
-
|
|
143
|
-
Examples:
|
|
144
|
-
- "feat: add new agent capabilities"
|
|
145
|
-
- "fix(logging): correct session duration calculation"
|
|
146
|
-
- "feat!: redesign agent API" (breaking change)
|
|
147
|
-
- "fix: typo\n\nBREAKING CHANGE: API renamed" (breaking in footer)
|
|
148
|
-
|
|
149
|
-
Breaking Changes Detection:
|
|
150
|
-
1. Exclamation mark after type/scope: "feat!:" or "feat(api)!:"
|
|
151
|
-
2. "BREAKING CHANGE:" in commit body/footer
|
|
152
|
-
3. "BREAKING:" as shorthand in body/footer
|
|
153
|
-
|
|
154
|
-
Args:
|
|
155
|
-
message: Full commit message including body
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
Tuple of:
|
|
159
|
-
- type: Commit type (feat, fix, etc.) or None
|
|
160
|
-
- scope: Optional scope in parentheses or None
|
|
161
|
-
- description: Commit description (subject line)
|
|
162
|
-
- is_breaking: True if breaking change detected
|
|
163
|
-
"""
|
|
164
|
-
# Check for breaking change indicators anywhere in the message
|
|
165
|
-
# This includes both the footer format and inline indicators
|
|
166
|
-
is_breaking = "BREAKING CHANGE:" in message or "BREAKING:" in message
|
|
167
|
-
|
|
168
|
-
# Parse conventional commit format: type(scope): description
|
|
169
|
-
# Also handles type!: for breaking changes
|
|
170
|
-
pattern = r"^(\w+)(?:\(([^)]+)\))?: (.+)"
|
|
171
|
-
match = re.match(pattern, message.split("\n")[0])
|
|
172
|
-
|
|
173
|
-
if match:
|
|
174
|
-
commit_type, scope, description = match.groups()
|
|
175
|
-
return commit_type, scope, description, is_breaking
|
|
176
|
-
|
|
177
|
-
# If not a conventional commit, return the first line as description
|
|
178
|
-
return None, None, message.split("\n")[0], is_breaking
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def get_commits_since_tag(tag: Optional[str] = None) -> List[dict]:
|
|
182
|
-
"""Get all commits since the last tag."""
|
|
183
|
-
if tag:
|
|
184
|
-
cmd = ["git", "log", f"{tag}..HEAD", "--pretty=format:%H|%ai|%s|%b|%an"]
|
|
185
|
-
else:
|
|
186
|
-
cmd = ["git", "log", "--pretty=format:%H|%ai|%s|%b|%an"]
|
|
187
|
-
|
|
188
|
-
output = run_command(cmd)
|
|
189
|
-
if not output:
|
|
190
|
-
return []
|
|
191
|
-
|
|
192
|
-
commits = []
|
|
193
|
-
for line in output.split("\n"):
|
|
194
|
-
if line:
|
|
195
|
-
parts = line.split("|", 4)
|
|
196
|
-
if len(parts) >= 5:
|
|
197
|
-
hash, date, subject, body, author = parts
|
|
198
|
-
commit_type, scope, description, is_breaking = parse_conventional_commit(subject)
|
|
199
|
-
commits.append({
|
|
200
|
-
"hash": hash[:7],
|
|
201
|
-
"date": date,
|
|
202
|
-
"type": commit_type,
|
|
203
|
-
"scope": scope,
|
|
204
|
-
"description": description,
|
|
205
|
-
"breaking": is_breaking,
|
|
206
|
-
"author": author,
|
|
207
|
-
"body": body
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
return commits
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def determine_version_bump(commits: List[dict]) -> str:
|
|
214
|
-
"""Determine version bump type based on commits using semantic versioning rules.
|
|
215
|
-
|
|
216
|
-
Version Bump Logic (in priority order):
|
|
217
|
-
1. MAJOR: Any commit with breaking changes
|
|
218
|
-
- BREAKING CHANGE: in footer
|
|
219
|
-
- feat!: or fix!: syntax
|
|
220
|
-
- Resets minor and patch to 0
|
|
221
|
-
|
|
222
|
-
2. MINOR: Any commit with new features (feat:)
|
|
223
|
-
- Only if no breaking changes
|
|
224
|
-
- Resets patch to 0
|
|
225
|
-
|
|
226
|
-
3. PATCH: Any commit with fixes (fix:) or performance improvements (perf:)
|
|
227
|
-
- Only if no breaking changes or features
|
|
228
|
-
- Increments patch version
|
|
229
|
-
|
|
230
|
-
4. DEFAULT: If no conventional commits found, defaults to patch
|
|
231
|
-
- Ensures version always increments
|
|
232
|
-
- Safe default for manual commits
|
|
233
|
-
|
|
234
|
-
This implements the semantic versioning specification where:
|
|
235
|
-
- MAJOR version for incompatible API changes
|
|
236
|
-
- MINOR version for backwards-compatible functionality additions
|
|
237
|
-
- PATCH version for backwards-compatible bug fixes
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
commits: List of parsed commit dictionaries
|
|
241
|
-
|
|
242
|
-
Returns:
|
|
243
|
-
Version bump type: "major", "minor", or "patch"
|
|
244
|
-
"""
|
|
245
|
-
# Check for breaking changes first (highest priority)
|
|
246
|
-
has_breaking = any(c["breaking"] for c in commits)
|
|
247
|
-
# Check for new features
|
|
248
|
-
has_minor = any(c["type"] in MINOR_TYPES for c in commits)
|
|
249
|
-
# Check for fixes or performance improvements
|
|
250
|
-
has_patch = any(c["type"] in PATCH_TYPES for c in commits)
|
|
251
|
-
|
|
252
|
-
# Apply semantic versioning rules in priority order
|
|
253
|
-
if has_breaking:
|
|
254
|
-
return "major"
|
|
255
|
-
elif has_minor:
|
|
256
|
-
return "minor"
|
|
257
|
-
elif has_patch:
|
|
258
|
-
return "patch"
|
|
259
|
-
return "patch" # Default to patch for safety
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def bump_version(current_version: str, bump_type: str) -> str:
|
|
263
|
-
"""Bump version according to semantic versioning specification.
|
|
264
|
-
|
|
265
|
-
Semantic Versioning Rules:
|
|
266
|
-
- MAJOR: Increment major, reset minor and patch to 0
|
|
267
|
-
- MINOR: Increment minor, reset patch to 0
|
|
268
|
-
- PATCH: Increment patch only
|
|
269
|
-
|
|
270
|
-
Version Cleaning:
|
|
271
|
-
- Removes development suffixes (.postN, .dirty, +gHASH)
|
|
272
|
-
- Extracts base semantic version (X.Y.Z)
|
|
273
|
-
- Handles various version formats from setuptools-scm
|
|
274
|
-
|
|
275
|
-
Examples:
|
|
276
|
-
- "1.2.3" + patch -> "1.2.4"
|
|
277
|
-
- "1.2.3.post4+g1234567" + minor -> "1.3.0"
|
|
278
|
-
- "1.2.3.dirty" + major -> "2.0.0"
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
current_version: Current version string (may include suffixes)
|
|
282
|
-
bump_type: Type of bump ("major", "minor", or "patch")
|
|
283
|
-
|
|
284
|
-
Returns:
|
|
285
|
-
New clean semantic version string (X.Y.Z format)
|
|
286
|
-
"""
|
|
287
|
-
# Clean version by extracting base semantic version
|
|
288
|
-
# This removes any PEP 440 local version identifiers
|
|
289
|
-
base_version = re.match(r"(\d+\.\d+\.\d+)", current_version)
|
|
290
|
-
if base_version:
|
|
291
|
-
current_version = base_version.group(1)
|
|
292
|
-
|
|
293
|
-
# Parse version components
|
|
294
|
-
major, minor, patch = map(int, current_version.split("."))
|
|
295
|
-
|
|
296
|
-
# Apply semantic versioning rules
|
|
297
|
-
if bump_type == "major":
|
|
298
|
-
# Breaking change: increment major, reset others
|
|
299
|
-
return f"{major + 1}.0.0"
|
|
300
|
-
elif bump_type == "minor":
|
|
301
|
-
# New feature: increment minor, reset patch
|
|
302
|
-
return f"{major}.{minor + 1}.0"
|
|
303
|
-
else: # patch
|
|
304
|
-
# Bug fix: increment patch only
|
|
305
|
-
return f"{major}.{minor}.{patch + 1}"
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def generate_changelog_entry(version: str, commits: List[dict], date: str) -> str:
|
|
309
|
-
"""Generate a changelog entry for a version."""
|
|
310
|
-
lines = [f"## [{version}] - {date}\n"]
|
|
311
|
-
|
|
312
|
-
# Group commits by type
|
|
313
|
-
grouped = {}
|
|
314
|
-
for commit in commits:
|
|
315
|
-
commit_type = commit["type"] or "other"
|
|
316
|
-
if commit_type not in grouped:
|
|
317
|
-
grouped[commit_type] = []
|
|
318
|
-
grouped[commit_type].append(commit)
|
|
319
|
-
|
|
320
|
-
# Add sections
|
|
321
|
-
for commit_type, section_name in COMMIT_TYPES.items():
|
|
322
|
-
if commit_type in grouped:
|
|
323
|
-
lines.append(f"\n### {section_name}\n")
|
|
324
|
-
for commit in grouped[commit_type]:
|
|
325
|
-
scope = f"**{commit['scope']}**: " if commit["scope"] else ""
|
|
326
|
-
lines.append(f"- {scope}{commit['description']} ([{commit['hash']}])")
|
|
327
|
-
if commit["breaking"]:
|
|
328
|
-
lines.append(f" - **BREAKING CHANGE**")
|
|
329
|
-
|
|
330
|
-
# Add uncategorized commits
|
|
331
|
-
if "other" in grouped:
|
|
332
|
-
lines.append(f"\n### Other Changes\n")
|
|
333
|
-
for commit in grouped["other"]:
|
|
334
|
-
lines.append(f"- {commit['description']} ([{commit['hash']}])")
|
|
335
|
-
|
|
336
|
-
return "\n".join(lines)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def update_changelog(new_entry: str):
|
|
340
|
-
"""Update CHANGELOG.md with new entry."""
|
|
341
|
-
changelog_path = Path("CHANGELOG.md")
|
|
342
|
-
|
|
343
|
-
if changelog_path.exists():
|
|
344
|
-
content = changelog_path.read_text()
|
|
345
|
-
# Insert after the header
|
|
346
|
-
parts = content.split("\n## ", 1)
|
|
347
|
-
if len(parts) == 2:
|
|
348
|
-
new_content = parts[0] + "\n" + new_entry + "\n## " + parts[1]
|
|
349
|
-
else:
|
|
350
|
-
new_content = content + "\n" + new_entry
|
|
351
|
-
else:
|
|
352
|
-
# Create new changelog
|
|
353
|
-
new_content = f"""# Changelog
|
|
354
|
-
|
|
355
|
-
All notable changes to Claude MPM will be documented in this file.
|
|
356
|
-
|
|
357
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
358
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
359
|
-
|
|
360
|
-
{new_entry}"""
|
|
361
|
-
|
|
362
|
-
changelog_path.write_text(new_content)
|
|
363
|
-
print(f"Updated CHANGELOG.md")
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
def update_version_file(version: str):
|
|
367
|
-
"""Update VERSION file."""
|
|
368
|
-
version_file = Path("VERSION")
|
|
369
|
-
version_file.write_text(version + "\n")
|
|
370
|
-
print(f"Updated VERSION file to {version}")
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
def create_git_tag(version: str, message: str):
|
|
374
|
-
"""Create an annotated git tag for the version.
|
|
375
|
-
|
|
376
|
-
Git Tag Strategy:
|
|
377
|
-
- Uses 'v' prefix convention (v1.2.3)
|
|
378
|
-
- Creates annotated tags (includes tagger info and message)
|
|
379
|
-
- Annotated tags are preferred for releases (shown in git describe)
|
|
380
|
-
- Tag message typically includes release title
|
|
381
|
-
|
|
382
|
-
The 'v' prefix is a widely adopted convention that:
|
|
383
|
-
- Distinguishes version tags from other tags
|
|
384
|
-
- Works well with GitHub releases
|
|
385
|
-
- Compatible with most CI/CD systems
|
|
386
|
-
- Recognized by setuptools-scm
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
version: Semantic version string (without 'v' prefix)
|
|
390
|
-
message: Tag annotation message
|
|
391
|
-
"""
|
|
392
|
-
tag = f"v{version}"
|
|
393
|
-
# Create annotated tag with message
|
|
394
|
-
# -a: Create annotated tag (not lightweight)
|
|
395
|
-
# -m: Provide message inline
|
|
396
|
-
run_command(["git", "tag", "-a", tag, "-m", message])
|
|
397
|
-
print(f"Created git tag: {tag}")
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def main():
|
|
401
|
-
parser = argparse.ArgumentParser(description="Manage Claude MPM versioning")
|
|
402
|
-
parser.add_argument("command", choices=["check", "bump", "changelog", "tag", "auto"],
|
|
403
|
-
help="Command to run")
|
|
404
|
-
parser.add_argument("--bump-type", choices=["major", "minor", "patch", "auto"],
|
|
405
|
-
default="auto", help="Version bump type")
|
|
406
|
-
parser.add_argument("--dry-run", action="store_true",
|
|
407
|
-
help="Don't make any changes")
|
|
408
|
-
parser.add_argument("--no-commit", action="store_true",
|
|
409
|
-
help="Don't commit changes")
|
|
410
|
-
|
|
411
|
-
args = parser.parse_args()
|
|
412
|
-
|
|
413
|
-
# Get current version
|
|
414
|
-
current_version = get_current_version()
|
|
415
|
-
print(f"Current version: {current_version}")
|
|
416
|
-
|
|
417
|
-
if args.command == "check":
|
|
418
|
-
# Just display current version
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
# Get latest tag
|
|
422
|
-
latest_tag = run_command(["git", "describe", "--tags", "--abbrev=0"])
|
|
423
|
-
if not latest_tag or latest_tag.startswith("fatal"):
|
|
424
|
-
latest_tag = None
|
|
425
|
-
|
|
426
|
-
# Get commits since last tag
|
|
427
|
-
commits = get_commits_since_tag(latest_tag)
|
|
428
|
-
print(f"Found {len(commits)} commits since {latest_tag or 'beginning'}")
|
|
429
|
-
|
|
430
|
-
if args.command in ["bump", "auto"]:
|
|
431
|
-
# Determine version bump
|
|
432
|
-
if args.bump_type == "auto":
|
|
433
|
-
bump_type = determine_version_bump(commits)
|
|
434
|
-
else:
|
|
435
|
-
bump_type = args.bump_type
|
|
436
|
-
|
|
437
|
-
new_version = bump_version(current_version, bump_type)
|
|
438
|
-
print(f"New version: {new_version} ({bump_type} bump)")
|
|
439
|
-
|
|
440
|
-
if not args.dry_run:
|
|
441
|
-
# Update VERSION file
|
|
442
|
-
update_version_file(new_version)
|
|
443
|
-
|
|
444
|
-
# Generate changelog entry
|
|
445
|
-
changelog_entry = generate_changelog_entry(
|
|
446
|
-
new_version, commits, datetime.now().strftime("%Y-%m-%d")
|
|
447
|
-
)
|
|
448
|
-
update_changelog(changelog_entry)
|
|
449
|
-
|
|
450
|
-
if not args.no_commit:
|
|
451
|
-
# Commit changes
|
|
452
|
-
run_command(["git", "add", "VERSION", "CHANGELOG.md"])
|
|
453
|
-
run_command(["git", "commit", "-m", f"chore: bump version to {new_version}"])
|
|
454
|
-
|
|
455
|
-
# Create tag
|
|
456
|
-
create_git_tag(new_version, f"Release {new_version}")
|
|
457
|
-
print(f"\nVersion bumped to {new_version}")
|
|
458
|
-
print("Run 'git push && git push --tags' to publish")
|
|
459
|
-
|
|
460
|
-
elif args.command == "changelog":
|
|
461
|
-
# Just generate changelog
|
|
462
|
-
if commits:
|
|
463
|
-
changelog_entry = generate_changelog_entry(
|
|
464
|
-
current_version, commits, datetime.now().strftime("%Y-%m-%d")
|
|
465
|
-
)
|
|
466
|
-
if args.dry_run:
|
|
467
|
-
print("\nChangelog entry:")
|
|
468
|
-
print(changelog_entry)
|
|
469
|
-
else:
|
|
470
|
-
update_changelog(changelog_entry)
|
|
471
|
-
|
|
472
|
-
elif args.command == "tag":
|
|
473
|
-
# Just create a tag for current version
|
|
474
|
-
if not args.dry_run:
|
|
475
|
-
create_git_tag(current_version, f"Release {current_version}")
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if __name__ == "__main__":
|
|
479
|
-
main()
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Pure Python daemon management for Socket.IO server.
|
|
4
|
-
No external dependencies required.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import time
|
|
10
|
-
import signal
|
|
11
|
-
import subprocess
|
|
12
|
-
import psutil
|
|
13
|
-
import json
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
# Add src to path
|
|
17
|
-
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
18
|
-
|
|
19
|
-
from claude_mpm.services.socketio_server import SocketIOServer
|
|
20
|
-
|
|
21
|
-
PID_FILE = Path.home() / ".claude-mpm" / "socketio-server.pid"
|
|
22
|
-
LOG_FILE = Path.home() / ".claude-mpm" / "socketio-server.log"
|
|
23
|
-
|
|
24
|
-
def ensure_dirs():
|
|
25
|
-
"""Ensure required directories exist."""
|
|
26
|
-
PID_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
-
|
|
28
|
-
def is_running():
|
|
29
|
-
"""Check if server is already running."""
|
|
30
|
-
if not PID_FILE.exists():
|
|
31
|
-
return False
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
with open(PID_FILE) as f:
|
|
35
|
-
pid = int(f.read().strip())
|
|
36
|
-
|
|
37
|
-
# Check if process exists
|
|
38
|
-
process = psutil.Process(pid)
|
|
39
|
-
return process.is_running()
|
|
40
|
-
except (ValueError, psutil.NoSuchProcess, psutil.AccessDenied):
|
|
41
|
-
# Clean up stale PID file
|
|
42
|
-
PID_FILE.unlink(missing_ok=True)
|
|
43
|
-
return False
|
|
44
|
-
|
|
45
|
-
def start_server():
|
|
46
|
-
"""Start the Socket.IO server as a daemon with conflict detection."""
|
|
47
|
-
if is_running():
|
|
48
|
-
print("Socket.IO daemon server is already running.")
|
|
49
|
-
print(f"Use '{__file__} status' for details")
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
# Check for HTTP-managed server conflict
|
|
53
|
-
try:
|
|
54
|
-
import requests
|
|
55
|
-
response = requests.get("http://localhost:8765/health", timeout=1.0)
|
|
56
|
-
if response.status_code == 200:
|
|
57
|
-
data = response.json()
|
|
58
|
-
if 'server_id' in data:
|
|
59
|
-
print(f"⚠️ HTTP-managed server already running: {data.get('server_id')}")
|
|
60
|
-
print(f" Stop it first: socketio_server_manager.py stop --port 8765")
|
|
61
|
-
print(f" Or diagnose: socketio_server_manager.py diagnose")
|
|
62
|
-
return
|
|
63
|
-
except:
|
|
64
|
-
pass # No HTTP server, continue
|
|
65
|
-
|
|
66
|
-
ensure_dirs()
|
|
67
|
-
|
|
68
|
-
# Fork to create daemon
|
|
69
|
-
pid = os.fork()
|
|
70
|
-
if pid > 0:
|
|
71
|
-
# Parent process
|
|
72
|
-
print(f"Starting Socket.IO server (PID: {pid})...")
|
|
73
|
-
with open(PID_FILE, 'w') as f:
|
|
74
|
-
f.write(str(pid))
|
|
75
|
-
print("Socket.IO server started successfully.")
|
|
76
|
-
print(f"PID file: {PID_FILE}")
|
|
77
|
-
print(f"Log file: {LOG_FILE}")
|
|
78
|
-
sys.exit(0)
|
|
79
|
-
|
|
80
|
-
# Child process - become daemon
|
|
81
|
-
os.setsid()
|
|
82
|
-
os.umask(0)
|
|
83
|
-
|
|
84
|
-
# Redirect stdout/stderr to log file
|
|
85
|
-
with open(LOG_FILE, 'a') as log:
|
|
86
|
-
os.dup2(log.fileno(), sys.stdout.fileno())
|
|
87
|
-
os.dup2(log.fileno(), sys.stderr.fileno())
|
|
88
|
-
|
|
89
|
-
# Start server
|
|
90
|
-
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Starting Socket.IO server...")
|
|
91
|
-
server = SocketIOServer(host="localhost", port=8765)
|
|
92
|
-
|
|
93
|
-
# Handle signals
|
|
94
|
-
def signal_handler(signum, frame):
|
|
95
|
-
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Received signal {signum}, shutting down...")
|
|
96
|
-
server.stop()
|
|
97
|
-
PID_FILE.unlink(missing_ok=True)
|
|
98
|
-
sys.exit(0)
|
|
99
|
-
|
|
100
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
|
101
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
102
|
-
|
|
103
|
-
# Start server
|
|
104
|
-
server.start()
|
|
105
|
-
|
|
106
|
-
# Keep running
|
|
107
|
-
try:
|
|
108
|
-
while True:
|
|
109
|
-
time.sleep(1)
|
|
110
|
-
except KeyboardInterrupt:
|
|
111
|
-
signal_handler(signal.SIGINT, None)
|
|
112
|
-
|
|
113
|
-
def stop_server():
|
|
114
|
-
"""Stop the Socket.IO daemon server."""
|
|
115
|
-
if not is_running():
|
|
116
|
-
print("Socket.IO daemon server is not running.")
|
|
117
|
-
print(f"Check for other servers: socketio_server_manager.py status")
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
try:
|
|
121
|
-
with open(PID_FILE) as f:
|
|
122
|
-
pid = int(f.read().strip())
|
|
123
|
-
|
|
124
|
-
print(f"Stopping Socket.IO server (PID: {pid})...")
|
|
125
|
-
os.kill(pid, signal.SIGTERM)
|
|
126
|
-
|
|
127
|
-
# Wait for process to stop
|
|
128
|
-
for _ in range(10):
|
|
129
|
-
if not is_running():
|
|
130
|
-
print("Socket.IO server stopped successfully.")
|
|
131
|
-
PID_FILE.unlink(missing_ok=True)
|
|
132
|
-
return
|
|
133
|
-
time.sleep(0.5)
|
|
134
|
-
|
|
135
|
-
# Force kill if still running
|
|
136
|
-
print("Server didn't stop gracefully, forcing...")
|
|
137
|
-
os.kill(pid, signal.SIGKILL)
|
|
138
|
-
PID_FILE.unlink(missing_ok=True)
|
|
139
|
-
|
|
140
|
-
except Exception as e:
|
|
141
|
-
print(f"Error stopping server: {e}")
|
|
142
|
-
|
|
143
|
-
def status_server():
|
|
144
|
-
"""Check server status with manager integration info."""
|
|
145
|
-
if is_running():
|
|
146
|
-
with open(PID_FILE) as f:
|
|
147
|
-
pid = int(f.read().strip())
|
|
148
|
-
print(f"Socket.IO daemon server is running (PID: {pid})")
|
|
149
|
-
print(f"PID file: {PID_FILE}")
|
|
150
|
-
|
|
151
|
-
# Check if port is listening
|
|
152
|
-
try:
|
|
153
|
-
import socket
|
|
154
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
155
|
-
result = sock.connect_ex(('localhost', 8765))
|
|
156
|
-
sock.close()
|
|
157
|
-
if result == 0:
|
|
158
|
-
print("✅ Server is listening on port 8765")
|
|
159
|
-
print("🔧 Management style: daemon")
|
|
160
|
-
else:
|
|
161
|
-
print("⚠️ WARNING: Server process exists but port 8765 is not accessible")
|
|
162
|
-
except:
|
|
163
|
-
pass
|
|
164
|
-
|
|
165
|
-
# Show management commands
|
|
166
|
-
print("\n🔧 Management Commands:")
|
|
167
|
-
print(f" • Stop: {__file__} stop")
|
|
168
|
-
print(f" • Restart: {__file__} restart")
|
|
169
|
-
|
|
170
|
-
# Check for manager conflicts
|
|
171
|
-
try:
|
|
172
|
-
import requests
|
|
173
|
-
response = requests.get("http://localhost:8765/health", timeout=1.0)
|
|
174
|
-
if response.status_code == 200:
|
|
175
|
-
data = response.json()
|
|
176
|
-
if 'server_id' in data and data.get('server_id') != 'daemon-socketio':
|
|
177
|
-
print(f"\n⚠️ POTENTIAL CONFLICT: HTTP-managed server also detected")
|
|
178
|
-
print(f" Server ID: {data.get('server_id')}")
|
|
179
|
-
print(f" Use 'socketio_server_manager.py diagnose' to resolve")
|
|
180
|
-
except:
|
|
181
|
-
pass
|
|
182
|
-
|
|
183
|
-
else:
|
|
184
|
-
print("Socket.IO daemon server is not running")
|
|
185
|
-
print(f"\n🔧 Start Commands:")
|
|
186
|
-
print(f" • Daemon: {__file__} start")
|
|
187
|
-
print(f" • HTTP-managed: socketio_server_manager.py start")
|
|
188
|
-
|
|
189
|
-
def main():
|
|
190
|
-
"""Main entry point."""
|
|
191
|
-
if len(sys.argv) < 2:
|
|
192
|
-
print("Usage: socketio-daemon.py {start|stop|restart|status}")
|
|
193
|
-
sys.exit(1)
|
|
194
|
-
|
|
195
|
-
command = sys.argv[1]
|
|
196
|
-
|
|
197
|
-
if command == "start":
|
|
198
|
-
start_server()
|
|
199
|
-
elif command == "stop":
|
|
200
|
-
stop_server()
|
|
201
|
-
elif command == "restart":
|
|
202
|
-
stop_server()
|
|
203
|
-
time.sleep(1)
|
|
204
|
-
start_server()
|
|
205
|
-
elif command == "status":
|
|
206
|
-
status_server()
|
|
207
|
-
else:
|
|
208
|
-
print(f"Unknown command: {command}")
|
|
209
|
-
print("Usage: socketio-daemon.py {start|stop|restart|status}")
|
|
210
|
-
sys.exit(1)
|
|
211
|
-
|
|
212
|
-
if __name__ == "__main__":
|
|
213
|
-
# Install psutil if not available
|
|
214
|
-
try:
|
|
215
|
-
import psutil
|
|
216
|
-
except ImportError:
|
|
217
|
-
print("Installing psutil...")
|
|
218
|
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "psutil"])
|
|
219
|
-
import psutil
|
|
220
|
-
|
|
221
|
-
main()
|