mcp-ticketer 0.3.1__py3-none-any.whl → 0.3.2__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/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +12 -15
- mcp_ticketer/adapters/github.py +7 -4
- mcp_ticketer/adapters/jira.py +23 -22
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +88 -89
- mcp_ticketer/adapters/linear/client.py +71 -52
- mcp_ticketer/adapters/linear/mappers.py +88 -68
- mcp_ticketer/adapters/linear/queries.py +28 -7
- mcp_ticketer/adapters/linear/types.py +57 -50
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/cli/adapter_diagnostics.py +86 -51
- mcp_ticketer/cli/diagnostics.py +165 -72
- mcp_ticketer/cli/linear_commands.py +156 -113
- mcp_ticketer/cli/main.py +153 -82
- mcp_ticketer/cli/simple_health.py +73 -45
- mcp_ticketer/cli/utils.py +15 -10
- mcp_ticketer/core/config.py +23 -19
- mcp_ticketer/core/env_discovery.py +5 -4
- mcp_ticketer/core/env_loader.py +109 -86
- mcp_ticketer/core/exceptions.py +20 -18
- mcp_ticketer/core/models.py +9 -0
- mcp_ticketer/core/project_config.py +1 -1
- mcp_ticketer/mcp/server.py +294 -139
- mcp_ticketer/queue/health_monitor.py +152 -121
- mcp_ticketer/queue/manager.py +11 -4
- mcp_ticketer/queue/queue.py +15 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +190 -132
- mcp_ticketer/queue/worker.py +54 -25
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/METADATA +1 -1
- mcp_ticketer-0.3.2.dist-info/RECORD +59 -0
- mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/main.py
CHANGED
|
@@ -12,15 +12,15 @@ from dotenv import load_dotenv
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
|
+
# Import adapters module to trigger registration
|
|
16
|
+
import mcp_ticketer.adapters # noqa: F401
|
|
17
|
+
|
|
15
18
|
from ..__version__ import __version__
|
|
16
19
|
from ..core import AdapterRegistry, Priority, TicketState
|
|
17
|
-
from ..core.models import
|
|
20
|
+
from ..core.models import Comment, SearchQuery
|
|
18
21
|
from ..queue import Queue, QueueStatus, WorkerManager
|
|
19
|
-
from ..queue.health_monitor import
|
|
22
|
+
from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
|
|
20
23
|
from ..queue.ticket_registry import TicketRegistry
|
|
21
|
-
|
|
22
|
-
# Import adapters module to trigger registration
|
|
23
|
-
import mcp_ticketer.adapters # noqa: F401
|
|
24
24
|
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
25
25
|
from .diagnostics import run_diagnostics
|
|
26
26
|
from .discover import app as discover_app
|
|
@@ -150,8 +150,8 @@ def _discover_from_env_files() -> Optional[str]:
|
|
|
150
150
|
|
|
151
151
|
Returns:
|
|
152
152
|
Adapter name if discovered, None otherwise
|
|
153
|
+
|
|
153
154
|
"""
|
|
154
|
-
import os
|
|
155
155
|
import logging
|
|
156
156
|
from pathlib import Path
|
|
157
157
|
|
|
@@ -166,12 +166,12 @@ def _discover_from_env_files() -> Optional[str]:
|
|
|
166
166
|
try:
|
|
167
167
|
# Simple .env parsing (key=value format)
|
|
168
168
|
env_vars = {}
|
|
169
|
-
with open(env_path
|
|
169
|
+
with open(env_path) as f:
|
|
170
170
|
for line in f:
|
|
171
171
|
line = line.strip()
|
|
172
|
-
if line and not line.startswith(
|
|
173
|
-
key, value = line.split(
|
|
174
|
-
env_vars[key.strip()] = value.strip().strip(
|
|
172
|
+
if line and not line.startswith("#") and "=" in line:
|
|
173
|
+
key, value = line.split("=", 1)
|
|
174
|
+
env_vars[key.strip()] = value.strip().strip("\"'")
|
|
175
175
|
|
|
176
176
|
# Check for adapter-specific variables
|
|
177
177
|
if env_vars.get("LINEAR_API_KEY"):
|
|
@@ -195,6 +195,7 @@ def _save_adapter_to_config(adapter_name: str) -> None:
|
|
|
195
195
|
|
|
196
196
|
Args:
|
|
197
197
|
adapter_name: Name of the adapter to save as default
|
|
198
|
+
|
|
198
199
|
"""
|
|
199
200
|
import logging
|
|
200
201
|
|
|
@@ -325,6 +326,7 @@ def _prompt_for_adapter_selection(console: Console) -> str:
|
|
|
325
326
|
|
|
326
327
|
Returns:
|
|
327
328
|
Selected adapter type
|
|
329
|
+
|
|
328
330
|
"""
|
|
329
331
|
console.print("\n[bold blue]🚀 MCP Ticketer Setup[/bold blue]")
|
|
330
332
|
console.print("Choose which ticket system you want to connect to:\n")
|
|
@@ -335,26 +337,26 @@ def _prompt_for_adapter_selection(console: Console) -> str:
|
|
|
335
337
|
"name": "linear",
|
|
336
338
|
"title": "Linear",
|
|
337
339
|
"description": "Modern project management (linear.app)",
|
|
338
|
-
"requirements": "API key and team ID"
|
|
340
|
+
"requirements": "API key and team ID",
|
|
339
341
|
},
|
|
340
342
|
{
|
|
341
343
|
"name": "github",
|
|
342
344
|
"title": "GitHub Issues",
|
|
343
345
|
"description": "GitHub repository issues",
|
|
344
|
-
"requirements": "Personal access token, owner, and repo"
|
|
346
|
+
"requirements": "Personal access token, owner, and repo",
|
|
345
347
|
},
|
|
346
348
|
{
|
|
347
349
|
"name": "jira",
|
|
348
350
|
"title": "JIRA",
|
|
349
351
|
"description": "Atlassian JIRA project management",
|
|
350
|
-
"requirements": "Server URL, email, and API token"
|
|
352
|
+
"requirements": "Server URL, email, and API token",
|
|
351
353
|
},
|
|
352
354
|
{
|
|
353
355
|
"name": "aitrackdown",
|
|
354
356
|
"title": "Local Files (AITrackdown)",
|
|
355
357
|
"description": "Store tickets in local files (no external service)",
|
|
356
|
-
"requirements": "None - works offline"
|
|
357
|
-
}
|
|
358
|
+
"requirements": "None - works offline",
|
|
359
|
+
},
|
|
358
360
|
]
|
|
359
361
|
|
|
360
362
|
# Display options
|
|
@@ -366,17 +368,17 @@ def _prompt_for_adapter_selection(console: Console) -> str:
|
|
|
366
368
|
# Get user selection
|
|
367
369
|
while True:
|
|
368
370
|
try:
|
|
369
|
-
choice = typer.prompt(
|
|
370
|
-
"Select adapter (1-4)",
|
|
371
|
-
type=int,
|
|
372
|
-
default=1
|
|
373
|
-
)
|
|
371
|
+
choice = typer.prompt("Select adapter (1-4)", type=int, default=1)
|
|
374
372
|
if 1 <= choice <= len(adapters):
|
|
375
373
|
selected_adapter = adapters[choice - 1]
|
|
376
|
-
console.print(
|
|
374
|
+
console.print(
|
|
375
|
+
f"\n[green]✓ Selected: {selected_adapter['title']}[/green]"
|
|
376
|
+
)
|
|
377
377
|
return selected_adapter["name"]
|
|
378
378
|
else:
|
|
379
|
-
console.print(
|
|
379
|
+
console.print(
|
|
380
|
+
f"[red]Please enter a number between 1 and {len(adapters)}[/red]"
|
|
381
|
+
)
|
|
380
382
|
except (ValueError, typer.Abort):
|
|
381
383
|
console.print("[yellow]Setup cancelled.[/yellow]")
|
|
382
384
|
raise typer.Exit(0)
|
|
@@ -447,6 +449,7 @@ def setup(
|
|
|
447
449
|
|
|
448
450
|
# Setup for different project
|
|
449
451
|
mcp-ticketer setup --path /path/to/project
|
|
452
|
+
|
|
450
453
|
"""
|
|
451
454
|
# Call init with all parameters
|
|
452
455
|
init(
|
|
@@ -571,6 +574,7 @@ def init(
|
|
|
571
574
|
|
|
572
575
|
# First try our improved .env configuration loader
|
|
573
576
|
from ..mcp.server import _load_env_configuration
|
|
577
|
+
|
|
574
578
|
env_config = _load_env_configuration()
|
|
575
579
|
|
|
576
580
|
if env_config:
|
|
@@ -580,8 +584,8 @@ def init(
|
|
|
580
584
|
)
|
|
581
585
|
|
|
582
586
|
# Show what was discovered
|
|
583
|
-
console.print(
|
|
584
|
-
console.print(
|
|
587
|
+
console.print("\n[dim]Configuration found in: .env files[/dim]")
|
|
588
|
+
console.print("[dim]Confidence: 100%[/dim]")
|
|
585
589
|
|
|
586
590
|
# Ask user to confirm auto-detected adapter
|
|
587
591
|
if not typer.confirm(
|
|
@@ -640,7 +644,7 @@ def init(
|
|
|
640
644
|
if adapter_type == "aitrackdown":
|
|
641
645
|
config["adapters"]["aitrackdown"] = {
|
|
642
646
|
"type": "aitrackdown",
|
|
643
|
-
"base_path": base_path or ".aitrackdown"
|
|
647
|
+
"base_path": base_path or ".aitrackdown",
|
|
644
648
|
}
|
|
645
649
|
|
|
646
650
|
elif adapter_type == "linear":
|
|
@@ -653,11 +657,12 @@ def init(
|
|
|
653
657
|
if not linear_api_key and not discovered:
|
|
654
658
|
console.print("\n[bold]Linear Configuration[/bold]")
|
|
655
659
|
console.print("You need a Linear API key to connect to Linear.")
|
|
656
|
-
console.print(
|
|
660
|
+
console.print(
|
|
661
|
+
"[dim]Get your API key at: https://linear.app/settings/api[/dim]\n"
|
|
662
|
+
)
|
|
657
663
|
|
|
658
664
|
linear_api_key = typer.prompt(
|
|
659
|
-
"Enter your Linear API key",
|
|
660
|
-
hide_input=True
|
|
665
|
+
"Enter your Linear API key", hide_input=True
|
|
661
666
|
)
|
|
662
667
|
|
|
663
668
|
if linear_api_key:
|
|
@@ -675,8 +680,12 @@ def init(
|
|
|
675
680
|
linear_config["team_id"] = linear_team_id
|
|
676
681
|
|
|
677
682
|
if not linear_config.get("api_key") or not linear_config.get("team_id"):
|
|
678
|
-
console.print(
|
|
679
|
-
|
|
683
|
+
console.print(
|
|
684
|
+
"[red]Error:[/red] Linear requires both API key and team ID"
|
|
685
|
+
)
|
|
686
|
+
console.print(
|
|
687
|
+
"Run 'mcp-ticketer init --adapter linear' with proper credentials"
|
|
688
|
+
)
|
|
680
689
|
raise typer.Exit(1)
|
|
681
690
|
|
|
682
691
|
linear_config["type"] = "linear"
|
|
@@ -704,18 +713,17 @@ def init(
|
|
|
704
713
|
|
|
705
714
|
if not token and not discovered:
|
|
706
715
|
console.print("\nYou need a JIRA API token.")
|
|
707
|
-
console.print(
|
|
708
|
-
|
|
709
|
-
token = typer.prompt(
|
|
710
|
-
"Enter your JIRA API token",
|
|
711
|
-
hide_input=True
|
|
716
|
+
console.print(
|
|
717
|
+
"[dim]Generate one at: https://id.atlassian.com/manage/api-tokens[/dim]\n"
|
|
712
718
|
)
|
|
713
719
|
|
|
720
|
+
token = typer.prompt("Enter your JIRA API token", hide_input=True)
|
|
721
|
+
|
|
714
722
|
if not project and not discovered:
|
|
715
723
|
project = typer.prompt(
|
|
716
724
|
"Default JIRA project key (optional, press Enter to skip)",
|
|
717
725
|
default="",
|
|
718
|
-
show_default=False
|
|
726
|
+
show_default=False,
|
|
719
727
|
)
|
|
720
728
|
|
|
721
729
|
# Validate required fields
|
|
@@ -735,7 +743,7 @@ def init(
|
|
|
735
743
|
"server": server,
|
|
736
744
|
"email": email,
|
|
737
745
|
"api_token": token,
|
|
738
|
-
"type": "jira"
|
|
746
|
+
"type": "jira",
|
|
739
747
|
}
|
|
740
748
|
|
|
741
749
|
if project:
|
|
@@ -755,19 +763,24 @@ def init(
|
|
|
755
763
|
console.print("\n[bold]GitHub Configuration[/bold]")
|
|
756
764
|
console.print("Enter your GitHub repository details.\n")
|
|
757
765
|
|
|
758
|
-
owner = typer.prompt(
|
|
766
|
+
owner = typer.prompt(
|
|
767
|
+
"GitHub repository owner (username or organization)"
|
|
768
|
+
)
|
|
759
769
|
|
|
760
770
|
if not repo and not discovered:
|
|
761
771
|
repo = typer.prompt("GitHub repository name")
|
|
762
772
|
|
|
763
773
|
if not token and not discovered:
|
|
764
774
|
console.print("\nYou need a GitHub Personal Access Token.")
|
|
765
|
-
console.print(
|
|
766
|
-
|
|
775
|
+
console.print(
|
|
776
|
+
"[dim]Create one at: https://github.com/settings/tokens/new[/dim]"
|
|
777
|
+
)
|
|
778
|
+
console.print(
|
|
779
|
+
"[dim]Required scopes: repo (for private repos) or public_repo (for public repos)[/dim]\n"
|
|
780
|
+
)
|
|
767
781
|
|
|
768
782
|
token = typer.prompt(
|
|
769
|
-
"Enter your GitHub Personal Access Token",
|
|
770
|
-
hide_input=True
|
|
783
|
+
"Enter your GitHub Personal Access Token", hide_input=True
|
|
771
784
|
)
|
|
772
785
|
|
|
773
786
|
# Validate required fields
|
|
@@ -780,14 +793,16 @@ def init(
|
|
|
780
793
|
raise typer.Exit(1)
|
|
781
794
|
|
|
782
795
|
if not token:
|
|
783
|
-
console.print(
|
|
796
|
+
console.print(
|
|
797
|
+
"[red]Error:[/red] GitHub Personal Access Token is required"
|
|
798
|
+
)
|
|
784
799
|
raise typer.Exit(1)
|
|
785
800
|
|
|
786
801
|
config["adapters"]["github"] = {
|
|
787
802
|
"owner": owner,
|
|
788
803
|
"repo": repo,
|
|
789
804
|
"token": token,
|
|
790
|
-
"type": "github"
|
|
805
|
+
"type": "github",
|
|
791
806
|
}
|
|
792
807
|
|
|
793
808
|
# 5. Save to appropriate location
|
|
@@ -831,13 +846,16 @@ def init(
|
|
|
831
846
|
_show_next_steps(console, adapter_type, config_file_path)
|
|
832
847
|
|
|
833
848
|
|
|
834
|
-
def _show_next_steps(
|
|
849
|
+
def _show_next_steps(
|
|
850
|
+
console: Console, adapter_type: str, config_file_path: Path
|
|
851
|
+
) -> None:
|
|
835
852
|
"""Show helpful next steps after initialization.
|
|
836
853
|
|
|
837
854
|
Args:
|
|
838
855
|
console: Rich console for output
|
|
839
856
|
adapter_type: Type of adapter that was configured
|
|
840
857
|
config_file_path: Path to the configuration file
|
|
858
|
+
|
|
841
859
|
"""
|
|
842
860
|
console.print("\n[bold green]🎉 Setup Complete![/bold green]")
|
|
843
861
|
console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
|
|
@@ -849,7 +867,9 @@ def _show_next_steps(console: Console, adapter_type: str, config_file_path: Path
|
|
|
849
867
|
console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
|
|
850
868
|
|
|
851
869
|
if adapter_type != "aitrackdown":
|
|
852
|
-
console.print(
|
|
870
|
+
console.print(
|
|
871
|
+
f"\n3. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]"
|
|
872
|
+
)
|
|
853
873
|
|
|
854
874
|
if adapter_type == "linear":
|
|
855
875
|
console.print(" Check your Linear workspace for the new ticket")
|
|
@@ -1155,11 +1175,14 @@ def status_command():
|
|
|
1155
1175
|
|
|
1156
1176
|
@app.command()
|
|
1157
1177
|
def health(
|
|
1158
|
-
auto_repair: bool = typer.Option(
|
|
1159
|
-
|
|
1178
|
+
auto_repair: bool = typer.Option(
|
|
1179
|
+
False, "--auto-repair", help="Attempt automatic repair of issues"
|
|
1180
|
+
),
|
|
1181
|
+
verbose: bool = typer.Option(
|
|
1182
|
+
False, "--verbose", "-v", help="Show detailed health information"
|
|
1183
|
+
),
|
|
1160
1184
|
) -> None:
|
|
1161
1185
|
"""Check queue system health and detect issues immediately."""
|
|
1162
|
-
|
|
1163
1186
|
health_monitor = QueueHealthMonitor()
|
|
1164
1187
|
health = health_monitor.check_health()
|
|
1165
1188
|
|
|
@@ -1168,14 +1191,14 @@ def health(
|
|
|
1168
1191
|
HealthStatus.HEALTHY: "green",
|
|
1169
1192
|
HealthStatus.WARNING: "yellow",
|
|
1170
1193
|
HealthStatus.CRITICAL: "red",
|
|
1171
|
-
HealthStatus.FAILED: "red"
|
|
1194
|
+
HealthStatus.FAILED: "red",
|
|
1172
1195
|
}
|
|
1173
1196
|
|
|
1174
1197
|
status_icon = {
|
|
1175
1198
|
HealthStatus.HEALTHY: "✓",
|
|
1176
1199
|
HealthStatus.WARNING: "⚠️",
|
|
1177
1200
|
HealthStatus.CRITICAL: "🚨",
|
|
1178
|
-
HealthStatus.FAILED: "❌"
|
|
1201
|
+
HealthStatus.FAILED: "❌",
|
|
1179
1202
|
}
|
|
1180
1203
|
|
|
1181
1204
|
color = status_color.get(health["status"], "white")
|
|
@@ -1198,7 +1221,10 @@ def health(
|
|
|
1198
1221
|
console.print("\n[green]✓ No issues detected[/green]")
|
|
1199
1222
|
|
|
1200
1223
|
# Auto-repair if requested
|
|
1201
|
-
if auto_repair and health["status"] in [
|
|
1224
|
+
if auto_repair and health["status"] in [
|
|
1225
|
+
HealthStatus.CRITICAL,
|
|
1226
|
+
HealthStatus.WARNING,
|
|
1227
|
+
]:
|
|
1202
1228
|
console.print("\n[yellow]Attempting automatic repair...[/yellow]")
|
|
1203
1229
|
repair_result = health_monitor.auto_repair()
|
|
1204
1230
|
|
|
@@ -1212,7 +1238,9 @@ def health(
|
|
|
1212
1238
|
new_health = health_monitor.check_health()
|
|
1213
1239
|
new_color = status_color.get(new_health["status"], "white")
|
|
1214
1240
|
new_icon = status_icon.get(new_health["status"], "?")
|
|
1215
|
-
console.print(
|
|
1241
|
+
console.print(
|
|
1242
|
+
f"[{new_color}]{new_icon} Updated Health: {new_health['status'].upper()}[/{new_color}]"
|
|
1243
|
+
)
|
|
1216
1244
|
else:
|
|
1217
1245
|
console.print("[yellow]No repair actions available[/yellow]")
|
|
1218
1246
|
|
|
@@ -1243,7 +1271,6 @@ def create(
|
|
|
1243
1271
|
),
|
|
1244
1272
|
) -> None:
|
|
1245
1273
|
"""Create a new ticket with comprehensive health checks."""
|
|
1246
|
-
|
|
1247
1274
|
# IMMEDIATE HEALTH CHECK - Critical for reliability
|
|
1248
1275
|
health_monitor = QueueHealthMonitor()
|
|
1249
1276
|
health = health_monitor.check_health()
|
|
@@ -1266,13 +1293,21 @@ def create(
|
|
|
1266
1293
|
# Re-check health after repair
|
|
1267
1294
|
health = health_monitor.check_health()
|
|
1268
1295
|
if health["status"] == HealthStatus.CRITICAL:
|
|
1269
|
-
console.print(
|
|
1270
|
-
|
|
1296
|
+
console.print(
|
|
1297
|
+
"[red]❌ Auto-repair failed. Manual intervention required.[/red]"
|
|
1298
|
+
)
|
|
1299
|
+
console.print(
|
|
1300
|
+
"[red]Cannot safely create ticket. Please check system status.[/red]"
|
|
1301
|
+
)
|
|
1271
1302
|
raise typer.Exit(1)
|
|
1272
1303
|
else:
|
|
1273
|
-
console.print(
|
|
1304
|
+
console.print(
|
|
1305
|
+
"[green]✓ Auto-repair successful. Proceeding with ticket creation.[/green]"
|
|
1306
|
+
)
|
|
1274
1307
|
else:
|
|
1275
|
-
console.print(
|
|
1308
|
+
console.print(
|
|
1309
|
+
"[red]❌ No repair actions available. Manual intervention required.[/red]"
|
|
1310
|
+
)
|
|
1276
1311
|
raise typer.Exit(1)
|
|
1277
1312
|
|
|
1278
1313
|
elif health["status"] == HealthStatus.WARNING:
|
|
@@ -1316,7 +1351,9 @@ def create(
|
|
|
1316
1351
|
|
|
1317
1352
|
# WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
|
|
1318
1353
|
if adapter_name == "linear":
|
|
1319
|
-
console.print(
|
|
1354
|
+
console.print(
|
|
1355
|
+
"[yellow]⚠️[/yellow] Using direct operation for Linear adapter (bypassing queue)"
|
|
1356
|
+
)
|
|
1320
1357
|
try:
|
|
1321
1358
|
# Load configuration and create adapter directly
|
|
1322
1359
|
config = load_config()
|
|
@@ -1324,20 +1361,27 @@ def create(
|
|
|
1324
1361
|
|
|
1325
1362
|
# Import and create adapter
|
|
1326
1363
|
from ..core.registry import AdapterRegistry
|
|
1364
|
+
|
|
1327
1365
|
adapter = AdapterRegistry.get_adapter(adapter_name, adapter_config)
|
|
1328
1366
|
|
|
1329
1367
|
# Create task directly
|
|
1330
|
-
from ..core.models import
|
|
1368
|
+
from ..core.models import Priority, Task
|
|
1369
|
+
|
|
1331
1370
|
task = Task(
|
|
1332
1371
|
title=task_data["title"],
|
|
1333
1372
|
description=task_data.get("description"),
|
|
1334
|
-
priority=
|
|
1373
|
+
priority=(
|
|
1374
|
+
Priority(task_data["priority"])
|
|
1375
|
+
if task_data.get("priority")
|
|
1376
|
+
else Priority.MEDIUM
|
|
1377
|
+
),
|
|
1335
1378
|
tags=task_data.get("tags", []),
|
|
1336
|
-
assignee=task_data.get("assignee")
|
|
1379
|
+
assignee=task_data.get("assignee"),
|
|
1337
1380
|
)
|
|
1338
1381
|
|
|
1339
1382
|
# Create ticket synchronously
|
|
1340
1383
|
import asyncio
|
|
1384
|
+
|
|
1341
1385
|
result = asyncio.run(adapter.create(task))
|
|
1342
1386
|
|
|
1343
1387
|
console.print(f"[green]✓[/green] Ticket created successfully: {result.id}")
|
|
@@ -1345,7 +1389,11 @@ def create(
|
|
|
1345
1389
|
console.print(f" Priority: {result.priority}")
|
|
1346
1390
|
console.print(f" State: {result.state}")
|
|
1347
1391
|
# Get URL from metadata if available
|
|
1348
|
-
if
|
|
1392
|
+
if (
|
|
1393
|
+
result.metadata
|
|
1394
|
+
and "linear" in result.metadata
|
|
1395
|
+
and "url" in result.metadata["linear"]
|
|
1396
|
+
):
|
|
1349
1397
|
console.print(f" URL: {result.metadata['linear']['url']}")
|
|
1350
1398
|
|
|
1351
1399
|
return result.id
|
|
@@ -1360,12 +1408,14 @@ def create(
|
|
|
1360
1408
|
ticket_data=task_data,
|
|
1361
1409
|
adapter=adapter_name,
|
|
1362
1410
|
operation="create",
|
|
1363
|
-
project_dir=str(Path.cwd()) # Explicitly pass current project directory
|
|
1411
|
+
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1364
1412
|
)
|
|
1365
1413
|
|
|
1366
1414
|
# Register in ticket registry for tracking
|
|
1367
1415
|
registry = TicketRegistry()
|
|
1368
|
-
registry.register_ticket_operation(
|
|
1416
|
+
registry.register_ticket_operation(
|
|
1417
|
+
queue_id, adapter_name, "create", title, task_data
|
|
1418
|
+
)
|
|
1369
1419
|
|
|
1370
1420
|
console.print(f"[green]✓[/green] Queued ticket creation: {queue_id}")
|
|
1371
1421
|
console.print(f" Title: {title}")
|
|
@@ -1382,6 +1432,7 @@ def create(
|
|
|
1382
1432
|
|
|
1383
1433
|
# Give immediate feedback on processing
|
|
1384
1434
|
import time
|
|
1435
|
+
|
|
1385
1436
|
time.sleep(1) # Brief pause to let worker start
|
|
1386
1437
|
|
|
1387
1438
|
# Check if item is being processed
|
|
@@ -1391,15 +1442,23 @@ def create(
|
|
|
1391
1442
|
elif item and item.status == QueueStatus.PENDING:
|
|
1392
1443
|
console.print("[yellow]⏳ Item is queued for processing[/yellow]")
|
|
1393
1444
|
else:
|
|
1394
|
-
console.print(
|
|
1445
|
+
console.print(
|
|
1446
|
+
"[red]⚠️ Item status unclear - check with 'mcp-ticketer check {queue_id}'[/red]"
|
|
1447
|
+
)
|
|
1395
1448
|
else:
|
|
1396
1449
|
# Worker didn't start - this is a problem
|
|
1397
1450
|
pending_count = queue.get_pending_count()
|
|
1398
1451
|
if pending_count > 1: # More than just this item
|
|
1399
|
-
console.print(
|
|
1400
|
-
|
|
1452
|
+
console.print(
|
|
1453
|
+
f"[red]❌ Worker failed to start with {pending_count} pending items![/red]"
|
|
1454
|
+
)
|
|
1455
|
+
console.print(
|
|
1456
|
+
"[red]This is a critical issue. Try 'mcp-ticketer queue worker start' manually.[/red]"
|
|
1457
|
+
)
|
|
1401
1458
|
else:
|
|
1402
|
-
console.print(
|
|
1459
|
+
console.print(
|
|
1460
|
+
"[yellow]Worker not started (no other pending items)[/yellow]"
|
|
1461
|
+
)
|
|
1403
1462
|
|
|
1404
1463
|
|
|
1405
1464
|
@app.command("list")
|
|
@@ -1444,7 +1503,7 @@ def list_tickets(
|
|
|
1444
1503
|
|
|
1445
1504
|
for ticket in tickets:
|
|
1446
1505
|
# Handle assignee field - Epic doesn't have assignee, Task does
|
|
1447
|
-
assignee = getattr(ticket,
|
|
1506
|
+
assignee = getattr(ticket, "assignee", None) or "-"
|
|
1448
1507
|
|
|
1449
1508
|
table.add_row(
|
|
1450
1509
|
ticket.id or "N/A",
|
|
@@ -1526,7 +1585,7 @@ def comment(
|
|
|
1526
1585
|
comment = Comment(
|
|
1527
1586
|
ticket_id=ticket_id,
|
|
1528
1587
|
content=content,
|
|
1529
|
-
author="cli-user" # Could be made configurable
|
|
1588
|
+
author="cli-user", # Could be made configurable
|
|
1530
1589
|
)
|
|
1531
1590
|
|
|
1532
1591
|
result = await adapter_instance.add_comment(comment)
|
|
@@ -1534,7 +1593,7 @@ def comment(
|
|
|
1534
1593
|
|
|
1535
1594
|
try:
|
|
1536
1595
|
result = asyncio.run(_comment())
|
|
1537
|
-
console.print(
|
|
1596
|
+
console.print("[green]✓[/green] Comment added successfully")
|
|
1538
1597
|
if result.id:
|
|
1539
1598
|
console.print(f"Comment ID: {result.id}")
|
|
1540
1599
|
console.print(f"Content: {content}")
|
|
@@ -1592,7 +1651,7 @@ def update(
|
|
|
1592
1651
|
ticket_data=updates,
|
|
1593
1652
|
adapter=adapter_name,
|
|
1594
1653
|
operation="update",
|
|
1595
|
-
project_dir=str(Path.cwd()) # Explicitly pass current project directory
|
|
1654
|
+
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1596
1655
|
)
|
|
1597
1656
|
|
|
1598
1657
|
console.print(f"[green]✓[/green] Queued ticket update: {queue_id}")
|
|
@@ -1660,7 +1719,7 @@ def transition(
|
|
|
1660
1719
|
},
|
|
1661
1720
|
adapter=adapter_name,
|
|
1662
1721
|
operation="transition",
|
|
1663
|
-
project_dir=str(Path.cwd()) # Explicitly pass current project directory
|
|
1722
|
+
project_dir=str(Path.cwd()), # Explicitly pass current project directory
|
|
1664
1723
|
)
|
|
1665
1724
|
|
|
1666
1725
|
console.print(f"[green]✓[/green] Queued state transition: {queue_id}")
|
|
@@ -1722,30 +1781,42 @@ app.add_typer(queue_app, name="queue")
|
|
|
1722
1781
|
# Add discover command to main app
|
|
1723
1782
|
app.add_typer(discover_app, name="discover")
|
|
1724
1783
|
|
|
1784
|
+
|
|
1725
1785
|
# Add diagnostics command
|
|
1726
1786
|
@app.command()
|
|
1727
1787
|
def diagnose(
|
|
1728
|
-
output_file: Optional[str] = typer.Option(
|
|
1729
|
-
|
|
1730
|
-
|
|
1788
|
+
output_file: Optional[str] = typer.Option(
|
|
1789
|
+
None, "--output", "-o", help="Save full report to file"
|
|
1790
|
+
),
|
|
1791
|
+
json_output: bool = typer.Option(
|
|
1792
|
+
False, "--json", help="Output report in JSON format"
|
|
1793
|
+
),
|
|
1794
|
+
simple: bool = typer.Option(
|
|
1795
|
+
False, "--simple", help="Use simple diagnostics (no heavy dependencies)"
|
|
1796
|
+
),
|
|
1731
1797
|
) -> None:
|
|
1732
1798
|
"""Run comprehensive system diagnostics and health check."""
|
|
1733
1799
|
if simple:
|
|
1734
1800
|
from .simple_health import simple_diagnose
|
|
1801
|
+
|
|
1735
1802
|
report = simple_diagnose()
|
|
1736
1803
|
if output_file:
|
|
1737
1804
|
import json
|
|
1738
|
-
|
|
1805
|
+
|
|
1806
|
+
with open(output_file, "w") as f:
|
|
1739
1807
|
json.dump(report, f, indent=2)
|
|
1740
1808
|
console.print(f"\n📄 Report saved to: {output_file}")
|
|
1741
1809
|
if json_output:
|
|
1742
1810
|
import json
|
|
1811
|
+
|
|
1743
1812
|
console.print("\n" + json.dumps(report, indent=2))
|
|
1744
1813
|
if report["issues"]:
|
|
1745
1814
|
raise typer.Exit(1)
|
|
1746
1815
|
else:
|
|
1747
1816
|
try:
|
|
1748
|
-
asyncio.run(
|
|
1817
|
+
asyncio.run(
|
|
1818
|
+
run_diagnostics(output_file=output_file, json_output=json_output)
|
|
1819
|
+
)
|
|
1749
1820
|
except typer.Exit:
|
|
1750
1821
|
# typer.Exit is expected - don't fall back to simple diagnostics
|
|
1751
1822
|
raise
|
|
@@ -1753,6 +1824,7 @@ def diagnose(
|
|
|
1753
1824
|
console.print(f"⚠️ Full diagnostics failed: {e}")
|
|
1754
1825
|
console.print("🔄 Falling back to simple diagnostics...")
|
|
1755
1826
|
from .simple_health import simple_diagnose
|
|
1827
|
+
|
|
1756
1828
|
report = simple_diagnose()
|
|
1757
1829
|
if report["issues"]:
|
|
1758
1830
|
raise typer.Exit(1)
|
|
@@ -1767,6 +1839,7 @@ def health() -> None:
|
|
|
1767
1839
|
if result != 0:
|
|
1768
1840
|
raise typer.Exit(result)
|
|
1769
1841
|
|
|
1842
|
+
|
|
1770
1843
|
# Create MCP configuration command group
|
|
1771
1844
|
mcp_app = typer.Typer(
|
|
1772
1845
|
name="mcp",
|
|
@@ -1817,9 +1890,6 @@ def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
|
|
|
1817
1890
|
console.print(f"\nRetry Count: {item.retry_count}")
|
|
1818
1891
|
|
|
1819
1892
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
1893
|
@app.command()
|
|
1824
1894
|
def serve(
|
|
1825
1895
|
adapter: Optional[AdapterType] = typer.Option(
|
|
@@ -1857,6 +1927,7 @@ def serve(
|
|
|
1857
1927
|
else:
|
|
1858
1928
|
# Priority 2: .env files
|
|
1859
1929
|
from ..mcp.server import _load_env_configuration
|
|
1930
|
+
|
|
1860
1931
|
env_config = _load_env_configuration()
|
|
1861
1932
|
if env_config:
|
|
1862
1933
|
adapter_type = env_config["adapter_type"]
|