claude-mpm 3.7.4__py3-none-any.whl → 3.8.1__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.
Files changed (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -78
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/schema/agent_schema.json +1 -1
  7. claude_mpm/agents/templates/code_analyzer.json +26 -11
  8. claude_mpm/agents/templates/data_engineer.json +4 -7
  9. claude_mpm/agents/templates/documentation.json +2 -2
  10. claude_mpm/agents/templates/engineer.json +2 -2
  11. claude_mpm/agents/templates/ops.json +3 -8
  12. claude_mpm/agents/templates/qa.json +2 -3
  13. claude_mpm/agents/templates/research.json +2 -3
  14. claude_mpm/agents/templates/security.json +3 -6
  15. claude_mpm/agents/templates/ticketing.json +4 -9
  16. claude_mpm/agents/templates/version_control.json +3 -3
  17. claude_mpm/agents/templates/web_qa.json +4 -4
  18. claude_mpm/agents/templates/web_ui.json +4 -4
  19. claude_mpm/cli/__init__.py +2 -2
  20. claude_mpm/cli/commands/__init__.py +2 -1
  21. claude_mpm/cli/commands/agents.py +118 -1
  22. claude_mpm/cli/commands/tickets.py +596 -19
  23. claude_mpm/cli/parser.py +228 -5
  24. claude_mpm/config/__init__.py +30 -39
  25. claude_mpm/config/socketio_config.py +8 -5
  26. claude_mpm/constants.py +13 -0
  27. claude_mpm/core/__init__.py +8 -18
  28. claude_mpm/core/cache.py +596 -0
  29. claude_mpm/core/claude_runner.py +166 -622
  30. claude_mpm/core/config.py +5 -1
  31. claude_mpm/core/constants.py +339 -0
  32. claude_mpm/core/container.py +461 -22
  33. claude_mpm/core/exceptions.py +392 -0
  34. claude_mpm/core/framework_loader.py +208 -93
  35. claude_mpm/core/interactive_session.py +432 -0
  36. claude_mpm/core/interfaces.py +424 -0
  37. claude_mpm/core/lazy.py +467 -0
  38. claude_mpm/core/logging_config.py +444 -0
  39. claude_mpm/core/oneshot_session.py +465 -0
  40. claude_mpm/core/optimized_agent_loader.py +485 -0
  41. claude_mpm/core/optimized_startup.py +490 -0
  42. claude_mpm/core/service_registry.py +52 -26
  43. claude_mpm/core/socketio_pool.py +162 -5
  44. claude_mpm/core/types.py +292 -0
  45. claude_mpm/core/typing_utils.py +477 -0
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  47. claude_mpm/dashboard/templates/index.html +5 -5
  48. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  49. claude_mpm/init.py +2 -1
  50. claude_mpm/services/__init__.py +78 -14
  51. claude_mpm/services/agent/__init__.py +24 -0
  52. claude_mpm/services/agent/deployment.py +2548 -0
  53. claude_mpm/services/agent/management.py +598 -0
  54. claude_mpm/services/agent/registry.py +813 -0
  55. claude_mpm/services/agents/deployment/agent_deployment.py +592 -269
  56. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  57. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  58. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  59. claude_mpm/services/async_session_logger.py +8 -3
  60. claude_mpm/services/communication/__init__.py +21 -0
  61. claude_mpm/services/communication/socketio.py +1933 -0
  62. claude_mpm/services/communication/websocket.py +479 -0
  63. claude_mpm/services/core/__init__.py +123 -0
  64. claude_mpm/services/core/base.py +247 -0
  65. claude_mpm/services/core/interfaces.py +951 -0
  66. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  67. claude_mpm/services/framework_claude_md_generator.py +3 -2
  68. claude_mpm/services/health_monitor.py +4 -3
  69. claude_mpm/services/hook_service.py +64 -4
  70. claude_mpm/services/infrastructure/__init__.py +21 -0
  71. claude_mpm/services/infrastructure/logging.py +202 -0
  72. claude_mpm/services/infrastructure/monitoring.py +893 -0
  73. claude_mpm/services/memory/indexed_memory.py +648 -0
  74. claude_mpm/services/project/__init__.py +21 -0
  75. claude_mpm/services/project/analyzer.py +864 -0
  76. claude_mpm/services/project/registry.py +608 -0
  77. claude_mpm/services/project_analyzer.py +95 -2
  78. claude_mpm/services/recovery_manager.py +15 -9
  79. claude_mpm/services/socketio/__init__.py +25 -0
  80. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  81. claude_mpm/services/socketio/handlers/base.py +121 -0
  82. claude_mpm/services/socketio/handlers/connection.py +198 -0
  83. claude_mpm/services/socketio/handlers/file.py +213 -0
  84. claude_mpm/services/socketio/handlers/git.py +723 -0
  85. claude_mpm/services/socketio/handlers/memory.py +27 -0
  86. claude_mpm/services/socketio/handlers/project.py +25 -0
  87. claude_mpm/services/socketio/handlers/registry.py +145 -0
  88. claude_mpm/services/socketio_client_manager.py +12 -7
  89. claude_mpm/services/socketio_server.py +156 -30
  90. claude_mpm/services/ticket_manager.py +377 -51
  91. claude_mpm/utils/agent_dependency_loader.py +66 -15
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/utils/robust_installer.py +587 -0
  94. claude_mpm/validation/agent_validator.py +27 -14
  95. claude_mpm/validation/frontmatter_validator.py +231 -0
  96. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
  97. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
  98. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  99. claude_mpm/agents/agent-template.yaml +0 -83
  100. claude_mpm/cli/README.md +0 -108
  101. claude_mpm/cli_module/refactoring_guide.md +0 -253
  102. claude_mpm/config/async_logging_config.yaml +0 -145
  103. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  104. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  105. claude_mpm/dashboard/README.md +0 -121
  106. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  107. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  108. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  109. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  110. claude_mpm/hooks/README.md +0 -96
  111. claude_mpm/schemas/agent_schema.json +0 -435
  112. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  113. claude_mpm/services/version_control/VERSION +0 -1
  114. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  115. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  116. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  117. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
@@ -1,28 +1,161 @@
1
1
  """
2
2
  Tickets command implementation for claude-mpm.
3
3
 
4
- WHY: This module handles ticket listing functionality, allowing users to view
5
- recent tickets created during Claude sessions.
4
+ WHY: This module provides comprehensive ticket management functionality, allowing users
5
+ to create, view, update, and manage tickets through the CLI. It integrates with
6
+ ai-trackdown-pytools for persistent ticket storage.
7
+
8
+ DESIGN DECISION: We implement full CRUD operations plus search and workflow management
9
+ to provide a complete ticket management system within the claude-mpm CLI. The commands
10
+ mirror the scripts/ticket.py interface for consistency.
6
11
  """
7
12
 
13
+ import sys
14
+ import subprocess
15
+ from typing import Optional, List, Dict, Any
16
+ from pathlib import Path
17
+
8
18
  from ...core.logger import get_logger
19
+ from ...constants import TicketCommands
20
+
21
+
22
+ def manage_tickets(args):
23
+ """
24
+ Main ticket command dispatcher.
25
+
26
+ WHY: This function routes ticket subcommands to their appropriate handlers,
27
+ providing a single entry point for all ticket-related operations.
28
+
29
+ DESIGN DECISION: We use a subcommand pattern similar to git, allowing for
30
+ intuitive command structure like 'claude-mpm tickets create "title"'.
31
+
32
+ Args:
33
+ args: Parsed command line arguments with 'tickets_command' attribute
34
+
35
+ Returns:
36
+ Exit code (0 for success, non-zero for errors)
37
+ """
38
+ logger = get_logger("cli.tickets")
39
+
40
+ # Handle case where no subcommand is provided - default to list
41
+ if not hasattr(args, 'tickets_command') or not args.tickets_command:
42
+ # Default to list command for backward compatibility
43
+ args.tickets_command = TicketCommands.LIST.value
44
+ # Set default limit if not present
45
+ if not hasattr(args, 'limit'):
46
+ args.limit = 10
47
+ if not hasattr(args, 'verbose'):
48
+ args.verbose = False
49
+
50
+ # Map subcommands to handler functions
51
+ handlers = {
52
+ TicketCommands.CREATE.value: create_ticket,
53
+ TicketCommands.LIST.value: list_tickets,
54
+ TicketCommands.VIEW.value: view_ticket,
55
+ TicketCommands.UPDATE.value: update_ticket,
56
+ TicketCommands.CLOSE.value: close_ticket,
57
+ TicketCommands.DELETE.value: delete_ticket,
58
+ TicketCommands.SEARCH.value: search_tickets,
59
+ TicketCommands.COMMENT.value: add_comment,
60
+ TicketCommands.WORKFLOW.value: update_workflow,
61
+ }
62
+
63
+ # Execute the appropriate handler
64
+ handler = handlers.get(args.tickets_command)
65
+ if handler:
66
+ try:
67
+ return handler(args)
68
+ except KeyboardInterrupt:
69
+ logger.info("Operation cancelled by user")
70
+ return 1
71
+ except Exception as e:
72
+ logger.error(f"Error executing {args.tickets_command}: {e}")
73
+ if hasattr(args, 'debug') and args.debug:
74
+ import traceback
75
+ traceback.print_exc()
76
+ return 1
77
+ else:
78
+ logger.error(f"Unknown ticket command: {args.tickets_command}")
79
+ return 1
80
+
81
+
82
+ def create_ticket(args):
83
+ """
84
+ Create a new ticket.
85
+
86
+ WHY: Users need to create tickets to track work items, bugs, and features.
87
+ This command provides a streamlined interface for ticket creation.
88
+
89
+ DESIGN DECISION: We parse description from remaining args to allow natural
90
+ command line usage like: tickets create "title" -d This is a description
91
+
92
+ Args:
93
+ args: Arguments with title, type, priority, description, tags, etc.
94
+
95
+ Returns:
96
+ Exit code (0 for success, non-zero for errors)
97
+ """
98
+ logger = get_logger("cli.tickets")
99
+
100
+ try:
101
+ from ...services.ticket_manager import TicketManager
102
+ except ImportError:
103
+ from claude_mpm.services.ticket_manager import TicketManager
104
+
105
+ ticket_manager = TicketManager()
106
+
107
+ # Parse description from remaining args or use default
108
+ description = " ".join(args.description) if args.description else ""
109
+
110
+ # Parse tags
111
+ tags = args.tags.split(",") if args.tags else []
112
+
113
+ # Create ticket with all provided parameters
114
+ ticket_id = ticket_manager.create_ticket(
115
+ title=args.title,
116
+ ticket_type=args.type,
117
+ description=description,
118
+ priority=args.priority,
119
+ tags=tags,
120
+ source="claude-mpm-cli",
121
+ parent_epic=getattr(args, 'parent_epic', None),
122
+ parent_issue=getattr(args, 'parent_issue', None)
123
+ )
124
+
125
+ if ticket_id:
126
+ print(f"✅ Created ticket: {ticket_id}")
127
+ if args.verbose:
128
+ print(f" Type: {args.type}")
129
+ print(f" Priority: {args.priority}")
130
+ if tags:
131
+ print(f" Tags: {', '.join(tags)}")
132
+ if getattr(args, 'parent_epic', None):
133
+ print(f" Parent Epic: {args.parent_epic}")
134
+ if getattr(args, 'parent_issue', None):
135
+ print(f" Parent Issue: {args.parent_issue}")
136
+ return 0
137
+ else:
138
+ print("❌ Failed to create ticket")
139
+ return 1
9
140
 
10
141
 
11
142
  def list_tickets(args):
12
143
  """
13
- List recent tickets.
144
+ List recent tickets with optional filtering.
14
145
 
15
146
  WHY: Users need to review tickets created during Claude sessions. This command
16
147
  provides a quick way to see recent tickets with their status and metadata.
17
148
 
18
149
  DESIGN DECISION: We show tickets in a compact format with emoji status indicators
19
- for better visual scanning. The limit is configurable to allow users to see more
20
- or fewer tickets as needed.
150
+ for better visual scanning. Filters allow focusing on specific ticket types/statuses.
21
151
 
22
152
  Args:
23
- args: Parsed command line arguments with 'limit' attribute
153
+ args: Arguments with limit, type filter, status filter, verbose flag
154
+
155
+ Returns:
156
+ Exit code (0 for success, non-zero for errors)
24
157
  """
25
- logger = get_logger("cli")
158
+ logger = get_logger("cli.tickets")
26
159
 
27
160
  try:
28
161
  try:
@@ -31,33 +164,477 @@ def list_tickets(args):
31
164
  from claude_mpm.services.ticket_manager import TicketManager
32
165
 
33
166
  ticket_manager = TicketManager()
34
- tickets = ticket_manager.list_recent_tickets(limit=args.limit)
35
167
 
36
- if not tickets:
37
- print("No tickets found")
38
- return
168
+ # Get tickets with limit
169
+ limit = getattr(args, 'limit', 10)
170
+ tickets = ticket_manager.list_recent_tickets(limit=limit * 2) # Get extra for filtering
39
171
 
40
- print(f"Recent tickets (showing {len(tickets)}):")
172
+ # Apply filters if specified
173
+ filtered_tickets = []
174
+ for ticket in tickets:
175
+ # Type filter
176
+ type_filter = getattr(args, 'type', 'all')
177
+ if type_filter != 'all':
178
+ ticket_type = ticket.get('metadata', {}).get('ticket_type', 'unknown')
179
+ if ticket_type != type_filter:
180
+ continue
181
+
182
+ # Status filter
183
+ status_filter = getattr(args, 'status', 'all')
184
+ if status_filter != 'all':
185
+ if ticket.get('status') != status_filter:
186
+ continue
187
+
188
+ filtered_tickets.append(ticket)
189
+ if len(filtered_tickets) >= limit:
190
+ break
191
+
192
+ if not filtered_tickets:
193
+ print("No tickets found matching criteria")
194
+ return 0
195
+
196
+ print(f"Recent tickets (showing {len(filtered_tickets)}):")
41
197
  print("-" * 80)
42
198
 
43
- for ticket in tickets:
199
+ for ticket in filtered_tickets:
44
200
  # Use emoji to indicate status visually
45
201
  status_emoji = {
46
202
  "open": "🔵",
47
203
  "in_progress": "🟡",
48
204
  "done": "🟢",
49
- "closed": "⚫"
50
- }.get(ticket['status'], "")
205
+ "closed": "⚫",
206
+ "blocked": "🔴"
207
+ }.get(ticket.get('status', 'unknown'), "⚪")
51
208
 
52
209
  print(f"{status_emoji} [{ticket['id']}] {ticket['title']}")
53
- print(f" Priority: {ticket['priority']} | Tags: {', '.join(ticket['tags'])}")
54
- print(f" Created: {ticket['created_at']}")
55
- print()
210
+
211
+ if getattr(args, 'verbose', False):
212
+ ticket_type = ticket.get('metadata', {}).get('ticket_type', 'task')
213
+ print(f" Type: {ticket_type} | Status: {ticket['status']} | Priority: {ticket['priority']}")
214
+ if ticket.get('tags'):
215
+ print(f" Tags: {', '.join(ticket['tags'])}")
216
+ print(f" Created: {ticket['created_at']}")
217
+ print()
218
+
219
+ return 0
56
220
 
57
221
  except ImportError:
58
222
  logger.error("ai-trackdown-pytools not installed")
59
223
  print("Error: ai-trackdown-pytools not installed")
60
224
  print("Install with: pip install ai-trackdown-pytools")
225
+ return 1
61
226
  except Exception as e:
62
227
  logger.error(f"Error listing tickets: {e}")
63
- print(f"Error: {e}")
228
+ print(f"Error: {e}")
229
+ return 1
230
+
231
+
232
+ def view_ticket(args):
233
+ """
234
+ View a specific ticket in detail.
235
+
236
+ WHY: Users need to see full ticket details including description, metadata,
237
+ and all associated information for understanding context and status.
238
+
239
+ Args:
240
+ args: Arguments with ticket id and verbose flag
241
+
242
+ Returns:
243
+ Exit code (0 for success, non-zero for errors)
244
+ """
245
+ logger = get_logger("cli.tickets")
246
+
247
+ try:
248
+ from ...services.ticket_manager import TicketManager
249
+ except ImportError:
250
+ from claude_mpm.services.ticket_manager import TicketManager
251
+
252
+ ticket_manager = TicketManager()
253
+ ticket = ticket_manager.get_ticket(args.id)
254
+
255
+ if not ticket:
256
+ print(f"❌ Ticket {args.id} not found")
257
+ return 1
258
+
259
+ print(f"Ticket: {ticket['id']}")
260
+ print("=" * 80)
261
+ print(f"Title: {ticket['title']}")
262
+ print(f"Type: {ticket.get('metadata', {}).get('ticket_type', 'unknown')}")
263
+ print(f"Status: {ticket['status']}")
264
+ print(f"Priority: {ticket['priority']}")
265
+
266
+ if ticket.get('tags'):
267
+ print(f"Tags: {', '.join(ticket['tags'])}")
268
+
269
+ if ticket.get('assignees'):
270
+ print(f"Assignees: {', '.join(ticket['assignees'])}")
271
+
272
+ # Show parent references if they exist
273
+ metadata = ticket.get('metadata', {})
274
+ if metadata.get('parent_epic'):
275
+ print(f"Parent Epic: {metadata['parent_epic']}")
276
+ if metadata.get('parent_issue'):
277
+ print(f"Parent Issue: {metadata['parent_issue']}")
278
+
279
+ print(f"\nDescription:")
280
+ print("-" * 40)
281
+ print(ticket.get('description', 'No description'))
282
+
283
+ print(f"\nCreated: {ticket['created_at']}")
284
+ print(f"Updated: {ticket['updated_at']}")
285
+
286
+ if args.verbose and ticket.get('metadata'):
287
+ print(f"\nMetadata:")
288
+ print("-" * 40)
289
+ for key, value in ticket['metadata'].items():
290
+ if key not in ['parent_epic', 'parent_issue', 'ticket_type']: # Already shown above
291
+ print(f" {key}: {value}")
292
+
293
+ return 0
294
+
295
+
296
+ def update_ticket(args):
297
+ """
298
+ Update a ticket's properties.
299
+
300
+ WHY: Tickets need to be updated as work progresses, priorities change,
301
+ or additional information becomes available.
302
+
303
+ DESIGN DECISION: For complex updates, we delegate to aitrackdown CLI
304
+ for operations not directly supported by our TicketManager interface.
305
+
306
+ Args:
307
+ args: Arguments with ticket id and update fields
308
+
309
+ Returns:
310
+ Exit code (0 for success, non-zero for errors)
311
+ """
312
+ logger = get_logger("cli.tickets")
313
+
314
+ try:
315
+ from ...services.ticket_manager import TicketManager
316
+ except ImportError:
317
+ from claude_mpm.services.ticket_manager import TicketManager
318
+
319
+ ticket_manager = TicketManager()
320
+
321
+ # Build update dictionary
322
+ updates = {}
323
+
324
+ if args.status:
325
+ updates['status'] = args.status
326
+
327
+ if args.priority:
328
+ updates['priority'] = args.priority
329
+
330
+ if args.description:
331
+ updates['description'] = " ".join(args.description)
332
+
333
+ if args.tags:
334
+ updates['tags'] = args.tags.split(",")
335
+
336
+ if args.assign:
337
+ updates['assignees'] = [args.assign]
338
+
339
+ if not updates:
340
+ print("❌ No updates specified")
341
+ return 1
342
+
343
+ # Try to update using TicketManager
344
+ success = ticket_manager.update_task(args.id, **updates)
345
+
346
+ if success:
347
+ print(f"✅ Updated ticket: {args.id}")
348
+ return 0
349
+ else:
350
+ # Fallback to aitrackdown CLI for status transitions
351
+ if args.status:
352
+ logger.info("Attempting update via aitrackdown CLI")
353
+ cmd = ["aitrackdown", "transition", args.id, args.status]
354
+
355
+ # Add comment with other updates
356
+ comment_parts = []
357
+ if args.priority:
358
+ comment_parts.append(f"Priority: {args.priority}")
359
+ if args.assign:
360
+ comment_parts.append(f"Assigned to: {args.assign}")
361
+ if args.tags:
362
+ comment_parts.append(f"Tags: {args.tags}")
363
+
364
+ if comment_parts:
365
+ comment = " | ".join(comment_parts)
366
+ cmd.extend(["--comment", comment])
367
+
368
+ try:
369
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
370
+ print(f"✅ Updated ticket: {args.id}")
371
+ return 0
372
+ except subprocess.CalledProcessError as e:
373
+ logger.error(f"Failed to update via CLI: {e}")
374
+ print(f"❌ Failed to update ticket: {args.id}")
375
+ return 1
376
+ else:
377
+ print(f"❌ Failed to update ticket: {args.id}")
378
+ return 1
379
+
380
+
381
+ def close_ticket(args):
382
+ """
383
+ Close a ticket.
384
+
385
+ WHY: Tickets need to be closed when work is completed or no longer relevant.
386
+
387
+ Args:
388
+ args: Arguments with ticket id and optional resolution
389
+
390
+ Returns:
391
+ Exit code (0 for success, non-zero for errors)
392
+ """
393
+ logger = get_logger("cli.tickets")
394
+
395
+ try:
396
+ from ...services.ticket_manager import TicketManager
397
+ except ImportError:
398
+ from claude_mpm.services.ticket_manager import TicketManager
399
+
400
+ ticket_manager = TicketManager()
401
+
402
+ # Try to close using TicketManager
403
+ resolution = getattr(args, 'resolution', None)
404
+ success = ticket_manager.close_task(args.id, resolution=resolution)
405
+
406
+ if success:
407
+ print(f"✅ Closed ticket: {args.id}")
408
+ return 0
409
+ else:
410
+ # Fallback to aitrackdown CLI
411
+ logger.info("Attempting close via aitrackdown CLI")
412
+ cmd = ["aitrackdown", "close", args.id]
413
+
414
+ if resolution:
415
+ cmd.extend(["--comment", resolution])
416
+
417
+ try:
418
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
419
+ print(f"✅ Closed ticket: {args.id}")
420
+ return 0
421
+ except subprocess.CalledProcessError:
422
+ print(f"❌ Failed to close ticket: {args.id}")
423
+ return 1
424
+
425
+
426
+ def delete_ticket(args):
427
+ """
428
+ Delete a ticket.
429
+
430
+ WHY: Sometimes tickets are created in error or are no longer needed
431
+ and should be removed from the system.
432
+
433
+ DESIGN DECISION: We delegate to aitrackdown CLI as deletion is a
434
+ destructive operation that should use the official tool.
435
+
436
+ Args:
437
+ args: Arguments with ticket id and force flag
438
+
439
+ Returns:
440
+ Exit code (0 for success, non-zero for errors)
441
+ """
442
+ logger = get_logger("cli.tickets")
443
+
444
+ # Confirm deletion unless forced
445
+ if not args.force:
446
+ response = input(f"Are you sure you want to delete ticket {args.id}? (y/N): ")
447
+ if response.lower() != 'y':
448
+ print("Deletion cancelled")
449
+ return 0
450
+
451
+ # Use aitrackdown CLI for deletion
452
+ cmd = ["aitrackdown", "delete", args.id]
453
+ if args.force:
454
+ cmd.append("--force")
455
+
456
+ try:
457
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
458
+ print(f"✅ Deleted ticket: {args.id}")
459
+ return 0
460
+ except subprocess.CalledProcessError:
461
+ print(f"❌ Failed to delete ticket: {args.id}")
462
+ return 1
463
+
464
+
465
+ def search_tickets(args):
466
+ """
467
+ Search tickets by query string.
468
+
469
+ WHY: Users need to find specific tickets based on content, tags, or other criteria.
470
+
471
+ DESIGN DECISION: We perform simple text matching on ticket data. For more advanced
472
+ search, users should use the aitrackdown CLI directly.
473
+
474
+ Args:
475
+ args: Arguments with search query and filters
476
+
477
+ Returns:
478
+ Exit code (0 for success, non-zero for errors)
479
+ """
480
+ logger = get_logger("cli.tickets")
481
+
482
+ try:
483
+ from ...services.ticket_manager import TicketManager
484
+ except ImportError:
485
+ from claude_mpm.services.ticket_manager import TicketManager
486
+
487
+ ticket_manager = TicketManager()
488
+
489
+ # Get all available tickets for searching
490
+ all_tickets = ticket_manager.list_recent_tickets(limit=100)
491
+
492
+ # Search tickets
493
+ query = args.query.lower()
494
+ matched_tickets = []
495
+
496
+ for ticket in all_tickets:
497
+ # Check if query matches title, description, or tags
498
+ if (query in ticket.get('title', '').lower() or
499
+ query in ticket.get('description', '').lower() or
500
+ any(query in tag.lower() for tag in ticket.get('tags', []))):
501
+
502
+ # Apply type filter
503
+ if args.type != 'all':
504
+ ticket_type = ticket.get('metadata', {}).get('ticket_type', 'unknown')
505
+ if ticket_type != args.type:
506
+ continue
507
+
508
+ # Apply status filter
509
+ if args.status != 'all':
510
+ if ticket.get('status') != args.status:
511
+ continue
512
+
513
+ matched_tickets.append(ticket)
514
+ if len(matched_tickets) >= args.limit:
515
+ break
516
+
517
+ if not matched_tickets:
518
+ print(f"No tickets found matching '{args.query}'")
519
+ return 0
520
+
521
+ print(f"Search results for '{args.query}' (showing {len(matched_tickets)}):")
522
+ print("-" * 80)
523
+
524
+ for ticket in matched_tickets:
525
+ status_emoji = {
526
+ "open": "🔵",
527
+ "in_progress": "🟡",
528
+ "done": "🟢",
529
+ "closed": "⚫",
530
+ "blocked": "🔴"
531
+ }.get(ticket.get('status', 'unknown'), "⚪")
532
+
533
+ print(f"{status_emoji} [{ticket['id']}] {ticket['title']}")
534
+
535
+ # Show snippet of description if it contains the query
536
+ desc = ticket.get('description', '')
537
+ if query in desc.lower():
538
+ # Find and show context around the match
539
+ idx = desc.lower().index(query)
540
+ start = max(0, idx - 30)
541
+ end = min(len(desc), idx + len(query) + 30)
542
+ snippet = desc[start:end]
543
+ if start > 0:
544
+ snippet = "..." + snippet
545
+ if end < len(desc):
546
+ snippet = snippet + "..."
547
+ print(f" {snippet}")
548
+
549
+ return 0
550
+
551
+
552
+ def add_comment(args):
553
+ """
554
+ Add a comment to a ticket.
555
+
556
+ WHY: Comments allow tracking progress, decisions, and additional context
557
+ on tickets over time.
558
+
559
+ DESIGN DECISION: We delegate to aitrackdown CLI as it has proper comment
560
+ tracking infrastructure.
561
+
562
+ Args:
563
+ args: Arguments with ticket id and comment text
564
+
565
+ Returns:
566
+ Exit code (0 for success, non-zero for errors)
567
+ """
568
+ logger = get_logger("cli.tickets")
569
+
570
+ # Join comment parts into single string
571
+ comment = " ".join(args.comment)
572
+
573
+ # Use aitrackdown CLI for comments
574
+ cmd = ["aitrackdown", "comment", args.id, comment]
575
+
576
+ try:
577
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
578
+ print(f"✅ Added comment to ticket: {args.id}")
579
+ return 0
580
+ except subprocess.CalledProcessError:
581
+ print(f"❌ Failed to add comment to ticket: {args.id}")
582
+ return 1
583
+
584
+
585
+ def update_workflow(args):
586
+ """
587
+ Update ticket workflow state.
588
+
589
+ WHY: Workflow states track the progress of tickets through defined stages
590
+ like todo, in_progress, ready, tested, done.
591
+
592
+ DESIGN DECISION: We use aitrackdown's transition command for workflow updates
593
+ as it maintains proper state machine transitions.
594
+
595
+ Args:
596
+ args: Arguments with ticket id, new state, and optional comment
597
+
598
+ Returns:
599
+ Exit code (0 for success, non-zero for errors)
600
+ """
601
+ logger = get_logger("cli.tickets")
602
+
603
+ # Map workflow states to status if needed
604
+ state_mapping = {
605
+ 'todo': 'open',
606
+ 'in_progress': 'in_progress',
607
+ 'ready': 'ready',
608
+ 'tested': 'tested',
609
+ 'done': 'done',
610
+ 'blocked': 'blocked'
611
+ }
612
+
613
+ # Use aitrackdown transition command
614
+ cmd = ["aitrackdown", "transition", args.id, args.state]
615
+
616
+ if getattr(args, 'comment', None):
617
+ cmd.extend(["--comment", args.comment])
618
+
619
+ try:
620
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
621
+ print(f"✅ Updated workflow state for {args.id} to: {args.state}")
622
+ return 0
623
+ except subprocess.CalledProcessError:
624
+ print(f"❌ Failed to update workflow state for ticket: {args.id}")
625
+ return 1
626
+
627
+
628
+ # Maintain backward compatibility with the old list_tickets function signature
629
+ def list_tickets_legacy(args):
630
+ """
631
+ Legacy list_tickets function for backward compatibility.
632
+
633
+ WHY: The old CLI interface expected a simple list_tickets function.
634
+ This wrapper maintains that interface while using the new implementation.
635
+
636
+ Args:
637
+ args: Parsed command line arguments with 'limit' attribute
638
+ """
639
+ # Call the new list_tickets function
640
+ return list_tickets(args)