mcp-ticketer 0.12.0__py3-none-any.whl → 2.0.1__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 +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +385 -6
- mcp_ticketer/adapters/asana/adapter.py +108 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github.py +525 -11
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +521 -0
- mcp_ticketer/adapters/linear/adapter.py +1784 -101
- mcp_ticketer/adapters/linear/client.py +85 -3
- mcp_ticketer/adapters/linear/mappers.py +96 -8
- mcp_ticketer/adapters/linear/queries.py +168 -1
- mcp_ticketer/adapters/linear/types.py +80 -4
- 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/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +851 -103
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +233 -3151
- mcp_ticketer/cli/mcp_configure.py +672 -98
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +264 -24
- mcp_ticketer/core/__init__.py +28 -6
- mcp_ticketer/core/adapter.py +166 -1
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/models.py +135 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/session_state.py +171 -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/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +31 -12
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +6 -8
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1184 -136
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- 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 +180 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1070 -123
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -7,10 +7,9 @@ in all adapters.
|
|
|
7
7
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from ..server_sdk import get_adapter
|
|
10
|
+
from ..server_sdk import get_adapter
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@mcp.tool()
|
|
14
13
|
async def ticket_create_pr(
|
|
15
14
|
ticket_id: str,
|
|
16
15
|
title: str,
|
|
@@ -58,7 +57,7 @@ async def ticket_create_pr(
|
|
|
58
57
|
source_branch = f"feature/{ticket_id}"
|
|
59
58
|
|
|
60
59
|
# Create PR via adapter
|
|
61
|
-
pr_data = await adapter.create_pull_request(
|
|
60
|
+
pr_data = await adapter.create_pull_request(
|
|
62
61
|
ticket_id=ticket_id,
|
|
63
62
|
title=title,
|
|
64
63
|
description=description,
|
|
@@ -86,7 +85,6 @@ async def ticket_create_pr(
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
@mcp.tool()
|
|
90
88
|
async def ticket_link_pr(
|
|
91
89
|
ticket_id: str,
|
|
92
90
|
pr_url: str,
|
|
@@ -118,9 +116,7 @@ async def ticket_link_pr(
|
|
|
118
116
|
|
|
119
117
|
# Check if adapter has specialized PR linking
|
|
120
118
|
if hasattr(adapter, "link_pull_request"):
|
|
121
|
-
result = await adapter.link_pull_request(
|
|
122
|
-
ticket_id=ticket_id, pr_url=pr_url
|
|
123
|
-
)
|
|
119
|
+
result = await adapter.link_pull_request(ticket_id=ticket_id, pr_url=pr_url)
|
|
124
120
|
return {
|
|
125
121
|
"status": "completed",
|
|
126
122
|
"ticket_id": ticket_id,
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""MCP tools for project status analysis and work planning.
|
|
2
|
+
|
|
3
|
+
This module provides PM-focused tools to analyze project health and
|
|
4
|
+
generate intelligent work plans with recommendations.
|
|
5
|
+
|
|
6
|
+
Tools:
|
|
7
|
+
- project_status: Comprehensive project/epic analysis with health assessment
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ....analysis.project_status import StatusAnalyzer
|
|
14
|
+
from ....core.project_config import ConfigResolver
|
|
15
|
+
from ..server_sdk import get_adapter, mcp
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@mcp.tool()
|
|
21
|
+
async def project_status(project_id: str | None = None) -> dict[str, Any]:
|
|
22
|
+
"""Analyze project/epic status and generate work plan with recommendations.
|
|
23
|
+
|
|
24
|
+
Provides comprehensive project analysis including:
|
|
25
|
+
- Health assessment (on_track, at_risk, off_track)
|
|
26
|
+
- Status breakdown by state and priority
|
|
27
|
+
- Dependency analysis and critical path
|
|
28
|
+
- Top 3 recommended tickets to start next
|
|
29
|
+
- Blocker identification
|
|
30
|
+
- Work distribution by assignee
|
|
31
|
+
- Actionable recommendations for project managers
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
project_id: ID of the project/epic to analyze (optional, uses default_project if not provided)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Complete project status analysis with recommendations, or error information
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
# Analyze specific project
|
|
41
|
+
result = await project_status(project_id="eac28953c267")
|
|
42
|
+
|
|
43
|
+
# Analyze default project
|
|
44
|
+
result = await project_status()
|
|
45
|
+
|
|
46
|
+
Example Response:
|
|
47
|
+
{
|
|
48
|
+
"status": "success",
|
|
49
|
+
"project_id": "eac28953c267",
|
|
50
|
+
"project_name": "MCP Ticketer",
|
|
51
|
+
"health": "at_risk",
|
|
52
|
+
"summary": {
|
|
53
|
+
"total": 4,
|
|
54
|
+
"open": 3,
|
|
55
|
+
"in_progress": 1,
|
|
56
|
+
"done": 0
|
|
57
|
+
},
|
|
58
|
+
"recommended_next": [
|
|
59
|
+
{
|
|
60
|
+
"ticket_id": "1M-317",
|
|
61
|
+
"title": "Fix project organization",
|
|
62
|
+
"priority": "critical",
|
|
63
|
+
"reason": "Critical priority, Unblocks 2 tickets",
|
|
64
|
+
"blocks": ["1M-315", "1M-316"]
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"recommendations": [
|
|
68
|
+
"Resolve 1M-317 first (critical) - Unblocks 2 tickets",
|
|
69
|
+
"1 critical priority ticket needs attention"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
adapter = get_adapter()
|
|
76
|
+
|
|
77
|
+
# Use default project if not provided
|
|
78
|
+
if not project_id:
|
|
79
|
+
resolver = ConfigResolver()
|
|
80
|
+
config = resolver.resolve()
|
|
81
|
+
project_id = config.default_project
|
|
82
|
+
|
|
83
|
+
if not project_id:
|
|
84
|
+
return {
|
|
85
|
+
"status": "error",
|
|
86
|
+
"error": "No project_id provided and no default_project configured",
|
|
87
|
+
"message": "Use config_set_project to set a default project, or provide project_id parameter",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Read the epic/project to get name
|
|
91
|
+
try:
|
|
92
|
+
epic = await adapter.read(project_id)
|
|
93
|
+
if epic is None:
|
|
94
|
+
return {
|
|
95
|
+
"status": "error",
|
|
96
|
+
"error": f"Project/Epic {project_id} not found",
|
|
97
|
+
}
|
|
98
|
+
project_name = epic.title or project_id
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning(
|
|
101
|
+
f"Failed to read project {project_id} for name: {e}. Using ID as name."
|
|
102
|
+
)
|
|
103
|
+
project_name = project_id
|
|
104
|
+
|
|
105
|
+
# Get all child issues
|
|
106
|
+
child_issue_ids = getattr(epic, "child_issues", [])
|
|
107
|
+
|
|
108
|
+
if not child_issue_ids:
|
|
109
|
+
return {
|
|
110
|
+
"status": "success",
|
|
111
|
+
"project_id": project_id,
|
|
112
|
+
"project_name": project_name,
|
|
113
|
+
"health": "on_track",
|
|
114
|
+
"summary": {"total": 0},
|
|
115
|
+
"message": "Project has no tickets yet",
|
|
116
|
+
"recommended_next": [],
|
|
117
|
+
"recommendations": ["Project is empty - Create tickets to get started"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Fetch each child issue
|
|
121
|
+
tickets = []
|
|
122
|
+
for issue_id in child_issue_ids:
|
|
123
|
+
try:
|
|
124
|
+
issue = await adapter.read(issue_id)
|
|
125
|
+
if issue:
|
|
126
|
+
tickets.append(issue)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning(f"Failed to read issue {issue_id}: {e}")
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
if not tickets:
|
|
132
|
+
return {
|
|
133
|
+
"status": "success",
|
|
134
|
+
"project_id": project_id,
|
|
135
|
+
"project_name": project_name,
|
|
136
|
+
"health": "at_risk",
|
|
137
|
+
"summary": {"total": 0},
|
|
138
|
+
"message": f"Could not load tickets for project (found {len(child_issue_ids)} IDs but couldn't read them)",
|
|
139
|
+
"recommended_next": [],
|
|
140
|
+
"recommendations": ["Check ticket IDs and permissions"],
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Perform status analysis
|
|
144
|
+
analyzer = StatusAnalyzer()
|
|
145
|
+
result = analyzer.analyze(project_id, project_name, tickets)
|
|
146
|
+
|
|
147
|
+
# Convert to dict and add success status
|
|
148
|
+
result_dict = result.model_dump()
|
|
149
|
+
result_dict["status"] = "success"
|
|
150
|
+
|
|
151
|
+
return result_dict
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Error analyzing project status: {e}", exc_info=True)
|
|
155
|
+
return {
|
|
156
|
+
"status": "error",
|
|
157
|
+
"error": f"Failed to analyze project status: {str(e)}",
|
|
158
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Project update management tools for status updates with health indicators.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for creating, listing, and retrieving project
|
|
4
|
+
status updates with health indicators across multiple platforms.
|
|
5
|
+
|
|
6
|
+
v2.0.0 Consolidation (Phase 3 Sprint 3.4):
|
|
7
|
+
- Single `project_update()` tool with action-based routing
|
|
8
|
+
- Replaces 4 separate tools (create, get, list) with unified interface
|
|
9
|
+
- Helper functions retained for internal use with deprecation warnings
|
|
10
|
+
- ~1,100 tokens saved (69% reduction)
|
|
11
|
+
|
|
12
|
+
Platform Support:
|
|
13
|
+
- Linear: Native ProjectUpdate entities with health, diff_markdown, staleness
|
|
14
|
+
- GitHub V2: ProjectV2StatusUpdate with status options
|
|
15
|
+
- Asana: Project Status Updates with color-coded health
|
|
16
|
+
- JIRA: Comments with custom formatting (workaround)
|
|
17
|
+
|
|
18
|
+
Primary Tool:
|
|
19
|
+
- project_update(action): Unified interface for all operations
|
|
20
|
+
- action="create": Create project status update
|
|
21
|
+
- action="get": Get specific update by ID
|
|
22
|
+
- action="list": List updates for a project
|
|
23
|
+
|
|
24
|
+
Internal Helpers (deprecated):
|
|
25
|
+
- project_update_create(): Use project_update(action="create") instead
|
|
26
|
+
- project_update_get(): Use project_update(action="get") instead
|
|
27
|
+
- project_update_list(): Use project_update(action="list") instead
|
|
28
|
+
|
|
29
|
+
Response Format:
|
|
30
|
+
{
|
|
31
|
+
"status": "completed" | "error",
|
|
32
|
+
"adapter": "adapter_type",
|
|
33
|
+
"adapter_name": "Adapter Display Name",
|
|
34
|
+
... action-specific data ...
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Related Tickets:
|
|
38
|
+
- 1M-238: Add project updates support with flexible project identification
|
|
39
|
+
- 1M-487: Phase 3 Sprint 3.4 - Consolidate project_update tools (v2.0.0)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import logging
|
|
43
|
+
import warnings
|
|
44
|
+
from typing import Any, Literal
|
|
45
|
+
|
|
46
|
+
from ....core.adapter import BaseAdapter
|
|
47
|
+
from ....core.models import ProjectUpdateHealth
|
|
48
|
+
from ..server_sdk import get_adapter, mcp
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_adapter_metadata(
|
|
54
|
+
adapter: BaseAdapter,
|
|
55
|
+
project_id: str | None = None,
|
|
56
|
+
) -> dict[str, Any]:
|
|
57
|
+
"""Build adapter metadata for MCP responses.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
adapter: The adapter that handled the operation
|
|
61
|
+
project_id: Optional project ID to include in metadata
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dictionary with adapter metadata fields
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
metadata = {
|
|
68
|
+
"adapter": adapter.adapter_type,
|
|
69
|
+
"adapter_name": adapter.adapter_display_name,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if project_id:
|
|
73
|
+
metadata["project_id"] = project_id
|
|
74
|
+
|
|
75
|
+
return metadata
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mcp.tool()
|
|
79
|
+
async def project_update(
|
|
80
|
+
action: Literal["create", "get", "list"],
|
|
81
|
+
project_id: str | None = None,
|
|
82
|
+
update_id: str | None = None,
|
|
83
|
+
body: str | None = None,
|
|
84
|
+
health: str | None = None,
|
|
85
|
+
limit: int = 10,
|
|
86
|
+
) -> dict[str, Any]:
|
|
87
|
+
"""Unified project update management with action-based routing.
|
|
88
|
+
|
|
89
|
+
This tool consolidates all project update operations into a single interface:
|
|
90
|
+
- create: Create new project status update
|
|
91
|
+
- get: Get specific update by ID
|
|
92
|
+
- list: List updates for a project
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
action: Operation to perform. Valid values:
|
|
96
|
+
- "create": Create new project status update
|
|
97
|
+
- "get": Get specific update by ID
|
|
98
|
+
- "list": List updates for a project
|
|
99
|
+
|
|
100
|
+
# Parameters for "create" action (required: project_id, body)
|
|
101
|
+
project_id: Project identifier (UUID, slugId, or URL)
|
|
102
|
+
body: Update content in Markdown format
|
|
103
|
+
health: Optional health status - must be one of:
|
|
104
|
+
- on_track: Project is progressing as planned
|
|
105
|
+
- at_risk: Project has some issues but recoverable
|
|
106
|
+
- off_track: Project is significantly behind or blocked
|
|
107
|
+
- complete: Project is finished (GitHub-specific)
|
|
108
|
+
- inactive: Project is not actively being worked on (GitHub-specific)
|
|
109
|
+
|
|
110
|
+
# Parameters for "get" action (required: update_id)
|
|
111
|
+
update_id: Project update identifier (UUID or platform-specific ID)
|
|
112
|
+
|
|
113
|
+
# Parameters for "list" action (required: project_id)
|
|
114
|
+
# project_id: Project identifier
|
|
115
|
+
limit: Maximum number of updates to return (default: 10, max: 50)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Results specific to action with status and relevant data
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
# Create update
|
|
122
|
+
project_update(action="create", project_id="PROJ-123",
|
|
123
|
+
body="Sprint completed with 15/20 stories done",
|
|
124
|
+
health="at_risk")
|
|
125
|
+
|
|
126
|
+
# Get update
|
|
127
|
+
project_update(action="get", update_id="update-456")
|
|
128
|
+
|
|
129
|
+
# List updates
|
|
130
|
+
project_update(action="list", project_id="PROJ-123", limit=5)
|
|
131
|
+
|
|
132
|
+
Note:
|
|
133
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
134
|
+
project identification.
|
|
135
|
+
Related to ticket 1M-484: Phase 2 Sprint 1.1 - Consolidate project_update tools.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
# Validate action
|
|
139
|
+
valid_actions = ["create", "get", "list"]
|
|
140
|
+
if action not in valid_actions:
|
|
141
|
+
return {
|
|
142
|
+
"status": "error",
|
|
143
|
+
"error": f"Invalid action '{action}'. Valid actions: {', '.join(valid_actions)}",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Route to appropriate handler based on action
|
|
147
|
+
if action == "create":
|
|
148
|
+
# Validate required parameters for create
|
|
149
|
+
if not project_id:
|
|
150
|
+
return {
|
|
151
|
+
"status": "error",
|
|
152
|
+
"error": "Parameter 'project_id' is required for action='create'",
|
|
153
|
+
}
|
|
154
|
+
if not body:
|
|
155
|
+
return {
|
|
156
|
+
"status": "error",
|
|
157
|
+
"error": "Parameter 'body' is required for action='create'",
|
|
158
|
+
}
|
|
159
|
+
return await project_update_create(
|
|
160
|
+
project_id=project_id,
|
|
161
|
+
body=body,
|
|
162
|
+
health=health,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
elif action == "get":
|
|
166
|
+
# Validate required parameters for get
|
|
167
|
+
if not update_id:
|
|
168
|
+
return {
|
|
169
|
+
"status": "error",
|
|
170
|
+
"error": "Parameter 'update_id' is required for action='get'",
|
|
171
|
+
}
|
|
172
|
+
return await project_update_get(update_id=update_id)
|
|
173
|
+
|
|
174
|
+
elif action == "list":
|
|
175
|
+
# Validate required parameters for list
|
|
176
|
+
if not project_id:
|
|
177
|
+
return {
|
|
178
|
+
"status": "error",
|
|
179
|
+
"error": "Parameter 'project_id' is required for action='list'",
|
|
180
|
+
}
|
|
181
|
+
return await project_update_list(
|
|
182
|
+
project_id=project_id,
|
|
183
|
+
limit=limit,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Should never reach here due to action validation above
|
|
187
|
+
return {
|
|
188
|
+
"status": "error",
|
|
189
|
+
"error": f"Unhandled action: {action}",
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def project_update_create(
|
|
194
|
+
project_id: str,
|
|
195
|
+
body: str,
|
|
196
|
+
health: str | None = None,
|
|
197
|
+
) -> dict[str, Any]:
|
|
198
|
+
"""Create a project status update.
|
|
199
|
+
|
|
200
|
+
.. deprecated::
|
|
201
|
+
Use project_update(action="create", ...) instead.
|
|
202
|
+
This tool will be removed in a future version.
|
|
203
|
+
|
|
204
|
+
Creates a status update for a project with optional health indicator.
|
|
205
|
+
Supports Linear (native), GitHub V2, Asana, and JIRA (via workaround).
|
|
206
|
+
|
|
207
|
+
Platform Support:
|
|
208
|
+
- Linear: Native ProjectUpdate entity with health, diff_markdown, staleness
|
|
209
|
+
- GitHub V2: ProjectV2StatusUpdate with status options
|
|
210
|
+
- Asana: Project Status Updates with color-coded health
|
|
211
|
+
- JIRA: Comments with custom formatting (workaround)
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
project_id: Project identifier (UUID, slugId, or URL)
|
|
215
|
+
body: Update content in Markdown format (required)
|
|
216
|
+
health: Optional health status - must be one of:
|
|
217
|
+
- on_track: Project is progressing as planned
|
|
218
|
+
- at_risk: Project has some issues but recoverable
|
|
219
|
+
- off_track: Project is significantly behind or blocked
|
|
220
|
+
- complete: Project is finished (GitHub-specific)
|
|
221
|
+
- inactive: Project is not actively being worked on (GitHub-specific)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Created ProjectUpdate details as JSON with adapter metadata, or error information
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> result = await project_update_create(
|
|
228
|
+
... project_id="PROJ-123",
|
|
229
|
+
... body="Sprint completed with 15/20 stories done",
|
|
230
|
+
... health="at_risk"
|
|
231
|
+
... )
|
|
232
|
+
Example: See Returns section
|
|
233
|
+
|
|
234
|
+
Note:
|
|
235
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
236
|
+
project identification.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
warnings.warn(
|
|
240
|
+
"project_update_create is deprecated. Use project_update(action='create', ...) instead.",
|
|
241
|
+
DeprecationWarning,
|
|
242
|
+
stacklevel=2,
|
|
243
|
+
)
|
|
244
|
+
try:
|
|
245
|
+
# Validate body is not empty (before calling get_adapter)
|
|
246
|
+
if not body or not body.strip():
|
|
247
|
+
return {
|
|
248
|
+
"status": "error",
|
|
249
|
+
"error": "Update body cannot be empty",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Parse health status if provided (before calling get_adapter)
|
|
253
|
+
health_enum = None
|
|
254
|
+
if health:
|
|
255
|
+
try:
|
|
256
|
+
health_enum = ProjectUpdateHealth(health.lower())
|
|
257
|
+
except ValueError:
|
|
258
|
+
valid_values = [h.value for h in ProjectUpdateHealth]
|
|
259
|
+
return {
|
|
260
|
+
"status": "error",
|
|
261
|
+
"error": f"Invalid health status '{health}'. Valid values: {', '.join(valid_values)}",
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
adapter = get_adapter()
|
|
265
|
+
|
|
266
|
+
# Check if adapter supports project updates
|
|
267
|
+
if not hasattr(adapter, "create_project_update"):
|
|
268
|
+
return {
|
|
269
|
+
"status": "error",
|
|
270
|
+
"error": f"Adapter '{adapter.adapter_type}' does not support project updates",
|
|
271
|
+
**_build_adapter_metadata(adapter, project_id),
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# Create project update
|
|
275
|
+
update = await adapter.create_project_update(
|
|
276
|
+
project_id=project_id,
|
|
277
|
+
body=body.strip(),
|
|
278
|
+
health=health_enum,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"status": "completed",
|
|
283
|
+
**_build_adapter_metadata(adapter, project_id),
|
|
284
|
+
"update": update.model_dump(),
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
except ValueError as e:
|
|
288
|
+
# Adapter-specific validation errors
|
|
289
|
+
return {
|
|
290
|
+
"status": "error",
|
|
291
|
+
"error": str(e),
|
|
292
|
+
}
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.error(f"Failed to create project update: {e}")
|
|
295
|
+
return {
|
|
296
|
+
"status": "error",
|
|
297
|
+
"error": f"Failed to create project update: {str(e)}",
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
async def project_update_list(
|
|
302
|
+
project_id: str,
|
|
303
|
+
limit: int = 10,
|
|
304
|
+
) -> dict[str, Any]:
|
|
305
|
+
"""List project updates for a project.
|
|
306
|
+
|
|
307
|
+
.. deprecated::
|
|
308
|
+
Use project_update(action="list", ...) instead.
|
|
309
|
+
This tool will be removed in a future version.
|
|
310
|
+
|
|
311
|
+
Retrieves status updates for a specific project with pagination support.
|
|
312
|
+
Returns updates in reverse chronological order (newest first).
|
|
313
|
+
|
|
314
|
+
Platform Support:
|
|
315
|
+
- Linear: Lists ProjectUpdate entities via project.projectUpdates
|
|
316
|
+
- GitHub V2: Lists ProjectV2StatusUpdate via project status updates
|
|
317
|
+
- Asana: Lists Project Status Updates
|
|
318
|
+
- JIRA: Returns formatted comments (workaround)
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
project_id: Project identifier (UUID, slugId, or URL)
|
|
322
|
+
limit: Maximum number of updates to return (default: 10, max: 50)
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of ProjectUpdate objects as JSON with adapter metadata, or error information
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
>>> result = await project_update_list(
|
|
329
|
+
... project_id="PROJ-123",
|
|
330
|
+
... limit=5
|
|
331
|
+
... )
|
|
332
|
+
Example: See Returns section
|
|
333
|
+
|
|
334
|
+
Note:
|
|
335
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
336
|
+
project identification.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
warnings.warn(
|
|
340
|
+
"project_update_list is deprecated. Use project_update(action='list', ...) instead.",
|
|
341
|
+
DeprecationWarning,
|
|
342
|
+
stacklevel=2,
|
|
343
|
+
)
|
|
344
|
+
try:
|
|
345
|
+
# Validate limit (before calling get_adapter)
|
|
346
|
+
if limit < 1 or limit > 50:
|
|
347
|
+
return {
|
|
348
|
+
"status": "error",
|
|
349
|
+
"error": "Limit must be between 1 and 50",
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
adapter = get_adapter()
|
|
353
|
+
|
|
354
|
+
# Check if adapter supports project updates
|
|
355
|
+
if not hasattr(adapter, "list_project_updates"):
|
|
356
|
+
return {
|
|
357
|
+
"status": "error",
|
|
358
|
+
"error": f"Adapter '{adapter.adapter_type}' does not support project updates",
|
|
359
|
+
**_build_adapter_metadata(adapter, project_id),
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
# List project updates
|
|
363
|
+
updates = await adapter.list_project_updates(
|
|
364
|
+
project_id=project_id,
|
|
365
|
+
limit=limit,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"status": "completed",
|
|
370
|
+
**_build_adapter_metadata(adapter, project_id),
|
|
371
|
+
"count": len(updates),
|
|
372
|
+
"updates": [update.model_dump() for update in updates],
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
except ValueError as e:
|
|
376
|
+
# Adapter-specific validation errors (e.g., project not found)
|
|
377
|
+
return {
|
|
378
|
+
"status": "error",
|
|
379
|
+
"error": str(e),
|
|
380
|
+
}
|
|
381
|
+
except Exception as e:
|
|
382
|
+
logger.error(f"Failed to list project updates: {e}")
|
|
383
|
+
return {
|
|
384
|
+
"status": "error",
|
|
385
|
+
"error": f"Failed to list project updates: {str(e)}",
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
async def project_update_get(
|
|
390
|
+
update_id: str,
|
|
391
|
+
) -> dict[str, Any]:
|
|
392
|
+
"""Get a specific project update by ID.
|
|
393
|
+
|
|
394
|
+
.. deprecated::
|
|
395
|
+
Use project_update(action="get", ...) instead.
|
|
396
|
+
This tool will be removed in a future version.
|
|
397
|
+
|
|
398
|
+
Retrieves detailed information about a single project status update.
|
|
399
|
+
|
|
400
|
+
Platform Support:
|
|
401
|
+
- Linear: Fetches ProjectUpdate entity by ID
|
|
402
|
+
- GitHub V2: Fetches ProjectV2StatusUpdate by node ID
|
|
403
|
+
- Asana: Fetches Project Status Update by GID
|
|
404
|
+
- JIRA: Returns formatted comment (workaround)
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
update_id: Project update identifier (UUID or platform-specific ID)
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
ProjectUpdate details as JSON with adapter metadata, or error information
|
|
411
|
+
|
|
412
|
+
Example:
|
|
413
|
+
>>> result = await project_update_get(
|
|
414
|
+
... update_id="update-456"
|
|
415
|
+
... )
|
|
416
|
+
Example: See Returns section
|
|
417
|
+
|
|
418
|
+
Note:
|
|
419
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
420
|
+
project identification.
|
|
421
|
+
|
|
422
|
+
"""
|
|
423
|
+
warnings.warn(
|
|
424
|
+
"project_update_get is deprecated. Use project_update(action='get', ...) instead.",
|
|
425
|
+
DeprecationWarning,
|
|
426
|
+
stacklevel=2,
|
|
427
|
+
)
|
|
428
|
+
try:
|
|
429
|
+
# Validate update_id is not empty (before calling get_adapter)
|
|
430
|
+
if not update_id or not update_id.strip():
|
|
431
|
+
return {
|
|
432
|
+
"status": "error",
|
|
433
|
+
"error": "Update ID cannot be empty",
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
adapter = get_adapter()
|
|
437
|
+
|
|
438
|
+
# Check if adapter supports project updates
|
|
439
|
+
if not hasattr(adapter, "get_project_update"):
|
|
440
|
+
return {
|
|
441
|
+
"status": "error",
|
|
442
|
+
"error": f"Adapter '{adapter.adapter_type}' does not support project updates",
|
|
443
|
+
**_build_adapter_metadata(adapter),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# Get project update
|
|
447
|
+
update = await adapter.get_project_update(update_id=update_id.strip())
|
|
448
|
+
|
|
449
|
+
if update is None:
|
|
450
|
+
return {
|
|
451
|
+
"status": "error",
|
|
452
|
+
"error": f"Project update '{update_id}' not found",
|
|
453
|
+
**_build_adapter_metadata(adapter),
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
"status": "completed",
|
|
458
|
+
**_build_adapter_metadata(adapter),
|
|
459
|
+
"update": update.model_dump(),
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
except ValueError as e:
|
|
463
|
+
# Adapter-specific validation errors
|
|
464
|
+
return {
|
|
465
|
+
"status": "error",
|
|
466
|
+
"error": str(e),
|
|
467
|
+
}
|
|
468
|
+
except Exception as e:
|
|
469
|
+
logger.error(f"Failed to get project update: {e}")
|
|
470
|
+
return {
|
|
471
|
+
"status": "error",
|
|
472
|
+
"error": f"Failed to get project update: {str(e)}",
|
|
473
|
+
}
|