mcp-ticketer 0.1.8__py3-none-any.whl → 0.1.12__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.

@@ -1343,6 +1343,226 @@ class LinearAdapter(BaseAdapter[Task]):
1343
1343
 
1344
1344
  return result.get("reactionCreate", {}).get("success", False)
1345
1345
 
1346
+ async def link_to_pull_request(
1347
+ self,
1348
+ ticket_id: str,
1349
+ pr_url: str,
1350
+ pr_number: Optional[int] = None,
1351
+ ) -> Dict[str, Any]:
1352
+ """Link a Linear issue to a GitHub pull request.
1353
+
1354
+ Args:
1355
+ ticket_id: Linear issue identifier (e.g., 'BTA-123')
1356
+ pr_url: GitHub PR URL
1357
+ pr_number: Optional PR number (extracted from URL if not provided)
1358
+
1359
+ Returns:
1360
+ Dictionary with link status and details
1361
+ """
1362
+ # Parse PR URL to extract details
1363
+ import re
1364
+ pr_pattern = r"github\.com/([^/]+)/([^/]+)/pull/(\d+)"
1365
+ match = re.search(pr_pattern, pr_url)
1366
+
1367
+ if not match:
1368
+ raise ValueError(f"Invalid GitHub PR URL format: {pr_url}")
1369
+
1370
+ owner, repo, extracted_pr_number = match.groups()
1371
+ if not pr_number:
1372
+ pr_number = int(extracted_pr_number)
1373
+
1374
+ # Create an attachment to link the PR
1375
+ create_query = gql("""
1376
+ mutation CreateAttachment($input: AttachmentCreateInput!) {
1377
+ attachmentCreate(input: $input) {
1378
+ attachment {
1379
+ id
1380
+ url
1381
+ title
1382
+ subtitle
1383
+ source
1384
+ }
1385
+ success
1386
+ }
1387
+ }
1388
+ """)
1389
+
1390
+ # Get the issue ID from the identifier
1391
+ issue = await self.read(ticket_id)
1392
+ if not issue:
1393
+ raise ValueError(f"Issue {ticket_id} not found")
1394
+
1395
+ # Create attachment input
1396
+ attachment_input = {
1397
+ "issueId": issue.metadata.get("linear", {}).get("id"),
1398
+ "url": pr_url,
1399
+ "title": f"Pull Request #{pr_number}",
1400
+ "subtitle": f"{owner}/{repo}",
1401
+ "source": {
1402
+ "type": "githubPr",
1403
+ "data": {
1404
+ "number": pr_number,
1405
+ "owner": owner,
1406
+ "repo": repo,
1407
+ }
1408
+ },
1409
+ }
1410
+
1411
+ async with self.client as session:
1412
+ result = await session.execute(
1413
+ create_query,
1414
+ variable_values={"input": attachment_input}
1415
+ )
1416
+
1417
+ if result.get("attachmentCreate", {}).get("success"):
1418
+ attachment = result["attachmentCreate"]["attachment"]
1419
+
1420
+ # Also add a comment about the PR link
1421
+ comment_text = f"Linked to GitHub PR: {pr_url}"
1422
+ await self.add_comment(
1423
+ Comment(
1424
+ ticket_id=ticket_id,
1425
+ content=comment_text,
1426
+ author="system",
1427
+ )
1428
+ )
1429
+
1430
+ return {
1431
+ "success": True,
1432
+ "attachment_id": attachment["id"],
1433
+ "pr_url": pr_url,
1434
+ "pr_number": pr_number,
1435
+ "linked_issue": ticket_id,
1436
+ "message": f"Successfully linked PR #{pr_number} to issue {ticket_id}",
1437
+ }
1438
+ else:
1439
+ return {
1440
+ "success": False,
1441
+ "pr_url": pr_url,
1442
+ "pr_number": pr_number,
1443
+ "linked_issue": ticket_id,
1444
+ "message": "Failed to create attachment link",
1445
+ }
1446
+
1447
+ async def create_pull_request_for_issue(
1448
+ self,
1449
+ ticket_id: str,
1450
+ github_config: Dict[str, Any],
1451
+ ) -> Dict[str, Any]:
1452
+ """Create a GitHub PR for a Linear issue using GitHub integration.
1453
+
1454
+ This requires GitHub integration to be configured in Linear.
1455
+
1456
+ Args:
1457
+ ticket_id: Linear issue identifier
1458
+ github_config: GitHub configuration including:
1459
+ - owner: GitHub repository owner
1460
+ - repo: GitHub repository name
1461
+ - base_branch: Target branch (default: main)
1462
+ - head_branch: Source branch (auto-generated if not provided)
1463
+
1464
+ Returns:
1465
+ Dictionary with PR creation status
1466
+ """
1467
+ # Get the issue details
1468
+ issue = await self.read(ticket_id)
1469
+ if not issue:
1470
+ raise ValueError(f"Issue {ticket_id} not found")
1471
+
1472
+ # Generate branch name if not provided
1473
+ head_branch = github_config.get("head_branch")
1474
+ if not head_branch:
1475
+ # Use Linear's branch naming convention
1476
+ # e.g., "bta-123-fix-authentication-bug"
1477
+ safe_title = "-".join(
1478
+ issue.title.lower()
1479
+ .replace("[", "")
1480
+ .replace("]", "")
1481
+ .replace("#", "")
1482
+ .replace("/", "-")
1483
+ .replace("\\", "-")
1484
+ .split()[:5] # Limit to 5 words
1485
+ )
1486
+ head_branch = f"{ticket_id.lower()}-{safe_title}"
1487
+
1488
+ # Update the issue with the branch name
1489
+ update_query = gql("""
1490
+ mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
1491
+ issueUpdate(id: $id, input: $input) {
1492
+ issue {
1493
+ id
1494
+ identifier
1495
+ branchName
1496
+ }
1497
+ success
1498
+ }
1499
+ }
1500
+ """)
1501
+
1502
+ linear_id = issue.metadata.get("linear", {}).get("id")
1503
+ if not linear_id:
1504
+ # Need to get the full issue ID
1505
+ search_result = await self._search_by_identifier(ticket_id)
1506
+ if not search_result:
1507
+ raise ValueError(f"Could not find Linear ID for issue {ticket_id}")
1508
+ linear_id = search_result["id"]
1509
+
1510
+ async with self.client as session:
1511
+ result = await session.execute(
1512
+ update_query,
1513
+ variable_values={
1514
+ "id": linear_id,
1515
+ "input": {"branchName": head_branch}
1516
+ }
1517
+ )
1518
+
1519
+ if result.get("issueUpdate", {}).get("success"):
1520
+ # Prepare PR metadata to return
1521
+ pr_metadata = {
1522
+ "branch_name": head_branch,
1523
+ "issue_id": ticket_id,
1524
+ "issue_title": issue.title,
1525
+ "issue_description": issue.description,
1526
+ "github_owner": github_config.get("owner"),
1527
+ "github_repo": github_config.get("repo"),
1528
+ "base_branch": github_config.get("base_branch", "main"),
1529
+ "message": f"Branch name '{head_branch}' set for issue {ticket_id}. Use GitHub integration or API to create the actual PR.",
1530
+ }
1531
+
1532
+ # Add a comment about the branch
1533
+ await self.add_comment(
1534
+ Comment(
1535
+ ticket_id=ticket_id,
1536
+ content=f"Branch created: `{head_branch}`\nReady for pull request to `{pr_metadata['base_branch']}`",
1537
+ author="system",
1538
+ )
1539
+ )
1540
+
1541
+ return pr_metadata
1542
+ else:
1543
+ raise ValueError(f"Failed to update issue {ticket_id} with branch name")
1544
+
1545
+ async def _search_by_identifier(self, identifier: str) -> Optional[Dict[str, Any]]:
1546
+ """Search for an issue by its identifier."""
1547
+ search_query = gql("""
1548
+ query SearchIssue($identifier: String!) {
1549
+ issue(id: $identifier) {
1550
+ id
1551
+ identifier
1552
+ }
1553
+ }
1554
+ """)
1555
+
1556
+ try:
1557
+ async with self.client as session:
1558
+ result = await session.execute(
1559
+ search_query,
1560
+ variable_values={"identifier": identifier}
1561
+ )
1562
+ return result.get("issue")
1563
+ except:
1564
+ return None
1565
+
1346
1566
  async def close(self) -> None:
1347
1567
  """Close the GraphQL client connection."""
1348
1568
  if hasattr(self.client, 'close_async'):