mcp-ticketer 0.12.0__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.

Files changed (129) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/aitrackdown.py +507 -6
  5. mcp_ticketer/adapters/asana/adapter.py +229 -0
  6. mcp_ticketer/adapters/asana/mappers.py +14 -0
  7. mcp_ticketer/adapters/github/__init__.py +26 -0
  8. mcp_ticketer/adapters/github/adapter.py +3229 -0
  9. mcp_ticketer/adapters/github/client.py +335 -0
  10. mcp_ticketer/adapters/github/mappers.py +797 -0
  11. mcp_ticketer/adapters/github/queries.py +692 -0
  12. mcp_ticketer/adapters/github/types.py +460 -0
  13. mcp_ticketer/adapters/hybrid.py +47 -5
  14. mcp_ticketer/adapters/jira/__init__.py +35 -0
  15. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  16. mcp_ticketer/adapters/jira/client.py +271 -0
  17. mcp_ticketer/adapters/jira/mappers.py +246 -0
  18. mcp_ticketer/adapters/jira/queries.py +216 -0
  19. mcp_ticketer/adapters/jira/types.py +304 -0
  20. mcp_ticketer/adapters/linear/adapter.py +2730 -139
  21. mcp_ticketer/adapters/linear/client.py +175 -3
  22. mcp_ticketer/adapters/linear/mappers.py +203 -8
  23. mcp_ticketer/adapters/linear/queries.py +280 -3
  24. mcp_ticketer/adapters/linear/types.py +120 -4
  25. mcp_ticketer/analysis/__init__.py +56 -0
  26. mcp_ticketer/analysis/dependency_graph.py +255 -0
  27. mcp_ticketer/analysis/health_assessment.py +304 -0
  28. mcp_ticketer/analysis/orphaned.py +218 -0
  29. mcp_ticketer/analysis/project_status.py +594 -0
  30. mcp_ticketer/analysis/similarity.py +224 -0
  31. mcp_ticketer/analysis/staleness.py +266 -0
  32. mcp_ticketer/automation/__init__.py +11 -0
  33. mcp_ticketer/automation/project_updates.py +378 -0
  34. mcp_ticketer/cli/adapter_diagnostics.py +3 -1
  35. mcp_ticketer/cli/auggie_configure.py +17 -5
  36. mcp_ticketer/cli/codex_configure.py +97 -61
  37. mcp_ticketer/cli/configure.py +1288 -105
  38. mcp_ticketer/cli/cursor_configure.py +314 -0
  39. mcp_ticketer/cli/diagnostics.py +13 -12
  40. mcp_ticketer/cli/discover.py +5 -0
  41. mcp_ticketer/cli/gemini_configure.py +17 -5
  42. mcp_ticketer/cli/init_command.py +880 -0
  43. mcp_ticketer/cli/install_mcp_server.py +418 -0
  44. mcp_ticketer/cli/instruction_commands.py +6 -0
  45. mcp_ticketer/cli/main.py +267 -3175
  46. mcp_ticketer/cli/mcp_configure.py +821 -119
  47. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  48. mcp_ticketer/cli/platform_detection.py +77 -12
  49. mcp_ticketer/cli/platform_installer.py +545 -0
  50. mcp_ticketer/cli/project_update_commands.py +350 -0
  51. mcp_ticketer/cli/setup_command.py +795 -0
  52. mcp_ticketer/cli/simple_health.py +12 -10
  53. mcp_ticketer/cli/ticket_commands.py +705 -103
  54. mcp_ticketer/cli/utils.py +113 -0
  55. mcp_ticketer/core/__init__.py +56 -6
  56. mcp_ticketer/core/adapter.py +533 -2
  57. mcp_ticketer/core/config.py +21 -21
  58. mcp_ticketer/core/exceptions.py +7 -1
  59. mcp_ticketer/core/label_manager.py +732 -0
  60. mcp_ticketer/core/mappers.py +31 -19
  61. mcp_ticketer/core/milestone_manager.py +252 -0
  62. mcp_ticketer/core/models.py +480 -0
  63. mcp_ticketer/core/onepassword_secrets.py +1 -1
  64. mcp_ticketer/core/priority_matcher.py +463 -0
  65. mcp_ticketer/core/project_config.py +132 -14
  66. mcp_ticketer/core/project_utils.py +281 -0
  67. mcp_ticketer/core/project_validator.py +376 -0
  68. mcp_ticketer/core/session_state.py +176 -0
  69. mcp_ticketer/core/state_matcher.py +625 -0
  70. mcp_ticketer/core/url_parser.py +425 -0
  71. mcp_ticketer/core/validators.py +69 -0
  72. mcp_ticketer/mcp/server/__main__.py +2 -1
  73. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  74. mcp_ticketer/mcp/server/main.py +106 -25
  75. mcp_ticketer/mcp/server/routing.py +723 -0
  76. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  77. mcp_ticketer/mcp/server/tools/__init__.py +33 -11
  78. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  79. mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
  80. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  81. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  82. mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
  83. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  84. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
  85. mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
  86. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  87. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  88. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  89. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  90. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  91. mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
  92. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  93. mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
  94. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
  95. mcp_ticketer/queue/queue.py +68 -0
  96. mcp_ticketer/queue/worker.py +1 -1
  97. mcp_ticketer/utils/__init__.py +5 -0
  98. mcp_ticketer/utils/token_utils.py +246 -0
  99. mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
  100. mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
  101. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  102. py_mcp_installer/examples/phase3_demo.py +178 -0
  103. py_mcp_installer/scripts/manage_version.py +54 -0
  104. py_mcp_installer/setup.py +6 -0
  105. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  106. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  107. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  108. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  109. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  110. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  111. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  112. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  113. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  114. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  115. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  116. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  117. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  118. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  119. py_mcp_installer/tests/__init__.py +0 -0
  120. py_mcp_installer/tests/platforms/__init__.py +0 -0
  121. py_mcp_installer/tests/test_platform_detector.py +17 -0
  122. mcp_ticketer/adapters/github.py +0 -1574
  123. mcp_ticketer/adapters/jira.py +0 -1258
  124. mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
  125. mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
  126. mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
  127. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  128. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  129. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,176 @@
1
+ """Session state management for tracking current ticket associations."""
2
+
3
+ import json
4
+ import logging
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timedelta
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Session timeout: 30 minutes of inactivity
14
+ SESSION_TIMEOUT_MINUTES = 30
15
+ SESSION_STATE_FILE = ".mcp-ticketer/session.json"
16
+
17
+
18
+ @dataclass
19
+ class SessionState:
20
+ """Track session-specific state for ticket associations."""
21
+
22
+ session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
23
+ current_ticket: str | None = None # Current ticket ID
24
+ ticket_opted_out: bool = False # User explicitly chose "none"
25
+ last_activity: str = field(default_factory=lambda: datetime.now().isoformat())
26
+
27
+ def to_dict(self) -> dict[str, Any]:
28
+ """Serialize to dictionary."""
29
+ return {
30
+ "session_id": self.session_id,
31
+ "current_ticket": self.current_ticket,
32
+ "ticket_opted_out": self.ticket_opted_out,
33
+ "last_activity": self.last_activity,
34
+ }
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: dict[str, Any]) -> "SessionState":
38
+ """Deserialize from dictionary."""
39
+ return cls(
40
+ session_id=data.get("session_id", str(uuid.uuid4())),
41
+ current_ticket=data.get("current_ticket"),
42
+ ticket_opted_out=data.get("ticket_opted_out", False),
43
+ last_activity=data.get("last_activity", datetime.now().isoformat()),
44
+ )
45
+
46
+ def is_expired(self) -> bool:
47
+ """Check if session has expired due to inactivity."""
48
+ try:
49
+ last_activity = datetime.fromisoformat(self.last_activity)
50
+ timeout = timedelta(minutes=SESSION_TIMEOUT_MINUTES)
51
+ return datetime.now() - last_activity > timeout
52
+ except (ValueError, TypeError):
53
+ # Invalid timestamp, consider expired
54
+ return True
55
+
56
+ def touch(self) -> None:
57
+ """Update last activity timestamp."""
58
+ self.last_activity = datetime.now().isoformat()
59
+
60
+
61
+ class SessionStateManager:
62
+ """Manage session state persistence and lifecycle."""
63
+
64
+ def __init__(self, project_path: Path | None = None):
65
+ """Initialize session state manager.
66
+
67
+ Args:
68
+ project_path: Project root directory (defaults to current directory)
69
+
70
+ """
71
+ self.project_path = project_path or Path.cwd()
72
+ self.state_file = self.project_path / SESSION_STATE_FILE
73
+
74
+ def load_session(self) -> SessionState:
75
+ """Load session state from file.
76
+
77
+ Automatically updates last_activity timestamp on every load to prevent
78
+ session expiration during active use.
79
+
80
+ Returns:
81
+ SessionState instance (creates new if expired or not found)
82
+
83
+ """
84
+ if not self.state_file.exists():
85
+ logger.debug("No session state file found, creating new session")
86
+ return SessionState()
87
+
88
+ try:
89
+ with open(self.state_file) as f:
90
+ data = json.load(f)
91
+
92
+ state = SessionState.from_dict(data)
93
+
94
+ # Check if session expired
95
+ if state.is_expired():
96
+ logger.info(
97
+ f"Session {state.session_id} expired after "
98
+ f"{SESSION_TIMEOUT_MINUTES} minutes, creating new session"
99
+ )
100
+ return SessionState()
101
+
102
+ # Auto-renew: Update last_activity and persist on every load
103
+ state.touch()
104
+ self.save_session(state)
105
+
106
+ return state
107
+
108
+ except (json.JSONDecodeError, FileNotFoundError, KeyError) as e:
109
+ logger.warning(f"Failed to load session state: {e}, creating new session")
110
+ return SessionState()
111
+
112
+ def save_session(self, state: SessionState) -> None:
113
+ """Save session state to file.
114
+
115
+ Args:
116
+ state: SessionState to persist
117
+
118
+ """
119
+ try:
120
+ # Ensure directory exists
121
+ self.state_file.parent.mkdir(parents=True, exist_ok=True)
122
+
123
+ # Touch before saving
124
+ state.touch()
125
+
126
+ # Write state
127
+ with open(self.state_file, "w") as f:
128
+ json.dump(state.to_dict(), f, indent=2)
129
+
130
+ logger.debug(f"Saved session state: session_id={state.session_id}")
131
+
132
+ except Exception as e:
133
+ logger.error(f"Failed to save session state: {e}")
134
+
135
+ def clear_session(self) -> None:
136
+ """Clear session state (delete file)."""
137
+ try:
138
+ if self.state_file.exists():
139
+ self.state_file.unlink()
140
+ logger.info("Session state cleared")
141
+ except Exception as e:
142
+ logger.error(f"Failed to clear session state: {e}")
143
+
144
+ def get_current_ticket(self) -> str | None:
145
+ """Get current ticket for this session (convenience method).
146
+
147
+ Returns:
148
+ Current ticket ID or None
149
+
150
+ """
151
+ state = self.load_session()
152
+
153
+ # If user opted out, return None
154
+ if state.ticket_opted_out:
155
+ return None
156
+
157
+ return state.current_ticket
158
+
159
+ def set_current_ticket(self, ticket_id: str | None) -> None:
160
+ """Set current ticket for this session (convenience method).
161
+
162
+ Args:
163
+ ticket_id: Ticket ID to set (None to clear)
164
+
165
+ """
166
+ state = self.load_session()
167
+ state.current_ticket = ticket_id
168
+ state.ticket_opted_out = False # Clear opt-out when setting ticket
169
+ self.save_session(state)
170
+
171
+ def opt_out_ticket(self) -> None:
172
+ """Mark that user doesn't want to associate work with a ticket (convenience method)."""
173
+ state = self.load_session()
174
+ state.current_ticket = None
175
+ state.ticket_opted_out = True
176
+ self.save_session(state)