claude-mpm 4.2.39__py3-none-any.whl → 4.2.42__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/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +114 -1
- claude_mpm/agents/BASE_OPS.md +156 -1
- claude_mpm/agents/INSTRUCTIONS.md +120 -11
- claude_mpm/agents/WORKFLOW.md +160 -10
- claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
- claude_mpm/agents/templates/react_engineer.json +217 -0
- claude_mpm/agents/templates/web_qa.json +40 -4
- claude_mpm/cli/__init__.py +3 -5
- claude_mpm/commands/mpm-browser-monitor.md +370 -0
- claude_mpm/commands/mpm-monitor.md +177 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
- claude_mpm/dashboard/static/css/dashboard.css +2 -0
- claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
- claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
- claude_mpm/dashboard/templates/index.html +62 -99
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/monitor/daemon.py +69 -36
- claude_mpm/services/monitor/daemon_manager.py +186 -29
- claude_mpm/services/monitor/handlers/browser.py +451 -0
- claude_mpm/services/monitor/server.py +272 -5
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
- claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
- claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
- claude_mpm/agents/templates/test-non-mpm.json +0 -20
- claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,443 @@
|
|
1
|
+
/**
|
2
|
+
* File Change Tracker Module
|
3
|
+
*
|
4
|
+
* Tracks all file operations (Edit, Write, Read, MultiEdit) from event history
|
5
|
+
* and builds a tree structure grouped by working directory.
|
6
|
+
* Supports session-based filtering and stores complete edit history with timestamps.
|
7
|
+
*
|
8
|
+
* Architecture:
|
9
|
+
* - Maintains a hierarchical structure of file changes
|
10
|
+
* - Tracks file lifecycle (create, edit, delete)
|
11
|
+
* - Provides session-aware filtering
|
12
|
+
* - Stores complete history for diff generation
|
13
|
+
*/
|
14
|
+
class FileChangeTracker {
|
15
|
+
constructor() {
|
16
|
+
// Main data structures
|
17
|
+
this.fileChanges = new Map(); // Map<filePath, FileChangeData>
|
18
|
+
this.sessionData = new Map(); // Map<sessionId, Set<filePath>>
|
19
|
+
this.workingDirectories = new Map(); // Map<workingDir, Set<filePath>>
|
20
|
+
|
21
|
+
// Current state
|
22
|
+
this.currentSessionId = null;
|
23
|
+
this.events = [];
|
24
|
+
|
25
|
+
// File operation types we track
|
26
|
+
this.FILE_OPERATIONS = {
|
27
|
+
READ: 'Read',
|
28
|
+
WRITE: 'Write',
|
29
|
+
EDIT: 'Edit',
|
30
|
+
MULTI_EDIT: 'MultiEdit',
|
31
|
+
DELETE: 'Delete',
|
32
|
+
CREATE: 'Create'
|
33
|
+
};
|
34
|
+
|
35
|
+
// Initialize
|
36
|
+
this.initialized = false;
|
37
|
+
console.log('FileChangeTracker initialized');
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Initialize the tracker
|
42
|
+
*/
|
43
|
+
initialize() {
|
44
|
+
if (this.initialized) return;
|
45
|
+
this.initialized = true;
|
46
|
+
console.log('FileChangeTracker ready');
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Process an event and extract file operations
|
51
|
+
* @param {Object} event - Event data
|
52
|
+
*/
|
53
|
+
processEvent(event) {
|
54
|
+
// Check if this is a file-related tool event
|
55
|
+
if (!this.isFileOperation(event)) return;
|
56
|
+
|
57
|
+
const fileOp = this.extractFileOperation(event);
|
58
|
+
if (!fileOp) return;
|
59
|
+
|
60
|
+
// Add to our tracking structures
|
61
|
+
this.addFileOperation(fileOp);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Check if an event is a file operation
|
66
|
+
* @param {Object} event - Event to check
|
67
|
+
* @returns {boolean}
|
68
|
+
*/
|
69
|
+
isFileOperation(event) {
|
70
|
+
// Check for tool events with file operations
|
71
|
+
if (event.type === 'tool' || event.subtype === 'pre_tool' || event.subtype === 'post_tool') {
|
72
|
+
const toolName = event.tool_name || (event.data && event.data.tool_name);
|
73
|
+
return Object.values(this.FILE_OPERATIONS).includes(toolName);
|
74
|
+
}
|
75
|
+
|
76
|
+
// Check for hook events with file operations
|
77
|
+
if (event.type === 'hook' && event.data) {
|
78
|
+
const toolName = event.data.tool_name;
|
79
|
+
return Object.values(this.FILE_OPERATIONS).includes(toolName);
|
80
|
+
}
|
81
|
+
|
82
|
+
return false;
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Extract file operation details from an event
|
87
|
+
* @param {Object} event - Event to process
|
88
|
+
* @returns {Object|null} File operation data
|
89
|
+
*/
|
90
|
+
extractFileOperation(event) {
|
91
|
+
const toolName = event.tool_name || (event.data && event.data.tool_name);
|
92
|
+
const params = event.tool_parameters || (event.data && event.data.tool_parameters) || {};
|
93
|
+
const result = event.tool_result || (event.data && event.data.tool_result);
|
94
|
+
|
95
|
+
// Extract file path based on tool type
|
96
|
+
let filePath = null;
|
97
|
+
let operation = toolName;
|
98
|
+
let content = null;
|
99
|
+
let oldContent = null;
|
100
|
+
|
101
|
+
switch (toolName) {
|
102
|
+
case this.FILE_OPERATIONS.READ:
|
103
|
+
filePath = params.file_path;
|
104
|
+
content = result && result.content;
|
105
|
+
break;
|
106
|
+
|
107
|
+
case this.FILE_OPERATIONS.WRITE:
|
108
|
+
filePath = params.file_path;
|
109
|
+
content = params.content;
|
110
|
+
break;
|
111
|
+
|
112
|
+
case this.FILE_OPERATIONS.EDIT:
|
113
|
+
filePath = params.file_path;
|
114
|
+
oldContent = params.old_string;
|
115
|
+
content = params.new_string;
|
116
|
+
break;
|
117
|
+
|
118
|
+
case this.FILE_OPERATIONS.MULTI_EDIT:
|
119
|
+
filePath = params.file_path;
|
120
|
+
// For MultiEdit, we track each edit
|
121
|
+
const edits = params.edits || [];
|
122
|
+
return {
|
123
|
+
filePath,
|
124
|
+
operation,
|
125
|
+
timestamp: event.timestamp,
|
126
|
+
sessionId: event.session_id || 'unknown',
|
127
|
+
workingDirectory: this.extractWorkingDirectory(event),
|
128
|
+
edits: edits.map(edit => ({
|
129
|
+
oldContent: edit.old_string,
|
130
|
+
newContent: edit.new_string,
|
131
|
+
replaceAll: edit.replace_all || false
|
132
|
+
})),
|
133
|
+
isMultiEdit: true,
|
134
|
+
success: event.subtype === 'post_tool' && result && result.success !== false
|
135
|
+
};
|
136
|
+
|
137
|
+
default:
|
138
|
+
return null;
|
139
|
+
}
|
140
|
+
|
141
|
+
if (!filePath) return null;
|
142
|
+
|
143
|
+
return {
|
144
|
+
filePath,
|
145
|
+
operation,
|
146
|
+
timestamp: event.timestamp,
|
147
|
+
sessionId: event.session_id || 'unknown',
|
148
|
+
workingDirectory: this.extractWorkingDirectory(event),
|
149
|
+
content,
|
150
|
+
oldContent,
|
151
|
+
isEdit: operation === this.FILE_OPERATIONS.EDIT,
|
152
|
+
isWrite: operation === this.FILE_OPERATIONS.WRITE,
|
153
|
+
isRead: operation === this.FILE_OPERATIONS.READ,
|
154
|
+
success: event.subtype === 'post_tool' && result && result.success !== false
|
155
|
+
};
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* Extract working directory from event
|
160
|
+
* @param {Object} event - Event data
|
161
|
+
* @returns {string} Working directory path
|
162
|
+
*/
|
163
|
+
extractWorkingDirectory(event) {
|
164
|
+
// Try to extract from event data
|
165
|
+
if (event.data && event.data.working_directory) {
|
166
|
+
return event.data.working_directory;
|
167
|
+
}
|
168
|
+
|
169
|
+
// Try to extract from context
|
170
|
+
if (event.context && event.context.working_directory) {
|
171
|
+
return event.context.working_directory;
|
172
|
+
}
|
173
|
+
|
174
|
+
// Try to extract from file path (get parent directory)
|
175
|
+
const filePath = event.tool_parameters?.file_path ||
|
176
|
+
(event.data && event.data.tool_parameters?.file_path);
|
177
|
+
if (filePath) {
|
178
|
+
const parts = filePath.split('/');
|
179
|
+
parts.pop(); // Remove filename
|
180
|
+
return parts.join('/') || '/';
|
181
|
+
}
|
182
|
+
|
183
|
+
return 'unknown';
|
184
|
+
}
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Add a file operation to our tracking structures
|
188
|
+
* @param {Object} fileOp - File operation data
|
189
|
+
*/
|
190
|
+
addFileOperation(fileOp) {
|
191
|
+
const { filePath, sessionId, workingDirectory } = fileOp;
|
192
|
+
|
193
|
+
// Initialize file change data if needed
|
194
|
+
if (!this.fileChanges.has(filePath)) {
|
195
|
+
this.fileChanges.set(filePath, {
|
196
|
+
path: filePath,
|
197
|
+
fileName: this.getFileName(filePath),
|
198
|
+
workingDirectory,
|
199
|
+
sessions: new Set(),
|
200
|
+
operations: [],
|
201
|
+
firstSeen: fileOp.timestamp,
|
202
|
+
lastModified: fileOp.timestamp,
|
203
|
+
currentContent: null,
|
204
|
+
initialContent: null,
|
205
|
+
totalEdits: 0,
|
206
|
+
totalReads: 0,
|
207
|
+
totalWrites: 0
|
208
|
+
});
|
209
|
+
}
|
210
|
+
|
211
|
+
const fileData = this.fileChanges.get(filePath);
|
212
|
+
|
213
|
+
// Update session tracking
|
214
|
+
fileData.sessions.add(sessionId);
|
215
|
+
if (!this.sessionData.has(sessionId)) {
|
216
|
+
this.sessionData.set(sessionId, new Set());
|
217
|
+
}
|
218
|
+
this.sessionData.get(sessionId).add(filePath);
|
219
|
+
|
220
|
+
// Update working directory tracking
|
221
|
+
if (!this.workingDirectories.has(workingDirectory)) {
|
222
|
+
this.workingDirectories.set(workingDirectory, new Set());
|
223
|
+
}
|
224
|
+
this.workingDirectories.get(workingDirectory).add(filePath);
|
225
|
+
|
226
|
+
// Add operation to history
|
227
|
+
fileData.operations.push(fileOp);
|
228
|
+
fileData.lastModified = fileOp.timestamp;
|
229
|
+
|
230
|
+
// Update content tracking
|
231
|
+
if (fileOp.isRead && fileOp.content && !fileData.initialContent) {
|
232
|
+
fileData.initialContent = fileOp.content;
|
233
|
+
fileData.currentContent = fileOp.content;
|
234
|
+
fileData.totalReads++;
|
235
|
+
} else if (fileOp.isWrite) {
|
236
|
+
if (!fileData.initialContent) {
|
237
|
+
fileData.initialContent = '';
|
238
|
+
}
|
239
|
+
fileData.currentContent = fileOp.content;
|
240
|
+
fileData.totalWrites++;
|
241
|
+
} else if (fileOp.isEdit) {
|
242
|
+
// Apply edit to current content
|
243
|
+
if (fileData.currentContent && fileOp.oldContent) {
|
244
|
+
fileData.currentContent = fileData.currentContent.replace(
|
245
|
+
fileOp.oldContent,
|
246
|
+
fileOp.content || ''
|
247
|
+
);
|
248
|
+
}
|
249
|
+
fileData.totalEdits++;
|
250
|
+
} else if (fileOp.isMultiEdit && fileOp.edits) {
|
251
|
+
// Apply multiple edits
|
252
|
+
let content = fileData.currentContent || '';
|
253
|
+
for (const edit of fileOp.edits) {
|
254
|
+
if (edit.replaceAll) {
|
255
|
+
content = content.replaceAll(edit.oldContent, edit.newContent);
|
256
|
+
} else {
|
257
|
+
content = content.replace(edit.oldContent, edit.newContent);
|
258
|
+
}
|
259
|
+
}
|
260
|
+
fileData.currentContent = content;
|
261
|
+
fileData.totalEdits += fileOp.edits.length;
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
/**
|
266
|
+
* Get file name from path
|
267
|
+
* @param {string} filePath - Full file path
|
268
|
+
* @returns {string} File name
|
269
|
+
*/
|
270
|
+
getFileName(filePath) {
|
271
|
+
const parts = filePath.split('/');
|
272
|
+
return parts[parts.length - 1] || filePath;
|
273
|
+
}
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Update with new events
|
277
|
+
* @param {Array} events - Array of events
|
278
|
+
*/
|
279
|
+
updateEvents(events) {
|
280
|
+
// Clear and rebuild
|
281
|
+
this.clear();
|
282
|
+
this.events = events;
|
283
|
+
|
284
|
+
// Process all events
|
285
|
+
for (const event of events) {
|
286
|
+
this.processEvent(event);
|
287
|
+
}
|
288
|
+
|
289
|
+
console.log(`FileChangeTracker updated: ${this.fileChanges.size} files tracked`);
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* Get files for current session
|
294
|
+
* @param {string} sessionId - Session ID to filter by
|
295
|
+
* @returns {Array} Array of file change data
|
296
|
+
*/
|
297
|
+
getFilesForSession(sessionId) {
|
298
|
+
if (!sessionId) {
|
299
|
+
return Array.from(this.fileChanges.values());
|
300
|
+
}
|
301
|
+
|
302
|
+
const sessionFiles = this.sessionData.get(sessionId);
|
303
|
+
if (!sessionFiles) return [];
|
304
|
+
|
305
|
+
return Array.from(sessionFiles).map(filePath =>
|
306
|
+
this.fileChanges.get(filePath)
|
307
|
+
).filter(Boolean);
|
308
|
+
}
|
309
|
+
|
310
|
+
/**
|
311
|
+
* Get file tree structure grouped by working directory
|
312
|
+
* @param {string} sessionId - Optional session filter
|
313
|
+
* @returns {Object} Tree structure
|
314
|
+
*/
|
315
|
+
getFileTree(sessionId = null) {
|
316
|
+
const files = this.getFilesForSession(sessionId);
|
317
|
+
const tree = {};
|
318
|
+
|
319
|
+
for (const fileData of files) {
|
320
|
+
const wd = fileData.workingDirectory || 'unknown';
|
321
|
+
if (!tree[wd]) {
|
322
|
+
tree[wd] = {
|
323
|
+
path: wd,
|
324
|
+
name: this.getDirectoryName(wd),
|
325
|
+
files: [],
|
326
|
+
totalOperations: 0,
|
327
|
+
totalEdits: 0,
|
328
|
+
totalReads: 0,
|
329
|
+
totalWrites: 0
|
330
|
+
};
|
331
|
+
}
|
332
|
+
|
333
|
+
tree[wd].files.push(fileData);
|
334
|
+
tree[wd].totalOperations += fileData.operations.length;
|
335
|
+
tree[wd].totalEdits += fileData.totalEdits;
|
336
|
+
tree[wd].totalReads += fileData.totalReads;
|
337
|
+
tree[wd].totalWrites += fileData.totalWrites;
|
338
|
+
}
|
339
|
+
|
340
|
+
// Sort files within each directory
|
341
|
+
Object.values(tree).forEach(dir => {
|
342
|
+
dir.files.sort((a, b) =>
|
343
|
+
new Date(b.lastModified) - new Date(a.lastModified)
|
344
|
+
);
|
345
|
+
});
|
346
|
+
|
347
|
+
return tree;
|
348
|
+
}
|
349
|
+
|
350
|
+
/**
|
351
|
+
* Get directory name from path
|
352
|
+
* @param {string} dirPath - Directory path
|
353
|
+
* @returns {string} Directory name
|
354
|
+
*/
|
355
|
+
getDirectoryName(dirPath) {
|
356
|
+
if (dirPath === 'unknown') return 'Unknown Directory';
|
357
|
+
const parts = dirPath.split('/');
|
358
|
+
return parts[parts.length - 1] || dirPath;
|
359
|
+
}
|
360
|
+
|
361
|
+
/**
|
362
|
+
* Get file change details
|
363
|
+
* @param {string} filePath - File path
|
364
|
+
* @returns {Object|null} File change data
|
365
|
+
*/
|
366
|
+
getFileDetails(filePath) {
|
367
|
+
return this.fileChanges.get(filePath) || null;
|
368
|
+
}
|
369
|
+
|
370
|
+
/**
|
371
|
+
* Get operations for a file
|
372
|
+
* @param {string} filePath - File path
|
373
|
+
* @param {string} sessionId - Optional session filter
|
374
|
+
* @returns {Array} Array of operations
|
375
|
+
*/
|
376
|
+
getFileOperations(filePath, sessionId = null) {
|
377
|
+
const fileData = this.fileChanges.get(filePath);
|
378
|
+
if (!fileData) return [];
|
379
|
+
|
380
|
+
let operations = fileData.operations;
|
381
|
+
if (sessionId) {
|
382
|
+
operations = operations.filter(op => op.sessionId === sessionId);
|
383
|
+
}
|
384
|
+
|
385
|
+
return operations.sort((a, b) =>
|
386
|
+
new Date(a.timestamp) - new Date(b.timestamp)
|
387
|
+
);
|
388
|
+
}
|
389
|
+
|
390
|
+
/**
|
391
|
+
* Get diff data for a file
|
392
|
+
* @param {string} filePath - File path
|
393
|
+
* @returns {Object} Diff data with initial and current content
|
394
|
+
*/
|
395
|
+
getFileDiff(filePath) {
|
396
|
+
const fileData = this.fileChanges.get(filePath);
|
397
|
+
if (!fileData) return null;
|
398
|
+
|
399
|
+
return {
|
400
|
+
filePath,
|
401
|
+
fileName: fileData.fileName,
|
402
|
+
initialContent: fileData.initialContent || '',
|
403
|
+
currentContent: fileData.currentContent || '',
|
404
|
+
hasChanges: fileData.initialContent !== fileData.currentContent,
|
405
|
+
operations: fileData.operations,
|
406
|
+
totalEdits: fileData.totalEdits,
|
407
|
+
totalWrites: fileData.totalWrites
|
408
|
+
};
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Clear all tracked data
|
413
|
+
*/
|
414
|
+
clear() {
|
415
|
+
this.fileChanges.clear();
|
416
|
+
this.sessionData.clear();
|
417
|
+
this.workingDirectories.clear();
|
418
|
+
this.events = [];
|
419
|
+
}
|
420
|
+
|
421
|
+
/**
|
422
|
+
* Get statistics
|
423
|
+
* @returns {Object} Statistics
|
424
|
+
*/
|
425
|
+
getStatistics() {
|
426
|
+
return {
|
427
|
+
totalFiles: this.fileChanges.size,
|
428
|
+
totalSessions: this.sessionData.size,
|
429
|
+
totalDirectories: this.workingDirectories.size,
|
430
|
+
filesWithEdits: Array.from(this.fileChanges.values())
|
431
|
+
.filter(f => f.totalEdits > 0).length,
|
432
|
+
filesWithWrites: Array.from(this.fileChanges.values())
|
433
|
+
.filter(f => f.totalWrites > 0).length,
|
434
|
+
readOnlyFiles: Array.from(this.fileChanges.values())
|
435
|
+
.filter(f => f.totalEdits === 0 && f.totalWrites === 0).length
|
436
|
+
};
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
440
|
+
// Export for use in other modules
|
441
|
+
if (typeof window !== 'undefined') {
|
442
|
+
window.FileChangeTracker = FileChangeTracker;
|
443
|
+
}
|