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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- 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/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- 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 +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -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 +1288 -105
- 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/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- 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/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -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/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- 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 +1391 -145
- 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/milestone_tools.py +338 -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 +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- 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.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- 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/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- 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/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -351,13 +351,18 @@ SEARCH_ISSUE_BY_IDENTIFIER_QUERY = """
|
|
|
351
351
|
"""
|
|
352
352
|
|
|
353
353
|
LIST_PROJECTS_QUERY = (
|
|
354
|
-
PROJECT_FRAGMENT
|
|
354
|
+
TEAM_FRAGMENT # Required by PROJECT_FRAGMENT which uses ...TeamFields
|
|
355
|
+
+ PROJECT_FRAGMENT
|
|
355
356
|
+ """
|
|
356
|
-
query ListProjects($filter: ProjectFilter, $first: Int
|
|
357
|
-
projects(filter: $filter, first: $first, orderBy: updatedAt) {
|
|
357
|
+
query ListProjects($filter: ProjectFilter, $first: Int!, $after: String) {
|
|
358
|
+
projects(filter: $filter, first: $first, after: $after, orderBy: updatedAt) {
|
|
358
359
|
nodes {
|
|
359
360
|
...ProjectFields
|
|
360
361
|
}
|
|
362
|
+
pageInfo {
|
|
363
|
+
hasNextPage
|
|
364
|
+
endCursor
|
|
365
|
+
}
|
|
361
366
|
}
|
|
362
367
|
}
|
|
363
368
|
"""
|
|
@@ -387,3 +392,275 @@ GET_CURRENT_USER_QUERY = (
|
|
|
387
392
|
}
|
|
388
393
|
"""
|
|
389
394
|
)
|
|
395
|
+
|
|
396
|
+
CREATE_LABEL_MUTATION = """
|
|
397
|
+
mutation CreateLabel($input: IssueLabelCreateInput!) {
|
|
398
|
+
issueLabelCreate(input: $input) {
|
|
399
|
+
success
|
|
400
|
+
issueLabel {
|
|
401
|
+
id
|
|
402
|
+
name
|
|
403
|
+
color
|
|
404
|
+
description
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
LIST_CYCLES_QUERY = """
|
|
411
|
+
query GetCycles($teamId: String!, $first: Int!, $after: String) {
|
|
412
|
+
team(id: $teamId) {
|
|
413
|
+
cycles(first: $first, after: $after) {
|
|
414
|
+
nodes {
|
|
415
|
+
id
|
|
416
|
+
name
|
|
417
|
+
number
|
|
418
|
+
startsAt
|
|
419
|
+
endsAt
|
|
420
|
+
completedAt
|
|
421
|
+
progress
|
|
422
|
+
}
|
|
423
|
+
pageInfo {
|
|
424
|
+
hasNextPage
|
|
425
|
+
endCursor
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
GET_ISSUE_STATUS_QUERY = """
|
|
433
|
+
query GetIssueStatus($issueId: String!) {
|
|
434
|
+
issue(id: $issueId) {
|
|
435
|
+
id
|
|
436
|
+
state {
|
|
437
|
+
id
|
|
438
|
+
name
|
|
439
|
+
type
|
|
440
|
+
color
|
|
441
|
+
description
|
|
442
|
+
position
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
"""
|
|
447
|
+
|
|
448
|
+
LIST_ISSUE_STATUSES_QUERY = """
|
|
449
|
+
query GetWorkflowStates($teamId: String!) {
|
|
450
|
+
team(id: $teamId) {
|
|
451
|
+
states {
|
|
452
|
+
nodes {
|
|
453
|
+
id
|
|
454
|
+
name
|
|
455
|
+
type
|
|
456
|
+
color
|
|
457
|
+
description
|
|
458
|
+
position
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
GET_CUSTOM_VIEW_QUERY = (
|
|
466
|
+
ISSUE_LIST_FRAGMENTS
|
|
467
|
+
+ """
|
|
468
|
+
query GetCustomView($viewId: String!, $first: Int!) {
|
|
469
|
+
customView(id: $viewId) {
|
|
470
|
+
id
|
|
471
|
+
name
|
|
472
|
+
description
|
|
473
|
+
issues(first: $first) {
|
|
474
|
+
nodes {
|
|
475
|
+
...IssueCompactFields
|
|
476
|
+
}
|
|
477
|
+
pageInfo {
|
|
478
|
+
hasNextPage
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
"""
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# Project Update queries and mutations (1M-238)
|
|
487
|
+
|
|
488
|
+
PROJECT_UPDATE_FRAGMENT = """
|
|
489
|
+
fragment ProjectUpdateFields on ProjectUpdate {
|
|
490
|
+
id
|
|
491
|
+
body
|
|
492
|
+
health
|
|
493
|
+
createdAt
|
|
494
|
+
updatedAt
|
|
495
|
+
diffMarkdown
|
|
496
|
+
url
|
|
497
|
+
user {
|
|
498
|
+
id
|
|
499
|
+
name
|
|
500
|
+
email
|
|
501
|
+
}
|
|
502
|
+
project {
|
|
503
|
+
id
|
|
504
|
+
name
|
|
505
|
+
slugId
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
CREATE_PROJECT_UPDATE_MUTATION = (
|
|
511
|
+
PROJECT_UPDATE_FRAGMENT
|
|
512
|
+
+ """
|
|
513
|
+
mutation ProjectUpdateCreate(
|
|
514
|
+
$projectId: String!
|
|
515
|
+
$body: String!
|
|
516
|
+
$health: ProjectUpdateHealthType
|
|
517
|
+
) {
|
|
518
|
+
projectUpdateCreate(
|
|
519
|
+
input: {
|
|
520
|
+
projectId: $projectId
|
|
521
|
+
body: $body
|
|
522
|
+
health: $health
|
|
523
|
+
}
|
|
524
|
+
) {
|
|
525
|
+
success
|
|
526
|
+
projectUpdate {
|
|
527
|
+
...ProjectUpdateFields
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
"""
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
LIST_PROJECT_UPDATES_QUERY = (
|
|
535
|
+
PROJECT_UPDATE_FRAGMENT
|
|
536
|
+
+ """
|
|
537
|
+
query ProjectUpdates($projectId: String!, $first: Int) {
|
|
538
|
+
project(id: $projectId) {
|
|
539
|
+
id
|
|
540
|
+
name
|
|
541
|
+
projectUpdates(first: $first) {
|
|
542
|
+
nodes {
|
|
543
|
+
...ProjectUpdateFields
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
"""
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
GET_PROJECT_UPDATE_QUERY = (
|
|
552
|
+
PROJECT_UPDATE_FRAGMENT
|
|
553
|
+
+ """
|
|
554
|
+
query ProjectUpdate($id: String!) {
|
|
555
|
+
projectUpdate(id: $id) {
|
|
556
|
+
...ProjectUpdateFields
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
"""
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
# Milestone/Cycle Operations (1M-607 Phase 2)
|
|
563
|
+
|
|
564
|
+
CREATE_CYCLE_MUTATION = """
|
|
565
|
+
mutation CycleCreate($input: CycleCreateInput!) {
|
|
566
|
+
cycleCreate(input: $input) {
|
|
567
|
+
success
|
|
568
|
+
cycle {
|
|
569
|
+
id
|
|
570
|
+
name
|
|
571
|
+
description
|
|
572
|
+
startsAt
|
|
573
|
+
endsAt
|
|
574
|
+
completedAt
|
|
575
|
+
progress
|
|
576
|
+
completedIssueCount
|
|
577
|
+
issueCount
|
|
578
|
+
team {
|
|
579
|
+
id
|
|
580
|
+
name
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
GET_CYCLE_QUERY = """
|
|
588
|
+
query Cycle($id: String!) {
|
|
589
|
+
cycle(id: $id) {
|
|
590
|
+
id
|
|
591
|
+
name
|
|
592
|
+
description
|
|
593
|
+
startsAt
|
|
594
|
+
endsAt
|
|
595
|
+
completedAt
|
|
596
|
+
progress
|
|
597
|
+
completedIssueCount
|
|
598
|
+
issueCount
|
|
599
|
+
team {
|
|
600
|
+
id
|
|
601
|
+
name
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
UPDATE_CYCLE_MUTATION = """
|
|
608
|
+
mutation CycleUpdate($id: String!, $input: CycleUpdateInput!) {
|
|
609
|
+
cycleUpdate(id: $id, input: $input) {
|
|
610
|
+
success
|
|
611
|
+
cycle {
|
|
612
|
+
id
|
|
613
|
+
name
|
|
614
|
+
description
|
|
615
|
+
startsAt
|
|
616
|
+
endsAt
|
|
617
|
+
completedAt
|
|
618
|
+
progress
|
|
619
|
+
completedIssueCount
|
|
620
|
+
issueCount
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
"""
|
|
625
|
+
|
|
626
|
+
ARCHIVE_CYCLE_MUTATION = """
|
|
627
|
+
mutation CycleArchive($id: String!) {
|
|
628
|
+
cycleArchive(id: $id) {
|
|
629
|
+
success
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
GET_CYCLE_ISSUES_QUERY = """
|
|
635
|
+
query CycleIssues($cycleId: String!, $first: Int!) {
|
|
636
|
+
cycle(id: $cycleId) {
|
|
637
|
+
issues(first: $first) {
|
|
638
|
+
nodes {
|
|
639
|
+
id
|
|
640
|
+
identifier
|
|
641
|
+
title
|
|
642
|
+
description
|
|
643
|
+
state {
|
|
644
|
+
id
|
|
645
|
+
name
|
|
646
|
+
type
|
|
647
|
+
}
|
|
648
|
+
priority
|
|
649
|
+
assignee {
|
|
650
|
+
id
|
|
651
|
+
email
|
|
652
|
+
name
|
|
653
|
+
}
|
|
654
|
+
labels {
|
|
655
|
+
nodes {
|
|
656
|
+
id
|
|
657
|
+
name
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
createdAt
|
|
661
|
+
updatedAt
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
"""
|
|
@@ -51,6 +51,34 @@ class LinearStateMapping:
|
|
|
51
51
|
"canceled": TicketState.CLOSED,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
# Semantic state name mappings for flexible workflow matching (1M-552)
|
|
55
|
+
# Maps universal states to common Linear state names (case-insensitive)
|
|
56
|
+
SEMANTIC_NAMES: dict[TicketState, list[str]] = {
|
|
57
|
+
TicketState.OPEN: ["todo", "to do", "open", "new", "backlog"],
|
|
58
|
+
TicketState.READY: ["ready", "triage", "ready for dev", "ready to start"],
|
|
59
|
+
TicketState.TESTED: [
|
|
60
|
+
"tested",
|
|
61
|
+
"in review",
|
|
62
|
+
"review",
|
|
63
|
+
"qa",
|
|
64
|
+
"testing",
|
|
65
|
+
"ready for review",
|
|
66
|
+
],
|
|
67
|
+
TicketState.WAITING: ["waiting", "on hold", "paused"],
|
|
68
|
+
TicketState.BLOCKED: ["blocked"],
|
|
69
|
+
TicketState.IN_PROGRESS: [
|
|
70
|
+
"in progress",
|
|
71
|
+
"in-progress",
|
|
72
|
+
"started",
|
|
73
|
+
"doing",
|
|
74
|
+
"active",
|
|
75
|
+
"in development",
|
|
76
|
+
"in dev",
|
|
77
|
+
],
|
|
78
|
+
TicketState.DONE: ["done", "completed", "finished"],
|
|
79
|
+
TicketState.CLOSED: ["closed", "canceled", "cancelled", "won't do", "wont do"],
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
|
|
55
83
|
class LinearWorkflowStateType(Enum):
|
|
56
84
|
"""Linear workflow state types."""
|
|
@@ -128,17 +156,99 @@ def get_linear_state_type(state: TicketState) -> str:
|
|
|
128
156
|
return LinearStateMapping.TO_LINEAR.get(state, "unstarted")
|
|
129
157
|
|
|
130
158
|
|
|
131
|
-
def get_universal_state(
|
|
132
|
-
|
|
159
|
+
def get_universal_state(
|
|
160
|
+
linear_state_type: str, state_name: str | None = None
|
|
161
|
+
) -> TicketState:
|
|
162
|
+
"""Convert Linear workflow state type to universal TicketState with synonym matching.
|
|
163
|
+
|
|
164
|
+
This function implements intelligent state mapping with fallback strategies:
|
|
165
|
+
1. Try exact match on state type (backlog, unstarted, started, completed, canceled)
|
|
166
|
+
2. Try synonym matching on state name (ToDo, In Review, Testing, etc.)
|
|
167
|
+
3. Default to OPEN for unknown states
|
|
168
|
+
|
|
169
|
+
Synonym Matching Rules (ticket 1M-164, fixed in v2.0.4):
|
|
170
|
+
- "done", "completed", "finished", "resolved" → DONE
|
|
171
|
+
- "closed", "canceled", "cancelled", "won't do" → CLOSED
|
|
172
|
+
- Everything else → OPEN
|
|
133
173
|
|
|
134
174
|
Args:
|
|
135
|
-
linear_state_type: Linear workflow state type string
|
|
175
|
+
linear_state_type: Linear workflow state type string (from state.type field)
|
|
176
|
+
state_name: Linear workflow state name (from state.name field, optional)
|
|
136
177
|
|
|
137
178
|
Returns:
|
|
138
179
|
Universal ticket state enum
|
|
139
180
|
|
|
140
181
|
"""
|
|
141
|
-
|
|
182
|
+
# First try exact type match
|
|
183
|
+
if linear_state_type in LinearStateMapping.FROM_LINEAR:
|
|
184
|
+
return LinearStateMapping.FROM_LINEAR[linear_state_type]
|
|
185
|
+
|
|
186
|
+
# If no exact match and state_name provided, try synonym matching
|
|
187
|
+
if state_name:
|
|
188
|
+
state_name_lower = state_name.lower().strip()
|
|
189
|
+
|
|
190
|
+
# DONE states: Work successfully completed
|
|
191
|
+
# - User finished the work
|
|
192
|
+
# - Requirements met
|
|
193
|
+
# - Quality verified
|
|
194
|
+
done_synonyms = [
|
|
195
|
+
"done",
|
|
196
|
+
"completed",
|
|
197
|
+
"finished",
|
|
198
|
+
"resolved",
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
if any(synonym in state_name_lower for synonym in done_synonyms):
|
|
202
|
+
return TicketState.DONE
|
|
203
|
+
|
|
204
|
+
# CLOSED states: Work terminated without completion
|
|
205
|
+
# - User decided not to do it
|
|
206
|
+
# - Requirements changed
|
|
207
|
+
# - Duplicate/invalid ticket
|
|
208
|
+
closed_synonyms = [
|
|
209
|
+
"closed",
|
|
210
|
+
"cancelled",
|
|
211
|
+
"canceled",
|
|
212
|
+
"won't do",
|
|
213
|
+
"wont do",
|
|
214
|
+
"rejected",
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
if any(synonym in state_name_lower for synonym in closed_synonyms):
|
|
218
|
+
return TicketState.CLOSED
|
|
219
|
+
|
|
220
|
+
# Check for "in progress" synonyms
|
|
221
|
+
in_progress_synonyms = [
|
|
222
|
+
"in progress",
|
|
223
|
+
"in-progress",
|
|
224
|
+
"working",
|
|
225
|
+
"active",
|
|
226
|
+
"started",
|
|
227
|
+
"doing",
|
|
228
|
+
"in development",
|
|
229
|
+
"in dev",
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
if any(synonym in state_name_lower for synonym in in_progress_synonyms):
|
|
233
|
+
return TicketState.IN_PROGRESS
|
|
234
|
+
|
|
235
|
+
# Check for "review/testing" synonyms
|
|
236
|
+
review_synonyms = [
|
|
237
|
+
"review",
|
|
238
|
+
"in review",
|
|
239
|
+
"in-review",
|
|
240
|
+
"testing",
|
|
241
|
+
"in test",
|
|
242
|
+
"in-test",
|
|
243
|
+
"qa",
|
|
244
|
+
"ready for review",
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
if any(synonym in state_name_lower for synonym in review_synonyms):
|
|
248
|
+
return TicketState.READY
|
|
249
|
+
|
|
250
|
+
# Default: everything else is OPEN (including "ToDo", "Backlog", "To Do", etc.)
|
|
251
|
+
return TicketState.OPEN
|
|
142
252
|
|
|
143
253
|
|
|
144
254
|
def build_issue_filter(
|
|
@@ -147,6 +257,7 @@ def build_issue_filter(
|
|
|
147
257
|
priority: Priority | None = None,
|
|
148
258
|
team_id: str | None = None,
|
|
149
259
|
project_id: str | None = None,
|
|
260
|
+
parent_id: str | None = None,
|
|
150
261
|
labels: list[str] | None = None,
|
|
151
262
|
created_after: str | None = None,
|
|
152
263
|
updated_after: str | None = None,
|
|
@@ -161,6 +272,7 @@ def build_issue_filter(
|
|
|
161
272
|
priority: Filter by priority
|
|
162
273
|
team_id: Filter by team ID
|
|
163
274
|
project_id: Filter by project ID
|
|
275
|
+
parent_id: Filter by parent issue ID (for listing sub-issues)
|
|
164
276
|
labels: Filter by label names
|
|
165
277
|
created_after: Filter by creation date (ISO string)
|
|
166
278
|
updated_after: Filter by update date (ISO string)
|
|
@@ -195,6 +307,10 @@ def build_issue_filter(
|
|
|
195
307
|
if project_id:
|
|
196
308
|
issue_filter["project"] = {"id": {"eq": project_id}}
|
|
197
309
|
|
|
310
|
+
# Parent filter (for listing children/sub-issues)
|
|
311
|
+
if parent_id:
|
|
312
|
+
issue_filter["parent"] = {"id": {"eq": parent_id}}
|
|
313
|
+
|
|
198
314
|
# Labels filter
|
|
199
315
|
if labels:
|
|
200
316
|
issue_filter["labels"] = {"some": {"name": {"in": labels}}}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Ticket analysis and cleanup tools for PM monitoring.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive analysis capabilities for ticket health:
|
|
4
|
+
- Similarity detection: Find duplicate or related tickets using TF-IDF
|
|
5
|
+
- Staleness detection: Identify old, inactive tickets
|
|
6
|
+
- Orphaned detection: Find tickets missing hierarchy (epic/project)
|
|
7
|
+
- Cleanup reports: Comprehensive analysis with recommendations
|
|
8
|
+
- Dependency graph: Build and analyze ticket dependency graphs
|
|
9
|
+
- Health assessment: Assess project health based on ticket metrics
|
|
10
|
+
- Project status: Comprehensive project status analysis and work planning
|
|
11
|
+
|
|
12
|
+
These tools help product managers maintain ticket health and development practices.
|
|
13
|
+
|
|
14
|
+
Note: Some analysis features require optional dependencies (scikit-learn, rapidfuzz).
|
|
15
|
+
Install with: pip install mcp-ticketer[analysis]
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Import dependency graph and health assessment (no optional deps required)
|
|
19
|
+
from .dependency_graph import DependencyGraph, DependencyNode
|
|
20
|
+
from .health_assessment import HealthAssessor, HealthMetrics, ProjectHealth
|
|
21
|
+
from .project_status import ProjectStatusResult, StatusAnalyzer, TicketRecommendation
|
|
22
|
+
|
|
23
|
+
# Import optional analysis modules (may fail if dependencies not installed)
|
|
24
|
+
try:
|
|
25
|
+
from .orphaned import OrphanedResult, OrphanedTicketDetector
|
|
26
|
+
from .similarity import SimilarityResult, TicketSimilarityAnalyzer
|
|
27
|
+
from .staleness import StalenessResult, StaleTicketDetector
|
|
28
|
+
|
|
29
|
+
ANALYSIS_AVAILABLE = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
# Set placeholder values when optional deps not available
|
|
32
|
+
OrphanedResult = None # type: ignore
|
|
33
|
+
OrphanedTicketDetector = None # type: ignore
|
|
34
|
+
SimilarityResult = None # type: ignore
|
|
35
|
+
TicketSimilarityAnalyzer = None # type: ignore
|
|
36
|
+
StalenessResult = None # type: ignore
|
|
37
|
+
StaleTicketDetector = None # type: ignore
|
|
38
|
+
ANALYSIS_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"DependencyGraph",
|
|
42
|
+
"DependencyNode",
|
|
43
|
+
"HealthAssessor",
|
|
44
|
+
"HealthMetrics",
|
|
45
|
+
"ProjectHealth",
|
|
46
|
+
"ProjectStatusResult",
|
|
47
|
+
"StatusAnalyzer",
|
|
48
|
+
"TicketRecommendation",
|
|
49
|
+
"SimilarityResult",
|
|
50
|
+
"TicketSimilarityAnalyzer",
|
|
51
|
+
"StalenessResult",
|
|
52
|
+
"StaleTicketDetector",
|
|
53
|
+
"OrphanedResult",
|
|
54
|
+
"OrphanedTicketDetector",
|
|
55
|
+
"ANALYSIS_AVAILABLE",
|
|
56
|
+
]
|