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
mcp_ticketer/core/mappers.py
CHANGED
|
@@ -98,13 +98,13 @@ class StateMapper(BaseMapper):
|
|
|
98
98
|
self._mapping: BiDirectionalDict | None = None
|
|
99
99
|
|
|
100
100
|
@lru_cache(maxsize=1)
|
|
101
|
-
def get_mapping(self) -> BiDirectionalDict:
|
|
101
|
+
def get_mapping(self) -> BiDirectionalDict[TicketState, str]:
|
|
102
102
|
"""Get cached bidirectional state mapping."""
|
|
103
103
|
if self._mapping is not None:
|
|
104
104
|
return self._mapping
|
|
105
105
|
|
|
106
106
|
# Default mappings by adapter type
|
|
107
|
-
default_mappings = {
|
|
107
|
+
default_mappings: dict[str, dict[TicketState, str]] = {
|
|
108
108
|
"github": {
|
|
109
109
|
TicketState.OPEN: "open",
|
|
110
110
|
TicketState.IN_PROGRESS: "open", # Uses labels
|
|
@@ -147,13 +147,16 @@ class StateMapper(BaseMapper):
|
|
|
147
147
|
},
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
mapping = default_mappings.get(self.adapter_type, {})
|
|
150
|
+
mapping: dict[TicketState, str] = default_mappings.get(self.adapter_type, {})
|
|
151
151
|
|
|
152
|
-
# Apply custom mappings
|
|
152
|
+
# Apply custom mappings (cast to proper type)
|
|
153
153
|
if self.custom_mappings:
|
|
154
|
-
|
|
154
|
+
# custom_mappings might have str keys, need to convert to TicketState
|
|
155
|
+
for key, value in self.custom_mappings.items():
|
|
156
|
+
if isinstance(key, TicketState):
|
|
157
|
+
mapping[key] = value
|
|
155
158
|
|
|
156
|
-
self._mapping = BiDirectionalDict(mapping)
|
|
159
|
+
self._mapping = BiDirectionalDict[TicketState, str](mapping)
|
|
157
160
|
return self._mapping
|
|
158
161
|
|
|
159
162
|
def to_system_state(self, adapter_state: str) -> TicketState:
|
|
@@ -168,7 +171,9 @@ class StateMapper(BaseMapper):
|
|
|
168
171
|
"""
|
|
169
172
|
cache_key = f"to_system_{adapter_state}"
|
|
170
173
|
if cache_key in self._cache:
|
|
171
|
-
|
|
174
|
+
cached = self._cache[cache_key]
|
|
175
|
+
if isinstance(cached, TicketState):
|
|
176
|
+
return cached
|
|
172
177
|
|
|
173
178
|
mapping = self.get_mapping()
|
|
174
179
|
result = mapping.get_reverse(adapter_state)
|
|
@@ -205,7 +210,9 @@ class StateMapper(BaseMapper):
|
|
|
205
210
|
"""
|
|
206
211
|
cache_key = f"from_system_{system_state.value}"
|
|
207
212
|
if cache_key in self._cache:
|
|
208
|
-
|
|
213
|
+
cached = self._cache[cache_key]
|
|
214
|
+
if isinstance(cached, str):
|
|
215
|
+
return cached
|
|
209
216
|
|
|
210
217
|
mapping = self.get_mapping()
|
|
211
218
|
result = mapping.get_forward(system_state)
|
|
@@ -273,13 +280,13 @@ class PriorityMapper(BaseMapper):
|
|
|
273
280
|
self._mapping: BiDirectionalDict | None = None
|
|
274
281
|
|
|
275
282
|
@lru_cache(maxsize=1)
|
|
276
|
-
def get_mapping(self) -> BiDirectionalDict:
|
|
283
|
+
def get_mapping(self) -> BiDirectionalDict[Priority, Any]:
|
|
277
284
|
"""Get cached bidirectional priority mapping."""
|
|
278
285
|
if self._mapping is not None:
|
|
279
286
|
return self._mapping
|
|
280
287
|
|
|
281
288
|
# Default mappings by adapter type
|
|
282
|
-
default_mappings = {
|
|
289
|
+
default_mappings: dict[str, dict[Priority, Any]] = {
|
|
283
290
|
"github": {
|
|
284
291
|
Priority.CRITICAL: "P0",
|
|
285
292
|
Priority.HIGH: "P1",
|
|
@@ -306,13 +313,16 @@ class PriorityMapper(BaseMapper):
|
|
|
306
313
|
},
|
|
307
314
|
}
|
|
308
315
|
|
|
309
|
-
mapping = default_mappings.get(self.adapter_type, {})
|
|
316
|
+
mapping: dict[Priority, Any] = default_mappings.get(self.adapter_type, {})
|
|
310
317
|
|
|
311
|
-
# Apply custom mappings
|
|
318
|
+
# Apply custom mappings (cast to proper type)
|
|
312
319
|
if self.custom_mappings:
|
|
313
|
-
|
|
320
|
+
# custom_mappings might have str keys, need to convert to Priority
|
|
321
|
+
for key, value in self.custom_mappings.items():
|
|
322
|
+
if isinstance(key, Priority):
|
|
323
|
+
mapping[key] = value
|
|
314
324
|
|
|
315
|
-
self._mapping = BiDirectionalDict(mapping)
|
|
325
|
+
self._mapping = BiDirectionalDict[Priority, Any](mapping)
|
|
316
326
|
return self._mapping
|
|
317
327
|
|
|
318
328
|
def to_system_priority(self, adapter_priority: Any) -> Priority:
|
|
@@ -327,7 +337,9 @@ class PriorityMapper(BaseMapper):
|
|
|
327
337
|
"""
|
|
328
338
|
cache_key = f"to_system_{adapter_priority}"
|
|
329
339
|
if cache_key in self._cache:
|
|
330
|
-
|
|
340
|
+
cached = self._cache[cache_key]
|
|
341
|
+
if isinstance(cached, Priority):
|
|
342
|
+
return cached
|
|
331
343
|
|
|
332
344
|
mapping = self.get_mapping()
|
|
333
345
|
result = mapping.get_reverse(adapter_priority)
|
|
@@ -524,10 +536,10 @@ class MapperRegistry:
|
|
|
524
536
|
@classmethod
|
|
525
537
|
def clear_cache(cls) -> None:
|
|
526
538
|
"""Clear all mapper caches."""
|
|
527
|
-
for
|
|
528
|
-
|
|
529
|
-
for
|
|
530
|
-
|
|
539
|
+
for state_mapper in cls._state_mappers.values():
|
|
540
|
+
state_mapper.clear_cache()
|
|
541
|
+
for priority_mapper in cls._priority_mappers.values():
|
|
542
|
+
priority_mapper.clear_cache()
|
|
531
543
|
|
|
532
544
|
@classmethod
|
|
533
545
|
def reset(cls) -> None:
|
mcp_ticketer/core/models.py
CHANGED
|
@@ -160,6 +160,37 @@ class TicketState(str, Enum):
|
|
|
160
160
|
"""
|
|
161
161
|
return target.value in self.valid_transitions().get(self, [])
|
|
162
162
|
|
|
163
|
+
def completion_level(self) -> int:
|
|
164
|
+
"""Get numeric completion level for state ordering.
|
|
165
|
+
|
|
166
|
+
Higher numbers indicate more complete states. Used for parent/child
|
|
167
|
+
state constraints where parents must be at least as complete as
|
|
168
|
+
their most complete child.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Completion level (0-7)
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> TicketState.OPEN.completion_level()
|
|
175
|
+
0
|
|
176
|
+
>>> TicketState.DONE.completion_level()
|
|
177
|
+
6
|
|
178
|
+
>>> TicketState.DONE.completion_level() > TicketState.IN_PROGRESS.completion_level()
|
|
179
|
+
True
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
levels = {
|
|
183
|
+
TicketState.OPEN: 0, # Not started
|
|
184
|
+
TicketState.BLOCKED: 1, # Blocked
|
|
185
|
+
TicketState.WAITING: 2, # Waiting
|
|
186
|
+
TicketState.IN_PROGRESS: 3, # In progress
|
|
187
|
+
TicketState.READY: 4, # Ready for review
|
|
188
|
+
TicketState.TESTED: 5, # Tested
|
|
189
|
+
TicketState.DONE: 6, # Done
|
|
190
|
+
TicketState.CLOSED: 7, # Closed (terminal)
|
|
191
|
+
}
|
|
192
|
+
return levels.get(self, 0)
|
|
193
|
+
|
|
163
194
|
|
|
164
195
|
class BaseTicket(BaseModel):
|
|
165
196
|
"""Base model for all ticket types with universal field mapping.
|
|
@@ -385,6 +416,109 @@ class Attachment(BaseModel):
|
|
|
385
416
|
return f"Attachment({self.filename}{size_str})"
|
|
386
417
|
|
|
387
418
|
|
|
419
|
+
class ProjectUpdateHealth(str, Enum):
|
|
420
|
+
"""Project health status indicator for status updates.
|
|
421
|
+
|
|
422
|
+
Represents the health/status of a project at the time of an update.
|
|
423
|
+
These states map to different platform-specific health indicators:
|
|
424
|
+
|
|
425
|
+
Platform Mappings:
|
|
426
|
+
- Linear: on_track, at_risk, off_track (1:1 mapping)
|
|
427
|
+
- GitHub V2: Uses ProjectV2StatusOptionConfiguration
|
|
428
|
+
- complete: Project is finished
|
|
429
|
+
- inactive: Project is not actively being worked on
|
|
430
|
+
- Asana: On Track, At Risk, Off Track (1:1 mapping)
|
|
431
|
+
- JIRA: Not directly supported (workaround via status comments)
|
|
432
|
+
|
|
433
|
+
Attributes:
|
|
434
|
+
ON_TRACK: Project is progressing as planned
|
|
435
|
+
AT_RISK: Project has some issues but recoverable
|
|
436
|
+
OFF_TRACK: Project is significantly behind or blocked
|
|
437
|
+
COMPLETE: Project is finished (GitHub-specific)
|
|
438
|
+
INACTIVE: Project is not actively being worked on (GitHub-specific)
|
|
439
|
+
|
|
440
|
+
Note:
|
|
441
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
442
|
+
project identification.
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
ON_TRACK = "on_track" # Linear, Asana
|
|
447
|
+
AT_RISK = "at_risk" # Linear, Asana
|
|
448
|
+
OFF_TRACK = "off_track" # Linear, Asana
|
|
449
|
+
COMPLETE = "complete" # GitHub only
|
|
450
|
+
INACTIVE = "inactive" # GitHub only
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class ProjectUpdate(BaseModel):
|
|
454
|
+
"""Represents a project status update across different platforms.
|
|
455
|
+
|
|
456
|
+
ProjectUpdate provides a unified interface for creating and retrieving
|
|
457
|
+
project status updates with health indicators, supporting Linear, GitHub V2,
|
|
458
|
+
Asana, and JIRA (via workaround).
|
|
459
|
+
|
|
460
|
+
Platform Mappings:
|
|
461
|
+
- Linear: ProjectUpdate entity with health, diff_markdown, staleness
|
|
462
|
+
- GitHub V2: ProjectV2StatusUpdate with status options
|
|
463
|
+
- Asana: Project Status Updates with color-coded health
|
|
464
|
+
- JIRA: Comments with custom formatting (workaround)
|
|
465
|
+
|
|
466
|
+
The model includes platform-specific optional fields to support features
|
|
467
|
+
like Linear's auto-generated diffs and staleness indicators.
|
|
468
|
+
|
|
469
|
+
Attributes:
|
|
470
|
+
id: Unique identifier for the update
|
|
471
|
+
project_id: ID of the project this update belongs to
|
|
472
|
+
project_name: Optional human-readable project name
|
|
473
|
+
body: Markdown-formatted update content (required)
|
|
474
|
+
health: Optional health status indicator
|
|
475
|
+
created_at: Timestamp when update was created
|
|
476
|
+
updated_at: Timestamp when update was last modified
|
|
477
|
+
author_id: Optional ID of the user who created the update
|
|
478
|
+
author_name: Optional human-readable author name
|
|
479
|
+
url: Optional direct URL to the update
|
|
480
|
+
diff_markdown: Linear-specific auto-generated diff of project changes
|
|
481
|
+
is_stale: Linear-specific indicator if update is outdated
|
|
482
|
+
|
|
483
|
+
Example:
|
|
484
|
+
>>> update = ProjectUpdate(
|
|
485
|
+
... project_id="PROJ-123",
|
|
486
|
+
... body="Sprint completed with 15/20 stories done",
|
|
487
|
+
... health=ProjectUpdateHealth.AT_RISK,
|
|
488
|
+
... created_at=datetime.now()
|
|
489
|
+
... )
|
|
490
|
+
>>> print(update.model_dump_json())
|
|
491
|
+
|
|
492
|
+
Note:
|
|
493
|
+
Related to ticket 1M-238: Add project updates support with flexible
|
|
494
|
+
project identification.
|
|
495
|
+
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
499
|
+
|
|
500
|
+
id: str = Field(..., description="Unique update identifier")
|
|
501
|
+
project_id: str = Field(..., description="Parent project identifier")
|
|
502
|
+
project_name: str | None = Field(None, description="Human-readable project name")
|
|
503
|
+
body: str = Field(..., min_length=1, description="Markdown update content")
|
|
504
|
+
health: ProjectUpdateHealth | None = Field(
|
|
505
|
+
None, description="Project health status"
|
|
506
|
+
)
|
|
507
|
+
created_at: datetime = Field(..., description="Creation timestamp")
|
|
508
|
+
updated_at: datetime | None = Field(None, description="Last update timestamp")
|
|
509
|
+
author_id: str | None = Field(None, description="Update author identifier")
|
|
510
|
+
author_name: str | None = Field(None, description="Update author name")
|
|
511
|
+
url: str | None = Field(None, description="Direct URL to update")
|
|
512
|
+
|
|
513
|
+
# Platform-specific fields
|
|
514
|
+
diff_markdown: str | None = Field(
|
|
515
|
+
None, description="Linear: Auto-generated diff of project changes"
|
|
516
|
+
)
|
|
517
|
+
is_stale: bool | None = Field(
|
|
518
|
+
None, description="Linear: Indicator if update is outdated"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
|
|
388
522
|
class SearchQuery(BaseModel):
|
|
389
523
|
"""Search query parameters."""
|
|
390
524
|
|
|
@@ -393,5 +527,6 @@ class SearchQuery(BaseModel):
|
|
|
393
527
|
priority: Priority | None = Field(None, description="Filter by priority")
|
|
394
528
|
tags: list[str] | None = Field(None, description="Filter by tags")
|
|
395
529
|
assignee: str | None = Field(None, description="Filter by assignee")
|
|
530
|
+
project: str | None = Field(None, description="Filter by project/epic ID or name")
|
|
396
531
|
limit: int = Field(10, gt=0, le=100, description="Maximum results")
|
|
397
532
|
offset: int = Field(0, ge=0, description="Result offset for pagination")
|