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
@@ -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,454 @@ 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
+
522
+ class Milestone(BaseModel):
523
+ """Universal milestone model for cross-platform support.
524
+
525
+ A milestone is a collection of issues grouped by labels with a target date.
526
+ Progress is calculated by counting closed vs total issues matching the labels.
527
+
528
+ Platform Mappings:
529
+ - Linear: Milestones (with labels and target dates)
530
+ - GitHub: Milestones (native support with due dates)
531
+ - JIRA: Versions/Releases (with target dates)
532
+ - Asana: Projects with dates (workaround via filtering)
533
+
534
+ The model follows the user's definition: "A milestone is a list of labels
535
+ with target dates, into which issues can be grouped."
536
+
537
+ Attributes:
538
+ id: Unique milestone identifier
539
+ name: Milestone name
540
+ target_date: Target completion date (ISO format: YYYY-MM-DD)
541
+ state: Milestone state (open, active, completed, closed)
542
+ description: Milestone description
543
+ labels: Labels that define this milestone's scope
544
+ total_issues: Total issues in milestone (calculated)
545
+ closed_issues: Closed issues in milestone (calculated)
546
+ progress_pct: Progress percentage 0-100 (calculated)
547
+ project_id: Associated project/epic ID
548
+ created_at: Creation timestamp
549
+ updated_at: Last update timestamp
550
+ platform_data: Platform-specific metadata
551
+
552
+ Example:
553
+ >>> milestone = Milestone(
554
+ ... name="v2.1.0 Release",
555
+ ... target_date=date(2025, 12, 31),
556
+ ... labels=["v2.1", "release"],
557
+ ... project_id="proj-123"
558
+ ... )
559
+ >>> milestone.total_issues = 15
560
+ >>> milestone.closed_issues = 8
561
+ >>> milestone.progress_pct = 53.3
562
+
563
+ Note:
564
+ Related to ticket 1M-607: Add milestone support (Phase 1 - Core Infrastructure)
565
+
566
+ """
567
+
568
+ model_config = ConfigDict(use_enum_values=True)
569
+
570
+ id: str | None = Field(None, description="Unique milestone identifier")
571
+ name: str = Field(..., min_length=1, description="Milestone name")
572
+ target_date: datetime | None = Field(
573
+ None, description="Target completion date (ISO format: YYYY-MM-DD)"
574
+ )
575
+ state: str = Field(
576
+ "open", description="Milestone state: open, active, completed, closed"
577
+ )
578
+ description: str = Field("", description="Milestone description")
579
+
580
+ # Label-based grouping (user's definition)
581
+ labels: list[str] = Field(
582
+ default_factory=list, description="Labels that define this milestone"
583
+ )
584
+
585
+ # Progress tracking (calculated fields)
586
+ total_issues: int = Field(0, ge=0, description="Total issues in milestone")
587
+ closed_issues: int = Field(0, ge=0, description="Closed issues in milestone")
588
+ progress_pct: float = Field(
589
+ 0.0, ge=0.0, le=100.0, description="Progress percentage (0-100)"
590
+ )
591
+
592
+ # Metadata
593
+ project_id: str | None = Field(None, description="Associated project ID")
594
+ created_at: datetime | None = Field(None, description="Creation timestamp")
595
+ updated_at: datetime | None = Field(None, description="Last update timestamp")
596
+
597
+ # Platform-specific data
598
+ platform_data: dict[str, Any] = Field(
599
+ default_factory=dict, description="Platform-specific metadata"
600
+ )
601
+
602
+
603
+ class ProjectState(str, Enum):
604
+ """Project state across platforms.
605
+
606
+ Maps to different platform concepts:
607
+ - Linear: planned, started, completed, paused, canceled
608
+ - GitHub V2: OPEN, CLOSED (with status field for more granular states)
609
+ - JIRA: Not directly supported (use project status or custom fields)
610
+
611
+ Attributes:
612
+ PLANNED: Project is planned but not yet started
613
+ ACTIVE: Project is actively being worked on
614
+ COMPLETED: Project is finished successfully
615
+ ARCHIVED: Project is archived (no longer active)
616
+ CANCELLED: Project was cancelled before completion
617
+
618
+ """
619
+
620
+ PLANNED = "planned"
621
+ ACTIVE = "active"
622
+ COMPLETED = "completed"
623
+ ARCHIVED = "archived"
624
+ CANCELLED = "cancelled"
625
+
626
+
627
+ class ProjectVisibility(str, Enum):
628
+ """Project visibility setting.
629
+
630
+ Controls who can view the project across platforms.
631
+
632
+ Attributes:
633
+ PUBLIC: Visible to everyone
634
+ PRIVATE: Visible only to members
635
+ TEAM: Visible to team members
636
+
637
+ """
638
+
639
+ PUBLIC = "public"
640
+ PRIVATE = "private"
641
+ TEAM = "team"
642
+
643
+
644
+ class ProjectScope(str, Enum):
645
+ """Project organizational scope.
646
+
647
+ Defines the level at which a project exists in the organization hierarchy.
648
+
649
+ Platform Mappings:
650
+ - Linear: TEAM (projects belong to teams) or ORGANIZATION
651
+ - GitHub: REPOSITORY, USER, or ORGANIZATION
652
+ - JIRA: PROJECT (inherent) or ORGANIZATION (via project hierarchy)
653
+
654
+ Attributes:
655
+ USER: User-level project (GitHub Projects V2)
656
+ TEAM: Team-level project (Linear, GitHub org teams)
657
+ ORGANIZATION: Organization-level project (cross-team)
658
+ REPOSITORY: Repository-scoped project (GitHub)
659
+
660
+ """
661
+
662
+ USER = "user"
663
+ TEAM = "team"
664
+ ORGANIZATION = "organization"
665
+ REPOSITORY = "repository"
666
+
667
+
668
+ class Project(BaseModel):
669
+ """Unified project model across platforms.
670
+
671
+ Projects represent strategic-level containers for issues, superseding the
672
+ Epic model with a more comprehensive structure that maps cleanly to:
673
+ - Linear Projects
674
+ - GitHub Projects V2
675
+ - JIRA Projects/Epics
676
+
677
+ This model provides backward compatibility through conversion utilities
678
+ (see project_utils.py) while enabling richer project management features.
679
+
680
+ Attributes:
681
+ id: Unique identifier in MCP Ticketer namespace
682
+ platform: Platform identifier ("linear", "github", "jira")
683
+ platform_id: Original platform-specific identifier
684
+ scope: Organizational scope of the project
685
+ name: Project name (required)
686
+ description: Detailed project description
687
+ state: Current project state
688
+ visibility: Who can view the project
689
+ url: Direct URL to project in platform
690
+ created_at: When project was created
691
+ updated_at: When project was last modified
692
+ start_date: Planned or actual start date
693
+ target_date: Target completion date
694
+ completed_at: Actual completion date
695
+ owner_id: Project owner/lead user ID
696
+ owner_name: Project owner/lead display name
697
+ team_id: Team this project belongs to
698
+ team_name: Team display name
699
+ child_issues: List of issue IDs in this project
700
+ issue_count: Total number of issues
701
+ completed_count: Number of completed issues
702
+ in_progress_count: Number of in-progress issues
703
+ progress_percentage: Overall completion percentage
704
+ extra_data: Platform-specific additional data
705
+
706
+ Example:
707
+ >>> project = Project(
708
+ ... id="proj-123",
709
+ ... platform="linear",
710
+ ... platform_id="eac28953c267",
711
+ ... scope=ProjectScope.TEAM,
712
+ ... name="MCP Ticketer v2.0",
713
+ ... state=ProjectState.ACTIVE,
714
+ ... visibility=ProjectVisibility.TEAM
715
+ ... )
716
+
717
+ """
718
+
719
+ model_config = ConfigDict(use_enum_values=True)
720
+
721
+ # Core identification
722
+ id: str = Field(..., description="Unique identifier")
723
+ platform: str = Field(..., description="Platform name (linear, github, jira)")
724
+ platform_id: str = Field(..., description="Original platform ID")
725
+ scope: ProjectScope = Field(..., description="Organizational scope")
726
+
727
+ # Basic information
728
+ name: str = Field(..., min_length=1, description="Project name")
729
+ description: str | None = Field(None, description="Project description")
730
+ state: ProjectState = Field(ProjectState.PLANNED, description="Current state")
731
+ visibility: ProjectVisibility = Field(
732
+ ProjectVisibility.TEAM, description="Visibility"
733
+ )
734
+
735
+ # URLs and references
736
+ url: str | None = Field(None, description="Direct URL to project")
737
+
738
+ # Dates
739
+ created_at: datetime | None = Field(None, description="Creation timestamp")
740
+ updated_at: datetime | None = Field(None, description="Last update timestamp")
741
+ start_date: datetime | None = Field(None, description="Start date")
742
+ target_date: datetime | None = Field(None, description="Target completion date")
743
+ completed_at: datetime | None = Field(None, description="Completion timestamp")
744
+
745
+ # Ownership
746
+ owner_id: str | None = Field(None, description="Owner user ID")
747
+ owner_name: str | None = Field(None, description="Owner display name")
748
+ team_id: str | None = Field(None, description="Team ID")
749
+ team_name: str | None = Field(None, description="Team display name")
750
+
751
+ # Issue relationships
752
+ child_issues: list[str] = Field(default_factory=list, description="Child issue IDs")
753
+ issue_count: int | None = Field(None, ge=0, description="Total issue count")
754
+ completed_count: int | None = Field(None, ge=0, description="Completed issues")
755
+ in_progress_count: int | None = Field(None, ge=0, description="In-progress issues")
756
+ progress_percentage: float | None = Field(
757
+ None, ge=0.0, le=100.0, description="Completion percentage"
758
+ )
759
+
760
+ # Platform-specific data
761
+ extra_data: dict[str, Any] = Field(
762
+ default_factory=dict, description="Platform-specific metadata"
763
+ )
764
+
765
+ def calculate_progress(self) -> float:
766
+ """Calculate progress percentage from issue counts.
767
+
768
+ Returns:
769
+ Progress percentage (0-100), or 0 if no issues
770
+
771
+ """
772
+ if not self.issue_count or self.issue_count == 0:
773
+ return 0.0
774
+
775
+ completed = self.completed_count or 0
776
+ return (completed / self.issue_count) * 100.0
777
+
778
+
779
+ class ProjectStatistics(BaseModel):
780
+ """Statistics and metrics for a project.
781
+
782
+ Provides calculated metrics for project health and progress tracking.
783
+ These statistics are typically computed from current project state
784
+ rather than stored directly.
785
+
786
+ Attributes:
787
+ project_id: ID of the project these stats belong to (optional for compatibility)
788
+ total_issues: Total number of issues (legacy field, use total_count)
789
+ completed_issues: Count of completed issues (legacy field, use completed_count)
790
+ in_progress_issues: Count of in-progress issues (legacy field, use in_progress_count)
791
+ open_issues: Count of open/backlog issues (legacy field, use open_count)
792
+ blocked_issues: Count of blocked issues (legacy field, use blocked_count)
793
+ total_count: Total number of issues (preferred)
794
+ open_count: Count of open issues (preferred)
795
+ in_progress_count: Count of in-progress issues (preferred)
796
+ completed_count: Count of completed issues (preferred)
797
+ blocked_count: Count of blocked issues (preferred)
798
+ priority_low_count: Count of low priority issues
799
+ priority_medium_count: Count of medium priority issues
800
+ priority_high_count: Count of high priority issues
801
+ priority_critical_count: Count of critical priority issues
802
+ health: Project health status (on_track, at_risk, off_track)
803
+ progress_percentage: Overall completion percentage
804
+ velocity: Issues completed per week (if available)
805
+ estimated_completion: Projected completion date
806
+
807
+ Example:
808
+ >>> stats = ProjectStatistics(
809
+ ... total_count=50,
810
+ ... completed_count=30,
811
+ ... in_progress_count=15,
812
+ ... open_count=5,
813
+ ... blocked_count=0,
814
+ ... priority_high_count=10,
815
+ ... health="on_track",
816
+ ... progress_percentage=60.0
817
+ ... )
818
+
819
+ """
820
+
821
+ model_config = ConfigDict(use_enum_values=True)
822
+
823
+ # Legacy fields for backward compatibility (optional)
824
+ project_id: str | None = Field(None, description="Project identifier (legacy)")
825
+ total_issues: int | None = Field(
826
+ None, ge=0, description="Total issue count (legacy)"
827
+ )
828
+ completed_issues: int | None = Field(
829
+ None, ge=0, description="Completed issues (legacy)"
830
+ )
831
+ in_progress_issues: int | None = Field(
832
+ None, ge=0, description="In-progress issues (legacy)"
833
+ )
834
+ open_issues: int | None = Field(
835
+ None, ge=0, description="Open/backlog issues (legacy)"
836
+ )
837
+ blocked_issues: int | None = Field(
838
+ None, ge=0, description="Blocked issues (legacy)"
839
+ )
840
+
841
+ # New preferred fields
842
+ total_count: int = Field(0, ge=0, description="Total issue count")
843
+ open_count: int = Field(0, ge=0, description="Open issues")
844
+ in_progress_count: int = Field(0, ge=0, description="In-progress issues")
845
+ completed_count: int = Field(0, ge=0, description="Completed issues")
846
+ blocked_count: int = Field(0, ge=0, description="Blocked issues")
847
+
848
+ # Priority distribution
849
+ priority_low_count: int = Field(0, ge=0, description="Low priority issues")
850
+ priority_medium_count: int = Field(0, ge=0, description="Medium priority issues")
851
+ priority_high_count: int = Field(0, ge=0, description="High priority issues")
852
+ priority_critical_count: int = Field(
853
+ 0, ge=0, description="Critical priority issues"
854
+ )
855
+
856
+ # Health and progress
857
+ health: str = Field(
858
+ "on_track", description="Health status: on_track, at_risk, off_track"
859
+ )
860
+ progress_percentage: float = Field(0.0, ge=0.0, le=100.0, description="Progress %")
861
+ velocity: float | None = Field(None, description="Issues/week completion rate")
862
+ estimated_completion: datetime | None = Field(
863
+ None, description="Projected completion date"
864
+ )
865
+
866
+
388
867
  class SearchQuery(BaseModel):
389
868
  """Search query parameters."""
390
869
 
@@ -393,5 +872,6 @@ class SearchQuery(BaseModel):
393
872
  priority: Priority | None = Field(None, description="Filter by priority")
394
873
  tags: list[str] | None = Field(None, description="Filter by tags")
395
874
  assignee: str | None = Field(None, description="Filter by assignee")
875
+ project: str | None = Field(None, description="Filter by project/epic ID or name")
396
876
  limit: int = Field(10, gt=0, le=100, description="Maximum results")
397
877
  offset: int = Field(0, ge=0, description="Result offset for pagination")
@@ -334,7 +334,7 @@ def check_op_cli_status() -> dict[str, Any]:
334
334
  """
335
335
  loader = OnePasswordSecretsLoader()
336
336
 
337
- status = {
337
+ status: dict[str, Any] = {
338
338
  "installed": loader.is_op_available(),
339
339
  "authenticated": False,
340
340
  "version": None,