mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +7 -7
- mcp_ticketer/__version__.py +4 -2
- mcp_ticketer/adapters/__init__.py +4 -4
- mcp_ticketer/adapters/aitrackdown.py +66 -49
- mcp_ticketer/adapters/github.py +192 -125
- mcp_ticketer/adapters/hybrid.py +99 -53
- mcp_ticketer/adapters/jira.py +161 -151
- mcp_ticketer/adapters/linear.py +396 -246
- mcp_ticketer/cache/__init__.py +1 -1
- mcp_ticketer/cache/memory.py +15 -16
- mcp_ticketer/cli/__init__.py +1 -1
- mcp_ticketer/cli/configure.py +69 -93
- mcp_ticketer/cli/discover.py +43 -35
- mcp_ticketer/cli/main.py +283 -298
- mcp_ticketer/cli/mcp_configure.py +39 -15
- mcp_ticketer/cli/migrate_config.py +11 -13
- mcp_ticketer/cli/queue_commands.py +21 -58
- mcp_ticketer/cli/utils.py +121 -66
- mcp_ticketer/core/__init__.py +2 -2
- mcp_ticketer/core/adapter.py +46 -39
- mcp_ticketer/core/config.py +128 -92
- mcp_ticketer/core/env_discovery.py +69 -37
- mcp_ticketer/core/http_client.py +57 -40
- mcp_ticketer/core/mappers.py +98 -54
- mcp_ticketer/core/models.py +38 -24
- mcp_ticketer/core/project_config.py +145 -80
- mcp_ticketer/core/registry.py +16 -16
- mcp_ticketer/mcp/__init__.py +1 -1
- mcp_ticketer/mcp/server.py +199 -145
- mcp_ticketer/queue/__init__.py +2 -2
- mcp_ticketer/queue/__main__.py +1 -1
- mcp_ticketer/queue/manager.py +30 -26
- mcp_ticketer/queue/queue.py +147 -85
- mcp_ticketer/queue/run_worker.py +2 -3
- mcp_ticketer/queue/worker.py +55 -40
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
- mcp_ticketer-0.1.23.dist-info/RECORD +42 -0
- mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/utils.py
CHANGED
|
@@ -2,71 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
+
import logging
|
|
5
6
|
import os
|
|
6
|
-
from typing import Optional, Dict, Any, Callable, TypeVar, List
|
|
7
|
-
from pathlib import Path
|
|
8
7
|
from functools import wraps
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
9
10
|
|
|
10
11
|
import typer
|
|
11
12
|
from rich.console import Console
|
|
12
|
-
from rich.table import Table
|
|
13
|
-
from rich.panel import Panel
|
|
14
13
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
from rich.table import Table
|
|
15
15
|
|
|
16
|
-
from ..core import
|
|
17
|
-
from ..
|
|
18
|
-
from ..queue import Queue, WorkerManager, QueueStatus
|
|
16
|
+
from ..core import AdapterRegistry, Priority, Task, TicketState
|
|
17
|
+
from ..queue import Queue, QueueStatus, WorkerManager
|
|
19
18
|
|
|
20
19
|
# Type variable for async functions
|
|
21
|
-
T = TypeVar(
|
|
20
|
+
T = TypeVar("T")
|
|
22
21
|
|
|
23
22
|
console = Console()
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class CommonPatterns:
|
|
27
27
|
"""Common CLI patterns and utilities."""
|
|
28
28
|
|
|
29
|
-
# Configuration file management
|
|
30
|
-
CONFIG_FILE = Path.
|
|
29
|
+
# Configuration file management - PROJECT-LOCAL ONLY
|
|
30
|
+
CONFIG_FILE = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
31
31
|
|
|
32
32
|
@staticmethod
|
|
33
33
|
def load_config() -> dict:
|
|
34
|
-
"""Load configuration from file.
|
|
34
|
+
"""Load configuration from project-local config file ONLY.
|
|
35
|
+
|
|
36
|
+
SECURITY: This method ONLY reads from the current project directory
|
|
37
|
+
to prevent configuration leakage across projects. It will NEVER read
|
|
38
|
+
from user home directory or system-wide locations.
|
|
35
39
|
|
|
36
40
|
Resolution order:
|
|
37
41
|
1. Project-specific config (.mcp-ticketer/config.json in cwd)
|
|
38
|
-
2.
|
|
42
|
+
2. Default to aitrackdown adapter
|
|
39
43
|
|
|
40
44
|
Returns:
|
|
41
|
-
Configuration dictionary
|
|
45
|
+
Configuration dictionary with adapter and config keys.
|
|
46
|
+
Defaults to aitrackdown if no local config exists.
|
|
47
|
+
|
|
42
48
|
"""
|
|
43
|
-
#
|
|
49
|
+
# ONLY check project-specific config in current working directory
|
|
44
50
|
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
45
51
|
if project_config.exists():
|
|
52
|
+
# Validate that config file is actually in project directory
|
|
46
53
|
try:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.print(f"[yellow]Warning: Could not load global config: {e}[/yellow]")
|
|
54
|
+
if not project_config.resolve().is_relative_to(Path.cwd().resolve()):
|
|
55
|
+
logger.error(
|
|
56
|
+
f"Security violation: Config file {project_config} "
|
|
57
|
+
"is not within project directory"
|
|
58
|
+
)
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Security violation: Config file {project_config} "
|
|
61
|
+
"is not within project directory"
|
|
62
|
+
)
|
|
63
|
+
except (ValueError, RuntimeError):
|
|
64
|
+
# is_relative_to may raise ValueError in some cases
|
|
65
|
+
pass
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
try:
|
|
68
|
+
with open(project_config) as f:
|
|
69
|
+
config = json.load(f)
|
|
70
|
+
logger.info(
|
|
71
|
+
f"Loaded configuration from project-local: {project_config}"
|
|
72
|
+
)
|
|
73
|
+
return config
|
|
74
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
75
|
+
logger.warning(f"Could not load project config: {e}, using defaults")
|
|
76
|
+
console.print(
|
|
77
|
+
f"[yellow]Warning: Could not load project config: {e}[/yellow]"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Default to aitrackdown with local base path
|
|
81
|
+
logger.info("No project-local config found, defaulting to aitrackdown adapter")
|
|
62
82
|
return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
|
|
63
83
|
|
|
64
84
|
@staticmethod
|
|
65
85
|
def save_config(config: dict) -> None:
|
|
66
|
-
"""Save configuration to file.
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
"""Save configuration to project-local config file ONLY.
|
|
87
|
+
|
|
88
|
+
SECURITY: This method ONLY saves to the current project directory
|
|
89
|
+
to prevent configuration leakage across projects.
|
|
90
|
+
"""
|
|
91
|
+
project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
|
|
92
|
+
project_config.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
with open(project_config, "w") as f:
|
|
69
94
|
json.dump(config, f, indent=2)
|
|
95
|
+
logger.info(f"Saved configuration to project-local: {project_config}")
|
|
70
96
|
|
|
71
97
|
@staticmethod
|
|
72
98
|
def merge_config(updates: dict) -> dict:
|
|
@@ -89,7 +115,9 @@ class CommonPatterns:
|
|
|
89
115
|
return config
|
|
90
116
|
|
|
91
117
|
@staticmethod
|
|
92
|
-
def get_adapter(
|
|
118
|
+
def get_adapter(
|
|
119
|
+
override_adapter: Optional[str] = None, override_config: Optional[dict] = None
|
|
120
|
+
):
|
|
93
121
|
"""Get configured adapter instance with environment variable support."""
|
|
94
122
|
config = CommonPatterns.load_config()
|
|
95
123
|
|
|
@@ -138,10 +166,10 @@ class CommonPatterns:
|
|
|
138
166
|
|
|
139
167
|
@staticmethod
|
|
140
168
|
def queue_operation(
|
|
141
|
-
ticket_data:
|
|
169
|
+
ticket_data: dict[str, Any],
|
|
142
170
|
operation: str,
|
|
143
171
|
adapter_name: Optional[str] = None,
|
|
144
|
-
show_progress: bool = True
|
|
172
|
+
show_progress: bool = True,
|
|
145
173
|
) -> str:
|
|
146
174
|
"""Queue an operation and optionally start the worker."""
|
|
147
175
|
if not adapter_name:
|
|
@@ -151,14 +179,14 @@ class CommonPatterns:
|
|
|
151
179
|
# Add to queue
|
|
152
180
|
queue = Queue()
|
|
153
181
|
queue_id = queue.add(
|
|
154
|
-
ticket_data=ticket_data,
|
|
155
|
-
adapter=adapter_name,
|
|
156
|
-
operation=operation
|
|
182
|
+
ticket_data=ticket_data, adapter=adapter_name, operation=operation
|
|
157
183
|
)
|
|
158
184
|
|
|
159
185
|
if show_progress:
|
|
160
186
|
console.print(f"[green]✓[/green] Queued {operation}: {queue_id}")
|
|
161
|
-
console.print(
|
|
187
|
+
console.print(
|
|
188
|
+
"[dim]Use 'mcp-ticketer check {queue_id}' to check progress[/dim]"
|
|
189
|
+
)
|
|
162
190
|
|
|
163
191
|
# Start worker if needed
|
|
164
192
|
manager = WorkerManager()
|
|
@@ -169,7 +197,7 @@ class CommonPatterns:
|
|
|
169
197
|
return queue_id
|
|
170
198
|
|
|
171
199
|
@staticmethod
|
|
172
|
-
def display_ticket_table(tickets:
|
|
200
|
+
def display_ticket_table(tickets: list[Task], title: str = "Tickets") -> None:
|
|
173
201
|
"""Display tickets in a formatted table."""
|
|
174
202
|
if not tickets:
|
|
175
203
|
console.print("[yellow]No tickets found[/yellow]")
|
|
@@ -194,7 +222,7 @@ class CommonPatterns:
|
|
|
194
222
|
console.print(table)
|
|
195
223
|
|
|
196
224
|
@staticmethod
|
|
197
|
-
def display_ticket_details(ticket: Task, comments: Optional[
|
|
225
|
+
def display_ticket_details(ticket: Task, comments: Optional[list] = None) -> None:
|
|
198
226
|
"""Display detailed ticket information."""
|
|
199
227
|
console.print(f"\n[bold]Ticket: {ticket.id}[/bold]")
|
|
200
228
|
console.print(f"Title: {ticket.title}")
|
|
@@ -202,7 +230,7 @@ class CommonPatterns:
|
|
|
202
230
|
console.print(f"Priority: [yellow]{ticket.priority}[/yellow]")
|
|
203
231
|
|
|
204
232
|
if ticket.description:
|
|
205
|
-
console.print(
|
|
233
|
+
console.print("\n[dim]Description:[/dim]")
|
|
206
234
|
console.print(ticket.description)
|
|
207
235
|
|
|
208
236
|
if ticket.tags:
|
|
@@ -238,32 +266,41 @@ class CommonPatterns:
|
|
|
238
266
|
# Show worker status
|
|
239
267
|
worker_status = manager.get_status()
|
|
240
268
|
if worker_status["running"]:
|
|
241
|
-
console.print(
|
|
269
|
+
console.print(
|
|
270
|
+
f"\n[green]● Worker is running[/green] (PID: {worker_status.get('pid')})"
|
|
271
|
+
)
|
|
242
272
|
else:
|
|
243
273
|
console.print("\n[red]○ Worker is not running[/red]")
|
|
244
274
|
if pending > 0:
|
|
245
|
-
console.print(
|
|
275
|
+
console.print(
|
|
276
|
+
"[yellow]Note: There are pending items. Start worker with 'mcp-ticketer queue start'[/yellow]"
|
|
277
|
+
)
|
|
246
278
|
|
|
247
279
|
|
|
248
280
|
def async_command(f: Callable[..., T]) -> Callable[..., T]:
|
|
249
281
|
"""Decorator to handle async CLI commands."""
|
|
282
|
+
|
|
250
283
|
@wraps(f)
|
|
251
284
|
def wrapper(*args, **kwargs):
|
|
252
285
|
return asyncio.run(f(*args, **kwargs))
|
|
286
|
+
|
|
253
287
|
return wrapper
|
|
254
288
|
|
|
255
289
|
|
|
256
290
|
def with_adapter(f: Callable) -> Callable:
|
|
257
291
|
"""Decorator to inject adapter instance into CLI commands."""
|
|
292
|
+
|
|
258
293
|
@wraps(f)
|
|
259
294
|
def wrapper(adapter: Optional[str] = None, *args, **kwargs):
|
|
260
295
|
adapter_instance = CommonPatterns.get_adapter(override_adapter=adapter)
|
|
261
296
|
return f(adapter_instance, *args, **kwargs)
|
|
297
|
+
|
|
262
298
|
return wrapper
|
|
263
299
|
|
|
264
300
|
|
|
265
301
|
def with_progress(message: str = "Processing..."):
|
|
266
302
|
"""Decorator to show progress spinner for long-running operations."""
|
|
303
|
+
|
|
267
304
|
def decorator(f: Callable) -> Callable:
|
|
268
305
|
@wraps(f)
|
|
269
306
|
def wrapper(*args, **kwargs):
|
|
@@ -274,12 +311,15 @@ def with_progress(message: str = "Processing..."):
|
|
|
274
311
|
) as progress:
|
|
275
312
|
progress.add_task(description=message, total=None)
|
|
276
313
|
return f(*args, **kwargs)
|
|
314
|
+
|
|
277
315
|
return wrapper
|
|
316
|
+
|
|
278
317
|
return decorator
|
|
279
318
|
|
|
280
319
|
|
|
281
320
|
def validate_required_fields(**field_map):
|
|
282
321
|
"""Decorator to validate required fields are provided."""
|
|
322
|
+
|
|
283
323
|
def decorator(f: Callable) -> Callable:
|
|
284
324
|
@wraps(f)
|
|
285
325
|
def wrapper(*args, **kwargs):
|
|
@@ -289,16 +329,21 @@ def validate_required_fields(**field_map):
|
|
|
289
329
|
missing_fields.append(display_name)
|
|
290
330
|
|
|
291
331
|
if missing_fields:
|
|
292
|
-
console.print(
|
|
332
|
+
console.print(
|
|
333
|
+
f"[red]Error:[/red] Missing required fields: {', '.join(missing_fields)}"
|
|
334
|
+
)
|
|
293
335
|
raise typer.Exit(1)
|
|
294
336
|
|
|
295
337
|
return f(*args, **kwargs)
|
|
338
|
+
|
|
296
339
|
return wrapper
|
|
340
|
+
|
|
297
341
|
return decorator
|
|
298
342
|
|
|
299
343
|
|
|
300
344
|
def handle_adapter_errors(f: Callable) -> Callable:
|
|
301
345
|
"""Decorator to handle common adapter errors gracefully."""
|
|
346
|
+
|
|
302
347
|
@wraps(f)
|
|
303
348
|
def wrapper(*args, **kwargs):
|
|
304
349
|
try:
|
|
@@ -314,6 +359,7 @@ def handle_adapter_errors(f: Callable) -> Callable:
|
|
|
314
359
|
except Exception as e:
|
|
315
360
|
console.print(f"[red]Unexpected Error:[/red] {e}")
|
|
316
361
|
raise typer.Exit(1)
|
|
362
|
+
|
|
317
363
|
return wrapper
|
|
318
364
|
|
|
319
365
|
|
|
@@ -321,7 +367,7 @@ class ConfigValidator:
|
|
|
321
367
|
"""Configuration validation utilities."""
|
|
322
368
|
|
|
323
369
|
@staticmethod
|
|
324
|
-
def validate_adapter_config(adapter_type: str, config: dict) ->
|
|
370
|
+
def validate_adapter_config(adapter_type: str, config: dict) -> list[str]:
|
|
325
371
|
"""Validate adapter configuration and return list of issues."""
|
|
326
372
|
issues = []
|
|
327
373
|
|
|
@@ -350,7 +396,9 @@ class ConfigValidator:
|
|
|
350
396
|
if not config.get(field):
|
|
351
397
|
env_var = ConfigValidator._get_env_var(adapter_type, field)
|
|
352
398
|
if env_var and not os.getenv(env_var):
|
|
353
|
-
issues.append(
|
|
399
|
+
issues.append(
|
|
400
|
+
f"Missing {display_name} (config.{field} or {env_var})"
|
|
401
|
+
)
|
|
354
402
|
|
|
355
403
|
return issues
|
|
356
404
|
|
|
@@ -358,8 +406,16 @@ class ConfigValidator:
|
|
|
358
406
|
def _get_env_var(adapter_type: str, field: str) -> Optional[str]:
|
|
359
407
|
"""Get corresponding environment variable name for a config field."""
|
|
360
408
|
env_mapping = {
|
|
361
|
-
"github": {
|
|
362
|
-
|
|
409
|
+
"github": {
|
|
410
|
+
"token": "GITHUB_TOKEN",
|
|
411
|
+
"owner": "GITHUB_OWNER",
|
|
412
|
+
"repo": "GITHUB_REPO",
|
|
413
|
+
},
|
|
414
|
+
"jira": {
|
|
415
|
+
"api_token": "JIRA_API_TOKEN",
|
|
416
|
+
"email": "JIRA_EMAIL",
|
|
417
|
+
"server": "JIRA_SERVER",
|
|
418
|
+
},
|
|
363
419
|
"linear": {"api_key": "LINEAR_API_KEY"},
|
|
364
420
|
}
|
|
365
421
|
return env_mapping.get(adapter_type, {}).get(field)
|
|
@@ -406,7 +462,9 @@ class CommandBuilder:
|
|
|
406
462
|
default_adapter = config.get("default_adapter", "aitrackdown")
|
|
407
463
|
adapter_config = config.get("adapters", {}).get(default_adapter, {})
|
|
408
464
|
|
|
409
|
-
issues = ConfigValidator.validate_adapter_config(
|
|
465
|
+
issues = ConfigValidator.validate_adapter_config(
|
|
466
|
+
default_adapter, adapter_config
|
|
467
|
+
)
|
|
410
468
|
if issues:
|
|
411
469
|
console.print("[red]Configuration Issues:[/red]")
|
|
412
470
|
for issue in issues:
|
|
@@ -417,6 +475,7 @@ class CommandBuilder:
|
|
|
417
475
|
|
|
418
476
|
def create_standard_ticket_command(operation: str):
|
|
419
477
|
"""Create a standard ticket operation command."""
|
|
478
|
+
|
|
420
479
|
def command_template(
|
|
421
480
|
ticket_id: Optional[str] = None,
|
|
422
481
|
title: Optional[str] = None,
|
|
@@ -424,7 +483,7 @@ def create_standard_ticket_command(operation: str):
|
|
|
424
483
|
priority: Optional[Priority] = None,
|
|
425
484
|
state: Optional[TicketState] = None,
|
|
426
485
|
assignee: Optional[str] = None,
|
|
427
|
-
tags: Optional[
|
|
486
|
+
tags: Optional[list[str]] = None,
|
|
428
487
|
adapter: Optional[str] = None,
|
|
429
488
|
):
|
|
430
489
|
"""Template for ticket commands."""
|
|
@@ -437,9 +496,11 @@ def create_standard_ticket_command(operation: str):
|
|
|
437
496
|
if description:
|
|
438
497
|
ticket_data["description"] = description
|
|
439
498
|
if priority:
|
|
440
|
-
ticket_data["priority"] =
|
|
499
|
+
ticket_data["priority"] = (
|
|
500
|
+
priority.value if hasattr(priority, "value") else priority
|
|
501
|
+
)
|
|
441
502
|
if state:
|
|
442
|
-
ticket_data["state"] = state.value if hasattr(state,
|
|
503
|
+
ticket_data["state"] = state.value if hasattr(state, "value") else state
|
|
443
504
|
if assignee:
|
|
444
505
|
ticket_data["assignee"] = assignee
|
|
445
506
|
if tags:
|
|
@@ -466,7 +527,7 @@ class TicketCommands:
|
|
|
466
527
|
adapter_instance,
|
|
467
528
|
state: Optional[TicketState] = None,
|
|
468
529
|
priority: Optional[Priority] = None,
|
|
469
|
-
limit: int = 10
|
|
530
|
+
limit: int = 10,
|
|
470
531
|
):
|
|
471
532
|
"""List tickets with filters."""
|
|
472
533
|
filters = {}
|
|
@@ -482,9 +543,7 @@ class TicketCommands:
|
|
|
482
543
|
@async_command
|
|
483
544
|
@handle_adapter_errors
|
|
484
545
|
async def show_ticket(
|
|
485
|
-
adapter_instance,
|
|
486
|
-
ticket_id: str,
|
|
487
|
-
show_comments: bool = False
|
|
546
|
+
adapter_instance, ticket_id: str, show_comments: bool = False
|
|
488
547
|
):
|
|
489
548
|
"""Show ticket details."""
|
|
490
549
|
ticket = await adapter_instance.read(ticket_id)
|
|
@@ -503,9 +562,9 @@ class TicketCommands:
|
|
|
503
562
|
title: str,
|
|
504
563
|
description: Optional[str] = None,
|
|
505
564
|
priority: Priority = Priority.MEDIUM,
|
|
506
|
-
tags: Optional[
|
|
565
|
+
tags: Optional[list[str]] = None,
|
|
507
566
|
assignee: Optional[str] = None,
|
|
508
|
-
adapter: Optional[str] = None
|
|
567
|
+
adapter: Optional[str] = None,
|
|
509
568
|
) -> str:
|
|
510
569
|
"""Create a new ticket."""
|
|
511
570
|
ticket_data = {
|
|
@@ -520,9 +579,7 @@ class TicketCommands:
|
|
|
520
579
|
|
|
521
580
|
@staticmethod
|
|
522
581
|
def update_ticket(
|
|
523
|
-
ticket_id: str,
|
|
524
|
-
updates: Dict[str, Any],
|
|
525
|
-
adapter: Optional[str] = None
|
|
582
|
+
ticket_id: str, updates: dict[str, Any], adapter: Optional[str] = None
|
|
526
583
|
) -> str:
|
|
527
584
|
"""Update a ticket."""
|
|
528
585
|
if not updates:
|
|
@@ -534,14 +591,12 @@ class TicketCommands:
|
|
|
534
591
|
|
|
535
592
|
@staticmethod
|
|
536
593
|
def transition_ticket(
|
|
537
|
-
ticket_id: str,
|
|
538
|
-
state: TicketState,
|
|
539
|
-
adapter: Optional[str] = None
|
|
594
|
+
ticket_id: str, state: TicketState, adapter: Optional[str] = None
|
|
540
595
|
) -> str:
|
|
541
596
|
"""Transition ticket state."""
|
|
542
597
|
ticket_data = {
|
|
543
598
|
"ticket_id": ticket_id,
|
|
544
|
-
"state": state.value if hasattr(state,
|
|
599
|
+
"state": state.value if hasattr(state, "value") else state,
|
|
545
600
|
}
|
|
546
601
|
|
|
547
|
-
return CommonPatterns.queue_operation(ticket_data, "transition", adapter)
|
|
602
|
+
return CommonPatterns.queue_operation(ticket_data, "transition", adapter)
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Core models and abstractions for MCP Ticketer."""
|
|
2
2
|
|
|
3
|
-
from .models import Epic, Task, Comment, TicketState, Priority, TicketType
|
|
4
3
|
from .adapter import BaseAdapter
|
|
4
|
+
from .models import Comment, Epic, Priority, Task, TicketState, TicketType
|
|
5
5
|
from .registry import AdapterRegistry
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
@@ -13,4 +13,4 @@ __all__ = [
|
|
|
13
13
|
"TicketType",
|
|
14
14
|
"BaseAdapter",
|
|
15
15
|
"AdapterRegistry",
|
|
16
|
-
]
|
|
16
|
+
]
|