mcp-ticketer 0.2.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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +58 -16
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +421 -0
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1284
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -1895
- mcp_ticketer-0.2.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.2.0.dist-info/RECORD +0 -58
- mcp_ticketer-0.2.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.2.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,
|
|
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 =
|
|
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,
|
|
102
|
-
legacy_config["adapters"][name] = adapter_config.model_dump(
|
|
103
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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[...,
|
|
319
|
-
"""
|
|
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
|
-
"""
|
|
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:
|
|
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(
|
|
340
|
-
|
|
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(
|
|
359
|
-
|
|
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
|
-
"""
|
|
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) ->
|
|
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:
|
|
519
|
-
title:
|
|
520
|
-
description:
|
|
521
|
-
priority:
|
|
522
|
-
state:
|
|
523
|
-
assignee:
|
|
524
|
-
tags:
|
|
525
|
-
adapter:
|
|
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:
|
|
567
|
-
priority:
|
|
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:
|
|
724
|
+
description: str | None = None,
|
|
602
725
|
priority: Priority = Priority.MEDIUM,
|
|
603
|
-
tags:
|
|
604
|
-
assignee:
|
|
605
|
-
adapter:
|
|
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:
|
|
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:
|
|
755
|
+
ticket_id: str, state: TicketState, adapter: str | None = None
|
|
633
756
|
) -> str:
|
|
634
757
|
"""Transition ticket state."""
|
|
635
758
|
ticket_data = {
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -1,16 +1,79 @@
|
|
|
1
1
|
"""Core models and abstractions for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
from .adapter import BaseAdapter
|
|
4
|
-
from .
|
|
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
|
]
|