claude-mpm 4.21.0__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/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-resume.md +372 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/services/core/base.py +26 -11
- claude_mpm/services/event_bus/relay.py +23 -7
- claude_mpm/services/memory/failure_tracker.py +19 -4
- 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-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/RECORD +23 -13
- claude_mpm/tools/code_tree_analyzer.py +0 -1825
- {claude_mpm-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.21.0.dist-info → claude_mpm-4.21.3.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.21.
|
|
1
|
+
4.21.3
|
claude_mpm/commands/mpm-help.md
CHANGED
|
@@ -87,6 +87,9 @@ Available Commands:
|
|
|
87
87
|
/mpm-init [update]
|
|
88
88
|
Initialize or update project documentation
|
|
89
89
|
|
|
90
|
+
/mpm-resume
|
|
91
|
+
Create session resume files for easy work resumption
|
|
92
|
+
|
|
90
93
|
/mpm-monitor [start|stop|restart|status|port]
|
|
91
94
|
Manage Socket.IO monitoring server and dashboard
|
|
92
95
|
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# /mpm-resume - Load Previous Session
|
|
2
|
+
|
|
3
|
+
Load and display context from the most recent paused session to seamlessly continue your work.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/mpm-resume
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
This command **loads and displays** the most recent paused session from automatic session saves, allowing you to resume work with full context restoration.
|
|
14
|
+
|
|
15
|
+
Unlike `/mpm-init pause` which *creates* a new session save, this command *loads* existing session data that was automatically created when context usage reached 70% (140k/200k tokens).
|
|
16
|
+
|
|
17
|
+
**Key Points:**
|
|
18
|
+
- Reads from `.claude-mpm/sessions/` directory (automatically created at 70% context)
|
|
19
|
+
- Displays up to ~40,000 tokens (~20% of 200k context budget)
|
|
20
|
+
- Shows session summary, completed work, in-progress tasks, and git context
|
|
21
|
+
- Does NOT create any new files - only reads and displays
|
|
22
|
+
|
|
23
|
+
## What Gets Displayed
|
|
24
|
+
|
|
25
|
+
When you run `/mpm-resume`, PM will display:
|
|
26
|
+
|
|
27
|
+
### Session Summary
|
|
28
|
+
- **Time Elapsed**: How long ago the session was paused
|
|
29
|
+
- **Context Usage**: Token usage at time of pause (e.g., "67.6% - 135,259/200,000 tokens")
|
|
30
|
+
- **Working On**: What you were working on when paused
|
|
31
|
+
- **Session Duration**: How long the previous session lasted
|
|
32
|
+
|
|
33
|
+
### Completed Work
|
|
34
|
+
- List of accomplishments from the paused session
|
|
35
|
+
- What was delivered and committed
|
|
36
|
+
- Key milestones achieved
|
|
37
|
+
|
|
38
|
+
### Current Tasks
|
|
39
|
+
- **In Progress**: Tasks that were being worked on
|
|
40
|
+
- **Pending**: Tasks that were planned but not started
|
|
41
|
+
- **Completed**: Recently finished tasks for context
|
|
42
|
+
|
|
43
|
+
### Git Context
|
|
44
|
+
- **Branch**: Current branch name
|
|
45
|
+
- **Recent Commits**: Last 5-10 commits with SHAs and messages
|
|
46
|
+
- **Status**: Clean/dirty working directory
|
|
47
|
+
- **Changes Since Pause**: New commits made since session was saved
|
|
48
|
+
|
|
49
|
+
### Next Recommended Actions
|
|
50
|
+
- Priority-ordered list of next steps
|
|
51
|
+
- Estimated time for each task
|
|
52
|
+
- Status and blockers for pending work
|
|
53
|
+
|
|
54
|
+
## Session Storage Location
|
|
55
|
+
|
|
56
|
+
Sessions are automatically saved to:
|
|
57
|
+
```
|
|
58
|
+
<project-root>/.claude-mpm/sessions/session-YYYYMMDD-HHMMSS.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Legacy location** (backward compatible):
|
|
62
|
+
```
|
|
63
|
+
<project-root>/.claude-mpm/sessions/pause/session-YYYYMMDD-HHMMSS.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The system automatically checks both locations and uses the most recent session.
|
|
67
|
+
|
|
68
|
+
## Example Output
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
================================================================================
|
|
72
|
+
📋 PAUSED SESSION FOUND
|
|
73
|
+
================================================================================
|
|
74
|
+
|
|
75
|
+
Paused: 2 hours ago
|
|
76
|
+
|
|
77
|
+
Last working on: Week 2 Skills Integration - Content Preparation
|
|
78
|
+
|
|
79
|
+
Completed:
|
|
80
|
+
✓ Week 1: Complete infrastructure - 8,900 lines of production-ready code
|
|
81
|
+
✓ Week 1: Skills loading system with automatic progressive disclosure
|
|
82
|
+
✓ Week 2: 15 of 23 skills downloaded (65% complete)
|
|
83
|
+
✓ Week 2: 2 Tier 1 skills refactored to progressive disclosure
|
|
84
|
+
✓ Code quality: All CRITICAL and HIGH issues resolved
|
|
85
|
+
|
|
86
|
+
Next steps:
|
|
87
|
+
• Refactor Tier 2 skills (verification-before-completion, webapp-testing)
|
|
88
|
+
• Download remaining 8 skills from community repositories
|
|
89
|
+
• Refactor remaining 13 skills to progressive disclosure
|
|
90
|
+
• Generate license attributions for all bundled skills
|
|
91
|
+
|
|
92
|
+
Git changes since pause: 3 commits
|
|
93
|
+
|
|
94
|
+
Recent commits:
|
|
95
|
+
ac765731 - feat(skills): Week 2 progress - 15 skills downloaded (Bob Matsuoka)
|
|
96
|
+
205e532e - fix(skills): address CRITICAL and HIGH priority issues (Bob Matsuoka)
|
|
97
|
+
06a6d6a0 - feat: add automated pre-publish cleanup to release (Bob Matsuoka)
|
|
98
|
+
|
|
99
|
+
Context Usage: 67.6% (135,259/200,000 tokens)
|
|
100
|
+
Session Duration: 12 hours
|
|
101
|
+
|
|
102
|
+
================================================================================
|
|
103
|
+
Use this context to resume work, or start fresh if not relevant.
|
|
104
|
+
================================================================================
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Implementation
|
|
108
|
+
|
|
109
|
+
When PM receives `/mpm-resume`, it should:
|
|
110
|
+
|
|
111
|
+
1. **Check for Sessions**
|
|
112
|
+
```python
|
|
113
|
+
from claude_mpm.services.cli.session_resume_helper import SessionResumeHelper
|
|
114
|
+
|
|
115
|
+
helper = SessionResumeHelper()
|
|
116
|
+
if not helper.has_paused_sessions():
|
|
117
|
+
return "No paused sessions found"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
2. **Load Most Recent Session**
|
|
121
|
+
```python
|
|
122
|
+
session_data = helper.get_most_recent_session()
|
|
123
|
+
if not session_data:
|
|
124
|
+
return "Failed to load session data"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
3. **Format and Display Context**
|
|
128
|
+
```python
|
|
129
|
+
# Extract key information
|
|
130
|
+
conversation = session_data.get("conversation", {})
|
|
131
|
+
git_context = session_data.get("git_context", {})
|
|
132
|
+
context_usage = session_data.get("context_usage", {})
|
|
133
|
+
todos = session_data.get("todos", {})
|
|
134
|
+
|
|
135
|
+
# Display formatted output (see Example Output above)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
4. **Calculate Git Changes**
|
|
139
|
+
```python
|
|
140
|
+
# Get commits since pause
|
|
141
|
+
paused_at = session_data.get("paused_at")
|
|
142
|
+
new_commits = helper.get_git_changes_since_pause(paused_at, [])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
5. **Limit Output to ~40k Tokens**
|
|
146
|
+
- Session summary: ~2k tokens
|
|
147
|
+
- Accomplishments (first 10): ~3k tokens
|
|
148
|
+
- Next steps (first 10): ~3k tokens
|
|
149
|
+
- Git context: ~5k tokens
|
|
150
|
+
- Todos: ~2k tokens
|
|
151
|
+
- Recent commits (up to 10): ~5k tokens
|
|
152
|
+
- **Total**: ~20k tokens (well under 40k limit)
|
|
153
|
+
|
|
154
|
+
## Token Budget Management
|
|
155
|
+
|
|
156
|
+
**Context Budget**: 200,000 tokens total
|
|
157
|
+
**Resume Load**: ~20,000-40,000 tokens (10-20% of context)
|
|
158
|
+
|
|
159
|
+
This leaves 160,000+ tokens for actual work after loading session context.
|
|
160
|
+
|
|
161
|
+
## Session Data Format
|
|
162
|
+
|
|
163
|
+
Sessions are stored as JSON with this structure:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"session_id": "session-YYYYMMDD-HHMMSS",
|
|
168
|
+
"paused_at": "ISO-8601 timestamp",
|
|
169
|
+
"duration_hours": 12,
|
|
170
|
+
"context_usage": {
|
|
171
|
+
"tokens_used": 135259,
|
|
172
|
+
"tokens_total": 200000,
|
|
173
|
+
"percentage": 67.6
|
|
174
|
+
},
|
|
175
|
+
"conversation": {
|
|
176
|
+
"primary_task": "What user was working on",
|
|
177
|
+
"current_phase": "Current phase of work",
|
|
178
|
+
"summary": "Brief summary",
|
|
179
|
+
"accomplishments": ["list of completed items"],
|
|
180
|
+
"next_steps": [
|
|
181
|
+
{
|
|
182
|
+
"priority": 1,
|
|
183
|
+
"task": "Task description",
|
|
184
|
+
"estimated_hours": "8-12",
|
|
185
|
+
"status": "ready"
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"git_context": {
|
|
190
|
+
"branch": "main",
|
|
191
|
+
"recent_commits": [...],
|
|
192
|
+
"status": {...}
|
|
193
|
+
},
|
|
194
|
+
"todos": {
|
|
195
|
+
"active": [...],
|
|
196
|
+
"completed": [...]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## When to Use This Command
|
|
202
|
+
|
|
203
|
+
Use `/mpm-resume` when:
|
|
204
|
+
- **Starting a new session**: After closing and reopening Claude CLI
|
|
205
|
+
- **Context unclear**: You need to remember what you were working on
|
|
206
|
+
- **After a break**: Coming back after hours or days
|
|
207
|
+
- **Team handoff**: Another developer wants to understand current state
|
|
208
|
+
- **Lost context**: Accidentally closed CLI and need to recover
|
|
209
|
+
|
|
210
|
+
## Differences from Automatic Resume
|
|
211
|
+
|
|
212
|
+
| Feature | Automatic Resume (70% context) | /mpm-resume Command |
|
|
213
|
+
|---------|-------------------------------|---------------------|
|
|
214
|
+
| **Trigger** | Automatic at 70% context | Manual user command |
|
|
215
|
+
| **When** | PM startup (if session exists) | Anytime during session |
|
|
216
|
+
| **Creates Files** | No (reads existing) | No (reads existing) |
|
|
217
|
+
| **Session Source** | Same (`.claude-mpm/sessions/`) | Same (`.claude-mpm/sessions/`) |
|
|
218
|
+
| **Display Format** | Identical | Identical |
|
|
219
|
+
| **Token Usage** | ~20-40k tokens | ~20-40k tokens |
|
|
220
|
+
|
|
221
|
+
Both features use the **same underlying system** (`SessionResumeHelper`), just triggered differently.
|
|
222
|
+
|
|
223
|
+
## No Files Created
|
|
224
|
+
|
|
225
|
+
**IMPORTANT**: This command does NOT create any new files.
|
|
226
|
+
|
|
227
|
+
It ONLY reads from existing session files that were automatically created by the system at 70% context usage.
|
|
228
|
+
|
|
229
|
+
If you want to manually create a session save (for example, at 50% context before hitting 70%), use a different workflow or wait for automatic save at 70%.
|
|
230
|
+
|
|
231
|
+
## Related Features
|
|
232
|
+
|
|
233
|
+
- **Automatic Session Save**: System creates sessions at 70% context automatically
|
|
234
|
+
- **Automatic Session Resume**: PM startup hook displays sessions automatically
|
|
235
|
+
- `/mpm-init pause`: Manual session pause workflow (if available)
|
|
236
|
+
- `/mpm-init context`: Analyze git history for intelligent resumption
|
|
237
|
+
- `/mpm-status`: Check current MPM status
|
|
238
|
+
|
|
239
|
+
## Error Handling
|
|
240
|
+
|
|
241
|
+
### No Sessions Found
|
|
242
|
+
```
|
|
243
|
+
No paused sessions found in .claude-mpm/sessions/
|
|
244
|
+
|
|
245
|
+
To create a session save, continue working until context reaches 70% (140k tokens),
|
|
246
|
+
at which point the system will automatically save your session state.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Failed to Load Session
|
|
250
|
+
```
|
|
251
|
+
Paused session file found but failed to load.
|
|
252
|
+
|
|
253
|
+
File: .claude-mpm/sessions/session-20251107-152740.json
|
|
254
|
+
Error: Invalid JSON format
|
|
255
|
+
|
|
256
|
+
You may need to manually inspect or delete this file.
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Invalid Session Format
|
|
260
|
+
```
|
|
261
|
+
Session file loaded but missing required fields.
|
|
262
|
+
|
|
263
|
+
The session file may be corrupted or from an older version.
|
|
264
|
+
Consider running /mpm-doctor to check system health.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Benefits
|
|
268
|
+
|
|
269
|
+
- **Instant Context**: Get full context in seconds without reading git logs
|
|
270
|
+
- **No Mental Load**: Don't need to remember what you were doing
|
|
271
|
+
- **Zero File Creation**: Pure read operation, no side effects
|
|
272
|
+
- **Team Collaboration**: Share context with team members
|
|
273
|
+
- **Graceful Recovery**: Recover from accidental CLI closures
|
|
274
|
+
- **Smart Filtering**: Only shows relevant information (~20k tokens)
|
|
275
|
+
- **Git Awareness**: See what changed since pause
|
|
276
|
+
|
|
277
|
+
## Best Practices
|
|
278
|
+
|
|
279
|
+
1. **Use Early**: Run `/mpm-resume` at start of each session if sessions exist
|
|
280
|
+
2. **Check Git Changes**: Pay attention to commits made since pause
|
|
281
|
+
3. **Validate Context**: Verify the session is still relevant before continuing
|
|
282
|
+
4. **Clear Old Sessions**: Periodically clean up old session files
|
|
283
|
+
5. **Combine with Git**: Use alongside `git log` for complete picture
|
|
284
|
+
|
|
285
|
+
## Technical Details
|
|
286
|
+
|
|
287
|
+
**Implementation Files:**
|
|
288
|
+
- Service: `/src/claude_mpm/services/cli/session_resume_helper.py`
|
|
289
|
+
- Hook: `/src/claude_mpm/hooks/session_resume_hook.py`
|
|
290
|
+
- Command: This file
|
|
291
|
+
|
|
292
|
+
**Key Functions:**
|
|
293
|
+
- `SessionResumeHelper.has_paused_sessions()` - Check if sessions exist
|
|
294
|
+
- `SessionResumeHelper.get_most_recent_session()` - Load latest session
|
|
295
|
+
- `SessionResumeHelper.format_resume_prompt()` - Format display output
|
|
296
|
+
- `SessionResumeHelper.get_git_changes_since_pause()` - Calculate git delta
|
|
297
|
+
|
|
298
|
+
**Token Estimation:**
|
|
299
|
+
- Session metadata: 1-2k tokens
|
|
300
|
+
- Accomplishments (10 items): 2-4k tokens
|
|
301
|
+
- Next steps (10 items): 2-4k tokens
|
|
302
|
+
- Git commits (10 commits): 3-5k tokens
|
|
303
|
+
- Todos (20 items): 2-3k tokens
|
|
304
|
+
- Formatting/structure: 1-2k tokens
|
|
305
|
+
- **Total**: 11-20k tokens (safely under 40k limit)
|
|
306
|
+
|
|
307
|
+
## Troubleshooting
|
|
308
|
+
|
|
309
|
+
### Session Not Found
|
|
310
|
+
**Problem**: Command reports no sessions exist
|
|
311
|
+
|
|
312
|
+
**Solutions:**
|
|
313
|
+
1. Check directory exists: `ls .claude-mpm/sessions/`
|
|
314
|
+
2. Check for legacy location: `ls .claude-mpm/sessions/pause/`
|
|
315
|
+
3. Verify session files: `ls .claude-mpm/sessions/session-*.json`
|
|
316
|
+
4. Session auto-saves at 70% context - may not exist yet
|
|
317
|
+
|
|
318
|
+
### Git Changes Not Showing
|
|
319
|
+
**Problem**: "No git changes since pause" but commits were made
|
|
320
|
+
|
|
321
|
+
**Solutions:**
|
|
322
|
+
1. Verify git repository: `git status`
|
|
323
|
+
2. Check commit timestamps: `git log --since="<pause_time>"`
|
|
324
|
+
3. Ensure session timestamp is correct
|
|
325
|
+
4. Check timezone issues
|
|
326
|
+
|
|
327
|
+
### Display Too Large
|
|
328
|
+
**Problem**: Session context exceeds token budget
|
|
329
|
+
|
|
330
|
+
**Solutions:**
|
|
331
|
+
1. System automatically limits to first 10 items
|
|
332
|
+
2. Full session details available in JSON file
|
|
333
|
+
3. Use `cat .claude-mpm/sessions/session-*.json` for complete data
|
|
334
|
+
4. Summary is optimized for 20k tokens max
|
|
335
|
+
|
|
336
|
+
## Example Session Resume Workflow
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# User starts new Claude CLI session
|
|
340
|
+
$ claude-code
|
|
341
|
+
|
|
342
|
+
# PM automatically checks for sessions on startup
|
|
343
|
+
# (Automatic resume hook displays session if found)
|
|
344
|
+
|
|
345
|
+
# OR user manually requests resume
|
|
346
|
+
User: "/mpm-resume"
|
|
347
|
+
|
|
348
|
+
# PM loads and displays session context
|
|
349
|
+
PM: [Displays formatted session context as shown in Example Output]
|
|
350
|
+
|
|
351
|
+
# User decides to continue work
|
|
352
|
+
User: "Let's continue with the next priority task"
|
|
353
|
+
|
|
354
|
+
# PM uses session context to understand what to do next
|
|
355
|
+
PM: "Based on the paused session, the next priority is to refactor
|
|
356
|
+
the verification-before-completion skill. I'll delegate this to Engineer..."
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Version History
|
|
360
|
+
|
|
361
|
+
- **v4.21.1**: Fixed command behavior - now loads sessions instead of creating files
|
|
362
|
+
- **v4.21.0**: Added `/mpm-resume` command (incorrect behavior - created files)
|
|
363
|
+
- **v4.19.0**: Automatic session resume infrastructure implemented
|
|
364
|
+
- **v4.18.x**: Session pause/resume foundation
|
|
365
|
+
|
|
366
|
+
## Support
|
|
367
|
+
|
|
368
|
+
For issues or questions:
|
|
369
|
+
- Run `/mpm-doctor` to check system health
|
|
370
|
+
- Check logs: `.claude-mpm/logs/claude-mpm.log`
|
|
371
|
+
- Verify session files: `ls -la .claude-mpm/sessions/`
|
|
372
|
+
- Review documentation: `/docs/features/session-auto-resume.md`
|
claude_mpm/commands/mpm.md
CHANGED
|
@@ -8,6 +8,7 @@ Available MPM commands:
|
|
|
8
8
|
- /mpm-help - Show command help
|
|
9
9
|
- /mpm-status - Show MPM status
|
|
10
10
|
- /mpm-config - Manage configuration
|
|
11
|
+
- /mpm-resume - Create session resume files
|
|
11
12
|
- /mpm-version - Display version information for project, agents, and skills
|
|
12
13
|
|
|
13
14
|
Claude MPM extends Claude Code with:
|
claude_mpm/services/core/base.py
CHANGED
|
@@ -9,6 +9,7 @@ and lifecycle management.
|
|
|
9
9
|
Part of TSK-0046: Service Layer Architecture Reorganization
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import threading
|
|
12
13
|
from abc import ABC, abstractmethod
|
|
13
14
|
from typing import Any, Dict, Optional
|
|
14
15
|
|
|
@@ -221,29 +222,43 @@ class SingletonService(SyncBaseService):
|
|
|
221
222
|
"""
|
|
222
223
|
Base class for singleton services.
|
|
223
224
|
|
|
224
|
-
Ensures only one instance of the service exists.
|
|
225
|
+
Ensures only one instance of the service exists with thread-safe initialization.
|
|
226
|
+
Uses double-checked locking pattern to prevent race conditions.
|
|
225
227
|
"""
|
|
226
228
|
|
|
227
229
|
_instances: Dict[type, "SingletonService"] = {}
|
|
230
|
+
_lock = threading.Lock()
|
|
228
231
|
|
|
229
232
|
def __new__(cls, *args, **kwargs):
|
|
230
|
-
"""Ensure only one instance exists."""
|
|
233
|
+
"""Ensure only one instance exists with thread-safe initialization."""
|
|
234
|
+
# Fast path - check without lock
|
|
231
235
|
if cls not in cls._instances:
|
|
232
|
-
|
|
236
|
+
# Slow path - acquire lock and double-check
|
|
237
|
+
with cls._lock:
|
|
238
|
+
if cls not in cls._instances:
|
|
239
|
+
cls._instances[cls] = super().__new__(cls)
|
|
233
240
|
return cls._instances[cls]
|
|
234
241
|
|
|
235
242
|
@classmethod
|
|
236
243
|
def get_instance(cls) -> "SingletonService":
|
|
237
|
-
"""Get the singleton instance."""
|
|
244
|
+
"""Get the singleton instance with thread-safe initialization."""
|
|
245
|
+
# Fast path - check without lock
|
|
238
246
|
if cls not in cls._instances:
|
|
239
|
-
|
|
247
|
+
# Slow path - acquire lock and double-check
|
|
248
|
+
with cls._lock:
|
|
249
|
+
if cls not in cls._instances:
|
|
250
|
+
cls._instances[cls] = cls()
|
|
240
251
|
return cls._instances[cls]
|
|
241
252
|
|
|
242
253
|
@classmethod
|
|
243
254
|
def clear_instance(cls) -> None:
|
|
244
|
-
"""Clear the singleton instance (useful for testing).
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
"""Clear the singleton instance (useful for testing).
|
|
256
|
+
|
|
257
|
+
Thread-safe implementation ensures proper cleanup.
|
|
258
|
+
"""
|
|
259
|
+
with cls._lock:
|
|
260
|
+
if cls in cls._instances:
|
|
261
|
+
instance = cls._instances[cls]
|
|
262
|
+
if hasattr(instance, "shutdown") and not instance.is_shutdown:
|
|
263
|
+
instance.shutdown()
|
|
264
|
+
del cls._instances[cls]
|
|
@@ -9,6 +9,7 @@ WHY separate relay component:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
|
+
import threading
|
|
12
13
|
import time
|
|
13
14
|
from datetime import datetime, timezone
|
|
14
15
|
from typing import Any, Dict, Optional
|
|
@@ -271,11 +272,15 @@ class SocketIORelay:
|
|
|
271
272
|
|
|
272
273
|
# Global relay instance
|
|
273
274
|
_relay_instance: Optional[SocketIORelay] = None
|
|
275
|
+
_relay_lock = threading.Lock()
|
|
274
276
|
|
|
275
277
|
|
|
276
278
|
def get_relay(port: Optional[int] = None) -> SocketIORelay:
|
|
277
279
|
"""Get or create the global SocketIO relay instance.
|
|
278
280
|
|
|
281
|
+
Thread-safe implementation using double-checked locking pattern to
|
|
282
|
+
prevent race conditions during concurrent initialization.
|
|
283
|
+
|
|
279
284
|
Args:
|
|
280
285
|
port: Optional port number
|
|
281
286
|
|
|
@@ -283,9 +288,16 @@ def get_relay(port: Optional[int] = None) -> SocketIORelay:
|
|
|
283
288
|
SocketIORelay: The relay instance
|
|
284
289
|
"""
|
|
285
290
|
global _relay_instance
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
|
|
292
|
+
# Fast path - check without lock
|
|
293
|
+
if _relay_instance is not None:
|
|
294
|
+
return _relay_instance
|
|
295
|
+
|
|
296
|
+
# Slow path - acquire lock and double-check
|
|
297
|
+
with _relay_lock:
|
|
298
|
+
if _relay_instance is None:
|
|
299
|
+
_relay_instance = SocketIORelay(port)
|
|
300
|
+
return _relay_instance
|
|
289
301
|
|
|
290
302
|
|
|
291
303
|
def start_relay(port: Optional[int] = None) -> SocketIORelay:
|
|
@@ -303,8 +315,12 @@ def start_relay(port: Optional[int] = None) -> SocketIORelay:
|
|
|
303
315
|
|
|
304
316
|
|
|
305
317
|
def stop_relay() -> None:
|
|
306
|
-
"""Stop the global SocketIO relay.
|
|
318
|
+
"""Stop the global SocketIO relay.
|
|
319
|
+
|
|
320
|
+
Thread-safe implementation ensures proper cleanup.
|
|
321
|
+
"""
|
|
307
322
|
global _relay_instance
|
|
308
|
-
|
|
309
|
-
_relay_instance
|
|
310
|
-
|
|
323
|
+
with _relay_lock:
|
|
324
|
+
if _relay_instance:
|
|
325
|
+
_relay_instance.stop()
|
|
326
|
+
_relay_instance = None
|
|
@@ -29,6 +29,7 @@ Example flow:
|
|
|
29
29
|
|
|
30
30
|
import logging
|
|
31
31
|
import re
|
|
32
|
+
import threading
|
|
32
33
|
from dataclasses import dataclass, field
|
|
33
34
|
from datetime import datetime, timezone
|
|
34
35
|
from typing import Dict, List, Optional, Tuple
|
|
@@ -536,6 +537,7 @@ class FailureTracker:
|
|
|
536
537
|
|
|
537
538
|
# Singleton instance for session-level tracking
|
|
538
539
|
_tracker_instance: Optional[FailureTracker] = None
|
|
540
|
+
_tracker_lock = threading.Lock()
|
|
539
541
|
|
|
540
542
|
|
|
541
543
|
def get_failure_tracker() -> FailureTracker:
|
|
@@ -544,13 +546,23 @@ def get_failure_tracker() -> FailureTracker:
|
|
|
544
546
|
WHY: Session-level tracking requires a singleton to maintain state
|
|
545
547
|
across multiple hook invocations during the same session.
|
|
546
548
|
|
|
549
|
+
Thread-safe implementation using double-checked locking pattern to
|
|
550
|
+
prevent race conditions during concurrent initialization.
|
|
551
|
+
|
|
547
552
|
Returns:
|
|
548
553
|
The FailureTracker singleton instance
|
|
549
554
|
"""
|
|
550
555
|
global _tracker_instance
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
556
|
+
|
|
557
|
+
# Fast path - check without lock
|
|
558
|
+
if _tracker_instance is not None:
|
|
559
|
+
return _tracker_instance
|
|
560
|
+
|
|
561
|
+
# Slow path - acquire lock and double-check
|
|
562
|
+
with _tracker_lock:
|
|
563
|
+
if _tracker_instance is None:
|
|
564
|
+
_tracker_instance = FailureTracker()
|
|
565
|
+
return _tracker_instance
|
|
554
566
|
|
|
555
567
|
|
|
556
568
|
def reset_failure_tracker() -> None:
|
|
@@ -558,6 +570,9 @@ def reset_failure_tracker() -> None:
|
|
|
558
570
|
|
|
559
571
|
WHY: Tests need to reset state between runs. Also useful for
|
|
560
572
|
explicitly starting a new tracking session.
|
|
573
|
+
|
|
574
|
+
Thread-safe implementation ensures proper cleanup.
|
|
561
575
|
"""
|
|
562
576
|
global _tracker_instance
|
|
563
|
-
|
|
577
|
+
with _tracker_lock:
|
|
578
|
+
_tracker_instance = None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Code Tree Analyzer
|
|
4
|
+
==================
|
|
5
|
+
|
|
6
|
+
Analyzes source code using AST to extract structure and metrics,
|
|
7
|
+
supporting multiple languages and emitting incremental events for visualization.
|
|
8
|
+
|
|
9
|
+
This module has been refactored from a single 1,825-line file into focused,
|
|
10
|
+
maintainable components while preserving complete backward compatibility.
|
|
11
|
+
|
|
12
|
+
Public API:
|
|
13
|
+
-----------
|
|
14
|
+
- CodeTreeAnalyzer: Main analyzer class
|
|
15
|
+
- CodeNode: Data structure for code nodes
|
|
16
|
+
- GitignoreManager: Gitignore pattern matching
|
|
17
|
+
- PythonAnalyzer: Python AST analysis
|
|
18
|
+
- MultiLanguageAnalyzer: Multi-language support
|
|
19
|
+
|
|
20
|
+
Example Usage:
|
|
21
|
+
--------------
|
|
22
|
+
from claude_mpm.tools.code_tree_analyzer import CodeTreeAnalyzer
|
|
23
|
+
|
|
24
|
+
analyzer = CodeTreeAnalyzer(emit_events=True)
|
|
25
|
+
result = analyzer.analyze_directory(Path("/path/to/code"))
|
|
26
|
+
print(result['stats'])
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# Public API - Backward compatible imports
|
|
30
|
+
from .core import CodeTreeAnalyzer
|
|
31
|
+
from .gitignore import GitignoreManager
|
|
32
|
+
from .models import CodeNode
|
|
33
|
+
from .multilang_analyzer import MultiLanguageAnalyzer
|
|
34
|
+
from .python_analyzer import PythonAnalyzer
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"CodeNode",
|
|
38
|
+
"CodeTreeAnalyzer",
|
|
39
|
+
"GitignoreManager",
|
|
40
|
+
"MultiLanguageAnalyzer",
|
|
41
|
+
"PythonAnalyzer",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Version info
|
|
45
|
+
__version__ = "2.0.0" # Major refactoring while maintaining API compatibility
|