mcp-ticketer 2.0.1__py3-none-any.whl → 2.2.13__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/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +122 -0
- mcp_ticketer/adapters/asana/adapter.py +121 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/{github.py → github/adapter.py} +1506 -365
- 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/jira/__init__.py +35 -0
- mcp_ticketer/adapters/{jira.py → jira/adapter.py} +250 -678
- 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/adapter.py +1000 -92
- mcp_ticketer/adapters/linear/client.py +91 -1
- mcp_ticketer/adapters/linear/mappers.py +107 -0
- mcp_ticketer/adapters/linear/queries.py +112 -2
- mcp_ticketer/adapters/linear/types.py +50 -10
- mcp_ticketer/cli/configure.py +524 -89
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/main.py +10 -0
- mcp_ticketer/cli/mcp_configure.py +177 -49
- mcp_ticketer/cli/platform_installer.py +9 -0
- mcp_ticketer/cli/setup_command.py +157 -1
- mcp_ticketer/cli/ticket_commands.py +443 -81
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +28 -0
- mcp_ticketer/core/adapter.py +367 -1
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +345 -0
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +6 -1
- mcp_ticketer/core/state_matcher.py +36 -3
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/routing.py +68 -0
- mcp_ticketer/mcp/server/tools/__init__.py +7 -4
- mcp_ticketer/mcp/server/tools/attachment_tools.py +3 -1
- mcp_ticketer/mcp/server/tools/config_tools.py +233 -35
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +30 -1
- mcp_ticketer/mcp/server/tools/ticket_tools.py +37 -1
- mcp_ticketer/queue/queue.py +68 -0
- {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/METADATA +33 -3
- {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/RECORD +72 -36
- mcp_ticketer-2.2.13.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-2.0.1.dist-info/top_level.txt +0 -1
- {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
mcp_ticketer/cli/utils.py
CHANGED
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
from collections.abc import Callable
|
|
8
|
+
from datetime import datetime
|
|
8
9
|
from functools import wraps
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, TypeVar
|
|
@@ -23,6 +24,118 @@ T = TypeVar("T")
|
|
|
23
24
|
console = Console()
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
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
|
+
|
|
26
139
|
|
|
27
140
|
class CommonPatterns:
|
|
28
141
|
"""Common CLI patterns and utilities."""
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -8,17 +8,28 @@ from .instructions import (
|
|
|
8
8
|
TicketInstructionsManager,
|
|
9
9
|
get_instructions,
|
|
10
10
|
)
|
|
11
|
+
from .milestone_manager import MilestoneManager
|
|
11
12
|
from .models import (
|
|
12
13
|
Attachment,
|
|
13
14
|
Comment,
|
|
14
15
|
Epic,
|
|
16
|
+
Milestone,
|
|
15
17
|
Priority,
|
|
18
|
+
Project,
|
|
19
|
+
ProjectScope,
|
|
20
|
+
ProjectState,
|
|
21
|
+
ProjectStatistics,
|
|
16
22
|
ProjectUpdate,
|
|
17
23
|
ProjectUpdateHealth,
|
|
24
|
+
ProjectVisibility,
|
|
18
25
|
Task,
|
|
19
26
|
TicketState,
|
|
20
27
|
TicketType,
|
|
21
28
|
)
|
|
29
|
+
from .project_utils import (
|
|
30
|
+
epic_to_project,
|
|
31
|
+
project_to_epic,
|
|
32
|
+
)
|
|
22
33
|
from .registry import AdapterRegistry
|
|
23
34
|
from .state_matcher import (
|
|
24
35
|
SemanticStateMatcher,
|
|
@@ -28,22 +39,39 @@ from .state_matcher import (
|
|
|
28
39
|
)
|
|
29
40
|
|
|
30
41
|
__all__ = [
|
|
42
|
+
# Core ticket models
|
|
31
43
|
"Epic",
|
|
32
44
|
"Task",
|
|
33
45
|
"Comment",
|
|
34
46
|
"Attachment",
|
|
47
|
+
"Milestone",
|
|
48
|
+
# Project models
|
|
49
|
+
"Project",
|
|
50
|
+
"ProjectScope",
|
|
51
|
+
"ProjectState",
|
|
52
|
+
"ProjectStatistics",
|
|
53
|
+
"ProjectVisibility",
|
|
35
54
|
"ProjectUpdate",
|
|
36
55
|
"ProjectUpdateHealth",
|
|
56
|
+
# Project utilities
|
|
57
|
+
"epic_to_project",
|
|
58
|
+
"project_to_epic",
|
|
59
|
+
# Enums
|
|
37
60
|
"TicketState",
|
|
38
61
|
"Priority",
|
|
39
62
|
"TicketType",
|
|
63
|
+
# Adapters
|
|
40
64
|
"BaseAdapter",
|
|
41
65
|
"AdapterRegistry",
|
|
66
|
+
# Managers
|
|
67
|
+
"MilestoneManager",
|
|
42
68
|
"TicketInstructionsManager",
|
|
69
|
+
# Instructions
|
|
43
70
|
"InstructionsError",
|
|
44
71
|
"InstructionsNotFoundError",
|
|
45
72
|
"InstructionsValidationError",
|
|
46
73
|
"get_instructions",
|
|
74
|
+
# State matching
|
|
47
75
|
"SemanticStateMatcher",
|
|
48
76
|
"StateMatchResult",
|
|
49
77
|
"ValidationResult",
|
mcp_ticketer/core/adapter.py
CHANGED
|
@@ -4,9 +4,22 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import builtins
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
8
9
|
|
|
9
|
-
from .models import
|
|
10
|
+
from .models import (
|
|
11
|
+
Comment,
|
|
12
|
+
Epic,
|
|
13
|
+
Milestone,
|
|
14
|
+
Project,
|
|
15
|
+
ProjectScope,
|
|
16
|
+
ProjectState,
|
|
17
|
+
ProjectStatistics,
|
|
18
|
+
SearchQuery,
|
|
19
|
+
Task,
|
|
20
|
+
TicketState,
|
|
21
|
+
TicketType,
|
|
22
|
+
)
|
|
10
23
|
from .state_matcher import get_state_matcher
|
|
11
24
|
|
|
12
25
|
if TYPE_CHECKING:
|
|
@@ -612,3 +625,356 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
612
625
|
async def close(self) -> None:
|
|
613
626
|
"""Close adapter and cleanup resources."""
|
|
614
627
|
pass
|
|
628
|
+
|
|
629
|
+
# Milestone Operations (Phase 1 - Abstract methods)
|
|
630
|
+
|
|
631
|
+
@abstractmethod
|
|
632
|
+
async def milestone_create(
|
|
633
|
+
self,
|
|
634
|
+
name: str,
|
|
635
|
+
target_date: datetime | None = None,
|
|
636
|
+
labels: list[str] | None = None,
|
|
637
|
+
description: str = "",
|
|
638
|
+
project_id: str | None = None,
|
|
639
|
+
) -> Milestone:
|
|
640
|
+
"""Create a new milestone.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
----
|
|
644
|
+
name: Milestone name
|
|
645
|
+
target_date: Target completion date (ISO format: YYYY-MM-DD)
|
|
646
|
+
labels: Labels that define this milestone
|
|
647
|
+
description: Milestone description
|
|
648
|
+
project_id: Associated project ID
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
-------
|
|
652
|
+
Created Milestone object
|
|
653
|
+
|
|
654
|
+
"""
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
@abstractmethod
|
|
658
|
+
async def milestone_get(self, milestone_id: str) -> Milestone | None:
|
|
659
|
+
"""Get milestone by ID with progress calculation.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
----
|
|
663
|
+
milestone_id: Milestone identifier
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
-------
|
|
667
|
+
Milestone object with calculated progress, None if not found
|
|
668
|
+
|
|
669
|
+
"""
|
|
670
|
+
pass
|
|
671
|
+
|
|
672
|
+
@abstractmethod
|
|
673
|
+
async def milestone_list(
|
|
674
|
+
self,
|
|
675
|
+
project_id: str | None = None,
|
|
676
|
+
state: str | None = None,
|
|
677
|
+
) -> builtins.list[Milestone]:
|
|
678
|
+
"""List milestones with optional filters.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
----
|
|
682
|
+
project_id: Filter by project
|
|
683
|
+
state: Filter by state (open, active, completed, closed)
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
-------
|
|
687
|
+
List of Milestone objects
|
|
688
|
+
|
|
689
|
+
"""
|
|
690
|
+
pass
|
|
691
|
+
|
|
692
|
+
@abstractmethod
|
|
693
|
+
async def milestone_update(
|
|
694
|
+
self,
|
|
695
|
+
milestone_id: str,
|
|
696
|
+
name: str | None = None,
|
|
697
|
+
target_date: datetime | None = None,
|
|
698
|
+
state: str | None = None,
|
|
699
|
+
labels: list[str] | None = None,
|
|
700
|
+
description: str | None = None,
|
|
701
|
+
) -> Milestone | None:
|
|
702
|
+
"""Update milestone properties.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
----
|
|
706
|
+
milestone_id: Milestone identifier
|
|
707
|
+
name: New name (optional)
|
|
708
|
+
target_date: New target date (optional)
|
|
709
|
+
state: New state (optional)
|
|
710
|
+
labels: New labels (optional)
|
|
711
|
+
description: New description (optional)
|
|
712
|
+
|
|
713
|
+
Returns:
|
|
714
|
+
-------
|
|
715
|
+
Updated Milestone object, None if not found
|
|
716
|
+
|
|
717
|
+
"""
|
|
718
|
+
pass
|
|
719
|
+
|
|
720
|
+
@abstractmethod
|
|
721
|
+
async def milestone_delete(self, milestone_id: str) -> bool:
|
|
722
|
+
"""Delete milestone.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
----
|
|
726
|
+
milestone_id: Milestone identifier
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
-------
|
|
730
|
+
True if deleted successfully, False otherwise
|
|
731
|
+
|
|
732
|
+
"""
|
|
733
|
+
pass
|
|
734
|
+
|
|
735
|
+
@abstractmethod
|
|
736
|
+
async def milestone_get_issues(
|
|
737
|
+
self,
|
|
738
|
+
milestone_id: str,
|
|
739
|
+
state: str | None = None,
|
|
740
|
+
) -> builtins.list[Task]:
|
|
741
|
+
"""Get issues associated with milestone.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
----
|
|
745
|
+
milestone_id: Milestone identifier
|
|
746
|
+
state: Filter by issue state (optional)
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
-------
|
|
750
|
+
List of Task objects (issues)
|
|
751
|
+
|
|
752
|
+
"""
|
|
753
|
+
pass
|
|
754
|
+
|
|
755
|
+
# Project Operations (Phase 1 - Abstract methods)
|
|
756
|
+
# These methods are optional - adapters that don't support projects
|
|
757
|
+
# can raise NotImplementedError with a helpful message
|
|
758
|
+
|
|
759
|
+
async def project_list(
|
|
760
|
+
self,
|
|
761
|
+
scope: ProjectScope | None = None,
|
|
762
|
+
state: ProjectState | None = None,
|
|
763
|
+
limit: int = 50,
|
|
764
|
+
offset: int = 0,
|
|
765
|
+
) -> builtins.list[Project]:
|
|
766
|
+
"""List projects with optional filters.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
----
|
|
770
|
+
scope: Filter by project scope (user, team, org, repo)
|
|
771
|
+
state: Filter by project state
|
|
772
|
+
limit: Maximum results (default: 50)
|
|
773
|
+
offset: Pagination offset (default: 0)
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
-------
|
|
777
|
+
List of Project objects
|
|
778
|
+
|
|
779
|
+
Raises:
|
|
780
|
+
------
|
|
781
|
+
NotImplementedError: If adapter doesn't support projects
|
|
782
|
+
|
|
783
|
+
"""
|
|
784
|
+
raise NotImplementedError(
|
|
785
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
786
|
+
"Use Epic operations for this adapter."
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
async def project_get(self, project_id: str) -> Project | None:
|
|
790
|
+
"""Get project by ID.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
----
|
|
794
|
+
project_id: Project identifier (platform-specific or unified)
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
-------
|
|
798
|
+
Project object if found, None otherwise
|
|
799
|
+
|
|
800
|
+
Raises:
|
|
801
|
+
------
|
|
802
|
+
NotImplementedError: If adapter doesn't support projects
|
|
803
|
+
|
|
804
|
+
"""
|
|
805
|
+
raise NotImplementedError(
|
|
806
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
807
|
+
"Use get_epic() for this adapter."
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
async def project_create(
|
|
811
|
+
self,
|
|
812
|
+
name: str,
|
|
813
|
+
description: str | None = None,
|
|
814
|
+
state: ProjectState = ProjectState.PLANNED,
|
|
815
|
+
target_date: datetime | None = None,
|
|
816
|
+
**kwargs: Any,
|
|
817
|
+
) -> Project:
|
|
818
|
+
"""Create new project.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
----
|
|
822
|
+
name: Project name (required)
|
|
823
|
+
description: Project description
|
|
824
|
+
state: Initial project state (default: PLANNED)
|
|
825
|
+
target_date: Target completion date
|
|
826
|
+
**kwargs: Platform-specific additional fields
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
-------
|
|
830
|
+
Created Project object
|
|
831
|
+
|
|
832
|
+
Raises:
|
|
833
|
+
------
|
|
834
|
+
NotImplementedError: If adapter doesn't support projects
|
|
835
|
+
|
|
836
|
+
"""
|
|
837
|
+
raise NotImplementedError(
|
|
838
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
839
|
+
"Use create_epic() for this adapter."
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
async def project_update(
|
|
843
|
+
self,
|
|
844
|
+
project_id: str,
|
|
845
|
+
name: str | None = None,
|
|
846
|
+
description: str | None = None,
|
|
847
|
+
state: ProjectState | None = None,
|
|
848
|
+
**kwargs: Any,
|
|
849
|
+
) -> Project | None:
|
|
850
|
+
"""Update project properties.
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
----
|
|
854
|
+
project_id: Project identifier
|
|
855
|
+
name: New name (optional)
|
|
856
|
+
description: New description (optional)
|
|
857
|
+
state: New state (optional)
|
|
858
|
+
**kwargs: Platform-specific fields to update
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
-------
|
|
862
|
+
Updated Project object, None if not found
|
|
863
|
+
|
|
864
|
+
Raises:
|
|
865
|
+
------
|
|
866
|
+
NotImplementedError: If adapter doesn't support projects
|
|
867
|
+
|
|
868
|
+
"""
|
|
869
|
+
raise NotImplementedError(
|
|
870
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
async def project_delete(self, project_id: str) -> bool:
|
|
874
|
+
"""Delete or archive project.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
----
|
|
878
|
+
project_id: Project identifier
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
-------
|
|
882
|
+
True if deleted successfully, False otherwise
|
|
883
|
+
|
|
884
|
+
Raises:
|
|
885
|
+
------
|
|
886
|
+
NotImplementedError: If adapter doesn't support projects
|
|
887
|
+
|
|
888
|
+
"""
|
|
889
|
+
raise NotImplementedError(
|
|
890
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
async def project_get_issues(
|
|
894
|
+
self, project_id: str, state: TicketState | None = None
|
|
895
|
+
) -> builtins.list[Task]:
|
|
896
|
+
"""Get all issues in project.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
----
|
|
900
|
+
project_id: Project identifier
|
|
901
|
+
state: Filter by issue state (optional)
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
-------
|
|
905
|
+
List of Task objects (issues in project)
|
|
906
|
+
|
|
907
|
+
Raises:
|
|
908
|
+
------
|
|
909
|
+
NotImplementedError: If adapter doesn't support projects
|
|
910
|
+
|
|
911
|
+
"""
|
|
912
|
+
raise NotImplementedError(
|
|
913
|
+
f"{self.__class__.__name__} does not support project operations. "
|
|
914
|
+
"Use list_issues_by_epic() for this adapter."
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
async def project_add_issue(self, project_id: str, issue_id: str) -> bool:
|
|
918
|
+
"""Add issue to project.
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
----
|
|
922
|
+
project_id: Project identifier
|
|
923
|
+
issue_id: Issue identifier to add
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
-------
|
|
927
|
+
True if added successfully, False otherwise
|
|
928
|
+
|
|
929
|
+
Raises:
|
|
930
|
+
------
|
|
931
|
+
NotImplementedError: If adapter doesn't support projects
|
|
932
|
+
|
|
933
|
+
"""
|
|
934
|
+
raise NotImplementedError(
|
|
935
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
async def project_remove_issue(self, project_id: str, issue_id: str) -> bool:
|
|
939
|
+
"""Remove issue from project.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
----
|
|
943
|
+
project_id: Project identifier
|
|
944
|
+
issue_id: Issue identifier to remove
|
|
945
|
+
|
|
946
|
+
Returns:
|
|
947
|
+
-------
|
|
948
|
+
True if removed successfully, False otherwise
|
|
949
|
+
|
|
950
|
+
Raises:
|
|
951
|
+
------
|
|
952
|
+
NotImplementedError: If adapter doesn't support projects
|
|
953
|
+
|
|
954
|
+
"""
|
|
955
|
+
raise NotImplementedError(
|
|
956
|
+
f"{self.__class__.__name__} does not support project operations."
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
async def project_get_statistics(self, project_id: str) -> ProjectStatistics:
|
|
960
|
+
"""Get project statistics and metrics.
|
|
961
|
+
|
|
962
|
+
Calculates or retrieves statistics including issue counts by state,
|
|
963
|
+
progress percentage, and velocity metrics.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
----
|
|
967
|
+
project_id: Project identifier
|
|
968
|
+
|
|
969
|
+
Returns:
|
|
970
|
+
-------
|
|
971
|
+
ProjectStatistics object with calculated metrics
|
|
972
|
+
|
|
973
|
+
Raises:
|
|
974
|
+
------
|
|
975
|
+
NotImplementedError: If adapter doesn't support projects
|
|
976
|
+
|
|
977
|
+
"""
|
|
978
|
+
raise NotImplementedError(
|
|
979
|
+
f"{self.__class__.__name__} does not support project statistics."
|
|
980
|
+
)
|