mcp-ticketer 0.3.0__py3-none-any.whl → 2.2.9__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 (160) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/__init__.py +2 -0
  5. mcp_ticketer/adapters/aitrackdown.py +930 -52
  6. mcp_ticketer/adapters/asana/__init__.py +15 -0
  7. mcp_ticketer/adapters/asana/adapter.py +1537 -0
  8. mcp_ticketer/adapters/asana/client.py +292 -0
  9. mcp_ticketer/adapters/asana/mappers.py +348 -0
  10. mcp_ticketer/adapters/asana/types.py +146 -0
  11. mcp_ticketer/adapters/github/__init__.py +26 -0
  12. mcp_ticketer/adapters/github/adapter.py +3229 -0
  13. mcp_ticketer/adapters/github/client.py +335 -0
  14. mcp_ticketer/adapters/github/mappers.py +797 -0
  15. mcp_ticketer/adapters/github/queries.py +692 -0
  16. mcp_ticketer/adapters/github/types.py +460 -0
  17. mcp_ticketer/adapters/hybrid.py +58 -16
  18. mcp_ticketer/adapters/jira/__init__.py +35 -0
  19. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  20. mcp_ticketer/adapters/jira/client.py +271 -0
  21. mcp_ticketer/adapters/jira/mappers.py +246 -0
  22. mcp_ticketer/adapters/jira/queries.py +216 -0
  23. mcp_ticketer/adapters/jira/types.py +304 -0
  24. mcp_ticketer/adapters/linear/__init__.py +1 -1
  25. mcp_ticketer/adapters/linear/adapter.py +3810 -462
  26. mcp_ticketer/adapters/linear/client.py +312 -69
  27. mcp_ticketer/adapters/linear/mappers.py +305 -85
  28. mcp_ticketer/adapters/linear/queries.py +317 -17
  29. mcp_ticketer/adapters/linear/types.py +187 -64
  30. mcp_ticketer/adapters/linear.py +2 -2
  31. mcp_ticketer/analysis/__init__.py +56 -0
  32. mcp_ticketer/analysis/dependency_graph.py +255 -0
  33. mcp_ticketer/analysis/health_assessment.py +304 -0
  34. mcp_ticketer/analysis/orphaned.py +218 -0
  35. mcp_ticketer/analysis/project_status.py +594 -0
  36. mcp_ticketer/analysis/similarity.py +224 -0
  37. mcp_ticketer/analysis/staleness.py +266 -0
  38. mcp_ticketer/automation/__init__.py +11 -0
  39. mcp_ticketer/automation/project_updates.py +378 -0
  40. mcp_ticketer/cache/memory.py +9 -8
  41. mcp_ticketer/cli/adapter_diagnostics.py +91 -54
  42. mcp_ticketer/cli/auggie_configure.py +116 -15
  43. mcp_ticketer/cli/codex_configure.py +274 -82
  44. mcp_ticketer/cli/configure.py +1323 -151
  45. mcp_ticketer/cli/cursor_configure.py +314 -0
  46. mcp_ticketer/cli/diagnostics.py +209 -114
  47. mcp_ticketer/cli/discover.py +297 -26
  48. mcp_ticketer/cli/gemini_configure.py +119 -26
  49. mcp_ticketer/cli/init_command.py +880 -0
  50. mcp_ticketer/cli/install_mcp_server.py +418 -0
  51. mcp_ticketer/cli/instruction_commands.py +435 -0
  52. mcp_ticketer/cli/linear_commands.py +256 -130
  53. mcp_ticketer/cli/main.py +140 -1544
  54. mcp_ticketer/cli/mcp_configure.py +1013 -100
  55. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  56. mcp_ticketer/cli/migrate_config.py +12 -8
  57. mcp_ticketer/cli/platform_commands.py +123 -0
  58. mcp_ticketer/cli/platform_detection.py +477 -0
  59. mcp_ticketer/cli/platform_installer.py +545 -0
  60. mcp_ticketer/cli/project_update_commands.py +350 -0
  61. mcp_ticketer/cli/python_detection.py +126 -0
  62. mcp_ticketer/cli/queue_commands.py +15 -15
  63. mcp_ticketer/cli/setup_command.py +794 -0
  64. mcp_ticketer/cli/simple_health.py +84 -59
  65. mcp_ticketer/cli/ticket_commands.py +1375 -0
  66. mcp_ticketer/cli/update_checker.py +313 -0
  67. mcp_ticketer/cli/utils.py +195 -72
  68. mcp_ticketer/core/__init__.py +64 -1
  69. mcp_ticketer/core/adapter.py +618 -18
  70. mcp_ticketer/core/config.py +77 -68
  71. mcp_ticketer/core/env_discovery.py +75 -16
  72. mcp_ticketer/core/env_loader.py +121 -97
  73. mcp_ticketer/core/exceptions.py +32 -24
  74. mcp_ticketer/core/http_client.py +26 -26
  75. mcp_ticketer/core/instructions.py +405 -0
  76. mcp_ticketer/core/label_manager.py +732 -0
  77. mcp_ticketer/core/mappers.py +42 -30
  78. mcp_ticketer/core/milestone_manager.py +252 -0
  79. mcp_ticketer/core/models.py +566 -19
  80. mcp_ticketer/core/onepassword_secrets.py +379 -0
  81. mcp_ticketer/core/priority_matcher.py +463 -0
  82. mcp_ticketer/core/project_config.py +189 -49
  83. mcp_ticketer/core/project_utils.py +281 -0
  84. mcp_ticketer/core/project_validator.py +376 -0
  85. mcp_ticketer/core/registry.py +3 -3
  86. mcp_ticketer/core/session_state.py +176 -0
  87. mcp_ticketer/core/state_matcher.py +592 -0
  88. mcp_ticketer/core/url_parser.py +425 -0
  89. mcp_ticketer/core/validators.py +69 -0
  90. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  91. mcp_ticketer/mcp/__init__.py +29 -1
  92. mcp_ticketer/mcp/__main__.py +60 -0
  93. mcp_ticketer/mcp/server/__init__.py +25 -0
  94. mcp_ticketer/mcp/server/__main__.py +60 -0
  95. mcp_ticketer/mcp/server/constants.py +58 -0
  96. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  97. mcp_ticketer/mcp/server/dto.py +195 -0
  98. mcp_ticketer/mcp/server/main.py +1343 -0
  99. mcp_ticketer/mcp/server/response_builder.py +206 -0
  100. mcp_ticketer/mcp/server/routing.py +723 -0
  101. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  102. mcp_ticketer/mcp/server/tools/__init__.py +69 -0
  103. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  104. mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
  105. mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
  106. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  107. mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
  108. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  109. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
  110. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  111. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  112. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  113. mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
  114. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  115. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  116. mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
  117. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  118. mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
  119. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  120. mcp_ticketer/queue/__init__.py +1 -0
  121. mcp_ticketer/queue/health_monitor.py +168 -136
  122. mcp_ticketer/queue/manager.py +78 -63
  123. mcp_ticketer/queue/queue.py +108 -21
  124. mcp_ticketer/queue/run_worker.py +2 -2
  125. mcp_ticketer/queue/ticket_registry.py +213 -155
  126. mcp_ticketer/queue/worker.py +96 -58
  127. mcp_ticketer/utils/__init__.py +5 -0
  128. mcp_ticketer/utils/token_utils.py +246 -0
  129. mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
  130. mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
  131. mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
  132. py_mcp_installer/examples/phase3_demo.py +178 -0
  133. py_mcp_installer/scripts/manage_version.py +54 -0
  134. py_mcp_installer/setup.py +6 -0
  135. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  136. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  137. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  138. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  139. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  140. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  141. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  142. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  143. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  144. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  145. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  146. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  147. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  148. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  149. py_mcp_installer/tests/__init__.py +0 -0
  150. py_mcp_installer/tests/platforms/__init__.py +0 -0
  151. py_mcp_installer/tests/test_platform_detector.py +17 -0
  152. mcp_ticketer/adapters/github.py +0 -1354
  153. mcp_ticketer/adapters/jira.py +0 -1011
  154. mcp_ticketer/mcp/server.py +0 -2030
  155. mcp_ticketer-0.3.0.dist-info/METADATA +0 -414
  156. mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
  157. mcp_ticketer-0.3.0.dist-info/top_level.txt +0 -1
  158. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
  159. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
  160. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
mcp_ticketer/cli/utils.py CHANGED
@@ -4,9 +4,11 @@ import asyncio
4
4
  import json
5
5
  import logging
6
6
  import os
7
+ from collections.abc import Callable
8
+ from datetime import datetime
7
9
  from functools import wraps
8
10
  from pathlib import Path
9
- from typing import Any, Callable, Optional, TypeVar
11
+ from typing import Any, TypeVar
10
12
 
11
13
  import typer
12
14
  from rich.console import Console
@@ -22,6 +24,118 @@ T = TypeVar("T")
22
24
  console = Console()
23
25
  logger = logging.getLogger(__name__)
24
26
 
27
+ # Get version from package
28
+ try:
29
+ from importlib.metadata import version
30
+
31
+ __version__ = version("mcp-ticketer")
32
+ except Exception:
33
+ __version__ = "2.2.2" # Fallback to known version
34
+
35
+
36
+ def format_json_response(status: str, data: Any, message: str | None = None) -> str:
37
+ """Format response as JSON with standard structure.
38
+
39
+ Args:
40
+ status: Response status - "success" or "error"
41
+ data: Response data (dict, list, or any JSON-serializable type)
42
+ message: Optional human-readable message
43
+
44
+ Returns:
45
+ JSON string with standard format
46
+
47
+ Example:
48
+ >>> format_json_response("success", {"id": "1M-123", "title": "Fix bug"})
49
+ {
50
+ "status": "success",
51
+ "data": {"id": "1M-123", "title": "Fix bug"},
52
+ "metadata": {
53
+ "timestamp": "2025-12-05T10:30:00Z",
54
+ "version": "2.2.2"
55
+ }
56
+ }
57
+ """
58
+ response = {
59
+ "status": status,
60
+ "data": data,
61
+ "metadata": {
62
+ "timestamp": datetime.utcnow().isoformat() + "Z",
63
+ "version": __version__,
64
+ },
65
+ }
66
+ if message:
67
+ response["message"] = message
68
+ return json.dumps(response, indent=2, default=str)
69
+
70
+
71
+ def format_error_json(error: str | Exception, ticket_id: str | None = None) -> str:
72
+ """Format error response as JSON.
73
+
74
+ Args:
75
+ error: Error message or exception
76
+ ticket_id: Optional ticket ID that caused the error
77
+
78
+ Returns:
79
+ JSON error response
80
+ """
81
+ error_msg = str(error)
82
+ data = {"error": error_msg}
83
+ if ticket_id:
84
+ data["ticket_id"] = ticket_id
85
+ return format_json_response("error", data, message=error_msg)
86
+
87
+
88
+ def serialize_task(task: Task) -> dict[str, Any]:
89
+ """Serialize Task object to JSON-compatible dict.
90
+
91
+ Args:
92
+ task: Task object to serialize
93
+
94
+ Returns:
95
+ Dictionary with task fields
96
+ """
97
+ task_dict = {
98
+ "id": task.id,
99
+ "title": task.title,
100
+ "state": task.state,
101
+ "priority": task.priority,
102
+ "description": task.description,
103
+ "tags": task.tags or [],
104
+ "assignee": task.assignee,
105
+ }
106
+
107
+ # Add timestamps if available
108
+ if task.created_at:
109
+ task_dict["created_at"] = (
110
+ task.created_at.isoformat()
111
+ if hasattr(task.created_at, "isoformat")
112
+ else str(task.created_at)
113
+ )
114
+ if task.updated_at:
115
+ task_dict["updated_at"] = (
116
+ task.updated_at.isoformat()
117
+ if hasattr(task.updated_at, "isoformat")
118
+ else str(task.updated_at)
119
+ )
120
+
121
+ # Add parent relationships
122
+ if hasattr(task, "parent_epic") and task.parent_epic:
123
+ task_dict["parent_epic"] = task.parent_epic
124
+ if hasattr(task, "parent_issue") and task.parent_issue:
125
+ task_dict["parent_issue"] = task.parent_issue
126
+
127
+ # Add URL from metadata if available
128
+ if task.metadata:
129
+ if isinstance(task.metadata, dict):
130
+ # Linear metadata structure
131
+ if "linear" in task.metadata and "url" in task.metadata["linear"]:
132
+ task_dict["url"] = task.metadata["linear"]["url"]
133
+ # Generic url field
134
+ elif "url" in task.metadata:
135
+ task_dict["url"] = task.metadata["url"]
136
+
137
+ return task_dict
138
+
25
139
 
26
140
  class CommonPatterns:
27
141
  """Common CLI patterns and utilities."""
@@ -81,6 +195,7 @@ class CommonPatterns:
81
195
  # Try environment discovery as fallback
82
196
  try:
83
197
  from ..core.config import ConfigurationManager
198
+
84
199
  config_manager = ConfigurationManager()
85
200
  app_config = config_manager.load_config()
86
201
 
@@ -88,24 +203,27 @@ class CommonPatterns:
88
203
  enabled_adapters = app_config.get_enabled_adapters()
89
204
  if enabled_adapters:
90
205
  # Use the first enabled adapter as default
91
- default_adapter = app_config.default_adapter or list(enabled_adapters.keys())[0]
206
+ default_adapter = (
207
+ app_config.default_adapter or list(enabled_adapters.keys())[0]
208
+ )
92
209
 
93
210
  # Convert to legacy format
94
- legacy_config = {
95
- "default_adapter": default_adapter,
96
- "adapters": {}
97
- }
211
+ legacy_config = {"default_adapter": default_adapter, "adapters": {}}
98
212
 
99
213
  # Convert adapter configs to dict format
100
214
  for name, adapter_config in enabled_adapters.items():
101
- if hasattr(adapter_config, 'model_dump'):
102
- legacy_config["adapters"][name] = adapter_config.model_dump(exclude_none=False)
103
- elif hasattr(adapter_config, 'dict'):
215
+ if hasattr(adapter_config, "model_dump"):
216
+ legacy_config["adapters"][name] = adapter_config.model_dump(
217
+ exclude_none=False
218
+ )
219
+ elif hasattr(adapter_config, "dict"):
104
220
  legacy_config["adapters"][name] = adapter_config.dict()
105
221
  else:
106
222
  legacy_config["adapters"][name] = adapter_config
107
223
 
108
- logger.info(f"Loaded configuration from environment discovery: {list(enabled_adapters.keys())}")
224
+ logger.info(
225
+ f"Loaded configuration from environment discovery: {list(enabled_adapters.keys())}"
226
+ )
109
227
  return legacy_config
110
228
 
111
229
  except Exception as e:
@@ -150,8 +268,8 @@ class CommonPatterns:
150
268
 
151
269
  @staticmethod
152
270
  def get_adapter(
153
- override_adapter: Optional[str] = None, override_config: Optional[dict] = None
154
- ):
271
+ override_adapter: str | None = None, override_config: dict | None = None
272
+ ) -> Any:
155
273
  """Get configured adapter instance with environment variable support."""
156
274
  config = CommonPatterns.load_config()
157
275
 
@@ -202,7 +320,7 @@ class CommonPatterns:
202
320
  def queue_operation(
203
321
  ticket_data: dict[str, Any],
204
322
  operation: str,
205
- adapter_name: Optional[str] = None,
323
+ adapter_name: str | None = None,
206
324
  show_progress: bool = True,
207
325
  ) -> str:
208
326
  """Queue an operation and optionally start the worker."""
@@ -212,12 +330,13 @@ class CommonPatterns:
212
330
 
213
331
  # Add to queue with explicit project directory
214
332
  from pathlib import Path
333
+
215
334
  queue = Queue()
216
335
  queue_id = queue.add(
217
336
  ticket_data=ticket_data,
218
337
  adapter=adapter_name,
219
338
  operation=operation,
220
- project_dir=str(Path.cwd()) # Explicitly pass current project directory
339
+ project_dir=str(Path.cwd()), # Explicitly pass current project directory
221
340
  )
222
341
 
223
342
  if show_progress:
@@ -260,7 +379,7 @@ class CommonPatterns:
260
379
  console.print(table)
261
380
 
262
381
  @staticmethod
263
- def display_ticket_details(ticket: Task, comments: Optional[list] = None) -> None:
382
+ def display_ticket_details(ticket: Task, comments: list | None = None) -> None:
264
383
  """Display detailed ticket information."""
265
384
  console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
266
385
  console.print(f"Title: {ticket.title}")
@@ -315,33 +434,35 @@ class CommonPatterns:
315
434
  )
316
435
 
317
436
 
318
- def async_command(f: Callable[..., T]) -> Callable[..., T]:
319
- """Decorator to handle async CLI commands."""
437
+ def async_command(f: Callable[..., Any]) -> Callable[..., Any]:
438
+ """Handle async CLI commands via decorator."""
320
439
 
321
440
  @wraps(f)
322
- def wrapper(*args, **kwargs):
441
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
323
442
  return asyncio.run(f(*args, **kwargs))
324
443
 
325
444
  return wrapper
326
445
 
327
446
 
328
- def with_adapter(f: Callable) -> Callable:
329
- """Decorator to inject adapter instance into CLI commands."""
447
+ def with_adapter(f: Callable[..., Any]) -> Callable[..., Any]:
448
+ """Inject adapter instance into CLI commands via decorator."""
330
449
 
331
450
  @wraps(f)
332
- def wrapper(adapter: Optional[str] = None, *args, **kwargs):
451
+ def wrapper(adapter: str | None = None, *args: Any, **kwargs: Any) -> Any:
333
452
  adapter_instance = CommonPatterns.get_adapter(override_adapter=adapter)
334
453
  return f(adapter_instance, *args, **kwargs)
335
454
 
336
455
  return wrapper
337
456
 
338
457
 
339
- def with_progress(message: str = "Processing..."):
340
- """Decorator to show progress spinner for long-running operations."""
458
+ def with_progress(
459
+ message: str = "Processing...",
460
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
461
+ """Show progress spinner for long-running operations via decorator."""
341
462
 
342
- def decorator(f: Callable) -> Callable:
463
+ def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
343
464
  @wraps(f)
344
- def wrapper(*args, **kwargs):
465
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
345
466
  with Progress(
346
467
  SpinnerColumn(),
347
468
  TextColumn("[progress.description]{task.description}"),
@@ -355,12 +476,14 @@ def with_progress(message: str = "Processing..."):
355
476
  return decorator
356
477
 
357
478
 
358
- def validate_required_fields(**field_map):
359
- """Decorator to validate required fields are provided."""
479
+ def validate_required_fields(
480
+ **field_map: str,
481
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
482
+ """Validate required fields are provided via decorator."""
360
483
 
361
- def decorator(f: Callable) -> Callable:
484
+ def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
362
485
  @wraps(f)
363
- def wrapper(*args, **kwargs):
486
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
364
487
  missing_fields = []
365
488
  for field_name, display_name in field_map.items():
366
489
  if field_name in kwargs and kwargs[field_name] is None:
@@ -370,7 +493,7 @@ def validate_required_fields(**field_map):
370
493
  console.print(
371
494
  f"[red]Error:[/red] Missing required fields: {', '.join(missing_fields)}"
372
495
  )
373
- raise typer.Exit(1)
496
+ raise typer.Exit(1) from None
374
497
 
375
498
  return f(*args, **kwargs)
376
499
 
@@ -379,24 +502,24 @@ def validate_required_fields(**field_map):
379
502
  return decorator
380
503
 
381
504
 
382
- def handle_adapter_errors(f: Callable) -> Callable:
383
- """Decorator to handle common adapter errors gracefully."""
505
+ def handle_adapter_errors(f: Callable[..., Any]) -> Callable[..., Any]:
506
+ """Handle common adapter errors gracefully via decorator."""
384
507
 
385
508
  @wraps(f)
386
- def wrapper(*args, **kwargs):
509
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
387
510
  try:
388
511
  return f(*args, **kwargs)
389
512
  except ConnectionError as e:
390
513
  console.print(f"[red]Connection Error:[/red] {e}")
391
514
  console.print("Check your network connection and adapter configuration")
392
- raise typer.Exit(1)
515
+ raise typer.Exit(1) from None
393
516
  except ValueError as e:
394
517
  console.print(f"[red]Configuration Error:[/red] {e}")
395
518
  console.print("Run 'mcp-ticketer init' to configure your adapter")
396
- raise typer.Exit(1)
519
+ raise typer.Exit(1) from None
397
520
  except Exception as e:
398
521
  console.print(f"[red]Unexpected Error:[/red] {e}")
399
- raise typer.Exit(1)
522
+ raise typer.Exit(1) from None
400
523
 
401
524
  return wrapper
402
525
 
@@ -441,7 +564,7 @@ class ConfigValidator:
441
564
  return issues
442
565
 
443
566
  @staticmethod
444
- def _get_env_var(adapter_type: str, field: str) -> Optional[str]:
567
+ def _get_env_var(adapter_type: str, field: str) -> str | None:
445
568
  """Get corresponding environment variable name for a config field."""
446
569
  env_mapping = {
447
570
  "github": {
@@ -462,39 +585,39 @@ class ConfigValidator:
462
585
  class CommandBuilder:
463
586
  """Builder for common CLI command patterns."""
464
587
 
465
- def __init__(self):
466
- self._validators = []
467
- self._handlers = []
468
- self._decorators = []
588
+ def __init__(self) -> None:
589
+ self._validators: list[Callable[..., Any]] = []
590
+ self._handlers: list[Callable[..., Any]] = []
591
+ self._decorators: list[Callable[..., Any]] = []
469
592
 
470
- def with_adapter_validation(self):
593
+ def with_adapter_validation(self) -> "CommandBuilder":
471
594
  """Add adapter configuration validation."""
472
595
  self._validators.append(self._validate_adapter)
473
596
  return self
474
597
 
475
- def with_async_support(self):
598
+ def with_async_support(self) -> "CommandBuilder":
476
599
  """Add async support to command."""
477
600
  self._decorators.append(async_command)
478
601
  return self
479
602
 
480
- def with_error_handling(self):
603
+ def with_error_handling(self) -> "CommandBuilder":
481
604
  """Add error handling decorator."""
482
605
  self._decorators.append(handle_adapter_errors)
483
606
  return self
484
607
 
485
- def with_progress(self, message: str = "Processing..."):
608
+ def with_progress(self, message: str = "Processing...") -> "CommandBuilder":
486
609
  """Add progress spinner."""
487
610
  self._decorators.append(with_progress(message))
488
611
  return self
489
612
 
490
- def build(self, func: Callable) -> Callable:
613
+ def build(self, func: Callable[..., Any]) -> Callable[..., Any]:
491
614
  """Build the decorated function."""
492
615
  decorated_func = func
493
616
  for decorator in reversed(self._decorators):
494
617
  decorated_func = decorator(decorated_func)
495
618
  return decorated_func
496
619
 
497
- def _validate_adapter(self, *args, **kwargs):
620
+ def _validate_adapter(self, *args: Any, **kwargs: Any) -> None:
498
621
  """Validate adapter configuration."""
499
622
  config = CommonPatterns.load_config()
500
623
  default_adapter = config.get("default_adapter", "aitrackdown")
@@ -508,22 +631,22 @@ class CommandBuilder:
508
631
  for issue in issues:
509
632
  console.print(f" • {issue}")
510
633
  console.print("Run 'mcp-ticketer init' to fix configuration")
511
- raise typer.Exit(1)
634
+ raise typer.Exit(1) from None
512
635
 
513
636
 
514
- def create_standard_ticket_command(operation: str):
637
+ def create_standard_ticket_command(operation: str) -> Callable[..., str]:
515
638
  """Create a standard ticket operation command."""
516
639
 
517
640
  def command_template(
518
- ticket_id: Optional[str] = None,
519
- title: Optional[str] = None,
520
- description: Optional[str] = None,
521
- priority: Optional[Priority] = None,
522
- state: Optional[TicketState] = None,
523
- assignee: Optional[str] = None,
524
- tags: Optional[list[str]] = None,
525
- adapter: Optional[str] = None,
526
- ):
641
+ ticket_id: str | None = None,
642
+ title: str | None = None,
643
+ description: str | None = None,
644
+ priority: Priority | None = None,
645
+ state: TicketState | None = None,
646
+ assignee: str | None = None,
647
+ tags: list[str] | None = None,
648
+ adapter: str | None = None,
649
+ ) -> str:
527
650
  """Template for ticket commands."""
528
651
  # Build ticket data
529
652
  ticket_data = {}
@@ -562,11 +685,11 @@ class TicketCommands:
562
685
  @async_command
563
686
  @handle_adapter_errors
564
687
  async def list_tickets(
565
- adapter_instance,
566
- state: Optional[TicketState] = None,
567
- priority: Optional[Priority] = None,
688
+ adapter_instance: Any,
689
+ state: TicketState | None = None,
690
+ priority: Priority | None = None,
568
691
  limit: int = 10,
569
- ):
692
+ ) -> None:
570
693
  """List tickets with filters."""
571
694
  filters = {}
572
695
  if state:
@@ -581,13 +704,13 @@ class TicketCommands:
581
704
  @async_command
582
705
  @handle_adapter_errors
583
706
  async def show_ticket(
584
- adapter_instance, ticket_id: str, show_comments: bool = False
585
- ):
707
+ adapter_instance: Any, ticket_id: str, show_comments: bool = False
708
+ ) -> None:
586
709
  """Show ticket details."""
587
710
  ticket = await adapter_instance.read(ticket_id)
588
711
  if not ticket:
589
712
  console.print(f"[red]✗[/red] Ticket not found: {ticket_id}")
590
- raise typer.Exit(1)
713
+ raise typer.Exit(1) from None
591
714
 
592
715
  comments = None
593
716
  if show_comments:
@@ -598,11 +721,11 @@ class TicketCommands:
598
721
  @staticmethod
599
722
  def create_ticket(
600
723
  title: str,
601
- description: Optional[str] = None,
724
+ description: str | None = None,
602
725
  priority: Priority = Priority.MEDIUM,
603
- tags: Optional[list[str]] = None,
604
- assignee: Optional[str] = None,
605
- adapter: Optional[str] = None,
726
+ tags: list[str] | None = None,
727
+ assignee: str | None = None,
728
+ adapter: str | None = None,
606
729
  ) -> str:
607
730
  """Create a new ticket."""
608
731
  ticket_data = {
@@ -617,19 +740,19 @@ class TicketCommands:
617
740
 
618
741
  @staticmethod
619
742
  def update_ticket(
620
- ticket_id: str, updates: dict[str, Any], adapter: Optional[str] = None
743
+ ticket_id: str, updates: dict[str, Any], adapter: str | None = None
621
744
  ) -> str:
622
745
  """Update a ticket."""
623
746
  if not updates:
624
747
  console.print("[yellow]No updates specified[/yellow]")
625
- raise typer.Exit(1)
748
+ raise typer.Exit(1) from None
626
749
 
627
750
  updates["ticket_id"] = ticket_id
628
751
  return CommonPatterns.queue_operation(updates, "update", adapter)
629
752
 
630
753
  @staticmethod
631
754
  def transition_ticket(
632
- ticket_id: str, state: TicketState, adapter: Optional[str] = None
755
+ ticket_id: str, state: TicketState, adapter: str | None = None
633
756
  ) -> str:
634
757
  """Transition ticket state."""
635
758
  ticket_data = {
@@ -1,16 +1,79 @@
1
1
  """Core models and abstractions for MCP Ticketer."""
2
2
 
3
3
  from .adapter import BaseAdapter
4
- from .models import Comment, Epic, Priority, Task, TicketState, TicketType
4
+ from .instructions import (
5
+ InstructionsError,
6
+ InstructionsNotFoundError,
7
+ InstructionsValidationError,
8
+ TicketInstructionsManager,
9
+ get_instructions,
10
+ )
11
+ from .milestone_manager import MilestoneManager
12
+ from .models import (
13
+ Attachment,
14
+ Comment,
15
+ Epic,
16
+ Milestone,
17
+ Priority,
18
+ Project,
19
+ ProjectScope,
20
+ ProjectState,
21
+ ProjectStatistics,
22
+ ProjectUpdate,
23
+ ProjectUpdateHealth,
24
+ ProjectVisibility,
25
+ Task,
26
+ TicketState,
27
+ TicketType,
28
+ )
29
+ from .project_utils import (
30
+ epic_to_project,
31
+ project_to_epic,
32
+ )
5
33
  from .registry import AdapterRegistry
34
+ from .state_matcher import (
35
+ SemanticStateMatcher,
36
+ StateMatchResult,
37
+ ValidationResult,
38
+ get_state_matcher,
39
+ )
6
40
 
7
41
  __all__ = [
42
+ # Core ticket models
8
43
  "Epic",
9
44
  "Task",
10
45
  "Comment",
46
+ "Attachment",
47
+ "Milestone",
48
+ # Project models
49
+ "Project",
50
+ "ProjectScope",
51
+ "ProjectState",
52
+ "ProjectStatistics",
53
+ "ProjectVisibility",
54
+ "ProjectUpdate",
55
+ "ProjectUpdateHealth",
56
+ # Project utilities
57
+ "epic_to_project",
58
+ "project_to_epic",
59
+ # Enums
11
60
  "TicketState",
12
61
  "Priority",
13
62
  "TicketType",
63
+ # Adapters
14
64
  "BaseAdapter",
15
65
  "AdapterRegistry",
66
+ # Managers
67
+ "MilestoneManager",
68
+ "TicketInstructionsManager",
69
+ # Instructions
70
+ "InstructionsError",
71
+ "InstructionsNotFoundError",
72
+ "InstructionsValidationError",
73
+ "get_instructions",
74
+ # State matching
75
+ "SemanticStateMatcher",
76
+ "StateMatchResult",
77
+ "ValidationResult",
78
+ "get_state_matcher",
16
79
  ]